diff --git a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoAggregationPipelineImpl.kt b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoAggregationPipelineImpl.kt --- a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoAggregationPipelineImpl.kt +++ b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoAggregationPipelineImpl.kt @@ -19,6 +19,7 @@ package opensavvy.ktmongo.coroutines +import opensavvy.ktmongo.api.MongoCollection import opensavvy.ktmongo.bson.BsonFieldWriter import opensavvy.ktmongo.dsl.BsonContext import opensavvy.ktmongo.dsl.DangerousMongoApi @@ -43,7 +44,7 @@ import kotlin.reflect.KType private class CoroutineMongoAggregationPipelineImpl @OptIn(LowLevelApi::class) constructor( - private val collectionName: String, + private val collection: MongoCollection<*>, context: BsonContext, chain: PipelineChainLink, private val executeAggregate: (List, Class) -> CoroutineMongoAggregateIterable, @@ -64,7 +65,7 @@ @LowLevelApi @DangerousMongoApi override fun withStage(stage: BsonNode): CoroutineMongoAggregationPipelineImpl = - CoroutineMongoAggregationPipelineImpl(collectionName, context, chain.withStage(stage), executeAggregate) + CoroutineMongoAggregationPipelineImpl(collection, context, chain.withStage(stage), executeAggregate) @Suppress("UNCHECKED_CAST") @LowLevelApi @@ -136,7 +137,7 @@ @OptIn(LowLevelApi::class) override fun embedInUnionWith(writer: BsonFieldWriter) = with(writer) { - writeString("coll", collectionName) + writeString("coll", collection.name) writeArray("pipeline") { this@CoroutineMongoAggregationPipelineImpl.writeTo(this) } @@ -144,13 +145,15 @@ // endregion + override fun toString(): String = + "$collection.aggregate(${super.toString()})" } @LowLevelApi internal fun CoroutineMongoAggregationPipeline( - collectionName: String, + collection: MongoCollection<*>, context: BsonContext, chain: PipelineChainLink, executeAggregate: (List, Class) -> CoroutineMongoAggregateIterable, ): CoroutineMongoAggregationPipeline = - CoroutineMongoAggregationPipelineImpl(collectionName, context, chain, executeAggregate) + CoroutineMongoAggregationPipelineImpl(collection, context, chain, executeAggregate) diff --git a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt --- a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt +++ b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoCollectionImpl.kt @@ -107,7 +107,7 @@ // region Find override fun find(): CoroutineMongoFindIterable = - inner.find().asKtMongo() + inner.find().asKtMongo(lazyStringRepresentation = { "$this.find({})" }) @OptIn(LowLevelApi::class) override fun find( @@ -127,7 +127,7 @@ .skip(model.options.readSkip()) .maxTime(model.options.readMaxTimeMS().toLong(), TimeUnit.MILLISECONDS) .sort(model.options.readSortDocument()) - .asKtMongo() + .asKtMongo(lazyStringRepresentation = { "$this.find($model)" }) } // endregion @@ -424,7 +424,7 @@ @OptIn(LowLevelApi::class) override fun aggregate(): CoroutineMongoAggregationPipeline = CoroutineMongoAggregationPipeline( - collectionName = name, + collection = this, context = context, chain = PipelineChainLink(context), executeAggregate = { pipeline, documentClass -> diff --git a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoIterableImpl.find.kt b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoIterableImpl.find.kt --- a/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoIterableImpl.find.kt +++ b/driver-coroutines/src/jvmMain/kotlin/CoroutineMongoIterableImpl.find.kt @@ -26,6 +26,7 @@ private class CoroutineMongoFindIterableImpl( private val inner: FindFlow, + private val lazyStringRepresentation: (() -> String)?, ) : CoroutineMongoFindIterable { override fun asOfficial(): FindFlow = @@ -42,10 +43,19 @@ override fun asFlow(): Flow = inner + + override fun toString(): String = lazyStringRepresentation?.invoke() + ?: super.toString() } /** * Instantiates a KtMongo [CoroutineMongoFindIterable] using an existing flow from the official Kotlin driver. */ fun FindFlow.asKtMongo(): CoroutineMongoFindIterable = - CoroutineMongoFindIterableImpl(this) + CoroutineMongoFindIterableImpl(this, lazyStringRepresentation = { "$this.asKtMongo()" }) + +// Same but allows customizing the toString() +internal fun FindFlow.asKtMongo( + lazyStringRepresentation: (() -> String)?, +): CoroutineMongoFindIterable = + CoroutineMongoFindIterableImpl(this, lazyStringRepresentation) diff --git a/dsl-template/src/commonMain/kotlin/aggregation/Pipeline.kt b/dsl-template/src/commonMain/kotlin/aggregation/Pipeline.kt --- a/dsl-template/src/commonMain/kotlin/aggregation/Pipeline.kt +++ b/dsl-template/src/commonMain/kotlin/aggregation/Pipeline.kt @@ -328,7 +328,7 @@ * JSON representation of this pipeline. */ @OptIn(LowLevelApi::class) - final override fun toString(): String = + override fun toString(): String = chain.toString() } diff --git a/dsl/src/commonMain/kotlin/aggregation/Pipeline.kt b/dsl/src/commonMain/kotlin/aggregation/Pipeline.kt --- a/dsl/src/commonMain/kotlin/aggregation/Pipeline.kt +++ b/dsl/src/commonMain/kotlin/aggregation/Pipeline.kt @@ -331,7 +331,7 @@ * JSON representation of this pipeline. */ @OptIn(LowLevelApi::class) - final override fun toString(): String = + override fun toString(): String = chain.toString() } diff --git a/test/src/commonMain/kotlin/operations/AggregationOperations.test.kt b/test/src/commonMain/kotlin/operations/AggregationOperations.test.kt --- a/test/src/commonMain/kotlin/operations/AggregationOperations.test.kt +++ b/test/src/commonMain/kotlin/operations/AggregationOperations.test.kt @@ -24,6 +24,7 @@ import opensavvy.ktmongo.tests.api.collection import opensavvy.prepared.suite.Prepared import opensavvy.prepared.suite.SuiteDsl +import opensavvy.prepared.suite.assertions.matches @Serializable data class AggregationOperationsUser( @@ -59,6 +60,8 @@ age = 25, ), ) + + check(collection().aggregate().toString() matches """.+MongoCollection\(.+\).aggregate\(\[\]\)""") val results = collection().aggregate().toList() check(results.size == 2) @@ -198,5 +201,14 @@ } .first() check(result.total == 60) + } + + test("toString representation of a complex aggregation") { + val request = collection().aggregate() + .sort { ascending(AggregationOperationsUser::age) } + .match { AggregationOperationsUser::age gte 18 } + .sample(1) + + check(request.toString() matches $$""".+MongoCollection\(.+\).aggregate\(\[\{"\$sort": \{"age": 1\}\}, \{"\$match": \{"age": \{"\$gte": 18\}\}\}, \{"\$sample": \{"size": 1\}\}\]\)""") } } diff --git a/test/src/commonMain/kotlin/operations/FindOperations.test.kt b/test/src/commonMain/kotlin/operations/FindOperations.test.kt --- a/test/src/commonMain/kotlin/operations/FindOperations.test.kt +++ b/test/src/commonMain/kotlin/operations/FindOperations.test.kt @@ -24,6 +24,7 @@ import opensavvy.ktmongo.tests.api.collection import opensavvy.prepared.suite.Prepared import opensavvy.prepared.suite.SuiteDsl +import opensavvy.prepared.suite.assertions.matches import kotlin.time.Duration.Companion.seconds @Serializable @@ -49,6 +50,7 @@ ), ) + check(collection().find().toString() matches """.+MongoCollection\(.+\).find\(\{\}\)""") check(collection().find().toList().isNotEmpty()) { "Expected at least one result, got none" } } @@ -64,9 +66,13 @@ ), ) - val results = collection().find { + val request = collection().find { FindOperationsUser::name eq "Alice" - }.toList() + } + + check(request.toString() matches $$""".+MongoCollection\(.+\).find\(\{"filter": \{"name": \{"\$eq": "Alice"\}\}\}\)""") + + val results = request.toList() check(results.size == 1) check(results[0].name == "Alice") @@ -138,13 +144,17 @@ FindOperationsUser(_id = collection().newId(), name = "Bob"), ) - val results = collection().find({ + val request = collection().find({ sort { ascending(FindOperationsUser::name) } }) { FindOperationsUser::name.exists() - }.toList() + } + + check(request.toString() matches $$""".+MongoCollection\(.+\).find\(\{"filter": \{"name": \{"\$exists": true\}\}, "sort": \{"name": 1\}\}\)""") + + val results = request.toList() check(results.map { it.name } == listOf("Alice", "Bob", "Carol")) }