Skip to content

Commit

Permalink
(dsl): Support Function Score Query (#224)
Browse files Browse the repository at this point in the history
  • Loading branch information
markaya authored May 30, 2023
1 parent 314e404 commit 1e5f9b1
Show file tree
Hide file tree
Showing 8 changed files with 1,915 additions and 24 deletions.
189 changes: 189 additions & 0 deletions docs/overview/queries/elastic_query_function_score.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
id: elastic_query_function_score
title: "Function Score Query"
---

The `FunctionScore` allows you to modify the score of documents that are retrieved by a query.

In order to use the `FunctionScore` query and create needed `FunctionScoreFunction` import the following:
```scala
import zio.elasticsearch.query.FunctionScoreQuery
import zio.elasticsearch.query.FunctionScoreFunction._
import zio.elasticsearch.ElasticQuery._
```

For creating `FunctionScore` query you require `FunctionScoreFunction` or multiple of them.
You can create these functions in following way.

<br/>

You can create `DecayFunction` with `DecayFunctionType.Exp` using the `expDecayFunction` method with origin and scale in the following manner:
```scala
val function: DecayFunction[Any] = expDecayFunction("field", origin = "11, 12", scale = "2km")
```
You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema)
`DecayFunction` with `DecayFunctionType.Exp` using the `expDecayFunction` method with origin and scale in the following manner:
```scala
val function: DecayFunction[Document] = expDecayFunction(field = Document.field, origin = "11, 12", scale = "2km")
```

<br/>

You can create `DecayFunction` with `DecayFunctionType.Gauss` using the `gaussDecayFunction` method with origin and scale in the following manner:
```scala
val function: DecayFunction[Any] = gaussDecayFunction(field = "field", origin = "11, 12", scale = "2km")
```
You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema)
`DecayFunction` with `DecayFunctionType.Gauss` using the `gaussDecayFunction` method with origin and scale in the following manner:
```scala
val function: DecayFunction[Document] = gaussDecayFunction(field = Document.field, origin = "11, 12", scale = "2km")
```

<br/>

You can create `DecayFunction` with `DecayFunctionType.Linear` using the `linearDecayFunction` method with origin and scale in the following manner:
```scala
val function: DecayFunction[Any] = linearDecayFunction(field = "field", origin = "11, 12", scale = "2km")
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema)
`DecayFunction` with `DecayFunctionType.Linear` using the `expDecayFunction` method with origin and scale in the following manner:
```scala
val function: DecayFunction[Document] = linearDecayFunction(field = Document.field, origin = "11, 12", scale = "2km")
```

<br/>

You can create `FieldValueFactor` using the `fieldValueFactor` method:

```scala
val function: FieldValueFactor[Any] = fieldValueFactor(field = "field")
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema)
`FieldValueFactor` using the `fieldValueFactor` method:

```scala
val function: FieldValueFactor[Document] = fieldValueFactor(field = Document.field)
```

<br/>

You can create `RandomScoreFunction` using the `randomScoreFunction` in three different ways depending on amount of parameters
you want to use:

```scala
val function: RandomScoreFunction[Any] = randomScoreFunction()

val function: RandomScoreFunction[Any] = randomScoreFunction(seed = 10)

val function: RandomScoreFunction[Any] = randomScoreFunction(seed = 10, field = "field")
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema)
`RandomScoreFunction` using the `randomScoreFunction` method:
```scala
val function: RandomScoreFunction = randomScoreFunction(seed = 10, field = Document.field)
```

<br/>

You can create `ScriptScoreFunction` using the `scriptScoreFunction` method with script in following manner:

```scala
val function: ScriptScoreFunction[Any] = scriptScoreFunction(script = Script("params.agg1 > 10"))
val function: ScriptScoreFunction[Any] = scriptScoreFunction(scriptSource = "params.agg1 > 10")
```

<br/>

You can create `WeightFunction` using the `weightFunction` method(you must provide `Any` type parameter when using):

```scala
val function: WeightFunction[Any] = scriptScoreFunction(weight = 10)
```

<br/><br/>

You can use these functions to create `FunctionScore` query using the `functionScore` method in the following manner:

```scala
val randomScoreFunction: RandomScoreFunction[Any] = randomScoreFunction()
val weightFunction: WeightFunction[Any] = scriptScoreFunction(weight = 10)
val query: FunctionScoreQuery[Any] = functionScore(randomScoreFunction, weightFunction)
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `FunctionScore` query
using the `functionScore`, if all functions are created type-safe, in the following manner:

