From 6902d9aa496ec5c77ac0ff800daab9c17695221b Mon Sep 17 00:00:00 2001 From: Liudmila Kornilova Date: Thu, 8 Oct 2020 14:16:06 +0800 Subject: [PATCH] [java-shell] JavaServiceProvider should return only JS objects so they support 'defineProperty' --- .../com/mongodb/mongosh/MongoShellContext.kt | 21 +++--- .../mongosh/service/JavaServiceProvider.kt | 70 ++++++++----------- .../collection/insertOne.expected.txt | 1 - .../resources/literal/undefined.expected.txt | 2 +- 4 files changed, 44 insertions(+), 50 deletions(-) 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 4b2285bafa..5ac1f57d49 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 @@ -15,8 +15,6 @@ import org.graalvm.polyglot.Context import org.graalvm.polyglot.Source import org.graalvm.polyglot.Value import org.graalvm.polyglot.proxy.ProxyExecutable -import org.graalvm.polyglot.proxy.ProxyObject -import org.graalvm.polyglot.proxy.ProxyObject.fromMap import org.intellij.lang.annotations.Language import java.io.Closeable import java.time.LocalDateTime @@ -34,7 +32,7 @@ import java.util.concurrent.TimeUnit import java.util.regex.Pattern internal class MongoShellContext(client: MongoClient) : Closeable { - private val ctx: Context = Context.create() + val ctx: Context = Context.create() private val serviceProvider = JavaServiceProvider(client, this) private val shellEvaluator: Value private val bsonTypes: BsonTypes @@ -227,6 +225,7 @@ internal class MongoShellContext(client: MongoClient) : Closeable { v.fitsInLong() -> LongResult(v.asLong()) v.fitsInFloat() -> FloatResult(v.asFloat()) v.fitsInDouble() -> DoubleResult(v.asDouble()) + v.equalsTo("undefined") -> VoidResult v.isNull -> NullResult v.isHostObject && v.asHostObject() is Unit -> VoidResult v.isHostObject && v.asHostObject() is Document -> DocumentResult(v.asHostObject()) @@ -253,7 +252,7 @@ internal class MongoShellContext(client: MongoClient) : Closeable { fun toJsPromise(promise: Either): Value { return when (promise) { - is Right -> evalInner("(v) => new Promise(((resolve) => resolve(v)))", "resolved_promise_script").execute(promise.value) + is Right -> evalInner("(v) => new Promise(((resolve) => resolve(v)))", "resolved_promise_script").execute(toJs(promise.value)) is Left -> evalInner("(v) => new Promise(((_, reject) => reject(v)))", "rejected_promise_script").execute(promise.value) } } @@ -266,19 +265,23 @@ internal class MongoShellContext(client: MongoClient) : Closeable { private fun Value.instanceOf(@Language("js") clazz: String): Boolean = evalInner("(x) => x instanceof $clazz", "instance_of_script").execute(this).asBoolean() + private fun Value.equalsTo(@Language("js") value: String): Boolean = evalInner("(x) => x === $value", "equals_script").execute(this).asBoolean() + fun toJs(o: Any?): Any? { return when (o) { is Iterable<*> -> toJs(o) is Map<*, *> -> toJs(o) + Unit -> evalInner("undefined") else -> o } } - private fun toJs(map: Map<*, *>): ProxyObject { - val convertedMap: Map = map.entries.asSequence() - .filter { (key, _) -> key is String } - .associate { e -> e.key as String to toJs(e.value) } - return fromMap(convertedMap) + private fun toJs(map: Map<*, *>): Value { + val jsMap = evalInner("new Object()") + for ((key, value) in map.entries) { + jsMap.putMember(key as String, toJs(value)) + } + return jsMap } private fun toJs(list: Iterable): Value { 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 af30e81bc5..202bc7100f 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 @@ -7,7 +7,6 @@ import com.mongodb.client.result.UpdateResult import com.mongodb.mongosh.MongoShellContext import com.mongodb.mongosh.result.ArrayResult import com.mongodb.mongosh.result.CommandException -import com.mongodb.mongosh.result.DeleteResult import com.mongodb.mongosh.result.DocumentResult import org.bson.Document import org.graalvm.polyglot.HostAccess @@ -25,9 +24,9 @@ internal class JavaServiceProvider(private val client: MongoClient, private val override fun runCommand(database: String, spec: Value): Value = promise { getDatabase(database, null).map { db -> if (spec.isString) { - context.toJs(db.runCommand(Document(spec.asString(), 1))) + db.runCommand(Document(spec.asString(), 1)) } else { - context.toJs(db.runCommand(toDocument(spec, "spec"))) + db.runCommand(toDocument(spec, "spec")) } } } @@ -44,7 +43,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val if (!isOk) { throw Exception(res.toJson()) } - context.toJs(res) + res } } @@ -54,9 +53,8 @@ internal class JavaServiceProvider(private val client: MongoClient, private val val dbOptions = toDocument(dbOptions, "dbOptions") getDatabase(database, dbOptions).map { db -> db.getCollection(collection).insertOne(document) - context.toJs(mapOf( - "result" to mapOf("ok" to true), - "insertedId" to "UNKNOWN")) + mapOf("result" to mapOf("ok" to true), + "insertedId" to "UNKNOWN") } } @@ -69,13 +67,11 @@ internal class JavaServiceProvider(private val client: MongoClient, private val getDatabase(database, dbOptions).flatMap { db -> convert(ReplaceOptions(), replaceOptionsConverters, replaceOptionsDefaultConverters, options).map { options -> val res = db.getCollection(collection).replaceOne(filter, replacement, options) - context.toJs(mapOf( - "result" to mapOf("ok" to res.wasAcknowledged()), + mapOf("result" to mapOf("ok" to res.wasAcknowledged()), "matchedCount" to res.matchedCount, "modifiedCount" to res.modifiedCount, "upsertedCount" to if (res.upsertedId == null) 0 else 1, - "upsertedId" to res.upsertedId - )) + "upsertedId" to res.upsertedId) } } } @@ -95,11 +91,11 @@ internal class JavaServiceProvider(private val client: MongoClient, private val update.hasMembers() -> Right(db.getCollection(collection).updateMany(filter, toDocument(update, "update"), updateOptions)) else -> Left(IllegalArgumentException("updatePipeline must be a list or object")) }.map { res -> - context.toJs(mapOf("result" to mapOf("ok" to res.wasAcknowledged()), + mapOf("result" to mapOf("ok" to res.wasAcknowledged()), "matchedCount" to res.matchedCount, "modifiedCount" to res.modifiedCount, "upsertedCount" to if (res.upsertedId == null) 0 else 1, - "upsertedId" to res.upsertedId)) + "upsertedId" to res.upsertedId) } } } @@ -127,12 +123,11 @@ internal class JavaServiceProvider(private val client: MongoClient, private val val pipeline = toList(update, "update")?.map { it as Document } coll.updateOne(filter, pipeline, updateOptions) } else coll.updateOne(filter, toDocument(update, "update"), updateOptions) - context.toJs(mapOf("result" to mapOf("ok" to res.wasAcknowledged()), + mapOf("result" to mapOf("ok" to res.wasAcknowledged()), "matchedCount" to res.matchedCount, "modifiedCount" to res.modifiedCount, "upsertedCount" to if (res.upsertedId == null) 0 else 1, - "upsertedId" to res.upsertedId - )) + "upsertedId" to res.upsertedId) } } } @@ -164,7 +159,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val val writeModels = requests.map { getWriteModel(it as Document) } unwrap(writeModels).map { requests -> val result = db.getCollection(collection).bulkWrite(requests, options) - context.toJs(mapOf( + mapOf( "result" to mapOf("ok" to result.wasAcknowledged()), "insertedCount" to result.insertedCount, "insertedIds" to "UNKNOWN", @@ -172,7 +167,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val "modifiedCount" to result.modifiedCount, "deletedCount" to result.deletedCount, "upsertedCount" to result.upserts.size, - "upsertedIds" to result.upserts.map { it.id })) + "upsertedIds" to result.upserts.map { it.id }) } } } @@ -218,9 +213,8 @@ internal class JavaServiceProvider(private val client: MongoClient, private val getDatabase(database, dbOptions).flatMap { db -> convert(DeleteOptions(), deleteConverters, deleteDefaultConverter, options).map { deleteOptions -> val result = db.getCollection(collection).deleteMany(filter, deleteOptions) - context.toJs(mapOf( - "result" to mapOf("ok" to result.wasAcknowledged()), - "deletedCount" to result.deletedCount)) + mapOf("result" to mapOf("ok" to result.wasAcknowledged()), + "deletedCount" to result.deletedCount) } } } @@ -233,9 +227,8 @@ internal class JavaServiceProvider(private val client: MongoClient, private val getDatabase(database, dbOptions).flatMap { db -> convert(DeleteOptions(), deleteConverters, deleteDefaultConverter, options).map { deleteOptions -> val result = db.getCollection(collection).deleteOne(filter, deleteOptions) - context.toJs(mapOf( - "result" to mapOf("ok" to result.wasAcknowledged()), - "deletedCount" to result.deletedCount)) + mapOf("result" to mapOf("ok" to result.wasAcknowledged()), + "deletedCount" to result.deletedCount) } } } @@ -247,7 +240,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val getDatabase(database, null).flatMap { db -> convert(FindOneAndDeleteOptions(), findOneAndDeleteConverters, findOneAndDeleteDefaultConverter, options).map { options -> val res = db.getCollection(collection).findOneAndDelete(filter, options) - context.toJs(mapOf("value" to res)) + mapOf("value" to res) } } } @@ -261,7 +254,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val convert(FindOneAndReplaceOptions(), findOneAndReplaceOptionsConverters, findOneAndReplaceOptionsDefaultConverters, options) .map { options -> val res = db.getCollection(collection).findOneAndReplace(filter, replacement, options) - context.toJs(mapOf("value" to res)) + mapOf("value" to res) } } } @@ -274,7 +267,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val getDatabase(database, null).flatMap { db -> convert(FindOneAndUpdateOptions(), findOneAndUpdateConverters, findOneAndUpdateDefaultConverter, options).map { options -> val res = db.getCollection(collection).findOneAndUpdate(filter, update, options) - context.toJs(mapOf("value" to res)) + mapOf("value" to res) } } @@ -287,9 +280,8 @@ internal class JavaServiceProvider(private val client: MongoClient, private val if (docs == null || docs.any { it !is Document }) return@promise Left(IllegalArgumentException("docs must be a list of objects")) getDatabase(database, dbOptions).map { db -> db.getCollection(collection).insertMany(docs.filterIsInstance()) - context.toJs(mapOf( - "result" to mapOf("ok" to true), - "insertedIds" to emptyList())) + mapOf("result" to mapOf("ok" to true), + "insertedIds" to emptyList()) } } @@ -392,7 +384,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val @HostAccess.Export override fun listDatabases(database: String): Value = promise { - Right(context.toJs(mapOf("databases" to client.listDatabases()))) + Right(mapOf("databases" to client.listDatabases())) } @HostAccess.Export @@ -407,7 +399,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val @HostAccess.Export override fun getIndexes(database: String, collection: String): Value = promise { getDatabase(database, null).map { db -> - context.toJs(db.getCollection(collection).listIndexes()) + db.getCollection(collection).listIndexes() } } @@ -417,26 +409,26 @@ internal class JavaServiceProvider(private val client: MongoClient, private val getDatabase(database, null).map { db -> val list = db.listCollections() if (filter != null) list.filter(filter) - context.toJs(list) + list } } @HostAccess.Export override fun stats(database: String, collection: String, options: Value?): Value = promise { getDatabase(database, null).map { db -> - context.toJs(db.runCommand(Document("collStats", collection))) + db.runCommand(Document("collStats", collection)) } } @HostAccess.Export - override fun remove(database: String, collection: String, query: Value, options: Value?, dbOptions: Value?): Value { + override fun remove(database: String, collection: String, query: Value, options: Value?, dbOptions: Value?): Value = promise { val query = toDocument(query, "query") val dbOptions = toDocument(dbOptions, "dbOptions") - val promise = getDatabase(database, dbOptions).map { db -> + getDatabase(database, dbOptions).map { db -> val result = db.getCollection(collection).deleteMany(query) - DeleteResult(result.wasAcknowledged(), result.deletedCount) + mapOf("acknowledged" to result.wasAcknowledged(), + "deletedCount" to result.deletedCount) } - return context.toJsPromise(promise) } @HostAccess.Export @@ -454,7 +446,7 @@ internal class JavaServiceProvider(private val client: MongoClient, private val } val indexes = unwrap(convertedIndexes) indexes.map { indexes -> - context.toJs(db.getCollection(collection).createIndexes(indexes)) + db.getCollection(collection).createIndexes(indexes) } } } diff --git a/packages/java-shell/src/test/resources/collection/insertOne.expected.txt b/packages/java-shell/src/test/resources/collection/insertOne.expected.txt index 7a2bf9529c..f48836758a 100644 --- a/packages/java-shell/src/test/resources/collection/insertOne.expected.txt +++ b/packages/java-shell/src/test/resources/collection/insertOne.expected.txt @@ -1,4 +1,3 @@ -null { "acknowledged": true, "insertedId": "UNKNOWN" } true [ { "_id": , "a": 1, "objectId": , "maxKey": { "$maxKey" : 1}, "minKey": { "$minKey" : 1}, "binData": { "$binary" : "MTIzNA==" , "$type" : 16}, "date": { "$date" : 1355875200000}, "isoDate": { "$date" : 1355875200000}, "numberInt": 24, "timestamp": Timestamp{value=429496729600, seconds=100, inc=0} } ] diff --git a/packages/java-shell/src/test/resources/literal/undefined.expected.txt b/packages/java-shell/src/test/resources/literal/undefined.expected.txt index 4a0ecb3af0..a4d04b808c 100644 --- a/packages/java-shell/src/test/resources/literal/undefined.expected.txt +++ b/packages/java-shell/src/test/resources/literal/undefined.expected.txt @@ -1 +1 @@ -NullResult: null \ No newline at end of file +VoidResult: \ No newline at end of file