diff --git a/packages/java-shell/build.gradle b/packages/java-shell/build.gradle index f655a0de23..2fc7b90513 100644 --- a/packages/java-shell/build.gradle +++ b/packages/java-shell/build.gradle @@ -28,7 +28,7 @@ compileJava.dependsOn browserifyShellApi dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" testCompile group: 'junit', name: 'junit', version: '4.12' - compile group: 'org.graalvm.js', name: 'js', version: '20.0.0' + compile group: 'org.graalvm.js', name: 'js', version: '20.2.0' compile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.7' compile group: 'org.apache.commons', name: 'commons-text', version: '1.8' } diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShell.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShell.kt index de8bba0c1f..c10e524f2f 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShell.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShell.kt @@ -2,9 +2,8 @@ package com.mongodb.mongosh import com.mongodb.client.MongoClient import com.mongodb.mongosh.result.MongoShellResult -import java.io.Closeable -class MongoShell(client: MongoClient) : Closeable { +class MongoShell(client: MongoClient) { private val context = MongoShellContext(client) fun eval(script: String): MongoShellResult<*> { @@ -14,5 +13,5 @@ class MongoShell(client: MongoClient) : Closeable { return context.extract(value, if (type.isString) type.asString() else null) } - override fun close() = context.close() + fun close() = context.close() } diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellContext.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellContext.kt index 5ac1f57d49..c40073aa75 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellContext.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/MongoShellContext.kt @@ -16,7 +16,7 @@ import org.graalvm.polyglot.Source import org.graalvm.polyglot.Value import org.graalvm.polyglot.proxy.ProxyExecutable import org.intellij.lang.annotations.Language -import java.io.Closeable +import java.lang.IllegalStateException import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter @@ -31,8 +31,8 @@ import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.regex.Pattern -internal class MongoShellContext(client: MongoClient) : Closeable { - val ctx: Context = Context.create() +internal class MongoShellContext(client: MongoClient) { + private var ctx: Context? = Context.create() private val serviceProvider = JavaServiceProvider(client, this) private val shellEvaluator: Value private val bsonTypes: BsonTypes @@ -44,6 +44,7 @@ internal class MongoShellContext(client: MongoClient) : Closeable { init { val setupScript = MongoShell::class.java.getResource("/js/all-standalone.js").readText() evalInner(setupScript, "all-standalone.js") + val ctx = checkClosed() val context = ctx.getBindings("js") val global = context["_global"]!! context.removeMember("_global") @@ -76,6 +77,7 @@ internal class MongoShellContext(client: MongoClient) : Closeable { context.putMember("Date", date) val isoDate = functionProducer.execute(ProxyExecutable { args -> dateHelper(true, args.toList()) }) context.putMember("ISODate", isoDate) + context.putMember("UUID", functionProducer.execute(ProxyExecutable { args -> if (args.isEmpty()) UUID.randomUUID() else UUID.fromString(args[0].asString()) })) } private fun dateHelper(createObject: Boolean, args: List): Any { @@ -124,9 +126,14 @@ internal class MongoShellContext(client: MongoClient) : Closeable { } operator fun get(value: String): Value? { + val ctx = checkClosed() return ctx.getBindings("js")[value] } + private fun checkClosed(): Context { + return this.ctx ?: throw IllegalStateException("Context has already been closed") + } + internal operator fun Value.get(identifier: String): Value? { return getMember(identifier) } @@ -158,7 +165,8 @@ internal class MongoShellContext(client: MongoClient) : Closeable { v.instanceOf("Promise") -> extract(unwrapPromise(v)) type == "Help" -> extract(v["attr"]!!) type == "Cursor" -> FindCursorResult(FindCursor(v, this)) - type == "AggregationCursor" -> AggregationCursorResult(AggregationCursor(v, this)) + // document with aggregation explain result also has type AggregationCursor, so we need to make sure that value contains cursor + type == "AggregationCursor" && v.hasMember("_cursor") -> AggregationCursorResult(AggregationCursor(v, this)) type == "InsertOneResult" -> InsertOneResult(v["acknowledged"]!!.asBoolean(), v["insertedId"]!!.asString()) type == "DeleteResult" -> DeleteResult(v["acknowledged"]!!.asBoolean(), v["deletedCount"]!!.asLong()) type == "UpdateResult" -> { @@ -240,6 +248,7 @@ internal class MongoShellContext(client: MongoClient) : Closeable { } private fun evalInner(@Language("js") script: String, name: String = "Unnamed"): Value { + val ctx = checkClosed() return ctx.eval(Source.newBuilder("js", script, name).buildLiteral()) } @@ -257,7 +266,11 @@ internal class MongoShellContext(client: MongoClient) : Closeable { } } - override fun close() = serviceProvider.close() + fun close() { + val ctx = checkClosed() + ctx.close(true) + this.ctx = null + } private fun Value.instanceOf(clazz: Value?): Boolean { return clazz != null && evalInner("(o, clazz) => o instanceof clazz", "instance_of_class_script").execute(this, clazz).asBoolean() diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/AggregationCursor.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/AggregationCursor.kt index b4b4c6406a..d7a8eaea72 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/AggregationCursor.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/AggregationCursor.kt @@ -1,20 +1,36 @@ package com.mongodb.mongosh.result import com.mongodb.mongosh.MongoShellContext -import org.bson.Document import org.graalvm.polyglot.Value -class AggregationCursor internal constructor(private val cursor: Value, private val context: MongoShellContext) : Cursor { +class AggregationCursor internal constructor(private var cursor: Value?, private var context: MongoShellContext?) : Cursor { override fun hasNext(): Boolean { + val (cursor, _) = checkClosed() return cursor.invokeMember("hasNext").asBoolean() } override fun next(): T { + val (cursor, context) = checkClosed() if (!hasNext()) throw NoSuchElementException() return context.extract(cursor.invokeMember("next")).value as T } override fun _asPrintable(): String { + val (cursor, context) = checkClosed() return context.extract(cursor.invokeMember("_asPrintable"))._asPrintable() } + + override fun close() { + val (c, _) = checkClosed() + c.invokeMember("close") + cursor = null + context = null + } + + private fun checkClosed(): Pair { + val cursor = this.cursor + val context = this.context + if (cursor == null || context == null) throw IllegalStateException("Cursor has already been closed") + return cursor to context + } } \ No newline at end of file diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/Cursor.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/Cursor.kt index c472595898..b43ecc6a3a 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/Cursor.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/Cursor.kt @@ -2,4 +2,5 @@ package com.mongodb.mongosh.result interface Cursor : Iterator { fun _asPrintable(): String + fun close() } diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/FindCursor.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/FindCursor.kt index cc29544541..e4673d8af7 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/FindCursor.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/result/FindCursor.kt @@ -1,24 +1,41 @@ package com.mongodb.mongosh.result import com.mongodb.mongosh.MongoShellContext -import org.bson.Document import org.graalvm.polyglot.Value -class FindCursor internal constructor(private val cursor: Value, private val context: MongoShellContext) : Cursor { +class FindCursor internal constructor(private var cursor: Value?, private var context: MongoShellContext?) : Cursor { override fun hasNext(): Boolean { + val (cursor, _) = checkClosed() return cursor.invokeMember("hasNext").asBoolean() } override fun next(): T { + val (cursor, context) = checkClosed() if (!hasNext()) throw NoSuchElementException() return context.extract(cursor.invokeMember("next")).value as T } fun batchSize(size: Int) { + val (cursor, _) = checkClosed() cursor.invokeMember("batchSize", size) } override fun _asPrintable(): String { + val (cursor, context) = checkClosed() return context.extract(cursor.invokeMember("_asPrintable"))._asPrintable() } + + override fun close() { + val (c, _) = checkClosed() + c.invokeMember("close") + cursor = null + context = null + } + + private fun checkClosed(): Pair { + val cursor = this.cursor + val context = this.context + if (cursor == null || context == null) throw IllegalStateException("Cursor has already been closed") + return cursor to context + } } \ No newline at end of file diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/AdminServiceProvider.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/AdminServiceProvider.kt new file mode 100644 index 0000000000..adec49a758 --- /dev/null +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/AdminServiceProvider.kt @@ -0,0 +1,10 @@ +package com.mongodb.mongosh.service + +import org.graalvm.polyglot.Value + +/** + * see service-provider-core/src/admin.ts + */ +internal interface AdminServiceProvider { + fun createCollection(database: String, collection: String, options: Value?): Value +} \ No newline at end of file diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/Cursor.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/Cursor.kt index deba6d2390..0650fb5c06 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/Cursor.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/Cursor.kt @@ -1,7 +1,6 @@ package com.mongodb.mongosh.service import com.mongodb.client.MongoCursor -import com.mongodb.client.model.Collation import com.mongodb.mongosh.MongoShellContext import com.mongodb.mongosh.result.DocumentResult import org.bson.Document @@ -56,7 +55,7 @@ internal class Cursor(private var helper: MongoIterableHelper<*>, private val co @HostAccess.Export override fun close() { closed = true - getOrCreateIterator().close() + iterator?.close() } @HostAccess.Export @@ -76,9 +75,7 @@ internal class Cursor(private var helper: MongoIterableHelper<*>, private val co if (!v.hasMembers()) { throw IllegalArgumentException("Expected one argument of type object. Got: $v") } - val collation = convert(Collation.builder(), collationConverters, collationDefaultConverter, toDocument(context, v)) - .getOrThrow() - .build() + val collation = toDocument(context, v) helper.collation(collation) return this } @@ -97,8 +94,9 @@ internal class Cursor(private var helper: MongoIterableHelper<*>, private val co } @HostAccess.Export - override fun explain(verbosity: String) { - throw NotImplementedError("explain is not supported") + override fun explain(verbosity: String?): Any? { + checkQueryNotExecuted() + return context.toJs(helper.explain(verbosity)) } @HostAccess.Export @@ -205,9 +203,10 @@ internal class Cursor(private var helper: MongoIterableHelper<*>, private val co } @HostAccess.Export - override fun setReadPreference(v: Value): Cursor { + override fun setReadPreference(v: String): Cursor { checkQueryNotExecuted() - throw NotImplementedError("setReadPreference is not supported") + helper = helper.readPrev(v, null) + return this } override fun showRecordId(v: Boolean): ServiceProviderCursor { diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/JavaServiceProvider.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/JavaServiceProvider.kt index 202bc7100f..6ca459b979 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/JavaServiceProvider.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/JavaServiceProvider.kt @@ -14,7 +14,7 @@ import org.graalvm.polyglot.Value import java.io.Closeable @Suppress("NAME_SHADOWING") -internal class JavaServiceProvider(private val client: MongoClient, private val context: MongoShellContext) : Closeable, ReadableServiceProvider, WritableServiceProvider { +internal class JavaServiceProvider(private val client: MongoClient, private val context: MongoShellContext) : ReadableServiceProvider, WritableServiceProvider, AdminServiceProvider { @JvmField @HostAccess.Export @@ -65,7 +65,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val val options = toDocument(options, "options") val dbOptions = toDocument(dbOptions, "dbOptions") getDatabase(database, dbOptions).flatMap { db -> - convert(ReplaceOptions(), replaceOptionsConverters, replaceOptionsDefaultConverters, options).map { options -> + convert(ReplaceOptions(), replaceOptionsConverters, replaceOptionsDefaultConverter, options).map { options -> val res = db.getCollection(collection).replaceOne(filter, replacement, options) mapOf("result" to mapOf("ok" to res.wasAcknowledged()), "matchedCount" to res.matchedCount, @@ -182,7 +182,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val }) } - private fun getWriteModel(model: Document): Either?> { + private fun getWriteModel(model: Document): Either> { if (model.keys.size != 1) return Left(IllegalArgumentException()) val key = model.keys.first() val innerDoc: Document = model[key] as? Document @@ -193,12 +193,43 @@ internal class JavaServiceProvider(private val client: MongoClient, private val ?: return Left(IllegalArgumentException("No property 'document' $innerDoc")) Right(InsertOneModel(doc)) } - "deleteOne" -> { + "deleteOne", "deleteMany" -> { val filter = innerDoc["filter"] as? Document ?: return Left(IllegalArgumentException("No property 'filter' $innerDoc")) val collationDoc = innerDoc["collation"] as? Document ?: Document() convert(Collation.builder(), collationConverters, collationDefaultConverter, collationDoc).map { collation -> - DeleteOneModel(filter, DeleteOptions().collation(collation.build())) + val opt = DeleteOptions().collation(collation.build()) + if (key == "deleteOne") DeleteOneModel(filter, opt) + else DeleteManyModel(filter, opt) + } + } + "updateOne", "updateMany" -> { + val filter = innerDoc["filter"] as? Document + ?: return Left(IllegalArgumentException("No property 'filter' $innerDoc")) + val update = innerDoc["update"] + ?: return Left(IllegalArgumentException("No property 'update' $innerDoc")) + convert(UpdateOptions(), updateOptionsConverters, updateOptionsDefaultConverter, innerDoc).flatMap { opt -> + val res: Either> = when (update) { + is Document -> { + val model: WriteModel = if (key == "updateOne") UpdateOneModel(filter, update, opt) else UpdateManyModel(filter, update, opt) + Right(model) + } + is List<*> -> { + val model: WriteModel = if (key == "updateOne") UpdateOneModel(filter, update.filterIsInstance(), opt) else UpdateManyModel(filter, update.filterIsInstance(), opt) + Right(model) + } + else -> Left(IllegalArgumentException("Property 'update' has to be a document of a list $innerDoc")) + } + res + } + } + "replaceOne" -> { + val filter = innerDoc["filter"] as? Document + ?: return Left(IllegalArgumentException("No property 'filter' $innerDoc")) + val replacement = innerDoc["replacement"] as? Document + ?: return Left(IllegalArgumentException("No property 'replacement' $innerDoc")) + convert(ReplaceOptions(), replaceOptionsConverters, replaceOptionsDefaultConverter, innerDoc).map { opt -> + ReplaceOneModel(filter, replacement, opt) } } else -> Left(IllegalArgumentException("Unknown bulk write operation $model")) @@ -292,9 +323,9 @@ internal class JavaServiceProvider(private val client: MongoClient, private val val options = toDocument(options, "options") val dbOptions = toDocument(dbOptions, "dbOptions") val db = getDatabase(database, dbOptions).getOrThrow() - val iterable = db.getCollection(collection).aggregate(pipeline.filterIsInstance()) - if (options != null) convert(iterable, aggregateConverters, aggregateDefaultConverter, options).getOrThrow() - return Cursor(helper(iterable, context), context) + val createOptions = AggregateCreateOptions(db, collection, pipeline.filterIsInstance(), options + ?: Document()) + return Cursor(AggregateIterableHelper(aggregate(createOptions), context, createOptions), context) } @HostAccess.Export @@ -304,9 +335,9 @@ internal class JavaServiceProvider(private val client: MongoClient, private val val options = toDocument(options, "options") val dbOptions = toDocument(dbOptions, "dbOptions") val db = getDatabase(database, dbOptions).getOrThrow() - val iterable = db.aggregate(pipeline.filterIsInstance()) - if (options != null) convert(iterable, aggregateConverters, aggregateDefaultConverter, options).getOrThrow() - return Cursor(helper(iterable, context), context) + val createOptions = AggregateCreateOptions(db, null, pipeline.filterIsInstance(), options + ?: Document()) + return Cursor(AggregateIterableHelper(aggregate(createOptions), context, createOptions), context) } @HostAccess.Export @@ -352,11 +383,9 @@ internal class JavaServiceProvider(private val client: MongoClient, private val override fun find(database: String, collection: String, filter: Value?, options: Value?): Cursor { val filter = toDocument(filter, "filter") val options = toDocument(options, "options") - val coll = client.getDatabase(database).getCollection(collection) - val iterable = if (filter == null) coll.find() else coll.find(filter) - val projection = options?.get("projection")?.let { it as Document } - if (projection != null) iterable.projection(projection) - return Cursor(helper(iterable, context), context) + val db = client.getDatabase(database) + val createOptions = FindCreateOptions(db, collection, filter ?: Document(), options ?: Document()) + return Cursor(FindIterableHelper(find(createOptions), context, createOptions), context) } private fun toDocument(value: Value?, fieldName: String): Document? { @@ -436,6 +465,16 @@ internal class JavaServiceProvider(private val client: MongoClient, private val Left(NotImplementedError()) } + @HostAccess.Export + override fun createCollection(database: String, collection: String, options: Value?): Value = promise { + val options = toDocument(options, "options") ?: Document() + getDatabase(database, null).flatMap { db -> + convert(CreateCollectionOptions(), createCollectionOptionsConverters, createCollectionOptionsConverter, options).map { opt -> + db.createCollection(collection, opt) + } + } + } + @HostAccess.Export override fun createIndexes(database: String, collection: String, indexSpecs: Value?): Value = promise { val indexSpecs = toList(indexSpecs, "indexSpecs") ?: emptyList() @@ -486,8 +525,6 @@ internal class JavaServiceProvider(private val client: MongoClient, private val Left(NotImplementedError()) } - override fun close() = client.close() - private fun promise(block: () -> Either): Value { return context.toJsPromise(block()) } diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/MongoIterableHelper.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/MongoIterableHelper.kt index 5a469bc135..8f5b1e170a 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/MongoIterableHelper.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/MongoIterableHelper.kt @@ -1,20 +1,15 @@ package com.mongodb.mongosh.service -import com.mongodb.CursorType +import com.mongodb.* import com.mongodb.client.AggregateIterable import com.mongodb.client.FindIterable +import com.mongodb.client.MongoDatabase import com.mongodb.client.MongoIterable -import com.mongodb.client.model.Collation import com.mongodb.mongosh.MongoShellContext import org.bson.Document import org.graalvm.polyglot.Value -import java.util.concurrent.TimeUnit - -internal open class MongoIterableHelper>(val iterable: T, private val context: MongoShellContext) { - fun batchSize(v: Int) { - iterable.batchSize(v) - } +internal open class MongoIterableHelper>(val iterable: T, protected val context: MongoShellContext) { fun map(function: Value): MongoIterableHelper<*> { return helper(iterable.map { v -> context.extract(function.execute(context.toJs(v))).value @@ -27,6 +22,7 @@ internal open class MongoIterableHelper>(val iterabl fun toArray(): List = iterable.toList() + open fun batchSize(v: Int): Unit = throw NotImplementedError("batchSize is not supported") open fun limit(v: Int): Unit = throw NotImplementedError("limit is not supported") open fun max(v: Document): Unit = throw NotImplementedError("max is not supported") open fun min(v: Document): Unit = throw NotImplementedError("min is not supported") @@ -35,7 +31,7 @@ internal open class MongoIterableHelper>(val iterabl open fun comment(v: String): Unit = throw NotImplementedError("comment is not supported") open fun hint(v: String): Unit = throw NotImplementedError("hint is not supported") open fun hint(v: Document): Unit = throw NotImplementedError("hint is not supported") - open fun collation(v: Collation): Unit = throw NotImplementedError("collation is not supported") + open fun collation(v: Document): Unit = throw NotImplementedError("collation is not supported") open fun allowPartialResults(): Unit = throw NotImplementedError("allowPartialResults is not supported") open fun count(): Int = throw NotImplementedError("count is not supported") open fun maxTimeMS(v: Long): Unit = throw NotImplementedError("maxTimeMS is not supported") @@ -44,96 +40,125 @@ internal open class MongoIterableHelper>(val iterabl open fun returnKey(v: Boolean): Unit = throw NotImplementedError("returnKey is not supported") open fun sort(spec: Document): Unit = throw NotImplementedError("sort is not supported") open fun tailable(): Unit = throw NotImplementedError("tailable is not supported") + open fun explain(verbosity: String?): Any? = throw NotImplementedError("explain is not supported") + open fun readPrev(v: String, tags: List?): MongoIterableHelper<*> = throw NotImplementedError("readPrev is not supported") } -internal class AggregateIterableHelper(iterable: AggregateIterable, context: MongoShellContext) : MongoIterableHelper>(iterable, context) { - override fun maxTimeMS(v: Long) { - iterable.maxTime(v, TimeUnit.MILLISECONDS) - } - - override fun comment(v: String) { - iterable.comment(v) - } - - override fun hint(v: Document) { - iterable.hint(v) - } - - override fun collation(v: Collation) { - iterable.collation(v) - } +internal data class AggregateCreateOptions(val db: MongoDatabase, + val collection: String?, + val pipeline: List, + val options: Document) + +internal class AggregateIterableHelper(iterable: AggregateIterable, + context: MongoShellContext, + private val createOptions: AggregateCreateOptions?) + : MongoIterableHelper>(iterable, context) { + + override fun explain(verbosity: String?): Any? { + check(createOptions != null) { "createOptions were not saved" } + val explain = Document() + explain["aggregate"] = createOptions.collection ?: 1 + explain["pipeline"] = createOptions.pipeline + explain["explain"] = true + explain.putAll(createOptions.options) + return createOptions.db.runCommand(explain) + } + + override fun readPrev(v: String, tags: List?): AggregateIterableHelper { + check(createOptions != null) { "createOptions were not saved" } + val newDb = if (tags == null) createOptions.db.withReadPreference(ReadPreference.valueOf(v)) + else createOptions.db.withReadPreference(ReadPreference.valueOf(v, tags)) + val newCreateOptions = createOptions.copy(db = newDb) + val newIterable = aggregate(newCreateOptions) + return AggregateIterableHelper(newIterable, context, newCreateOptions) + } + + private fun set(key: String, value: Any?) { + val options = createOptions?.options ?: Document() + options[key] = value + convert(iterable, aggregateConverters, aggregateDefaultConverter, options, key) + } + + override fun batchSize(v: Int) = set("batchSize", v) + override fun maxTimeMS(v: Long) = set("maxTimeMS", v) + override fun comment(v: String) = set("comment", v) + override fun hint(v: Document) = set("hint", v) + override fun collation(v: Document) = set("collation", v) } -internal class FindIterableHelper(iterable: FindIterable, context: MongoShellContext) : MongoIterableHelper>(iterable, context) { - override fun allowPartialResults() { - iterable.partial(true) - } - - override fun oplogReplay() { - iterable.oplogReplay(true) - } - - override fun noCursorTimeout() { - iterable.noCursorTimeout(true) - } - - override fun maxTimeMS(v: Long) { - iterable.maxTime(v, TimeUnit.MILLISECONDS) - } - - override fun projection(v: Document) { - iterable.projection(v) - } - - override fun limit(v: Int) { - iterable.limit(v) - } - - override fun max(v: Document) { - iterable.max(v) - } - - override fun min(v: Document) { - iterable.min(v) - } - - override fun skip(v: Int) { - iterable.skip(v) - } - - override fun comment(v: String) { - iterable.comment(v) - } - - override fun hint(v: Document) { - iterable.hint(v) - } - - override fun hint(v: String) { - iterable.hintString(v) - } - - override fun collation(v: Collation) { - iterable.collation(v) - } +internal fun find(createOptions: FindCreateOptions): FindIterable { + val coll = createOptions.collection + val db = createOptions.db + val find = createOptions.find + val iterable = db.getCollection(coll).find(find) + convert(iterable, findConverters, findDefaultConverter, createOptions.options).getOrThrow() + return iterable +} - override fun returnKey(v: Boolean) { - iterable.returnKey(v) - } +internal fun aggregate(createOptions: AggregateCreateOptions): AggregateIterable { + val coll = createOptions.collection + val db = createOptions.db + val pipeline = createOptions.pipeline + val iterable = if (coll == null) db.aggregate(pipeline) + else db.getCollection(coll).aggregate(pipeline) - override fun sort(spec: Document) { - iterable.sort(spec) - } + convert(iterable, aggregateConverters, aggregateDefaultConverter, createOptions.options).getOrThrow() + return iterable +} - override fun tailable() { - iterable.cursorType(CursorType.Tailable) +internal data class FindCreateOptions(val db: MongoDatabase, + val collection: String, + val find: Document, + val options: Document) + +internal class FindIterableHelper(iterable: FindIterable, + context: MongoShellContext, + private val createOptions: FindCreateOptions?) + : MongoIterableHelper>(iterable, context) { + + override fun readPrev(v: String, tags: List?): FindIterableHelper { + check(createOptions != null) { "createOptions were not saved" } + val newDb = if (tags == null) createOptions.db.withReadPreference(ReadPreference.valueOf(v)) + else createOptions.db.withReadPreference(ReadPreference.valueOf(v, tags)) + val newCreateOptions = createOptions.copy(db = newDb) + val newIterable = find(newCreateOptions) + return FindIterableHelper(newIterable, context, newCreateOptions) + } + + private fun set(key: String, value: Any?) { + val options = createOptions?.options ?: Document() + options[key] = value + convert(iterable, findConverters, findDefaultConverter, options, key) + } + + override fun batchSize(v: Int) = set("batchSize", v) + override fun allowPartialResults() = set("allowPartialResults", true) + override fun oplogReplay() = set("oplogReplay", true) + override fun noCursorTimeout() = set("noCursorTimeout", true) + override fun maxTimeMS(v: Long) = set("maxTimeMS", v) + override fun projection(v: Document) = set("projection", v) + override fun limit(v: Int) = set("limit", v) + override fun max(v: Document) = set("max", v) + override fun min(v: Document) = set("min", v) + override fun skip(v: Int) = set("skip", v) + override fun comment(v: String) = set("comment", v) + override fun hint(v: Document) = set("hint", v) + override fun hint(v: String) = set("hint", v) + override fun collation(v: Document) = set("collation", v) + override fun returnKey(v: Boolean) = set("returnKey", v) + override fun sort(spec: Document) = set("sort", spec) + override fun tailable() = set("tailable", CursorType.Tailable.toString()) + + override fun explain(verbosity: String?): Any? { + set("explain", true) + return iterable.first() } } internal fun helper(iterable: MongoIterable, context: MongoShellContext): MongoIterableHelper<*> { return when (iterable) { - is FindIterable -> FindIterableHelper(iterable, context) - is AggregateIterable -> AggregateIterableHelper(iterable, context) + is FindIterable -> FindIterableHelper(iterable, context, null) + is AggregateIterable -> AggregateIterableHelper(iterable, context, null) else -> MongoIterableHelper(iterable, context) } } \ No newline at end of file diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/ServiceProviderCursor.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/ServiceProviderCursor.kt index 67a003c075..6665ee2c8e 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/ServiceProviderCursor.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/ServiceProviderCursor.kt @@ -28,11 +28,11 @@ interface ServiceProviderCursor { fun next(): Any? fun project(v: Value): ServiceProviderCursor fun returnKey(v: Value): ServiceProviderCursor - fun setReadPreference(v: Value): ServiceProviderCursor + fun setReadPreference(v: String): ServiceProviderCursor fun showRecordId(v: Boolean): ServiceProviderCursor fun size(): Value fun skip(v: Int): ServiceProviderCursor fun sort(spec: Value): ServiceProviderCursor fun toArray(): Any? - fun explain(verbosity: String): Any? + fun explain(verbosity: String?): Any? } diff --git a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/optionConverters.kt b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/optionConverters.kt index 1a52543526..f008979a3a 100644 --- a/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/optionConverters.kt +++ b/packages/java-shell/src/main/kotlin/com/mongodb/mongosh/service/optionConverters.kt @@ -1,10 +1,13 @@ package com.mongodb.mongosh.service +import com.mongodb.CursorType import com.mongodb.ReadConcern import com.mongodb.ReadConcernLevel import com.mongodb.ReadPreference import com.mongodb.client.AggregateIterable +import com.mongodb.client.FindIterable import com.mongodb.client.MongoDatabase +import com.mongodb.client.MongoIterable import com.mongodb.client.model.* import com.mongodb.mongosh.result.CommandException import org.bson.Document @@ -14,11 +17,14 @@ import java.util.concurrent.TimeUnit internal fun convert(o: T, converters: Map Either>, defaultConverter: (T, String, Any?) -> Either, - map: Map<*, *>?): Either { + map: Map<*, *>?, + prop: String? = null): Either { if (map == null) return Right(o) var accumulator = o - for ((key, value) in map.entries) { + val keys = if (prop == null) map.keys else listOf(prop) + for (key in keys) { if (key !is String) continue + val value = map[key] val converter = converters[key] val res = if (converter != null) converter(accumulator, value) else defaultConverter(accumulator, key, value) when (res) { @@ -125,8 +131,8 @@ internal val collationConverters: Map Eithe internal val collationDefaultConverter = unrecognizedField("collation") -internal val aggregateConverters: Map, Any?) -> Either>> = mapOf( - typed("collation", Map::class.java) { iterable, value -> +internal val aggregateConverters: Map, Any?) -> Either>> = mapOf( + typed("collation", Document::class.java) { iterable, value -> val collation = convert(Collation.builder(), collationConverters, collationDefaultConverter, value) .getOrThrow() .build() @@ -135,8 +141,11 @@ internal val aggregateConverters: Map, Any? typed("allowDiskUse", Boolean::class.java) { iterable, value -> iterable.allowDiskUse(value) }, - typed("cursor", Map::class.java) { iterable, value -> - convert(iterable, cursorConverters, cursorDefaultConverter, value).getOrThrow() + typed("batchSize", Number::class.java) { iterable, value -> + iterable.batchSize(value.toInt()) + }, + typed("cursor", Document::class.java) { iterable, value -> + convert(iterable, cursorConverters, cursorDefaultConverter, value).getOrThrow() as AggregateIterable<*> }, typed("maxTimeMS", Number::class.java) { iterable, value -> iterable.maxTime(value.toLong(), TimeUnit.MILLISECONDS) @@ -149,7 +158,7 @@ internal val aggregateConverters: Map, Any? "hint" to { iterable, value -> when (value) { is String -> Right(iterable.hint(Document(value, 1))) - is Map<*, *> -> Right(iterable.hint(Document(value as Map))) + is Document -> Right(iterable.hint(value)) else -> Left(CommandException("hint must be string or object value", "TypeMismatch")) } }, @@ -158,15 +167,83 @@ internal val aggregateConverters: Map, Any? } ) -internal val aggregateDefaultConverter = unrecognizedField>("aggregate options") +internal val aggregateDefaultConverter = unrecognizedField>("aggregate options") + +internal val findConverters: Map, Any?) -> Either>> = mapOf( + typed("allowPartialResults", Boolean::class.java) { iterable, value -> + iterable.partial(value) + }, + typed("batchSize", Number::class.java) { iterable, value -> + iterable.batchSize(value.toInt()) + }, + typed("collation", Document::class.java) { iterable, value -> + val collation = convert(Collation.builder(), collationConverters, collationDefaultConverter, value) + .getOrThrow() + .build() + iterable.collation(collation) + }, + typed("comment", String::class.java) { iterable, value -> + iterable.comment(value) + }, + typed("cursor", Document::class.java) { iterable, value -> + convert(iterable, cursorConverters, cursorDefaultConverter, value).getOrThrow() as FindIterable<*> + }, + typed("explain", Boolean::class.java) { iterable, value -> + iterable.modifiers(Document("\$explain", value)) + }, + "hint" to { iterable, value -> + when (value) { + is String -> Right(iterable.hint(Document(value, 1))) + is Document -> Right(iterable.hint(value)) + else -> Left(CommandException("hint must be string or object value", "TypeMismatch")) + } + }, + typed("limit", Number::class.java) { iterable, value -> + iterable.limit(value.toInt()) + }, + typed("max", Document::class.java) { iterable, value -> + iterable.max(value) + }, + typed("maxTimeMS", Number::class.java) { iterable, value -> + iterable.maxTime(value.toLong(), TimeUnit.MILLISECONDS) + }, + typed("min", Document::class.java) { iterable, value -> + iterable.min(value) + }, + typed("noCursorTimeout", Boolean::class.java) { iterable, value -> + iterable.noCursorTimeout(value) + }, + typed("oplogReplay", Boolean::class.java) { iterable, value -> + iterable.oplogReplay(value) + }, + typed("projection", Document::class.java) { iterable, value -> + iterable.projection(value) + }, + typed("returnKey", Boolean::class.java) { iterable, value -> + iterable.returnKey(value) + }, + "readConcern" to { iterable, _ -> Right(iterable) }, // the value is copied to dbOptions + typed("sort", Document::class.java) { iterable, value -> + iterable.sort(value) + }, + typed("skip", Number::class.java) { iterable, value -> + iterable.skip(value.toInt()) + }, + typed("tailable", String::class.java) { iterable, value -> + iterable.cursorType(CursorType.valueOf(value)) + }, + "writeConcern" to { iterable, _ -> Right(iterable) } // the value is copied to dbOptions +) + +internal val findDefaultConverter = unrecognizedField>("find options") -internal val cursorConverters: Map, Any?) -> Either>> = mapOf( +internal val cursorConverters: Map, Any?) -> Either>> = mapOf( typed("batchSize", Int::class.java) { iterable, v -> iterable.batchSize(v) } ) -internal val cursorDefaultConverter = unrecognizedField>("cursor") +internal val cursorDefaultConverter = unrecognizedField>("cursor") internal val countOptionsConverters: Map Either> = mapOf( typed("limit", Number::class.java) { opt, value -> @@ -217,10 +294,12 @@ internal val replaceOptionsConverters: Map Eit }, typed("bypassDocumentValidation", Boolean::class.java) { opt, value -> opt.bypassDocumentValidation(value) - } + }, + "filter" to { opt, _ -> Right(opt) }, + "replacement" to { opt, _ -> Right(opt) } ) -internal val replaceOptionsDefaultConverters = unrecognizedField("replace options") +internal val replaceOptionsDefaultConverter = unrecognizedField("replace options") internal val findOneAndReplaceOptionsConverters: Map Either> = mapOf( typed("projection", Document::class.java) { opt, value -> @@ -384,6 +463,70 @@ internal val indexModelConverters: Map Either("index model") +internal val createCollectionOptionsConverters: Map Either> = mapOf( + typed("capped", Boolean::class.java) { opt, value -> + opt.capped(value) + }, + typed("autoIndexId", Boolean::class.java) { opt, value -> + opt.autoIndex(value) + }, + typed("size", Number::class.java) { opt, value -> + opt.sizeInBytes(value.toLong()) + }, + typed("max", Number::class.java) { opt, value -> + opt.maxDocuments(value.toLong()) + }, + typed("storageEngine", Document::class.java) { opt, value -> + opt.storageEngineOptions(value) + }, + typed("validator", Document::class.java) { opt, value -> + opt.validationOptions.validator(value) + opt + }, + typed("validationLevel", String::class.java) { opt, value -> + opt.validationOptions.validationLevel(ValidationLevel.fromString(value)) + opt + }, + typed("validationAction", String::class.java) { opt, value -> + opt.validationOptions.validationAction(ValidationAction.fromString(value)) + opt + }, + typed("indexOptionDefaults", Document::class.java) { opt, value -> + opt.indexOptionDefaults(IndexOptionDefaults().storageEngine(value)) + }, + typed("collation", Document::class.java) { opt, value -> + val collation = convert(Collation.builder(), collationConverters, collationDefaultConverter, value) + .getOrThrow() + .build() + opt.collation(collation) + }, + "writeConcern" to { iterable, _ -> Right(iterable) } // the value is copied to dbOptions +) + +internal val createCollectionOptionsConverter = unrecognizedField("create collection options") + +internal val updateOptionsConverters: Map Either> = mapOf( + typed("collation", Document::class.java) { opt, value -> + val collation = convert(Collation.builder(), collationConverters, collationDefaultConverter, value) + .getOrThrow() + .build() + opt.collation(collation) + }, + typed("upsert", Boolean::class.java) { opt, value -> + opt.upsert(value) + }, + typed("bypassDocumentValidation", Boolean::class.java) { opt, value -> + opt.bypassDocumentValidation(value) + }, + typed("arrayFilters", List::class.java) { opt, value -> + opt.arrayFilters(value.filterIsInstance()) + }, + "filter" to { opt, _ -> Right(opt) }, + "update" to { opt, _ -> Right(opt) } +) + +internal val updateOptionsDefaultConverter = unrecognizedField("update options") + internal fun typed(name: String, clazz: Class, apply: (T, C) -> T): Pair Either> = name to { o, value -> val casted = value as? C diff --git a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CollectionTest.kt b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CollectionTest.kt index 51d444c356..8266839fbe 100644 --- a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CollectionTest.kt +++ b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CollectionTest.kt @@ -31,7 +31,6 @@ class CollectionTest : ShellTestCase() { @Test fun testEmptyCollection() = test() @Test fun testEstimatedDocumentCount() = test() @Test fun testEstimatedDocumentCountWithMaxTime() = test() - @Test fun testExplainFind() = test() @Test fun testFind() = test() @Test fun testFindOneAndDelete() = test() @Test fun testFindOneAndReplace() = test() diff --git a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CursorTest.kt b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CursorTest.kt index 297ac1efa1..17602b7d9f 100644 --- a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CursorTest.kt +++ b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/CursorTest.kt @@ -4,10 +4,14 @@ import org.junit.Test class CursorTest : ShellTestCase() { + @Test fun testAggregateExplain() = test() @Test fun testAggregateIsClosed() = test() + @Test fun testAggregateReadPref() = test() + @Test fun testBatchSize() = test() @Test fun testCollation() = test() @Test fun testComment() = test() @Test fun testCursorHelp() = test() + @Test fun testExplain() = test() @Test fun testForEach() = test() @Test fun testHint() = test() @Test fun testIsClosed() = test() @@ -23,6 +27,7 @@ class CursorTest : ShellTestCase() { @Test fun testNoCursorTimeout() = test() @Test fun testProjection() = test() @Test fun testReadConcern() = test() + @Test fun testReadPref() = test() @Test fun testReturnKey() = test() @Test fun testSkip() = test() @Test fun testSort() = test() diff --git a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/DbTest.kt b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/DbTest.kt index 64672c268f..30f9924ed8 100644 --- a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/DbTest.kt +++ b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/DbTest.kt @@ -4,6 +4,7 @@ import org.junit.Test class DbTest : ShellTestCase() { + @Test fun testCreateCollection() = test() @Test fun testDbHelp() = test() @Test fun testDefaultDb() = test() @Test fun testGetCollection() = test() @@ -11,6 +12,7 @@ class DbTest : ShellTestCase() { @Test fun testHelp() = test() @Test fun testRunCommand() = test() @Test fun testRunCommandUserInfo() = test() + @Test fun testServerStatus() = test() @Test fun testShowCollections() = test() @Test fun testShowDatabases() = test() @Test fun testUseDb() = test() diff --git a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/LiteralsTest.kt b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/LiteralsTest.kt index d3c25b10b3..062c21969b 100644 --- a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/LiteralsTest.kt +++ b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/LiteralsTest.kt @@ -37,7 +37,7 @@ class LiteralsTest : ShellTestCase() { @Test fun testUnknownSymbol() = test() @Test fun testUUID() = test() - fun test() { + private fun test() { val name = (Throwable()).stackTrace[1].methodName.removePrefix("test") withShell { shell -> diff --git a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/util.kt b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/util.kt index 231cb97364..4069edc91d 100644 --- a/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/util.kt +++ b/packages/java-shell/src/test/kotlin/com/mongodb/mongosh/util.kt @@ -72,6 +72,9 @@ fun doTest(testName: String, shell: MongoShell, testDataPath: String, db: String (result.value as Cursor<*>).hasNext() // test that value is iterable } val actualValue = getActualValue(result, cmd.options) + if (result is CursorResult) { + (result.value as Cursor<*>).close() // test close + } val normalized = if (cmd.options.dontReplaceId) actualValue.trim() else replaceUUID(replaceId(actualValue)).trim() sb.append(normalized) } catch (e: Throwable) { diff --git a/packages/java-shell/src/test/resources/collection/aggregateWithExplain.expected.txt b/packages/java-shell/src/test/resources/collection/aggregateWithExplain.expected.txt index 48df1aed7e..56a6051ca2 100644 --- a/packages/java-shell/src/test/resources/collection/aggregateWithExplain.expected.txt +++ b/packages/java-shell/src/test/resources/collection/aggregateWithExplain.expected.txt @@ -1 +1 @@ -kotlin.NotImplementedError: explain is not supported \ No newline at end of file +1 \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/collection/aggregateWithExplain.js b/packages/java-shell/src/test/resources/collection/aggregateWithExplain.js index c9d31d9df3..d47e4f12f1 100644 --- a/packages/java-shell/src/test/resources/collection/aggregateWithExplain.js +++ b/packages/java-shell/src/test/resources/collection/aggregateWithExplain.js @@ -6,7 +6,7 @@ db.coll.insertOne({status: "A", amount: 3, group: "11"}); db.coll.insertOne({status: "A", amount: 1, group: "2"}); db.coll.insertOne({status: "A", amount: 1, group: "2"}); db.coll.insertOne({status: "B", amount: 1, group: "2"}); -// command +// command extractProperty=ok db.coll.aggregate([ {$match: {status: "A"}}, {$group: {_id: "$group", total: {$sum: "$amount"}}}, diff --git a/packages/java-shell/src/test/resources/collection/bulkWrite.expected.txt b/packages/java-shell/src/test/resources/collection/bulkWrite.expected.txt index 0bd739516e..d4f369f985 100644 --- a/packages/java-shell/src/test/resources/collection/bulkWrite.expected.txt +++ b/packages/java-shell/src/test/resources/collection/bulkWrite.expected.txt @@ -1 +1,2 @@ -{ "acknowledged": true, "insertedCount": 2, "matchedCount": 0, "modifiedCount": 0, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": [ ] } \ No newline at end of file +{ "acknowledged": true, "insertedCount": 2, "matchedCount": 3, "modifiedCount": 3, "deletedCount": 2, "upsertedCount": 1, "upsertedIds": [ { } ] } +[ { "_id": , "v": "after update" }, { "_id": , "v": "after update many" }, { "_id": , "v": "after update many" }, { "_id": , "a": 1 }, { "_id": , "a": 1 }, { "_id": , "v2": "after replace" } ] \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/collection/bulkWrite.js b/packages/java-shell/src/test/resources/collection/bulkWrite.js index 07228ba10e..71f144528d 100644 --- a/packages/java-shell/src/test/resources/collection/bulkWrite.js +++ b/packages/java-shell/src/test/resources/collection/bulkWrite.js @@ -1,6 +1,20 @@ // before db.coll.remove({}); +db.coll.insertOne({v: "before update"}); +db.coll.insertOne({v: "to delete"}); +db.coll.insertOne({v: "to delete"}); +db.coll.insertOne({v: "before update many"}); +db.coll.insertOne({v: "before update many"}); // command -db.coll.bulkWrite([{insertOne: {document: {a: 1}}},{insertOne: {document: {a: 1}}}]); +db.coll.bulkWrite([ + {insertOne: {document: {a: 1}}}, + {insertOne: {document: {a: 1}}}, + {updateOne: {filter: {v: "before update"}, update: {$set: {v: "after update"}}, upsert: false}}, + {deleteMany: {filter: {v: "to delete"}}}, + {updateMany: {filter: {v: "before update many"}, update: {$set: {v: "after update many"}}}}, + {replaceOne: {filter: {v: "before replace"}, replacement: {v2: "after replace"}, upsert: true}}, +]); +// command +db.coll.find(); // clear db.coll.drop(); diff --git a/packages/java-shell/src/test/resources/collection/explainFind-ignored.js b/packages/java-shell/src/test/resources/collection/explainFind-ignored.js deleted file mode 100644 index 04491f834a..0000000000 --- a/packages/java-shell/src/test/resources/collection/explainFind-ignored.js +++ /dev/null @@ -1,7 +0,0 @@ -// before -db.coll.remove({}); -db.coll.insertOne({a: 1}); -// command -db.coll.find().explain(); -// clear -db.coll.drop(); diff --git a/packages/java-shell/src/test/resources/collection/explainFind.expected.txt b/packages/java-shell/src/test/resources/collection/explainFind.expected.txt deleted file mode 100644 index 48df1aed7e..0000000000 --- a/packages/java-shell/src/test/resources/collection/explainFind.expected.txt +++ /dev/null @@ -1 +0,0 @@ -kotlin.NotImplementedError: explain is not supported \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/aggregateExplain.expected.txt b/packages/java-shell/src/test/resources/cursor/aggregateExplain.expected.txt new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/aggregateExplain.expected.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/aggregateExplain.js b/packages/java-shell/src/test/resources/cursor/aggregateExplain.js new file mode 100644 index 0000000000..31a403235b --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/aggregateExplain.js @@ -0,0 +1,9 @@ +// before +db.coll.remove({}); +db.coll.insert({"_id": 1, name: "Vasya"}); +db.coll.insert({"_id": 2, name: "Petya"}); +db.coll.insert({"_id": 3, name: "Lyusya"}); +// command extractProperty=ok +db.coll.explain().aggregate([{$sort: {_id: -1}}], {collation: {"locale": "en_US", strength: 1}}); +// clear +db.coll.drop(); \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/aggregateReadPref-ignored.js b/packages/java-shell/src/test/resources/cursor/aggregateReadPref-ignored.js new file mode 100644 index 0000000000..759017a8b4 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/aggregateReadPref-ignored.js @@ -0,0 +1,16 @@ +// before +db.coll.remove({}); +db.coll.insertOne({a: "a"}); +db.coll.insertOne({a: "A"}); +db.coll.insertOne({a: "á"}); +// command +db.coll.aggregate([{$match: {a: "a"}}], {collation: {"locale": "en_US", strength: 1}}) + .readPref('nearest'); +// command +db.coll.aggregate([{$match: {a: "a"}}], {collation: {"locale": "en_US", strength: 1}}) + .readPref("secondary", [{"region": "South"}]); +// command +db.coll.aggregate([{$match: {a: "a"}}], {collation: {"locale": "en_US", strength: 1}}) + .readPref("secondary", [{"region": "South", "datacenter": "A"}, {}]); +// clear +db.coll.drop(); diff --git a/packages/java-shell/src/test/resources/cursor/aggregateReadPref.expected.txt b/packages/java-shell/src/test/resources/cursor/aggregateReadPref.expected.txt new file mode 100644 index 0000000000..95616a5926 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/aggregateReadPref.expected.txt @@ -0,0 +1,3 @@ +[ { "_id": , "a": "a" }, { "_id": , "a": "A" }, { "_id": , "a": "\u00E1" } ] +[ { "_id": , "a": "a" }, { "_id": , "a": "A" }, { "_id": , "a": "\u00E1" } ] +[ { "_id": , "a": "a" }, { "_id": , "a": "A" }, { "_id": , "a": "\u00E1" } ] \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/batchSize.expected.txt b/packages/java-shell/src/test/resources/cursor/batchSize.expected.txt new file mode 100644 index 0000000000..270cb0b2e8 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/batchSize.expected.txt @@ -0,0 +1 @@ +[ { "_id": , "a": "a" } ] \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/batchSize.js b/packages/java-shell/src/test/resources/cursor/batchSize.js new file mode 100644 index 0000000000..fab6786a60 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/batchSize.js @@ -0,0 +1,9 @@ +// before +db.coll.remove({}); +db.coll.insertOne({a: "a"}); +db.coll.insertOne({a: "A"}); +db.coll.insertOne({a: "á"}); +// command +db.coll.find({a: "a"}).batchSize(10); +// clear +db.coll.drop(); diff --git a/packages/java-shell/src/test/resources/cursor/explain.expected.txt b/packages/java-shell/src/test/resources/cursor/explain.expected.txt new file mode 100644 index 0000000000..7c17da72b6 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/explain.expected.txt @@ -0,0 +1,4 @@ +"admin.coll" +"admin.coll" +"admin.coll" +"admin.coll" \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/explain.js b/packages/java-shell/src/test/resources/cursor/explain.js new file mode 100644 index 0000000000..785aae127d --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/explain.js @@ -0,0 +1,15 @@ +// before +db.coll.remove({}); +db.coll.insert({"_id": 1, name: "Vasya"}); +db.coll.insert({"_id": 2, name: "Petya"}); +db.coll.insert({"_id": 3, name: "Lyusya"}); +// command extractProperty=queryPlanner extractProperty=namespace +db.coll.explain().find(); +// command extractProperty=queryPlanner extractProperty=namespace +db.coll.explain("executionStats").find(); +// command extractProperty=queryPlanner extractProperty=namespace +db.coll.find().explain(); +// command extractProperty=queryPlanner extractProperty=namespace +db.coll.find().explain("executionStats"); +// clear +db.coll.drop(); \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/readPref.expected.txt b/packages/java-shell/src/test/resources/cursor/readPref.expected.txt new file mode 100644 index 0000000000..63de8d03b5 --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/readPref.expected.txt @@ -0,0 +1,3 @@ +[ { "_id": , "a": "a" }, { "_id": , "a": "A" }, { "_id": , "a": "\u00E1" } ] +java.lang.Exception: MongoshUnimplementedError: the tagSet argument is not yet supported. +java.lang.Exception: MongoshUnimplementedError: the tagSet argument is not yet supported. \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/cursor/readPref.js b/packages/java-shell/src/test/resources/cursor/readPref.js new file mode 100644 index 0000000000..857bedcc3b --- /dev/null +++ b/packages/java-shell/src/test/resources/cursor/readPref.js @@ -0,0 +1,19 @@ +// before +db.coll.remove({}); +db.coll.insertOne({a: "a"}); +db.coll.insertOne({a: "A"}); +db.coll.insertOne({a: "á"}); +// command +db.coll.find({a: "a"}) + .collation({"locale": "en_US", strength: 1}) + .readPref('nearest'); +// command +db.coll.find({a: "a"}) + .collation({"locale": "en_US", strength: 1}) + .readPref("secondary", [{"region": "South"}]); +// command +db.coll.find({a: "a"}) + .collation({"locale": "en_US", strength: 1}) + .readPref("secondary", [{"region": "South", "datacenter": "A"}, {}]); +// clear +db.coll.drop(); diff --git a/packages/java-shell/src/test/resources/db/createCollection.expected.txt b/packages/java-shell/src/test/resources/db/createCollection.expected.txt new file mode 100644 index 0000000000..ce2e5a343d --- /dev/null +++ b/packages/java-shell/src/test/resources/db/createCollection.expected.txt @@ -0,0 +1 @@ +com.mongodb.MongoBulkWriteException: Bulk write operation error on server localhost:27017. Write errors: [BulkWriteError{index=0, code=121, message='Document failed validation', details={}}]. \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/db/createCollection.js b/packages/java-shell/src/test/resources/db/createCollection.js new file mode 100644 index 0000000000..64c2a762cb --- /dev/null +++ b/packages/java-shell/src/test/resources/db/createCollection.js @@ -0,0 +1,34 @@ +// before +db.coll.drop(); +db.collWithValidator.drop(); +// command +db.createCollection('coll') +// command +db.createCollection('collWithValidator', { + validator: { + $jsonSchema: { + bsonType: "object", + required: ["phone"], + properties: { + phone: { + bsonType: "string", + description: "must be a string and is required" + }, + email: { + bsonType: "string", + pattern: "@mongodb\.com$", + description: "must be a string and match the regular expression pattern" + }, + status: { + enum: ["Unknown", "Incomplete"], + description: "can only be one of the enum values" + } + } + } + } +}) +// command +db.collWithValidator.insert( { name: "Amanda", status: "Updated" } ) +// clear +db.coll.drop(); +db.collWithValidator.drop(); \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/db/serverStatus.expected.txt b/packages/java-shell/src/test/resources/db/serverStatus.expected.txt new file mode 100644 index 0000000000..670c46712a --- /dev/null +++ b/packages/java-shell/src/test/resources/db/serverStatus.expected.txt @@ -0,0 +1,2 @@ +DocumentResult +StringResult \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/db/serverStatus.js b/packages/java-shell/src/test/resources/db/serverStatus.js new file mode 100644 index 0000000000..ef882537a6 --- /dev/null +++ b/packages/java-shell/src/test/resources/db/serverStatus.js @@ -0,0 +1,4 @@ +// command dontCheckValue +db.serverStatus() +// command dontCheckValue +db.serverStatus().version \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/literal/UUID.expected.txt b/packages/java-shell/src/test/resources/literal/UUID.expected.txt index e7f611ebe1..e0ed1ccf6d 100644 --- a/packages/java-shell/src/test/resources/literal/UUID.expected.txt +++ b/packages/java-shell/src/test/resources/literal/UUID.expected.txt @@ -1,4 +1,3 @@ -UUIDResult: { "$binary" : "MTIzNA==" , "$type" : 4} -UUIDResult: { "$binary" : "MTIzNA==" , "$type" : 4} -UUIDResult: { "$binary" : "UiC0GPj3DUzZ9dIHNbb42ZDF" , "$type" : 4} -UUIDResult \ No newline at end of file +UUIDResult: +UUIDResult: +UUIDResult: \ No newline at end of file diff --git a/packages/java-shell/src/test/resources/literal/UUID-ignored.js b/packages/java-shell/src/test/resources/literal/UUID.js similarity index 69% rename from packages/java-shell/src/test/resources/literal/UUID-ignored.js rename to packages/java-shell/src/test/resources/literal/UUID.js index f7208bc84e..a023d6de68 100644 --- a/packages/java-shell/src/test/resources/literal/UUID-ignored.js +++ b/packages/java-shell/src/test/resources/literal/UUID.js @@ -1,8 +1,6 @@ // command checkResultClass -new UUID('31323334') +new UUID('5220b418-8f7d-4cd9-bd27-35b6f8d990c5') // command checkResultClass -UUID('31323334') +UUID('5220b418-8f7d-4cd9-bd27-35b6f8d990c5') // command checkResultClass -new UUID('5220b418-8f7d-4cd9-bd27-35b6f8d990c5') -// command dontCheckValue new UUID() \ No newline at end of file