Querying¶
To build a search query use a SearchQuery builder.
In these samples we will utilize following document:
package samples.querying
import dev.evo.elasticmagic.doc.Document
object UserDoc : Document() {
val id by int()
val isActive by boolean("is_active")
val groups by keyword()
val rating by float()
val about by text()
}
Query¶
You can pass a query to SearchQuery
directly via its constructor:
package samples.querying
import dev.evo.elasticmagic.query.FunctionScore
import dev.evo.elasticmagic.SearchQuery
import dev.evo.elasticmagic.query.match
var q = SearchQuery(
FunctionScore(
UserDoc.about.match("fake"),
listOf(
FunctionScore.FieldValueFactor(
UserDoc.rating,
missing = 0.0F,
)
)
)
)
Also it is possible to replace existing query using a query
method:
package samples.querying
import dev.evo.elasticmagic.query.Bool
import dev.evo.elasticmagic.query.FunctionScore
import dev.evo.elasticmagic.query.match
val q2 = q.query(
FunctionScore(
Bool.should(
UserDoc.about.match("fake"),
UserDoc.about.match("real"),
),
listOf(
FunctionScore.FieldValueFactor(
UserDoc.rating,
missing = 0.0F,
)
)
)
)
See full list of available query expressions.
Cloning¶
In the last example q
and q2
variables point to the same object. For efficiency all
SearchQuery
methods modify current instance and return it for chaining method calls.
If you want to get independent query instance you should clone it explicitly:
package samples.querying
import dev.evo.elasticmagic.query.match
val clonedQuery = q.clone()
.query(UserDoc.about.match("fake"))
Now you can modify clonedQuery
without touching its ancestor q
.
Filtering¶
Using filter
method you can filter your query. All filters passed to the filter
method will be combined
using AND
operation.
package samples.querying
import dev.evo.elasticmagic.query.Bool
import dev.evo.elasticmagic.query.match
// Select only active users that id is not equal 0
val activeUsersQuery = q
.filter(UserDoc.isActive.eq(true))
.filter(UserDoc.id.ne(0))
// The same as above
val activeUsersQuery2 = q
.filter(
UserDoc.isActive.eq(true),
UserDoc.id.ne(0)
)
// If you need one condition OR another use Bool.should expression.
// In the following example we select non-active users OR "fake" users
val activeUsersQuery3 = q
.filter(
Bool.should(
UserDoc.isActive.ne(true),
UserDoc.about.match("fake")
)
)
Sorting¶
sort method has 2 flavors:
- a shortcut that accepts document fields
- and full-featured version accepting Sort expressions
package samples.querying
import dev.evo.elasticmagic.query.Sort
// Sort by user id with ascending order
val sortedByIdQuery = q.sort(UserDoc.id)
// Sort by user id descending
val sortedByIdDescQuery = q.sort(UserDoc.id.desc())
// Sort by several criteria
val multipleSortedQuery = q.sort(UserDoc.isActive.desc(), UserDoc.id.asc())
// Use Sort explicitly to customize sort behaviour
val sortedByRatingQuery = q.sort(Sort(UserDoc.rating, missing = Sort.Missing.First))
See:
Aggregations¶
Use aggs method to define aggregations:
package samples.querying
import dev.evo.elasticmagic.aggs.AvgAgg
import dev.evo.elasticmagic.aggs.HistogramAgg
import dev.evo.elasticmagic.aggs.TermsAgg
// Build aggregations by groups and calculate average rating as well as
// build histogram by rating for every group
val ratingHistogramQuery = q.aggs(
"groups" to TermsAgg(
UserDoc.groups,
aggs = mapOf(
"avg_rating" to AvgAgg(UserDoc.rating),
"rating_histogram" to HistogramAgg(
UserDoc.rating, interval = 10.0F
),
)
)
)
Query Nodes¶
Sometimes you don't know final form of your query when creating it. For such a usecase it is possible to replace parts of the query after creation using special query expressions and queryNode method:
package samples.querying
import dev.evo.elasticmagic.SearchQuery
import dev.evo.elasticmagic.doc.BaseDocSource
import dev.evo.elasticmagic.doc.BoundField
import dev.evo.elasticmagic.doc.Document
import dev.evo.elasticmagic.doc.MappingField
import dev.evo.elasticmagic.doc.SubDocument
import dev.evo.elasticmagic.query.Bool
import dev.evo.elasticmagic.query.DisMax
import dev.evo.elasticmagic.query.FunctionScore
import dev.evo.elasticmagic.query.match
import dev.evo.elasticmagic.query.NodeHandle
import dev.evo.elasticmagic.query.QueryExpressionNode
import kotlin.random.Random
class TranslationDoc(field: BoundField<BaseDocSource, Nothing>) : SubDocument(field) {
val en by text()
val de by text()
val ru by text()
}
object QuestionDoc : Document() {
val title by obj(::TranslationDoc)
val text by obj(::TranslationDoc)
val rating by float()
val votes by int()
}
val scoringHandle = NodeHandle<FunctionScore>()
val langHandle = NodeHandle<DisMax>()
val searchTerm = "hello world"
val skeletonQuery = SearchQuery(
QueryExpressionNode(
scoringHandle,
FunctionScore(
QueryExpressionNode(
langHandle,
DisMax(
queries = listOf(
Bool.should(
QuestionDoc.title.en.match(searchTerm),
QuestionDoc.text.en.match(searchTerm),
)
),
)
),
functions = listOf(
FunctionScore.FieldValueFactor(QuestionDoc.rating),
),
scoreMode = FunctionScore.ScoreMode.MULTIPLY,
)
)
)
val boostByNumberOfVotes = Random.nextBoolean()
var boostedQuery = if (boostByNumberOfVotes) {
skeletonQuery.queryNode(scoringHandle) { node ->
node.copy(
functions = node.functions + listOf(
FunctionScore.Weight(
1.1F,
filter = QuestionDoc.votes.range(gte = 10, lt = 50)
),
FunctionScore.Weight(
1.5F,
filter = QuestionDoc.votes.gte(50)
),
)
)
}
} else {
skeletonQuery
}
val additionalLanguages = listOf("de", "ru")
val userLang = additionalLanguages[Random.nextInt(additionalLanguages.size)]
val additionalLangFields: List<MappingField<String>> = listOfNotNull(
QuestionDoc.title.getFieldByName(userLang),
QuestionDoc.text.getFieldByName(userLang),
)
val langQuery = if (additionalLangFields.isNotEmpty()) {
boostedQuery.queryNode(langHandle) { node ->
node.copy(
queries = node.queries + listOf(
Bool.should(
*additionalLangFields.map { it.match(searchTerm) }.toTypedArray()
)
)
)
}
} else {
boostedQuery
}