diff --git a/es/enums/boost-mode/boost_mode.go b/es/enums/boost-mode/boost_mode.go new file mode 100644 index 0000000..63b5863 --- /dev/null +++ b/es/enums/boost-mode/boost_mode.go @@ -0,0 +1,46 @@ +package boostmode + +// BoostMode represents the different boost modes for function_score queries. +// +// BoostMode is a string type used to specify how the computed score from the +// function_score query is combined with the query score. It provides various +// options for combining the function score with the original query score. +// +// Example usage: +// +// var mode BoostMode = Multiply +// +// // Use mode in a function_score query configuration +// +// Constants: +// - Multiply: Multiply the query score with the function score (default). +// - Replace: Replace the query score with the function score. +// - Sum: Add the query score and the function score. +// - Avg: Average the query score and the function score. +// - Max: Use the maximum of the query score and the function score. +// - Min: Use the minimum of the query score and the function score. +type BoostMode string + +const ( + // Multiply indicates that the query score should be multiplied with the function score. + Multiply BoostMode = "multiply" + + // Replace indicates that the query score should be replaced with the function score. + Replace BoostMode = "replace" + + // Sum indicates that the query score and the function score should be added. + Sum BoostMode = "sum" + + // Avg indicates that the query score and the function score should be averaged. + Avg BoostMode = "avg" + + // Max indicates that the maximum of the query score and the function score should be used. + Max BoostMode = "max" + + // Min indicates that the minimum of the query score and the function score should be used. + Min BoostMode = "min" +) + +func (boostMode BoostMode) String() string { + return string(boostMode) +} diff --git a/es/enums/boost-mode/boost_mode_test.go b/es/enums/boost-mode/boost_mode_test.go new file mode 100644 index 0000000..50f6ad6 --- /dev/null +++ b/es/enums/boost-mode/boost_mode_test.go @@ -0,0 +1,29 @@ +package boostmode_test + +import ( + "testing" + + BoostMode "github.com/Trendyol/es-query-builder/es/enums/boost-mode" + + "github.com/Trendyol/es-query-builder/test/assert" +) + +func Test_BoostModeString(t *testing.T) { + tests := []struct { + mode BoostMode.BoostMode + result string + }{ + {BoostMode.Multiply, "multiply"}, + {BoostMode.Replace, "replace"}, + {BoostMode.Sum, "sum"}, + {BoostMode.Avg, "avg"}, + {BoostMode.Max, "max"}, + {BoostMode.Min, "min"}, + } + + for _, test := range tests { + t.Run(test.result, func(t *testing.T) { + assert.Equal(t, test.result, test.mode.String()) + }) + } +} diff --git a/es/enums/modifier/modifier.go b/es/enums/modifier/modifier.go new file mode 100644 index 0000000..2bda614 --- /dev/null +++ b/es/enums/modifier/modifier.go @@ -0,0 +1,61 @@ +package modifier + +// Modifier represents the different modifier functions for field_value_factor in function_score queries. +// +// Modifier is a string type used to specify the mathematical function applied to the field value +// before it is used to compute the score in a field_value_factor function. +// +// Example usage: +// +// var mod Modifier = Log1p +// +// // Use mod in a field_value_factor configuration +// +// Constants: +// - None: Do not apply any multiplier to the field value. +// - Log: Take the common logarithm of the field value. +// - Log1p: Add 1 to the field value and take the common logarithm. +// - Log2p: Add 2 to the field value and take the common logarithm. +// - Ln: Take the natural logarithm of the field value. +// - Ln1p: Add 1 to the field value and take the natural logarithm. +// - Ln2p: Add 2 to the field value and take the natural logarithm. +// - Square: Square the field value (multiply it with itself). +// - Sqrt: Take the square root of the field value. +// - Reciprocal: Reciprocate the field value (1/x). +type Modifier string + +const ( + // None indicates that no modifier should be applied to the field value. + None Modifier = "none" + + // Log indicates that the common logarithm should be applied to the field value. + Log Modifier = "log" + + // Log1p indicates that 1 should be added to the field value before taking the common logarithm. + Log1p Modifier = "log1p" + + // Log2p indicates that 2 should be added to the field value before taking the common logarithm. + Log2p Modifier = "log2p" + + // Ln indicates that the natural logarithm should be applied to the field value. + Ln Modifier = "ln" + + // Ln1p indicates that 1 should be added to the field value before taking the natural logarithm. + Ln1p Modifier = "ln1p" + + // Ln2p indicates that 2 should be added to the field value before taking the natural logarithm. + Ln2p Modifier = "ln2p" + + // Square indicates that the field value should be squared. + Square Modifier = "square" + + // Sqrt indicates that the square root should be applied to the field value. + Sqrt Modifier = "sqrt" + + // Reciprocal indicates that the reciprocal (1/x) should be applied to the field value. + Reciprocal Modifier = "reciprocal" +) + +func (modifier Modifier) String() string { + return string(modifier) +} diff --git a/es/enums/modifier/modifier_test.go b/es/enums/modifier/modifier_test.go new file mode 100644 index 0000000..3e0614c --- /dev/null +++ b/es/enums/modifier/modifier_test.go @@ -0,0 +1,33 @@ +package modifier_test + +import ( + "testing" + + Modifier "github.com/Trendyol/es-query-builder/es/enums/modifier" + + "github.com/Trendyol/es-query-builder/test/assert" +) + +func Test_ModifierString(t *testing.T) { + tests := []struct { + modifier Modifier.Modifier + result string + }{ + {Modifier.None, "none"}, + {Modifier.Log, "log"}, + {Modifier.Log1p, "log1p"}, + {Modifier.Log2p, "log2p"}, + {Modifier.Ln, "ln"}, + {Modifier.Ln1p, "ln1p"}, + {Modifier.Ln2p, "ln2p"}, + {Modifier.Square, "square"}, + {Modifier.Sqrt, "sqrt"}, + {Modifier.Reciprocal, "reciprocal"}, + } + + for _, test := range tests { + t.Run(test.result, func(t *testing.T) { + assert.Equal(t, test.result, test.modifier.String()) + }) + } +} diff --git a/es/enums/multi-values-mode/multi_values_mode.go b/es/enums/multi-values-mode/multi_values_mode.go new file mode 100644 index 0000000..30fa158 --- /dev/null +++ b/es/enums/multi-values-mode/multi_values_mode.go @@ -0,0 +1,38 @@ +package multivaluesmode + +// MultiValuesMode represents the different modes for handling multi-valued fields in decay functions. +// +// MultiValuesMode is a string type used to specify how a decay function should handle +// fields that contain multiple values. It determines which value is chosen for the +// distance calculation. +// +// Example usage: +// +// var mode MultiValuesMode = Min +// +// // Use mode in a decay function configuration +// +// Constants: +// - Min: Use the minimum distance from the origin. +// - Max: Use the maximum distance from the origin. +// - Avg: Use the average distance from the origin. +// - Sum: Use the sum of all distances from the origin. +type MultiValuesMode string + +const ( + // Min indicates that the minimum distance should be used. + Min MultiValuesMode = "min" + + // Max indicates that the maximum distance should be used. + Max MultiValuesMode = "max" + + // Avg indicates that the average distance should be used. + Avg MultiValuesMode = "avg" + + // Sum indicates that the sum of distances should be used. + Sum MultiValuesMode = "sum" +) + +func (multiValuesMode MultiValuesMode) String() string { + return string(multiValuesMode) +} diff --git a/es/enums/multi-values-mode/multi_values_mode_test.go b/es/enums/multi-values-mode/multi_values_mode_test.go new file mode 100644 index 0000000..114fde2 --- /dev/null +++ b/es/enums/multi-values-mode/multi_values_mode_test.go @@ -0,0 +1,27 @@ +package multivaluesmode_test + +import ( + "testing" + + MultiValuesMode "github.com/Trendyol/es-query-builder/es/enums/multi-values-mode" + + "github.com/Trendyol/es-query-builder/test/assert" +) + +func Test_MultiValuesModeString(t *testing.T) { + tests := []struct { + mode MultiValuesMode.MultiValuesMode + result string + }{ + {MultiValuesMode.Min, "min"}, + {MultiValuesMode.Max, "max"}, + {MultiValuesMode.Avg, "avg"}, + {MultiValuesMode.Sum, "sum"}, + } + + for _, test := range tests { + t.Run(test.result, func(t *testing.T) { + assert.Equal(t, test.result, test.mode.String()) + }) + } +} diff --git a/es/function_score_decay.go b/es/function_score_decay.go new file mode 100644 index 0000000..339fae3 --- /dev/null +++ b/es/function_score_decay.go @@ -0,0 +1,134 @@ +package es + +import MultiValuesMode "github.com/Trendyol/es-query-builder/es/enums/multi-values-mode" + +type decayFunctionType Object + +// Decay creates a new es.decayFunctionType object with the specified field. +// +// This function initializes a decay function configuration for use with decay functions +// (gauss, linear, exp) in a function_score query. The field parameter represents the name +// of the field on which the decay function operates. +// +// Example usage: +// +// d := es.Decay("date").Origin("now").Scale("10d") +// // d now contains a decay configuration for the "date" field. +// +// Parameters: +// - field: A string representing the field name for the decay function. +// +// Returns: +// +// An es.decayFunctionType object with the specified field. +func Decay(field string) decayFunctionType { + return decayFunctionType{ + field: Object{}, + } +} + +// Origin sets the "origin" parameter in the decay function field configuration. +// +// This method specifies the point of origin used to calculate distance. The origin must +// be provided for numeric and geo fields. For date fields, the default is "now". +// +// Example usage: +// +// d := es.Decay("date").Origin("now") +// // d now includes an "origin" parameter set to "now" for the "date" field. +// +// Parameters: +// - origin: A value representing the origin point. The type depends on the field type. +// +// Returns: +// +// The updated es.decayFunctionType object with the "origin" parameter set. +func (d decayFunctionType) Origin(origin any) decayFunctionType { + return d.putInTheField("origin", origin) +} + +// Scale sets the "scale" parameter in the decay function field configuration. +// +// This method specifies the distance from the origin at which the computed score equals +// the decay parameter. For geo fields, the scale can be defined as a number with a unit +// (e.g., "1km", "12mi"). For date fields, it can be defined as a duration (e.g., "10d"). +// +// Example usage: +// +// d := es.Decay("date").Origin("now").Scale("10d") +// // d now includes a "scale" parameter set to "10d" for the "date" field. +// +// Parameters: +// - scale: A value representing the scale distance. +// +// Returns: +// +// The updated es.decayFunctionType object with the "scale" parameter set. +func (d decayFunctionType) Scale(scale any) decayFunctionType { + return d.putInTheField("scale", scale) +} + +// Offset sets the "offset" parameter in the decay function field configuration. +// +// This method specifies a distance from the origin within which no decay is applied. +// Documents within this offset distance from the origin receive a score of 1.0. +// +// Example usage: +// +// d := es.Decay("date").Origin("now").Scale("10d").Offset("5d") +// // d now includes an "offset" parameter set to "5d" for the "date" field. +// +// Parameters: +// - offset: A value representing the offset distance. +// +// Returns: +// +// The updated es.decayFunctionType object with the "offset" parameter set. +func (d decayFunctionType) Offset(offset any) decayFunctionType { + return d.putInTheField("offset", offset) +} + +// DecayValue sets the "decay" parameter in the decay function field configuration. +// +// This method specifies the score at the scale distance from the origin. The default +// value is 0.5 if not specified. +// +// Example usage: +// +// d := es.Decay("date").Origin("now").Scale("10d").DecayValue(0.5) +// // d now includes a "decay" parameter set to 0.5 for the "date" field. +// +// Parameters: +// - decay: A float64 value representing the decay score at the scale distance. +// +// Returns: +// +// The updated es.decayFunctionType object with the "decay" parameter set. +func (d decayFunctionType) DecayValue(decay float64) decayFunctionType { + return d.putInTheField("decay", decay) +} + +// MultiValueMode sets the "multi_value_mode" parameter in the decay function configuration. +// +// This method specifies how the decay function should handle fields that contain multiple values. +// It determines which value is chosen for the distance calculation. +// +// Example usage: +// +// d := es.Decay("location").Origin("0,0").Scale("5km").MultiValueMode(MultiValuesMode.Min) +// // d now includes a "multi_value_mode" parameter set to "min". +// +// Parameters: +// - multiValueMode: A MultiValuesMode.MultiValuesMode value representing the mode. +// +// Returns: +// +// The updated es.decayFunctionType object with the "multi_value_mode" parameter set. +func (d decayFunctionType) MultiValueMode(multiValueMode MultiValuesMode.MultiValuesMode) decayFunctionType { + d["multi_value_mode"] = multiValueMode + return d +} + +func (d decayFunctionType) putInTheField(key string, value any) decayFunctionType { + return genericPutInTheFieldOfFirstObject(d, key, value) +} diff --git a/es/function_score_decay_test.go b/es/function_score_decay_test.go new file mode 100644 index 0000000..50e8fac --- /dev/null +++ b/es/function_score_decay_test.go @@ -0,0 +1,185 @@ +package es_test + +import ( + "testing" + + MultiValuesMode "github.com/Trendyol/es-query-builder/es/enums/multi-values-mode" + + "github.com/Trendyol/es-query-builder/es" + "github.com/Trendyol/es-query-builder/test/assert" +) + +//// Decay //// + +func Test_Decay_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.Decay) +} + +func Test_Decay_method_should_create_decayFunctionType(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // Then + assert.NotNil(t, d) + assert.IsTypeString(t, "es.decayFunctionType", d) +} + +func Test_Decay_should_create_json_with_field(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"date\":{}}", bodyJSON) +} + +//// Origin //// + +func Test_Decay_should_have_Origin_method(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // When Then + assert.NotNil(t, d.Origin) +} + +func Test_Decay_Origin_should_create_json_with_origin_field(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date").Origin("now") + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"date\":{\"origin\":\"now\"}}", bodyJSON) +} + +//// Scale //// + +func Test_Decay_should_have_Scale_method(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // When Then + assert.NotNil(t, d.Scale) +} + +func Test_Decay_Scale_should_create_json_with_scale_field(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date").Origin("now").Scale("10d") + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"date\":{\"origin\":\"now\",\"scale\":\"10d\"}}", bodyJSON) +} + +//// Offset //// + +func Test_Decay_should_have_Offset_method(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // When Then + assert.NotNil(t, d.Offset) +} + +func Test_Decay_Offset_should_create_json_with_offset_field(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date").Origin("now").Scale("10d").Offset("5d") + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"date\":{\"offset\":\"5d\",\"origin\":\"now\",\"scale\":\"10d\"}}", bodyJSON) +} + +//// DecayValue //// + +func Test_Decay_should_have_DecayValue_method(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // When Then + assert.NotNil(t, d.DecayValue) +} + +func Test_Decay_DecayValue_should_create_json_with_decay_field(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date").Origin("now").Scale("10d").DecayValue(0.5) + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"date\":{\"decay\":0.5,\"origin\":\"now\",\"scale\":\"10d\"}}", bodyJSON) +} + +//// MultiValueMode //// + +func Test_Decay_should_have_MultiValueMode_method(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date") + + // When Then + assert.NotNil(t, d.MultiValueMode) +} + +func Test_Decay_MultiValueMode_should_create_json_with_multi_value_mode_field(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("location").Origin("0,0").Scale("5km").MultiValueMode(MultiValuesMode.Min) + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"location\":{\"origin\":\"0,0\",\"scale\":\"5km\"},\"multi_value_mode\":\"min\"}", bodyJSON) +} + +//// Combined //// + +func Test_Decay_should_create_json_with_all_parameters(t *testing.T) { + t.Parallel() + // Given + d := es.Decay("date"). + Origin("now"). + Scale("10d"). + Offset("5d"). + DecayValue(0.5) + + // When Then + assert.NotNil(t, d) + bodyJSON := assert.MarshalWithoutError(t, d) + assert.Equal(t, "{\"date\":{\"decay\":0.5,\"offset\":\"5d\",\"origin\":\"now\",\"scale\":\"10d\"}}", bodyJSON) +} + +func Test_DecayFunction_with_filter_and_weight_in_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + Functions( + es.DecayFunction("gauss", es.Decay("date").Origin("now").Scale("10d")). + Filter(es.Term("status", "published")). + Weight(2), + ), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"functions\":[{\"filter\":{\"term\":{\"status\":{\"value\":\"published\"}}},\"gauss\":{\"date\":{\"origin\":\"now\",\"scale\":\"10d\"}},\"weight\":2}],\"query\":{\"match_all\":{}}}}}", bodyJSON) +} diff --git a/es/function_score_field_value_factor.go b/es/function_score_field_value_factor.go new file mode 100644 index 0000000..d13d792 --- /dev/null +++ b/es/function_score_field_value_factor.go @@ -0,0 +1,93 @@ +package es + +import Modifier "github.com/Trendyol/es-query-builder/es/enums/modifier" + +type fieldValueFactorType Object + +// FieldValueFactor creates a new es.fieldValueFactorType object with the specified field. +// +// This function initializes a field_value_factor configuration that uses the value of a +// document field to influence the score. The field parameter represents the name of the +// field whose value will be used in the score calculation. +// +// Example usage: +// +// fvf := es.FieldValueFactor("likes") +// // fvf now contains a field_value_factor configuration for the "likes" field. +// +// Parameters: +// - field: A string representing the field name whose value will be used for scoring. +// +// Returns: +// +// An es.fieldValueFactorType object with the specified field. +func FieldValueFactor(field string) fieldValueFactorType { + return fieldValueFactorType{ + "field": field, + } +} + +// Factor sets the "factor" parameter in an es.fieldValueFactorType. +// +// This method specifies an optional factor to multiply the field value with before +// applying the modifier. The default value is 1. +// +// Example usage: +// +// fvf := es.FieldValueFactor("likes").Factor(1.2) +// // fvf now includes a "factor" parameter set to 1.2. +// +// Parameters: +// - factor: A float64 value representing the factor to multiply the field value with. +// +// Returns: +// +// The updated es.fieldValueFactorType object with the "factor" parameter set. +func (fvf fieldValueFactorType) Factor(factor float64) fieldValueFactorType { + fvf["factor"] = factor + return fvf +} + +// Modifier sets the "modifier" parameter in an es.fieldValueFactorType. +// +// This method specifies the mathematical function to apply to the field value before +// using it in the score calculation. Available modifiers include none, log, log1p, +// log2p, ln, ln1p, ln2p, square, sqrt, and reciprocal. +// +// Example usage: +// +// fvf := es.FieldValueFactor("likes").Modifier(Modifier.Log1p) +// // fvf now includes a "modifier" parameter set to "log1p". +// +// Parameters: +// - modifier: A Modifier.Modifier value representing the modifier function. +// +// Returns: +// +// The updated es.fieldValueFactorType object with the "modifier" parameter set. +func (fvf fieldValueFactorType) Modifier(modifier Modifier.Modifier) fieldValueFactorType { + fvf["modifier"] = modifier + return fvf +} + +// Missing sets the "missing" parameter in an es.fieldValueFactorType. +// +// This method specifies the value to use if the document does not have the specified field. +// The modifier and factor are still applied to the missing value as though it were coming +// from the document. +// +// Example usage: +// +// fvf := es.FieldValueFactor("likes").Missing(1) +// // fvf now includes a "missing" parameter set to 1. +// +// Parameters: +// - missing: A value to use when the document lacks the field. +// +// Returns: +// +// The updated es.fieldValueFactorType object with the "missing" parameter set. +func (fvf fieldValueFactorType) Missing(missing any) fieldValueFactorType { + fvf["missing"] = missing + return fvf +} diff --git a/es/function_score_field_value_factor_test.go b/es/function_score_field_value_factor_test.go new file mode 100644 index 0000000..f1b5c5f --- /dev/null +++ b/es/function_score_field_value_factor_test.go @@ -0,0 +1,121 @@ +package es_test + +import ( + "testing" + + Modifier "github.com/Trendyol/es-query-builder/es/enums/modifier" + + "github.com/Trendyol/es-query-builder/es" + "github.com/Trendyol/es-query-builder/test/assert" +) + +//// FieldValueFactor //// + +func Test_FieldValueFactor_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.FieldValueFactor) +} + +func Test_FieldValueFactor_method_should_create_fieldValueFactorType(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes") + + // Then + assert.NotNil(t, fvf) + assert.IsTypeString(t, "es.fieldValueFactorType", fvf) +} + +func Test_FieldValueFactor_should_create_json_with_field(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes") + + // When Then + assert.NotNil(t, fvf) + bodyJSON := assert.MarshalWithoutError(t, fvf) + assert.Equal(t, "{\"field\":\"likes\"}", bodyJSON) +} + +//// Factor //// + +func Test_FieldValueFactor_should_have_Factor_method(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes") + + // When Then + assert.NotNil(t, fvf.Factor) +} + +func Test_FieldValueFactor_Factor_should_create_json_with_factor_field(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes").Factor(1.2) + + // When Then + assert.NotNil(t, fvf) + bodyJSON := assert.MarshalWithoutError(t, fvf) + assert.Equal(t, "{\"factor\":1.2,\"field\":\"likes\"}", bodyJSON) +} + +//// Modifier //// + +func Test_FieldValueFactor_should_have_Modifier_method(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes") + + // When Then + assert.NotNil(t, fvf.Modifier) +} + +func Test_FieldValueFactor_Modifier_should_create_json_with_modifier_field(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes").Modifier(Modifier.Log1p) + + // When Then + assert.NotNil(t, fvf) + bodyJSON := assert.MarshalWithoutError(t, fvf) + assert.Equal(t, "{\"field\":\"likes\",\"modifier\":\"log1p\"}", bodyJSON) +} + +//// Missing //// + +func Test_FieldValueFactor_should_have_Missing_method(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes") + + // When Then + assert.NotNil(t, fvf.Missing) +} + +func Test_FieldValueFactor_Missing_should_create_json_with_missing_field(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes").Missing(1) + + // When Then + assert.NotNil(t, fvf) + bodyJSON := assert.MarshalWithoutError(t, fvf) + assert.Equal(t, "{\"field\":\"likes\",\"missing\":1}", bodyJSON) +} + +//// Combined //// + +func Test_FieldValueFactor_should_create_json_with_all_parameters(t *testing.T) { + t.Parallel() + // Given + fvf := es.FieldValueFactor("likes"). + Factor(1.2). + Modifier(Modifier.Log1p). + Missing(1) + + // When Then + assert.NotNil(t, fvf) + bodyJSON := assert.MarshalWithoutError(t, fvf) + assert.Equal(t, "{\"factor\":1.2,\"field\":\"likes\",\"missing\":1,\"modifier\":\"log1p\"}", bodyJSON) +} diff --git a/es/function_score_query.go b/es/function_score_query.go new file mode 100644 index 0000000..d1d69f1 --- /dev/null +++ b/es/function_score_query.go @@ -0,0 +1,464 @@ +package es + +import ( + BoostMode "github.com/Trendyol/es-query-builder/es/enums/boost-mode" + ScoreMode "github.com/Trendyol/es-query-builder/es/enums/score-mode" +) + +type functionScoreType Object + +type functionScoreFunction Object + +// FunctionScore creates a new es.functionScoreType object with the specified query. +// +// This function initializes an es.functionScoreType object that wraps a given query, +// allowing custom scoring functions to be applied to the matching documents. The query +// parameter represents the base query used to determine which documents match. +// +// Example usage: +// +// fs := es.FunctionScore(es.Bool().Must(es.Term("status", "active"))) +// // fs now contains an es.functionScoreType object with the specified query. +// +// Parameters: +// - query: An object representing the base query. It can be of any type. +// +// Returns: +// +// An es.functionScoreType object containing the specified query. +func FunctionScore(query any) functionScoreType { + o := Object{} + if field, ok := correctType(query); ok { + o["query"] = field + } + return functionScoreType{ + "function_score": o, + } +} + +// Boost sets the "boost" parameter in an es.functionScoreType query. +// +// This method allows you to specify a boost factor for the function_score query, +// which influences the relevance score of matching documents. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).Boost(5) +// // fs now includes a "boost" parameter set to 5. +// +// Parameters: +// - boost: A float64 value representing the boost factor. +// +// Returns: +// +// The updated es.functionScoreType object with the "boost" parameter set. +func (fs functionScoreType) Boost(boost float64) functionScoreType { + return fs.putInTheField("boost", boost) +} + +// MaxBoost sets the "max_boost" parameter in an es.functionScoreType query. +// +// This method restricts the maximum boost that can be applied by the function_score query. +// Regardless of the computed function score, the final boost will not exceed this value. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).MaxBoost(42) +// // fs now includes a "max_boost" parameter set to 42. +// +// Parameters: +// - maxBoost: A float64 value representing the maximum boost allowed. +// +// Returns: +// +// The updated es.functionScoreType object with the "max_boost" parameter set. +func (fs functionScoreType) MaxBoost(maxBoost float64) functionScoreType { + return fs.putInTheField("max_boost", maxBoost) +} + +// ScoreMode sets the "score_mode" parameter in an es.functionScoreType query. +// +// This method specifies how the scores computed by the individual scoring functions +// should be combined. The available modes include multiply, sum, avg, first, max, and min. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).ScoreMode(ScoreMode.Sum) +// // fs now includes a "score_mode" parameter set to "sum". +// +// Parameters: +// - scoreMode: A ScoreMode.ScoreMode value representing the scoring combination mode. +// +// Returns: +// +// The updated es.functionScoreType object with the "score_mode" parameter set. +func (fs functionScoreType) ScoreMode(scoreMode ScoreMode.ScoreMode) functionScoreType { + return fs.putInTheField("score_mode", scoreMode) +} + +// BoostMode sets the "boost_mode" parameter in an es.functionScoreType query. +// +// This method specifies how the computed function score is combined with the query score. +// The available modes include multiply, replace, sum, avg, max, and min. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).BoostMode(BoostMode.Replace) +// // fs now includes a "boost_mode" parameter set to "replace". +// +// Parameters: +// - boostMode: A BoostMode.BoostMode value representing the boost combination mode. +// +// Returns: +// +// The updated es.functionScoreType object with the "boost_mode" parameter set. +func (fs functionScoreType) BoostMode(boostMode BoostMode.BoostMode) functionScoreType { + return fs.putInTheField("boost_mode", boostMode) +} + +// MinScore sets the "min_score" parameter in an es.functionScoreType query. +// +// This method excludes documents whose combined score is below the specified threshold. +// Documents with a score lower than min_score will not be included in the results. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).MinScore(5) +// // fs now includes a "min_score" parameter set to 5. +// +// Parameters: +// - minScore: A float64 value representing the minimum score threshold. +// +// Returns: +// +// The updated es.functionScoreType object with the "min_score" parameter set. +func (fs functionScoreType) MinScore(minScore float64) functionScoreType { + return fs.putInTheField("min_score", minScore) +} + +// Functions adds one or more scoring functions to the es.functionScoreType query. +// +// This method sets the "functions" array in the function_score query, which defines +// the individual scoring functions to be applied to matching documents. Each function +// can optionally include a filter to restrict which documents it applies to. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()). +// Functions( +// es.ScriptScoreFunction(es.ScriptSource("_score * doc['likes'].value", ScriptLanguage.Painless)), +// es.RandomScoreFunction().Seed(42).Field("_seq_no"), +// ) +// // fs now includes a "functions" array with the specified scoring functions. +// +// Parameters: +// - functions: A variadic list of es.functionScoreFunction objects. +// +// Returns: +// +// The updated es.functionScoreType object with the "functions" parameter set. +func (fs functionScoreType) Functions(functions ...functionScoreFunction) functionScoreType { + if len(functions) == 1 && functions[0] == nil { + return fs + } + funcs := make(Array, 0, len(functions)) + for i := 0; i < len(functions); i++ { + if functions[i] != nil { + funcs = append(funcs, functions[i]) + } + } + return fs.putInTheField("functions", funcs) +} + +// ScriptScore sets the "script_score" parameter directly in the es.functionScoreType query. +// +// This method sets a script_score function at the top level of the function_score query, +// which is used when only a single scoring function is needed without the "functions" array. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()). +// ScriptScore(es.ScriptSource("_score * doc['likes'].value", ScriptLanguage.Painless)) +// // fs now includes a "script_score" parameter with the specified script. +// +// Parameters: +// - script: A scriptType object representing the script to compute the score. +// +// Returns: +// +// The updated es.functionScoreType object with the "script_score" parameter set. +func (fs functionScoreType) ScriptScore(script scriptType) functionScoreType { + return fs.putInTheField("script_score", Object{ + "script": script, + }) +} + +// RandomScore sets the "random_score" parameter directly in the es.functionScoreType query. +// +// This method sets a random_score function at the top level of the function_score query, +// which is used when only a single scoring function is needed without the "functions" array. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).RandomScore(42, "_seq_no") +// // fs now includes a "random_score" parameter with seed 42 and field "_seq_no". +// +// Parameters: +// - seed: An integer value used as the random seed. +// - field: A string representing the field to use for generating the random score. +// +// Returns: +// +// The updated es.functionScoreType object with the "random_score" parameter set. +func (fs functionScoreType) RandomScore(seed int, field string) functionScoreType { + return fs.putInTheField("random_score", Object{ + "seed": seed, + "field": field, + }) +} + +// FieldValueFactor sets the "field_value_factor" parameter directly in the es.functionScoreType query. +// +// This method sets a field_value_factor function at the top level of the function_score query, +// which is used when only a single scoring function is needed without the "functions" array. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()). +// FieldValueFactor(es.FieldValueFactor("likes")) +// // fs now includes a "field_value_factor" parameter for the "likes" field. +// +// Parameters: +// - fieldValueFactor: A fieldValueFactorType object representing the field value factor configuration. +// +// Returns: +// +// The updated es.functionScoreType object with the "field_value_factor" parameter set. +func (fs functionScoreType) FieldValueFactor(fieldValueFactor fieldValueFactorType) functionScoreType { + return fs.putInTheField("field_value_factor", fieldValueFactor) +} + +// Weight sets the "weight" parameter directly in the es.functionScoreType query. +// +// This method sets a weight at the top level of the function_score query, which is used +// when only a single scoring function is needed without the "functions" array. +// +// Example usage: +// +// fs := es.FunctionScore(es.MatchAll()).Weight(2) +// // fs now includes a "weight" parameter set to 2. +// +// Parameters: +// - weight: A float64 value representing the weight to apply. +// +// Returns: +// +// The updated es.functionScoreType object with the "weight" parameter set. +func (fs functionScoreType) Weight(weight float64) functionScoreType { + return fs.putInTheField("weight", weight) +} + +func (fs functionScoreType) putInTheField(key string, value any) functionScoreType { + return genericPutInTheField(fs, "function_score", key, value) +} + +// ScriptScoreFunction creates a new es.functionScoreFunction with a script_score configuration. +// +// This function initializes a function entry for use in the "functions" array of a function_score query. +// The script is used to compute a custom score for each matching document. +// +// Example usage: +// +// fn := es.ScriptScoreFunction(es.ScriptSource("_score * doc['likes'].value", ScriptLanguage.Painless)) +// // fn now contains a function entry with a script_score configuration. +// +// Parameters: +// - script: A scriptType object representing the script to compute the score. +// +// Returns: +// +// An es.functionScoreFunction object containing the script_score configuration. +func ScriptScoreFunction(script scriptType) functionScoreFunction { + return functionScoreFunction{ + "script_score": Object{ + "script": script, + }, + } +} + +// RandomScoreFunction creates a new es.functionScoreFunction with a random_score configuration. +// +// This function initializes a function entry for use in the "functions" array of a function_score query. +// The random_score generates a uniformly distributed random score. +// +// Example usage: +// +// fn := es.RandomScoreFunction().Seed(42).Field("_seq_no") +// // fn now contains a function entry with a random_score configuration. +// +// Returns: +// +// An es.functionScoreFunction object containing an empty random_score configuration. +func RandomScoreFunction() functionScoreFunction { + return functionScoreFunction{ + "random_score": Object{}, + } +} + +// WeightFunction creates a new es.functionScoreFunction with a weight configuration. +// +// This function initializes a function entry for use in the "functions" array of a function_score query. +// The weight is a number that the score is multiplied by. +// +// Example usage: +// +// fn := es.WeightFunction(2) +// // fn now contains a function entry with a weight of 2. +// +// Parameters: +// - weight: A float64 value representing the weight to apply. +// +// Returns: +// +// An es.functionScoreFunction object containing the weight configuration. +func WeightFunction(weight float64) functionScoreFunction { + return functionScoreFunction{ + "weight": weight, + } +} + +// FieldValueFactorFunction creates a new es.functionScoreFunction with a field_value_factor configuration. +// +// This function initializes a function entry for use in the "functions" array of a function_score query. +// The field_value_factor uses the value of a document field to influence the score. +// +// Example usage: +// +// fn := es.FieldValueFactorFunction(es.FieldValueFactor("likes")) +// // fn now contains a function entry with a field_value_factor configuration. +// +// Parameters: +// - fieldValueFactor: A fieldValueFactorType object representing the field value factor configuration. +// +// Returns: +// +// An es.functionScoreFunction object containing the field_value_factor configuration. +func FieldValueFactorFunction(fieldValueFactor fieldValueFactorType) functionScoreFunction { + return functionScoreFunction{ + "field_value_factor": fieldValueFactor, + } +} + +// DecayFunction creates a new es.functionScoreFunction with a decay function configuration. +// +// This function initializes a function entry for use in the "functions" array of a function_score query. +// Decay functions score documents based on the distance of a numeric, date, or geo-point field value +// from a given origin. The decayType parameter specifies the decay curve (gauss, linear, or exp). +// +// Example usage: +// +// fn := es.DecayFunction("gauss", es.Decay("date").Origin("now").Scale("10d").Offset("5d").DecayValue(0.5)) +// // fn now contains a function entry with a gauss decay configuration. +// +// Parameters: +// - decayType: A string representing the type of decay function ("gauss", "linear", or "exp"). +// - decay: A decayType object representing the decay configuration. +// +// Returns: +// +// An es.functionScoreFunction object containing the decay function configuration. +func DecayFunction(decayType string, decay decayFunctionType) functionScoreFunction { + return functionScoreFunction{ + decayType: decay, + } +} + +// Filter sets the "filter" parameter in an es.functionScoreFunction. +// +// This method restricts the scoring function to only apply to documents matching the filter. +// Documents that do not match the filter will not have this function applied to their score. +// +// Example usage: +// +// fn := es.WeightFunction(2).Filter(es.Term("status", "published")) +// // fn now includes a "filter" parameter with the specified term query. +// +// Parameters: +// - filter: An object representing the filter query. It can be of any type. +// +// Returns: +// +// The updated es.functionScoreFunction object with the "filter" parameter set. +func (f functionScoreFunction) Filter(filter any) functionScoreFunction { + if field, ok := correctType(filter); ok { + f["filter"] = field + } + return f +} + +// Weight sets the "weight" parameter in an es.functionScoreFunction. +// +// This method applies a weight multiplier to the score computed by the function. +// The weight is multiplied with the function score before combining with other functions. +// +// Example usage: +// +// fn := es.RandomScoreFunction().Seed(42).Field("_seq_no").Weight(2) +// // fn now includes a "weight" parameter set to 2. +// +// Parameters: +// - weight: A float64 value representing the weight to apply. +// +// Returns: +// +// The updated es.functionScoreFunction object with the "weight" parameter set. +func (f functionScoreFunction) Weight(weight float64) functionScoreFunction { + f["weight"] = weight + return f +} + +// Seed sets the "seed" parameter in the random_score of an es.functionScoreFunction. +// +// This method sets the random seed for the random_score function. Documents with the same +// seed and field value will receive the same score, making the random scoring reproducible. +// +// Example usage: +// +// fn := es.RandomScoreFunction().Seed(42) +// // fn now includes a "seed" parameter set to 42 in the random_score. +// +// Parameters: +// - seed: An integer value used as the random seed. +// +// Returns: +// +// The updated es.functionScoreFunction object with the "seed" parameter set. +func (f functionScoreFunction) Seed(seed int) functionScoreFunction { + if randomScore, ok := f["random_score"].(Object); ok { + randomScore["seed"] = seed + } + return f +} + +// Field sets the "field" parameter in the random_score of an es.functionScoreFunction. +// +// This method sets the field used for generating the random score. The field value is used +// together with the seed to produce a deterministic random score. +// +// Example usage: +// +// fn := es.RandomScoreFunction().Seed(42).Field("_seq_no") +// // fn now includes a "field" parameter set to "_seq_no" in the random_score. +// +// Parameters: +// - field: A string representing the field to use for random score generation. +// +// Returns: +// +// The updated es.functionScoreFunction object with the "field" parameter set. +func (f functionScoreFunction) Field(field string) functionScoreFunction { + if randomScore, ok := f["random_score"].(Object); ok { + randomScore["field"] = field + } + return f +} diff --git a/es/function_score_query_test.go b/es/function_score_query_test.go new file mode 100644 index 0000000..db79c38 --- /dev/null +++ b/es/function_score_query_test.go @@ -0,0 +1,543 @@ +package es_test + +import ( + "testing" + + BoostMode "github.com/Trendyol/es-query-builder/es/enums/boost-mode" + ScoreMode "github.com/Trendyol/es-query-builder/es/enums/score-mode" + ScriptLanguage "github.com/Trendyol/es-query-builder/es/enums/script-language" + + "github.com/Trendyol/es-query-builder/es" + "github.com/Trendyol/es-query-builder/test/assert" +) + +//// FunctionScore //// + +func Test_FunctionScore_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.FunctionScore) +} + +func Test_FunctionScore_method_should_create_functionScoreType(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // Then + assert.NotNil(t, fs) + assert.IsTypeString(t, "es.functionScoreType", fs) +} + +func Test_FunctionScore_should_create_json_with_function_score_field_inside_query(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +func Test_FunctionScore_should_create_json_with_nil_query(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(nil), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{}}}", bodyJSON) +} + +func Test_FunctionScore_should_create_json_with_bool_query(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore( + es.Bool().Must(es.Term("status", "active")), + ), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"bool\":{\"must\":[{\"term\":{\"status\":{\"value\":\"active\"}}}]}}}}}", bodyJSON) +} + +//// Boost //// + +func Test_FunctionScore_should_have_Boost_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.Boost) +} + +func Test_FunctionScore_Boost_should_create_json_with_boost_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).Boost(5), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"boost\":5,\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +//// MaxBoost //// + +func Test_FunctionScore_should_have_MaxBoost_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.MaxBoost) +} + +func Test_FunctionScore_MaxBoost_should_create_json_with_max_boost_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).MaxBoost(42), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"max_boost\":42,\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +//// ScoreMode //// + +func Test_FunctionScore_should_have_ScoreMode_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.ScoreMode) +} + +func Test_FunctionScore_ScoreMode_should_create_json_with_score_mode_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).ScoreMode(ScoreMode.Sum), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"match_all\":{}},\"score_mode\":\"sum\"}}}", bodyJSON) +} + +//// BoostMode //// + +func Test_FunctionScore_should_have_BoostMode_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.BoostMode) +} + +func Test_FunctionScore_BoostMode_should_create_json_with_boost_mode_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).BoostMode(BoostMode.Replace), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"boost_mode\":\"replace\",\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +//// MinScore //// + +func Test_FunctionScore_should_have_MinScore_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.MinScore) +} + +func Test_FunctionScore_MinScore_should_create_json_with_min_score_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).MinScore(5), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"min_score\":5,\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +//// Weight //// + +func Test_FunctionScore_should_have_Weight_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.Weight) +} + +func Test_FunctionScore_Weight_should_create_json_with_weight_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).Weight(2), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"match_all\":{}},\"weight\":2}}}", bodyJSON) +} + +//// ScriptScore //// + +func Test_FunctionScore_should_have_ScriptScore_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.ScriptScore) +} + +func Test_FunctionScore_ScriptScore_should_create_json_with_script_score_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + ScriptScore(es.ScriptSource("_score * doc['likes'].value", ScriptLanguage.Painless)), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"match_all\":{}},\"script_score\":{\"script\":{\"lang\":\"painless\",\"source\":\"_score * doc['likes'].value\"}}}}}", bodyJSON) +} + +//// RandomScore //// + +func Test_FunctionScore_should_have_RandomScore_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.RandomScore) +} + +func Test_FunctionScore_RandomScore_should_create_json_with_random_score_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()).RandomScore(42, "_seq_no"), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"match_all\":{}},\"random_score\":{\"field\":\"_seq_no\",\"seed\":42}}}}", bodyJSON) +} + +//// FieldValueFactor //// + +func Test_FunctionScore_should_have_FieldValueFactor_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.FieldValueFactor) +} + +func Test_FunctionScore_FieldValueFactor_should_create_json_with_field_value_factor_field_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + FieldValueFactor(es.FieldValueFactor("likes")), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"field_value_factor\":{\"field\":\"likes\"},\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +//// Functions //// + +func Test_FunctionScore_should_have_Functions_method(t *testing.T) { + t.Parallel() + // Given + fs := es.FunctionScore(es.MatchAll()) + + // When Then + assert.NotNil(t, fs.Functions) +} + +func Test_FunctionScore_Functions_should_create_json_with_functions_array_inside_function_score(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + Functions( + es.WeightFunction(2), + ), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"functions\":[{\"weight\":2}],\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +func Test_FunctionScore_Functions_should_skip_nil_functions(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + Functions(nil), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + assert.Equal(t, "{\"query\":{\"function_score\":{\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +func Test_FunctionScore_Functions_should_create_json_with_multiple_functions(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + Functions( + es.WeightFunction(2).Filter(es.Term("status", "published")), + es.RandomScoreFunction().Seed(42).Field("_seq_no"), + ), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"functions\":[{\"filter\":{\"term\":{\"status\":{\"value\":\"published\"}}},\"weight\":2},{\"random_score\":{\"field\":\"_seq_no\",\"seed\":42}}],\"query\":{\"match_all\":{}}}}}", bodyJSON) +} + +//// ScriptScoreFunction //// + +func Test_ScriptScoreFunction_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.ScriptScoreFunction) +} + +func Test_ScriptScoreFunction_should_create_functionScoreFunction(t *testing.T) { + t.Parallel() + // Given + fn := es.ScriptScoreFunction(es.ScriptSource("_score * 2", ScriptLanguage.Painless)) + + // Then + assert.NotNil(t, fn) + assert.IsTypeString(t, "es.functionScoreFunction", fn) +} + +func Test_ScriptScoreFunction_should_create_json_with_script_score(t *testing.T) { + t.Parallel() + // Given + fn := es.ScriptScoreFunction(es.ScriptSource("_score * 2", ScriptLanguage.Painless)) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"script_score\":{\"script\":{\"lang\":\"painless\",\"source\":\"_score * 2\"}}}", bodyJSON) +} + +//// RandomScoreFunction //// + +func Test_RandomScoreFunction_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.RandomScoreFunction) +} + +func Test_RandomScoreFunction_should_create_json_with_seed_and_field(t *testing.T) { + t.Parallel() + // Given + fn := es.RandomScoreFunction().Seed(42).Field("_seq_no") + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"random_score\":{\"field\":\"_seq_no\",\"seed\":42}}", bodyJSON) +} + +//// WeightFunction //// + +func Test_WeightFunction_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.WeightFunction) +} + +func Test_WeightFunction_should_create_json_with_weight(t *testing.T) { + t.Parallel() + // Given + fn := es.WeightFunction(23) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"weight\":23}", bodyJSON) +} + +//// FieldValueFactorFunction //// + +func Test_FieldValueFactorFunction_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.FieldValueFactorFunction) +} + +func Test_FieldValueFactorFunction_should_create_json_with_field_value_factor(t *testing.T) { + t.Parallel() + // Given + fn := es.FieldValueFactorFunction(es.FieldValueFactor("likes")) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"field_value_factor\":{\"field\":\"likes\"}}", bodyJSON) +} + +//// DecayFunction //// + +func Test_DecayFunction_should_exist_on_es_package(t *testing.T) { + t.Parallel() + // Given When Then + assert.NotNil(t, es.DecayFunction) +} + +func Test_DecayFunction_should_create_json_with_gauss_decay(t *testing.T) { + t.Parallel() + // Given + fn := es.DecayFunction("gauss", es.Decay("date").Origin("now").Scale("10d")) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"gauss\":{\"date\":{\"origin\":\"now\",\"scale\":\"10d\"}}}", bodyJSON) +} + +func Test_DecayFunction_should_create_json_with_linear_decay(t *testing.T) { + t.Parallel() + // Given + fn := es.DecayFunction("linear", es.Decay("price").Origin(0).Scale(20)) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"linear\":{\"price\":{\"origin\":0,\"scale\":20}}}", bodyJSON) +} + +func Test_DecayFunction_should_create_json_with_exp_decay(t *testing.T) { + t.Parallel() + // Given + fn := es.DecayFunction("exp", es.Decay("date").Origin("now").Scale("10d").Offset("5d").DecayValue(0.5)) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"exp\":{\"date\":{\"decay\":0.5,\"offset\":\"5d\",\"origin\":\"now\",\"scale\":\"10d\"}}}", bodyJSON) +} + +//// FunctionScoreFunction Filter //// + +func Test_FunctionScoreFunction_Filter_should_create_json_with_filter(t *testing.T) { + t.Parallel() + // Given + fn := es.WeightFunction(2).Filter(es.Term("status", "published")) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"filter\":{\"term\":{\"status\":{\"value\":\"published\"}}},\"weight\":2}", bodyJSON) +} + +func Test_FunctionScoreFunction_Filter_should_create_json_with_bool_filter(t *testing.T) { + t.Parallel() + // Given + fn := es.WeightFunction(2).Filter(es.Bool().Must(es.Term("status", "active"))) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + // nolint:golint,lll + assert.Equal(t, "{\"filter\":{\"bool\":{\"must\":[{\"term\":{\"status\":{\"value\":\"active\"}}}]}},\"weight\":2}", bodyJSON) +} + +//// FunctionScoreFunction Weight //// + +func Test_FunctionScoreFunction_Weight_should_add_weight_to_function(t *testing.T) { + t.Parallel() + // Given + fn := es.ScriptScoreFunction(es.ScriptSource("_score * 2", ScriptLanguage.Painless)).Weight(5) + + // When Then + assert.NotNil(t, fn) + bodyJSON := assert.MarshalWithoutError(t, fn) + assert.Equal(t, "{\"script_score\":{\"script\":{\"lang\":\"painless\",\"source\":\"_score * 2\"}},\"weight\":5}", bodyJSON) +} + +//// Complex Function Score Query //// + +func Test_FunctionScore_should_create_complex_json_with_all_parameters(t *testing.T) { + t.Parallel() + // Given + query := es.NewQuery( + es.FunctionScore(es.MatchAll()). + Boost(5). + MaxBoost(42). + ScoreMode(ScoreMode.Max). + BoostMode(BoostMode.Multiply). + MinScore(5). + Functions( + es.WeightFunction(23).Filter(es.Term("status", "published")), + es.ScriptScoreFunction(es.ScriptSource("_score * doc['likes'].value", ScriptLanguage.Painless)), + es.RandomScoreFunction().Seed(42).Field("_seq_no"), + ), + ) + + // When Then + assert.NotNil(t, query) + bodyJSON := assert.MarshalWithoutError(t, query) + // nolint:golint,lll + assert.Equal(t, "{\"query\":{\"function_score\":{\"boost\":5,\"boost_mode\":\"multiply\",\"functions\":[{\"filter\":{\"term\":{\"status\":{\"value\":\"published\"}}},\"weight\":23},{\"script_score\":{\"script\":{\"lang\":\"painless\",\"source\":\"_score * doc['likes'].value\"}}},{\"random_score\":{\"field\":\"_seq_no\",\"seed\":42}}],\"max_boost\":42,\"min_score\":5,\"query\":{\"match_all\":{}},\"score_mode\":\"max\"}}}", bodyJSON) +}