```scala
val decayFunction: DecayFunction[Document] = expDecayFunction(field = Document.field, origin = "11, 12", scale = "2km")
val randomScoreFunction: RandomScoreFunction[Document] = randomScoreFunction(seed = 10, field = Document.field)
val weightFunction: WeightFunction[Any] = scriptScoreFunction(weight = 10)
val query: FunctionScoreQuery[Document] = functionScore(decayFunction, randomScoreFunction, weightFunction)
```

<br/>

If you want to change the `boost`, you can use `boost` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).boost(5.0)
```

<br/>

If you want to change the `boostMode`, you can use `boostMode` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).boostMode(FunctionScoreBoostMode.Max)
```

<br/>

If you want to change the `maxBoost`, you can use `maxBoost` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).maxBoost(5.0)
```

<br/>

If you want to change the `minScore`, you can use `minScore` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).minScore(5.0)
```

<br/>

If you want to change the `query`, you can use `query` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).query(matches(Document.field, "value"))
```

<br/>

If you want to change the `scoreMode`, you can use `scoreMode` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).scoreMode(FunctionScoreScoreMode.Max)
```

<br/>

If you want to add a one or multiple new `FunctionScoreFunction` you can use `withFunctions` method:
```scala
import zio.elasticsearch.query.DistanceUnit

val queryWithDistance: FunctionScoreQuery[Document] = functionScore(randomScoreFunction(seed = 10, field = Document.field)).withFunctions(scriptScoreFunction(weight = 10))
```

