All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.expr.FilterExpression.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2024, OpenSavvy and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package opensavvy.ktmongo.dsl.expr

import opensavvy.ktmongo.bson.BsonContext
import opensavvy.ktmongo.bson.BsonFieldWriter
import opensavvy.ktmongo.dsl.DangerousMongoApi
import opensavvy.ktmongo.dsl.KtMongoDsl
import opensavvy.ktmongo.dsl.LowLevelApi
import opensavvy.ktmongo.dsl.expr.common.AbstractCompoundExpression
import opensavvy.ktmongo.dsl.expr.common.AbstractExpression
import opensavvy.ktmongo.dsl.expr.common.Expression
import opensavvy.ktmongo.dsl.path.Field
import opensavvy.ktmongo.dsl.path.FieldDsl
import opensavvy.ktmongo.dsl.path.Path

/**
 * Implementation of the [FilterOperators] interface.
 */
@KtMongoDsl
class FilterExpression(
	context: BsonContext,
) : AbstractCompoundExpression(context),
	FilterOperators,
	FieldDsl {

	// region Low-level operations

	@OptIn(DangerousMongoApi::class)
	@LowLevelApi
	override fun simplify(children: List): AbstractExpression? =
		when (children.size) {
			0 -> null
			1 -> this
			else -> AndFilterExpressionNode(children, context)
		}

	@LowLevelApi
	private sealed class FilterExpressionNode(context: BsonContext) : AbstractExpression(context)

	// endregion
	// region $and, $or

	@OptIn(LowLevelApi::class, DangerousMongoApi::class)
	@KtMongoDsl
	override fun and(block: FilterOperators.() -> Unit) {
		accept(AndFilterExpressionNode(FilterExpression(context).apply(block).children, context))
	}

	@DangerousMongoApi
	@LowLevelApi
	private class AndFilterExpressionNode(
		val declaredChildren: List,
		context: BsonContext,
	) : FilterExpressionNode(context) {

		override fun simplify(): AbstractExpression? {
			if (declaredChildren.isEmpty())
				return null

			if (declaredChildren.size == 1)
				return FilterExpression(context).apply { accept(declaredChildren.single()) }

			// If there are nested $and operators, we combine them into the current one
			val nestedChildren = ArrayList()

			for (child in declaredChildren) {
				if (child is AndFilterExpressionNode<*>) {
					for (nestedChild in child.declaredChildren) {
						nestedChildren += nestedChild
					}
				} else {
					nestedChildren += child
				}
			}

			return AndFilterExpressionNode(nestedChildren, context)
		}

		override fun write(writer: BsonFieldWriter) = with(writer) {
			writeArray("\$and") {
				for (child in declaredChildren) {
					writeDocument {
						child.writeTo(this)
					}
				}
			}
		}
	}

	@OptIn(LowLevelApi::class, DangerousMongoApi::class)
	@KtMongoDsl
	override fun or(block: FilterOperators.() -> Unit) {
		accept(OrFilterExpressionNode(FilterExpression(context).apply(block).children, context))
	}

	@DangerousMongoApi
	@LowLevelApi
	private class OrFilterExpressionNode(
		val declaredChildren: List,
		context: BsonContext,
	) : FilterExpressionNode(context) {

		override fun simplify(): AbstractExpression? {
			if (declaredChildren.isEmpty())
				return null

			if (declaredChildren.size == 1)
				return FilterExpression(context).apply { accept(declaredChildren.single()) }

			return super.simplify()
		}

		override fun write(writer: BsonFieldWriter) = with(writer) {
			writeArray("\$or") {
				for (child in declaredChildren) {
					writeDocument {
						child.writeTo(this)
					}
				}
			}
		}
	}

	// endregion
	// region Predicate access

	@OptIn(LowLevelApi::class, DangerousMongoApi::class)
	@Suppress("INVISIBLE_REFERENCE")
	@KtMongoDsl
	override operator fun <@kotlin.internal.OnlyInputTypes V> Field.invoke(block: PredicateOperators.() -> Unit) {
		accept(PredicateInFilterExpression(path, PredicateExpression(context).apply(block), context))
	}

	@LowLevelApi
	private class PredicateInFilterExpression(
		val target: Path,
		val expression: Expression,
		context: BsonContext,
	) : FilterExpressionNode(context) {

		override fun simplify(): AbstractExpression? =
			expression.simplify()
				?.let { PredicateInFilterExpression(target, it, context) }

		override fun write(writer: BsonFieldWriter) = with(writer) {
			writeDocument(target.toString()) {
				expression.writeTo(this)
			}
		}
	}

	// endregion
	// region $elemMatch

	@OptIn(LowLevelApi::class, DangerousMongoApi::class)
	@KtMongoDsl
	override fun  Field>.anyValue(block: PredicateOperators.() -> Unit) {
		accept(ElementMatchExpressionNode(this.path, PredicateExpression(context).apply(block), context))
	}

	@OptIn(LowLevelApi::class, DangerousMongoApi::class)
	@KtMongoDsl
	override fun  Field>.any(block: FilterOperators.() -> Unit) {
		accept(ElementMatchExpressionNode(path, FilterExpression(context).apply(block), context))
	}

	@DangerousMongoApi
	@LowLevelApi
	private class ElementMatchExpressionNode(
		val target: Path,
		val expression: Expression,
		context: BsonContext,
	) : FilterExpressionNode(context) {

		override fun simplify(): AbstractExpression =
			ElementMatchExpressionNode(target, expression.simplify()
				?: OrFilterExpressionNode(emptyList(), context), context)

		override fun write(writer: BsonFieldWriter) = with(writer) {
			writeDocument(target.toString()) {
				writeDocument("\$elemMatch") {
					expression.writeTo(this)
				}
			}
		}
	}

	// endregion
	// region $all

	@OptIn(LowLevelApi::class, DangerousMongoApi::class)
	@KtMongoDsl
	override infix fun  Field>.containsAll(values: Collection) {
		accept(ArrayAllExpressionNode(path, values, context))
	}

	@LowLevelApi
	private class ArrayAllExpressionNode(
		val path: Path,
		val values: Collection,
		context: BsonContext,
	) : FilterExpressionNode(context) {

		override fun write(writer: BsonFieldWriter) = with(writer) {
			writeDocument(path.toString()) {
				writeArray("\$all") {
					for (value in values) {
						writeObjectSafe(value, context)
					}
				}
			}
		}
	}

	// endregion

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy