diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/MongoDbEnvironmentTestExtensions.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/MongoDbEnvironmentTestExtensions.kt index 468aa891..90308e5d 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/MongoDbEnvironmentTestExtensions.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/fixtures/MongoDbEnvironmentTestExtensions.kt @@ -11,8 +11,8 @@ import com.mongodb.ConnectionString import com.mongodb.client.MongoClient import com.mongodb.client.MongoClients import com.mongodb.jbplugin.accessadapter.MongoDbDriver -import com.mongodb.jbplugin.accessadapter.Namespace import com.mongodb.jbplugin.accessadapter.datagrip.DataGripBasedReadModelProvider +import com.mongodb.jbplugin.mql.Namespace import org.bson.Document import org.bson.conversions.Bson import org.junit.jupiter.api.extension.* diff --git a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/observability/probe/NewConnectionActivatedProbeTest.kt b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/observability/probe/NewConnectionActivatedProbeTest.kt index a4512e33..c1f32535 100644 --- a/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/observability/probe/NewConnectionActivatedProbeTest.kt +++ b/packages/jetbrains-plugin/src/test/kotlin/com/mongodb/jbplugin/observability/probe/NewConnectionActivatedProbeTest.kt @@ -75,21 +75,23 @@ internal abstract class NewConnectionActivatedProbeTest( } @RequiresMongoDbCluster(version = MongoDbVersion.V7_0) -internal class NewConnectionActivatedProbeTestForLocalEnvironment : NewConnectionActivatedProbeTest( - isAtlas = false, - isLocalAtlas = false, - isLocalhost = true, - isEnterprise = false, - isGenuine = true, - version = "7.0.11", -) +internal class NewConnectionActivatedProbeTestForLocalEnvironment : + NewConnectionActivatedProbeTest( + isAtlas = false, + isLocalAtlas = false, + isLocalhost = true, + isEnterprise = false, + isGenuine = true, + version = "7.0.12", + ) @RequiresMongoDbCluster(MongoDbTestingEnvironment.LOCAL_ATLAS) -internal class NewConnectionActivatedProbeTestForAtlasCliEnvironment : NewConnectionActivatedProbeTest( - isAtlas = false, - isLocalAtlas = true, - isLocalhost = true, - isEnterprise = true, - isGenuine = true, - version = "7.0.11", -) +internal class NewConnectionActivatedProbeTestForAtlasCliEnvironment : + NewConnectionActivatedProbeTest( + isAtlas = false, + isLocalAtlas = true, + isLocalhost = true, + isEnterprise = true, + isGenuine = true, + version = "7.0.12", + ) diff --git a/packages/mongodb-access-adapter/build.gradle.kts b/packages/mongodb-access-adapter/build.gradle.kts index 38ebafbc..580b2259 100644 --- a/packages/mongodb-access-adapter/build.gradle.kts +++ b/packages/mongodb-access-adapter/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { + implementation(project(":packages:mongodb-mql-model")) implementation(libs.mongodb.driver) implementation(libs.gson) } diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts b/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts index 28d97076..77302aed 100644 --- a/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts +++ b/packages/mongodb-access-adapter/datagrip-access-adapter/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(libs.owasp.encoder) implementation(libs.mongodb.driver) implementation(project(":packages:mongodb-access-adapter")) + implementation(project(":packages:mongodb-mql-model")) testImplementation("com.jetbrains.intellij.platform:test-framework-junit5:241.15989.155") { exclude("ai.grazie.spell") diff --git a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt index 5c6e56c2..b694fb53 100644 --- a/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt +++ b/packages/mongodb-access-adapter/datagrip-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/datagrip/adapter/DataGripMongoDbDriver.kt @@ -14,7 +14,7 @@ import com.intellij.database.run.ConsoleRunConfiguration import com.intellij.openapi.project.Project import com.mongodb.ConnectionString import com.mongodb.jbplugin.accessadapter.MongoDbDriver -import com.mongodb.jbplugin.accessadapter.Namespace +import com.mongodb.jbplugin.mql.Namespace import org.bson.conversions.Bson import org.bson.json.JsonMode import org.bson.json.JsonWriterSettings @@ -195,12 +195,13 @@ internal class DataGripMongoDbDriver( } @VisibleForTesting - private fun withActiveConnectionList(fn: (MutableSet) -> Unit): Unit { + private fun withActiveConnectionList(fn: (MutableSet) -> Unit) { runBlocking { val connectionsManager = DatabaseConnectionManager.getInstance() val myConnectionsField = connectionsManager.javaClass - .getDeclaredField("myConnections").apply { + .getDeclaredField("myConnections") + .apply { isAccessible = true } val myConnections = myConnectionsField.get(connectionsManager) as MutableSet diff --git a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt index 9755adae..968e48e3 100644 --- a/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt +++ b/packages/mongodb-access-adapter/src/main/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriver.kt @@ -10,40 +10,13 @@ package com.mongodb.jbplugin.accessadapter import com.mongodb.ConnectionString +import com.mongodb.jbplugin.mql.Namespace import org.bson.conversions.Bson import java.util.* import kotlin.reflect.KClass import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -/** - * Represents a MongoDB Namespace (db/coll) - * - * @property database - * @property collection - */ -class Namespace private constructor( - val database: String, - val collection: String, -) { - override fun toString(): String = "$database.$collection" - - override fun equals(other: Any?): Boolean = other is Namespace && hashCode() == other.hashCode() - - override fun hashCode(): Int = Objects.hash(database, collection) - - companion object { - operator fun invoke( - database: String, - collection: String, - ): Namespace = - Namespace( - database, - collection, - ) - } -} - /** * Represents the MongoDB Driver facade that we will use internally. * Usually, we won't use this class directly, only in tests. What we diff --git a/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt b/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt index 4ff15cb8..7d213c91 100644 --- a/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt +++ b/packages/mongodb-access-adapter/src/test/kotlin/com/mongodb/jbplugin/accessadapter/MongoDbDriverTest.kt @@ -1,5 +1,6 @@ package com.mongodb.jbplugin.accessadapter +import com.mongodb.jbplugin.mql.Namespace import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -25,12 +26,6 @@ class MongoDbDriverTest { assertEquals("myco\"ll", namespace.collection) } - @Test - fun `serialises to a valid namespace string`() { - val namespace = Namespace("mydb", "my.cool.col") - assertEquals("mydb.my.cool.col", namespace.toString()) - } - @Test fun `can parse back a serialised namespace`() { val namespace = Namespace("mydb", "my.cool.col") diff --git a/packages/mongodb-mql-model/build.gradle.kts b/packages/mongodb-mql-model/build.gradle.kts new file mode 100644 index 00000000..02bd1a2c --- /dev/null +++ b/packages/mongodb-mql-model/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + implementation(libs.owasp.encoder) +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/BsonType.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/BsonType.kt new file mode 100644 index 00000000..bcb4cb7a --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/BsonType.kt @@ -0,0 +1,126 @@ +/** + * Represents all supported Bson types. We are not using the ones defined in the driver as we need more information, + * like nullability and composability (for example, a value that can be either int or bool). + */ + +package com.mongodb.jbplugin.mql + +import java.math.BigDecimal +import java.math.BigInteger +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* + +/** + * Represents any of the valid BSON types. + */ +interface BsonType + +/** + * A double (64 bit floating point) + */ +data object BsonDouble : BsonType + +/** + * BSON String + */ +data object BsonString : BsonType + +/** + * Represents a map of key -> type. + * + * @property schema + */ +data class BsonObject( + val schema: Map, +) : BsonType + +/** + * Represents the possible types that can be included in an array. + * + * @property schema + */ +data class BsonArray( + val schema: BsonType, +) : BsonType + +/** + * Boolean + */ +data object BsonBoolean : BsonType + +/** + * Date + */ +data object BsonDate : BsonType + +/** + * null / non existing field + */ + +data object BsonNull : BsonType + +/** + * 32-bit integer + */ + +data object BsonInt32 : BsonType + +/** + * 64-bit integer + */ +data object BsonInt64 : BsonType + +/** + * Decimal128 (128 bit floating point) + */ +data object BsonDecimal128 : BsonType + +/** + * This is not a BSON type per se, but need a value for an unknown + * bson type. + */ +data object BsonAny : BsonType + +/** + * This is not a BSON type per se, but a schema is dynamic and for a single + * field we can have multiple types of values, so we will map this scenario + * with the AnyOf type. + * + * @property types + */ +data class BsonAnyOf( + val types: List, +) : BsonType { + constructor(vararg types: BsonType) : this(types.toList()) +} + +/** + * Returns the inferred BSON type of the current Java class, considering it's nullability. + */ +fun Class<*>.toBsonType(): BsonType { + return when (this) { + Double::class.javaPrimitiveType -> BsonDouble + Double::class.javaObjectType -> BsonAnyOf(BsonNull, BsonDouble) + Boolean::class.javaPrimitiveType -> BsonBoolean + Boolean::class.javaObjectType -> BsonAnyOf(BsonNull, BsonBoolean) + Int::class.javaPrimitiveType -> BsonInt32 + Int::class.javaObjectType -> BsonAnyOf(BsonNull, BsonInt32) + CharSequence::class.java, String::class.java -> BsonAnyOf(BsonNull, BsonString) + Date::class.java, Instant::class.java, LocalDate::class.java, LocalDateTime::class.java -> + BsonAnyOf(BsonNull, BsonDate) + BigInteger::class.java -> BsonAnyOf(BsonNull, BsonInt64) + BigDecimal::class.java -> BsonAnyOf(BsonNull, BsonDecimal128) + else -> if (Collection::class.java.isAssignableFrom(this)) { + return BsonAnyOf(BsonNull, BsonArray(BsonAny)) // types are lost at runtime + } else { + val fields = + this.declaredFields.associate { + it.name to it.type.toBsonType() + } + + return BsonAnyOf(BsonNull, BsonObject(fields)) + } + } +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/CollectionSchema.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/CollectionSchema.kt new file mode 100644 index 00000000..202cfe2e --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/CollectionSchema.kt @@ -0,0 +1,61 @@ +package com.mongodb.jbplugin.mql + +import org.intellij.lang.annotations.Language +import java.lang.Integer.parseInt + +/** + * @property namespace + * @property schema + */ +class CollectionSchema( + val namespace: Namespace, + val schema: BsonObject, +) { + fun typeOf( + @Language("JSONPath") jsonPath: String, + ): BsonType { + val splitJsonPath = jsonPath.split('.').toList() + return recursivelyGetType(splitJsonPath, schema) + } + + private fun recursivelyGetType( + jsonPath: List, + root: BsonType, + ): BsonType { + if (jsonPath.isEmpty()) { + return root + } + + val current = jsonPath.first() + val isCurrentNumber = kotlin.runCatching { parseInt(current) }.isSuccess + + val listOfOptions = mutableListOf() + + when (root) { + is BsonArray -> if (isCurrentNumber) { + val childType = recursivelyGetType(jsonPath.subList(1, jsonPath.size), root.schema) + listOfOptions.add(childType) + } else { + listOfOptions.add(BsonNull) + } + is BsonObject -> { + val objectType = root.schema[jsonPath[0]] + listOfOptions.add( + objectType?.let { +recursivelyGetType(jsonPath.subList(1, jsonPath.size), objectType) +} ?: BsonNull, + ) + } + is BsonAnyOf -> listOfOptions.addAll( + root.types.map { recursivelyGetType(jsonPath, it) }, + ) + else -> listOfOptions.add(BsonNull) + } + + return if (listOfOptions.size == 1) { + listOfOptions.first() + } else { + BsonAnyOf(listOfOptions) + } + } +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/Namespace.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/Namespace.kt new file mode 100644 index 00000000..8b1aa686 --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/Namespace.kt @@ -0,0 +1,31 @@ +package com.mongodb.jbplugin.mql + +import java.util.* + +/** + * Represents a MongoDB Namespace (db/coll) + * + * @property database + * @property collection + */ +class Namespace private constructor( + val database: String, + val collection: String, +) { + override fun toString(): String = "$database.$collection" + + override fun equals(other: Any?): Boolean = other is Namespace && hashCode() == other.hashCode() + + override fun hashCode(): Int = Objects.hash(database, collection) + + companion object { + operator fun invoke( + database: String, + collection: String, + ): Namespace = + Namespace( + database, + collection, + ) + } +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/Node.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/Node.kt new file mode 100644 index 00000000..017972cb --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/Node.kt @@ -0,0 +1,43 @@ +/** + * Node and components are the main building blocks of the query model. + */ + +package com.mongodb.jbplugin.mql + +/** A component represents the semantics of a Node. When a Node has some special meaning, we will attach a component + * that adds that specific meaning. For example, take into consideration the following Java Query: + * ```java + * Filters.eq("myField", 42) + * ``` + * This query contains three semantic units: + * * Named: The operation that is executing has a name. + * * HasFieldReference: It refers to a field in a document. + * * HasValueReference: It refers to a value in code. + * + * @see Node + * @see com.mongodb.jbplugin.mql.components.Named + * @see com.mongodb.jbplugin.mql.components.HasFieldReference + * @see com.mongodb.jbplugin.mql.components.HasValueReference + */ +interface Component + +/** + * Represents the building block of a query in this model. Nodes don't have any semantic per se, but they can + * hold Components that will give them specific meaning. + * + * @see Component + * + * @param S + * @property source + * @property components + */ +data class Node( + val source: S, + val components: List, +) { + inline fun component(): C? = components.firstOrNull { it is C } as C? + + inline fun components(): List = components.filterIsInstance() + + inline fun hasComponent(): Boolean = component() != null +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasChildren.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasChildren.kt new file mode 100644 index 00000000..14b43c60 --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasChildren.kt @@ -0,0 +1,12 @@ +package com.mongodb.jbplugin.mql.components + +import com.mongodb.jbplugin.mql.Component +import com.mongodb.jbplugin.mql.Node + +/** + * @param S + * @property children + */ +data class HasChildren( + val children: List>, +) : Component diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasCollectionReference.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasCollectionReference.kt new file mode 100644 index 00000000..3e693243 --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasCollectionReference.kt @@ -0,0 +1,27 @@ +package com.mongodb.jbplugin.mql.components + +import com.mongodb.jbplugin.mql.Namespace + +/** + * @property reference + */ +class HasCollectionReference( + val reference: CollectionReference, +) { + data object Unknown : CollectionReference + sealed interface CollectionReference + + /** + * @property namespace + */ +data class Known( + val namespace: Namespace, + ) : CollectionReference + + /** + * @property collection + */ +data class OnlyCollection( + val collection: String, + ) : CollectionReference +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasFieldReference.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasFieldReference.kt new file mode 100644 index 00000000..4495213e --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasFieldReference.kt @@ -0,0 +1,20 @@ +package com.mongodb.jbplugin.mql.components + +import com.mongodb.jbplugin.mql.Component + +/** + * @property reference + */ +class HasFieldReference( + val reference: FieldReference, +) : Component { + data object Unknown : FieldReference + sealed interface FieldReference + + /** + * @property fieldName + */ +data class Known( + val fieldName: String, + ) : FieldReference +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasFilter.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasFilter.kt new file mode 100644 index 00000000..9725a22b --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasFilter.kt @@ -0,0 +1,11 @@ +package com.mongodb.jbplugin.mql.components + +import com.mongodb.jbplugin.mql.Node + +/** + * @param S + * @property filter + */ +data class HasFilter( + val filter: Node, +) diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasValueReference.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasValueReference.kt new file mode 100644 index 00000000..8f8fd4e0 --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/HasValueReference.kt @@ -0,0 +1,29 @@ +package com.mongodb.jbplugin.mql.components + +import com.mongodb.jbplugin.mql.Component + +/** + * @property reference + */ +data class HasValueReference( + val reference: ValueReference, +) : Component { + data object Unknown : ValueReference + sealed interface ValueReference + + /** + * @property value + * @property type + */ +data class Constant( + val value: Any, + val type: String, + ) : ValueReference + + /** + * @property type + */ +data class Runtime( + val type: String, + ) : ValueReference +} diff --git a/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/Named.kt b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/Named.kt new file mode 100644 index 00000000..079bc8cb --- /dev/null +++ b/packages/mongodb-mql-model/src/main/kotlin/com/mongodb/jbplugin/mql/components/Named.kt @@ -0,0 +1,10 @@ +package com.mongodb.jbplugin.mql.components + +import com.mongodb.jbplugin.mql.Component + +/** + * @property name + */ +data class Named( + val name: String, +) : Component diff --git a/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/BsonTypeTest.kt b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/BsonTypeTest.kt new file mode 100644 index 00000000..3bde93fd --- /dev/null +++ b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/BsonTypeTest.kt @@ -0,0 +1,59 @@ +package com.mongodb.jbplugin.mql + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.math.BigDecimal +import java.math.BigInteger +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* + +class BsonTypeTest { + @ParameterizedTest + @MethodSource("java to bson") + fun `should map correctly all java types`( + javaClass: Class<*>, + expected: BsonType, + ) { + assertEquals(expected, javaClass.toBsonType()) + } + + companion object { + @JvmStatic + fun `java to bson`(): Array = + arrayOf( + arrayOf(Double::class.javaObjectType, BsonAnyOf(BsonNull, BsonDouble)), + arrayOf(Double::class.javaPrimitiveType, BsonDouble), + arrayOf(CharSequence::class.java, BsonAnyOf(BsonNull, BsonString)), + arrayOf(String::class.java, BsonAnyOf(BsonNull, BsonString)), + arrayOf(Boolean::class.javaObjectType, BsonAnyOf(BsonNull, BsonBoolean)), + arrayOf(Boolean::class.javaPrimitiveType, BsonBoolean), + arrayOf(Date::class.java, BsonAnyOf(BsonNull, BsonDate)), + arrayOf(Instant::class.java, BsonAnyOf(BsonNull, BsonDate)), + arrayOf(LocalDate::class.java, BsonAnyOf(BsonNull, BsonDate)), + arrayOf(LocalDateTime::class.java, BsonAnyOf(BsonNull, BsonDate)), + arrayOf(Int::class.javaObjectType, BsonAnyOf(BsonNull, BsonInt32)), + arrayOf(Int::class.javaPrimitiveType, BsonInt32), + arrayOf(BigInteger::class.java, BsonAnyOf(BsonNull, BsonInt64)), + arrayOf(BigDecimal::class.java, BsonAnyOf(BsonNull, BsonDecimal128)), + arrayOf(ArrayList::class.java, BsonAnyOf(BsonNull, BsonArray(BsonAny))), + arrayOf( + ExampleClass::class.java, + BsonAnyOf( + BsonNull, + BsonObject( + mapOf( + "field" to BsonAnyOf(BsonNull, BsonString), + ), + ), + ), + ), + ) + + data class ExampleClass( + val field: String, + ) + } +} diff --git a/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/CollectionSchemaTest.kt b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/CollectionSchemaTest.kt new file mode 100644 index 00000000..8a843549 --- /dev/null +++ b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/CollectionSchemaTest.kt @@ -0,0 +1,75 @@ +package com.mongodb.jbplugin.mql + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CollectionSchemaTest { + @Test + fun `should return the type of a field in the root object`() { + val schema = + CollectionSchema( + Namespace("a", "b"), + BsonObject( + mapOf( + "myField" to BsonInt32, + ), + ), + ) + + assertEquals(BsonInt32, schema.typeOf("myField")) + } + + @Test + fun `should be able to merge when multiple options inside an object`() { + val schema = + CollectionSchema( + Namespace("a", "b"), + BsonObject( + mapOf( + "myField" to + BsonAnyOf( + BsonString, + BsonInt32, + ), + ), + ), + ) + + assertEquals( + BsonAnyOf( + BsonString, + BsonInt32, + ), + schema.typeOf("myField"), + ) + } + + @Test + fun `should be able to iterate over an array with objects`() { + val schema = + CollectionSchema( + Namespace("a", "b"), + BsonObject( + mapOf( + "myField" to + BsonArray( + BsonAnyOf( + BsonString, + BsonObject( + mapOf("otherField" to BsonDouble), + ), + ), + ), + ), + ), + ) + + assertEquals( + BsonAnyOf( + BsonNull, + BsonDouble, + ), + schema.typeOf("myField.0.otherField"), + ) + } +} diff --git a/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/NamespaceTest.kt b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/NamespaceTest.kt new file mode 100644 index 00000000..f3f58ce8 --- /dev/null +++ b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/NamespaceTest.kt @@ -0,0 +1,12 @@ +package com.mongodb.jbplugin.mql + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class NamespaceTest { + @Test + fun `serialises to a valid namespace string`() { + val namespace = Namespace("mydb", "my.cool.col") + assertEquals("mydb.my.cool.col", namespace.toString()) + } +} diff --git a/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/NodeTest.kt b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/NodeTest.kt new file mode 100644 index 00000000..51c234a0 --- /dev/null +++ b/packages/mongodb-mql-model/src/test/kotlin/com/mongodb/jbplugin/mql/NodeTest.kt @@ -0,0 +1,63 @@ +package com.mongodb.jbplugin.mql + +import com.mongodb.jbplugin.mql.components.HasFieldReference +import com.mongodb.jbplugin.mql.components.Named +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class NodeTest { + @Test + fun `is able to get a component if exists`() { + val node = Node(null, listOf(Named("myName"))) + val named = node.component() + + assertEquals("myName", named!!.name) + } + + @Test + fun `returns null if a component does not exist`() { + val node = Node(null, listOf(Named("myName"))) + val named = node.component() + + assertNull(named) + } + + @Test + fun `is able to get all components of the same type`() { + val node = + Node( + null, + listOf(HasFieldReference(HasFieldReference.Known("field1")), HasFieldReference(HasFieldReference.Known( +"field2" +))), + ) + val fieldReferences = node.components() + + assertEquals("field1", (fieldReferences[0].reference as HasFieldReference.Known).fieldName) + assertEquals("field2", (fieldReferences[1].reference as HasFieldReference.Known).fieldName) + } + + @Test + fun `returns true if a component of that type exists`() { + val node = + Node( + null, + listOf(HasFieldReference(HasFieldReference.Known("field1"))), + ) + val hasFieldReferences = node.hasComponent() + + assertTrue(hasFieldReferences) + } + + @Test + fun `returns false if a component of that type does not exist`() { + val node = + Node( + null, + listOf(HasFieldReference(HasFieldReference.Known("field1"))), + ) + val hasNamedComponent = node.hasComponent() + + assertFalse(hasNamedComponent) + } +}