Skip to content

Commit

Permalink
Immutable query nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
anti-social committed Mar 16, 2023
1 parent 5d49697 commit 841307f
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 444 deletions.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ subprojects {
if (project.hasProperty("showPassedTests")) {
add(TestLogEvent.PASSED)
}
if (project.hasProperty("showTestsOutput")) {
add(TestLogEvent.STANDARD_OUT)
add(TestLogEvent.STANDARD_ERROR)
}
}
exceptionFormat = TestExceptionFormat.FULL
stackTraceFilters = setOf(
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/querying.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ See [Search Aggregations](https://www.elastic.co/guide/en/elasticsearch/referenc
## Query Nodes

Sometimes you don't know final form of your query when creating it. For such a usecase it is
possible to modify parts of the query after creation using special query expressions and
possible to replace parts of the query after creation using special query expressions and
[queryNode](https://anti-social.github.io/elasticmagic-kt/api/latest/elasticmagic/dev.evo.elasticmagic/-base-search-query/query-node.html)
method:

Expand Down
244 changes: 81 additions & 163 deletions elasticmagic/api/elasticmagic.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,18 @@ abstract class BaseSearchQuery<S: BaseDocSource, T: BaseSearchQuery<S, T>>(
private fun collectNodes(
expression: QueryExpression?
): Map<NodeHandle<*>, QueryExpressionNode<*>> {
val nodes = mutableMapOf<NodeHandle<*>, QueryExpressionNode<*>>()
expression?.collect { node ->
if (node is QueryExpressionNode<*>) {
nodes[node.handle] = node
return buildMap {
expression?.collect { node ->
if (node is QueryExpressionNode<*>) {
if (node.handle in this) {
throw IllegalArgumentException(
"Found duplicated node handle: ${node.handle.name}"
)
}
put(node.handle, node)
}
}
}
return nodes
}
}

Expand Down Expand Up @@ -141,8 +146,9 @@ abstract class BaseSearchQuery<S: BaseDocSource, T: BaseSearchQuery<S, T>>(
)
}

@PublishedApi
@Suppress("UNCHECKED_CAST")
protected fun self(): T = this as T
internal fun self(): T = this as T

protected fun self(block: () -> Unit): T {
block()
Expand All @@ -163,39 +169,48 @@ abstract class BaseSearchQuery<S: BaseDocSource, T: BaseSearchQuery<S, T>>(
updateQueryNodes()
}

@PublishedApi
@Suppress("FunctionName")
internal fun updateQueryNodes() {
this.queryNodes = collectNodes(query)
}

/**
* Allows modifying specific query expression node using [handle] of the node.
* Allows to replace a specific query expression node using a [handle] of the node.
*
* @param handle a handle bound to the specific query expression node.
* @param block a function that modifies the query expression node.
* @param block a function that returns new query expression node.
* @throws IllegalArgumentException if a node specified by the [handle] is missing.
*
* @sample samples.code.SearchQuery.queryNode
*/
inline fun <reified N: QueryExpressionNode<N>> queryNode(
inline fun <reified N: QueryExpression> queryNode(
handle: NodeHandle<N>,
block: (N) -> Unit
block: (N) -> N
): T {
val node = requireNotNull(findNode(handle)) {
"Node handle not found: $handle"
"Node handle is not found: ${handle.name}"
}
block(node as N)
updateQueryNodes()
val newNode = QueryExpressionNode(
handle,
block(node.expression as N)
)
rewriteQuery(newNode)

@Suppress("UNCHECKED_CAST")
return this as T
return self()
}

@PublishedApi
@Suppress("FunctionName")
internal fun findNode(handle: NodeHandle<*>): QueryExpressionNode<*>? {
return queryNodes[handle]
}

@PublishedApi
@Suppress("FunctionName")
internal fun updateQueryNodes() {
this.queryNodes = collectNodes(query)
internal fun rewriteQuery(newNode: QueryExpressionNode<*>) {
val query = this.query
if (query != null) {
query(query.rewrite(newNode))
}
}

/**
Expand Down Expand Up @@ -532,16 +547,11 @@ open class SearchQuery<S: BaseDocSource>(
query: QueryExpression? = null,
params: Params = Params(),
): SearchQuery<DynDocSource> {
return SearchQuery(::dynDocSourceFactory, query = query, params = params)
}

@Suppress("UnusedPrivateMember")
private fun dynDocSourceFactory(obj: Deserializer.ObjectCtx): DynDocSource {
return DynDocSource()
return SearchQuery({ DynDocSource() }, query = query, params = params)
}
}

override fun new(docSourceFactory: (obj: Deserializer.ObjectCtx) -> S): SearchQuery<S> {
override fun new(docSourceFactory: (Deserializer.ObjectCtx) -> S): SearchQuery<S> {
return SearchQuery(docSourceFactory)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,14 @@ package dev.evo.elasticmagic.query
import dev.evo.elasticmagic.compile.SearchQueryCompiler
import dev.evo.elasticmagic.serde.Serializer

interface BoolExpression : QueryExpression {
val filter: List<QueryExpression>
val should: List<QueryExpression>
val must: List<QueryExpression>
val mustNot: List<QueryExpression>

override fun children(): Iterator<QueryExpression> = iterator {
yieldAll(filter)
yieldAll(should)
yieldAll(must)
yieldAll(mustNot)
}
}

data class Bool(
override val filter: List<QueryExpression> = emptyList(),
override val should: List<QueryExpression> = emptyList(),
override val must: List<QueryExpression> = emptyList(),
override val mustNot: List<QueryExpression> = emptyList(),
val filter: List<QueryExpression> = emptyList(),
val should: List<QueryExpression> = emptyList(),
val must: List<QueryExpression> = emptyList(),
val mustNot: List<QueryExpression> = emptyList(),
val minimumShouldMatch: MinimumShouldMatch? = null,
val boost: Float? = null,
) : BoolExpression {
) : QueryExpression {
override val name = "bool"

companion object {
Expand All @@ -40,6 +26,29 @@ data class Bool(

override fun clone() = copy()

override fun children(): Iterator<QueryExpression> = iterator {
yieldAll(filter)
yieldAll(should)
yieldAll(must)
yieldAll(mustNot)
}

override fun rewrite(newNode: QueryExpressionNode<*>): Bool {
replaceNodeInExpressions(filter, { it.rewrite(newNode) }) {
return copy(filter = it)
}
replaceNodeInExpressions(should, { it.rewrite(newNode) }) {
return copy(should = it)
}
replaceNodeInExpressions(must, { it.rewrite(newNode) }) {
return copy(must = it)
}
replaceNodeInExpressions(mustNot, { it.rewrite(newNode) }) {
return copy(mustNot = it)
}
return this
}

override fun reduce(): QueryExpression? {
val filter = filter.mapNotNull { it.reduce() }
val should = should.mapNotNull { it.reduce() }
Expand Down Expand Up @@ -96,51 +105,3 @@ data class Bool(
}
}
}

data class BoolNode(
override val handle: NodeHandle<BoolNode>,
override var filter: MutableList<QueryExpression>,
override var should: MutableList<QueryExpression>,
override var must: MutableList<QueryExpression>,
override var mustNot: MutableList<QueryExpression>,
var minimumShouldMatch: MinimumShouldMatch? = null,
) : QueryExpressionNode<BoolNode>(), BoolExpression {
override val name: String = "bool"

companion object {
operator fun invoke(
handle: NodeHandle<BoolNode>,
filter: List<QueryExpression> = emptyList(),
should: List<QueryExpression> = emptyList(),
must: List<QueryExpression> = emptyList(),
mustNot: List<QueryExpression> = emptyList(),
minimumShouldMatch: MinimumShouldMatch? = null,
): BoolNode {
return BoolNode(
handle,
filter = filter.toMutableList(),
should = should.toMutableList(),
must = must.toMutableList(),
mustNot = mustNot.toMutableList(),
minimumShouldMatch = minimumShouldMatch,
)
}
}

override fun clone() = copy(
filter = filter.toMutableList(),
should = should.toMutableList(),
must = must.toMutableList(),
mustNot = mustNot.toMutableList()
)

override fun toQueryExpression(): Bool {
return Bool(
filter = filter,
should = should,
must = must,
mustNot = mustNot,
minimumShouldMatch = minimumShouldMatch,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@ package dev.evo.elasticmagic.query
import dev.evo.elasticmagic.compile.SearchQueryCompiler
import dev.evo.elasticmagic.serde.Serializer

interface DisMaxExpression : QueryExpression {
val queries: List<QueryExpression>

override fun children(): Iterator<QueryExpression> = iterator {
yieldAll(queries)
}
}

data class DisMax(
override val queries: List<QueryExpression>,
val queries: List<QueryExpression>,
val tieBreaker: Float? = null,
) : DisMaxExpression {
) : QueryExpression {
override val name = "dis_max"

override fun clone() = copy()

override fun children(): Iterator<QueryExpression> = iterator {
yieldAll(queries)
}

override fun rewrite(newNode: QueryExpressionNode<*>): DisMax {
replaceNodeInExpressions(queries, { it.rewrite(newNode) }) {
return copy(queries = it)
}
return this
}

override fun reduce(): QueryExpression? {
return when {
queries.isEmpty() -> null
Expand All @@ -34,34 +37,3 @@ data class DisMax(
ctx.fieldIfNotNull("tie_breaker", tieBreaker)
}
}

data class DisMaxNode(
override val handle: NodeHandle<DisMaxNode>,
override var queries: MutableList<QueryExpression>,
var tieBreaker: Float? = null,
) : QueryExpressionNode<DisMaxNode>(), DisMaxExpression {
override val name = "dis_max"

companion object {
operator fun invoke(
handle: NodeHandle<DisMaxNode>,
queries: List<QueryExpression> = emptyList(),
tieBreaker: Float? = null,
): DisMaxNode {
return DisMaxNode(
handle,
queries = queries.toMutableList(),
tieBreaker = tieBreaker,
)
}
}

override fun clone() = copy(queries = queries.toMutableList())

override fun toQueryExpression(): DisMax {
return DisMax(
queries = queries,
tieBreaker = tieBreaker
)
}
}
Loading

0 comments on commit 841307f

Please sign in to comment.