You can find more information about `FunctionScore` query [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-function-score-query.html).
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import zio.elasticsearch.executor.response.{
MaxAggregationResponse,
TermsAggregationResponse
}
import zio.elasticsearch.query.FunctionScoreFunction.randomScoreFunction
import zio.elasticsearch.query.{FunctionScoreBoostMode, FunctionScoreFunction}
import zio.elasticsearch.query.sort.SortMode.Max
import zio.elasticsearch.query.sort.SortOrder._
import zio.elasticsearch.query.sort.SourceType.NumberType
Expand Down Expand Up @@ -1704,6 +1706,91 @@ object HttpExecutorSpec extends IntegrationSpec {
)
}
} @@ after(Executor.execute(ElasticRequest.deleteIndex(geoDistanceIndex)).orDie)
),
suite("search for documents using FunctionScore query")(
test("using randomScore function") {
checkOnce(genTestDocument, genTestDocument) { (firstDocument, secondDocument) =>
val secondDocumentUpdated = secondDocument.copy(stringField = firstDocument.stringField)
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
_ <- Executor.execute(
ElasticRequest.create[TestDocument](firstSearchIndex, firstDocument).refreshTrue
)
_ <- Executor.execute(
ElasticRequest
.create[TestDocument](
firstSearchIndex,
secondDocumentUpdated
)
.refreshTrue
)
r1 <- Executor
.execute(
ElasticRequest.search(
firstSearchIndex,
ElasticQuery
.functionScore(randomScoreFunction())
.query(matches("stringField", firstDocument.stringField))
)
)
.documentAs[TestDocument]
} yield assert(r1)(
hasSameElements(Chunk(firstDocument, secondDocumentUpdated))
)
}
} @@ around(
Executor.execute(
ElasticRequest.createIndex(
firstSearchIndex,
"""{ "mappings": { "properties": { "subDocumentList": { "type": "nested" } } } }"""
)
),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("using randomScore function and weight function") {
checkOnce(genTestDocument, genTestDocument) { (firstDocument, secondDocument) =>
val secondDocumentUpdated = secondDocument.copy(stringField = firstDocument.stringField)
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
_ <- Executor.execute(
ElasticRequest.create[TestDocument](firstSearchIndex, firstDocument).refreshTrue
)
_ <- Executor.execute(
ElasticRequest
.create[TestDocument](
firstSearchIndex,
secondDocumentUpdated
)
.refreshTrue
)
r1 <- Executor
.execute(
ElasticRequest.search(
firstSearchIndex,
ElasticQuery
.functionScore(
FunctionScoreFunction.randomScoreFunction(),
FunctionScoreFunction.weightFunction(2)
)
.query(matches("stringField", firstDocument.stringField))
.boost(2.0)
.boostMode(FunctionScoreBoostMode.Max)
)
)
.documentAs[TestDocument]
} yield assert(r1)(
hasSameElements(Chunk(firstDocument, secondDocumentUpdated))
)
}
} @@ around(
Executor.execute(
ElasticRequest.createIndex(
firstSearchIndex,
"""{ "mappings": { "properties": { "subDocumentList": { "type": "nested" } } } }"""
)
),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
)
)
) @@ nondeterministic @@ sequential @@ prepareElasticsearchIndexForTests @@ afterAll(
Executor.execute(ElasticRequest.deleteIndex(index)).orDie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ object ElasticQuery {
Exists(field = field, boost = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must satisfy the criteria using
* the specified parameters.
* Constructs a type-safe instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must satisfy the
* criteria using the specified parameters.
*
* @param queries
* a list of queries to add to `filter` inside of the `Bool` query
Expand Down Expand Up @@ -124,6 +124,54 @@ object ElasticQuery {
minimumShouldMatch = None
)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.FunctionScore]] query with one or multiple
* [[zio.elasticsearch.query.FunctionScoreFunction]].
*
* @param functions
* [[zio.elasticsearch.query.FunctionScoreFunction]] functions that will be part of
* [[zio.elasticsearch.query.FunctionScore]] query
* @return
* an instance of [[zio.elasticsearch.query.FunctionScore]] that represents the Function Score Query with functions
* that are used to calculate score for result.
*/
final def functionScore[S: Schema](
functions: FunctionScoreFunction[S]*
): FunctionScoreQuery[S] =
FunctionScore[S](
functionScoreFunctions = Chunk.fromIterable(functions),
boost = None,
boostMode = None,
maxBoost = None,
minScore = None,
query = None,
scoreMode = None
)

/**
* Constructs an instance of [[zio.elasticsearch.query.FunctionScore]] query with one or multiple
* [[zio.elasticsearch.query.FunctionScoreFunction]].
*
* @param functions
* [[zio.elasticsearch.query.FunctionScoreFunction]] functions that will be part of
* [[zio.elasticsearch.query.FunctionScore]] query
* @return
* an instance of [[zio.elasticsearch.query.FunctionScore]] that represents the Function Score Query with functions
* that are used to calculate score for result.
*/
final def functionScore(
functions: FunctionScoreFunction[Any]*
): FunctionScoreQuery[Any] =
FunctionScore[Any](
functionScoreFunctions = Chunk.fromIterable(functions),
boost = None,
boostMode = None,
maxBoost = None,
minScore = None,
query = None,
scoreMode = None
)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.GeoDistanceQuery]] using the specified parameters.
*
Expand Down Expand Up @@ -381,8 +429,8 @@ object ElasticQuery {
MatchPhrase(field = field, value = value, boost = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must satisfy the criteria using
* the specified parameters.
* Constructs a type-safe instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must satisfy the
* criteria using the specified parameters.
*
* @param queries
* a list of queries to add to `must` inside of the `Bool` query
Expand Down Expand Up @@ -423,8 +471,8 @@ object ElasticQuery {
)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must not satisfy the criteria
* using the specified parameters.
* Constructs a type-safe instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must not satisfy the
* criteria using the specified parameters.
*
* @param queries
* a list of queries to add to `mustNot` inside of the `Bool` query
Expand Down
Loading

0 comments on commit 1e5f9b1

Please sign in to comment.