From 2ac6c072eed05ca3184e14fec688ca4c9d0dc6be Mon Sep 17 00:00:00 2001 From: nikkiing <1031497516@qq.com> Date: Mon, 27 Mar 2023 16:36:30 +0800 Subject: [PATCH] es --- pkg/es/README.md | 9 + pkg/es/query.go | 13 + pkg/es/script.go | 160 +++++++ pkg/es/search_aggs.go | 16 + pkg/es/search_aggs_bucket_terms.go | 422 +++++++++++++++++++ pkg/es/search_aggs_metrics_avg.go | 112 +++++ pkg/es/search_aggs_metrics_cardinality.go | 127 ++++++ pkg/es/search_aggs_metrics_max.go | 108 +++++ pkg/es/search_aggs_metrics_min.go | 109 +++++ pkg/es/search_queries_bool.go | 203 +++++++++ pkg/es/search_queries_match.go | 189 +++++++++ pkg/es/search_queries_match_phrase.go | 89 ++++ pkg/es/search_queries_match_phrase_prefix.go | 89 ++++ pkg/es/search_queries_prefix.go | 76 ++++ pkg/es/search_queries_query_string.go | 350 +++++++++++++++ pkg/es/search_queries_range.go | 155 +++++++ pkg/es/search_queries_rank_feature.go | 213 ++++++++++ pkg/es/search_queries_raw_string.go | 27 ++ pkg/es/search_queries_regexp.go | 91 ++++ pkg/es/search_queries_script.go | 51 +++ pkg/es/search_queries_script_score.go | 110 +++++ pkg/es/search_queries_slice.go | 53 +++ pkg/es/search_queries_term.go | 67 +++ pkg/es/search_queries_terms.go | 88 ++++ pkg/es/search_terms_lookup.go | 76 ++++ 25 files changed, 3003 insertions(+) create mode 100644 pkg/es/README.md create mode 100644 pkg/es/query.go create mode 100644 pkg/es/script.go create mode 100644 pkg/es/search_aggs.go create mode 100644 pkg/es/search_aggs_bucket_terms.go create mode 100644 pkg/es/search_aggs_metrics_avg.go create mode 100644 pkg/es/search_aggs_metrics_cardinality.go create mode 100644 pkg/es/search_aggs_metrics_max.go create mode 100644 pkg/es/search_aggs_metrics_min.go create mode 100644 pkg/es/search_queries_bool.go create mode 100644 pkg/es/search_queries_match.go create mode 100644 pkg/es/search_queries_match_phrase.go create mode 100644 pkg/es/search_queries_match_phrase_prefix.go create mode 100644 pkg/es/search_queries_prefix.go create mode 100644 pkg/es/search_queries_query_string.go create mode 100644 pkg/es/search_queries_range.go create mode 100644 pkg/es/search_queries_rank_feature.go create mode 100644 pkg/es/search_queries_raw_string.go create mode 100644 pkg/es/search_queries_regexp.go create mode 100644 pkg/es/search_queries_script.go create mode 100644 pkg/es/search_queries_script_score.go create mode 100644 pkg/es/search_queries_slice.go create mode 100644 pkg/es/search_queries_term.go create mode 100644 pkg/es/search_queries_terms.go create mode 100644 pkg/es/search_terms_lookup.go diff --git a/pkg/es/README.md b/pkg/es/README.md new file mode 100644 index 0000000..d6ef888 --- /dev/null +++ b/pkg/es/README.md @@ -0,0 +1,9 @@ +### es + + > 复制 gitea.drugeyes.vip/pharnexcloud/elastic/v7 的构建es查询参数的代码 +> 方便构造 github.com/elastic/go-elasticsearch/v7 查询语句 + + + + + diff --git a/pkg/es/query.go b/pkg/es/query.go new file mode 100644 index 0000000..ef70161 --- /dev/null +++ b/pkg/es/query.go @@ -0,0 +1,13 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// Query represents the generic query interface. A query's sole purpose +// is to return the source of the query as a JSON-serializable object. +// Returning map[string]interface{} is the norm for queries. +type Query interface { + // Source returns the JSON-serializable query request. + Source() (interface{}, error) +} diff --git a/pkg/es/script.go b/pkg/es/script.go new file mode 100644 index 0000000..aa3e44a --- /dev/null +++ b/pkg/es/script.go @@ -0,0 +1,160 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// Script holds all the parameters necessary to compile or find in cache +// and then execute a script. +// +// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html +// for details of scripting. +type Script struct { + script string + typ string + lang string + params map[string]interface{} +} + +// NewScript creates and initializes a new Script. By default, it is of +// type "inline". Use NewScriptStored for a stored script (where type is "id"). +func NewScript(script string) *Script { + return &Script{ + script: script, + typ: "inline", + params: make(map[string]interface{}), + } +} + +// NewScriptInline creates and initializes a new inline script, i.e. code. +func NewScriptInline(script string) *Script { + return NewScript(script).Type("inline") +} + +// NewScriptStored creates and initializes a new stored script. +func NewScriptStored(script string) *Script { + return NewScript(script).Type("id") +} + +// Script is either the cache key of the script to be compiled/executed +// or the actual script source code for inline scripts. For indexed +// scripts this is the id used in the request. For file scripts this is +// the file name. +func (s *Script) Script(script string) *Script { + s.script = script + return s +} + +// Type sets the type of script: "inline" or "id". +func (s *Script) Type(typ string) *Script { + s.typ = typ + return s +} + +// Lang sets the language of the script. The default scripting language +// is Painless ("painless"). +// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/modules-scripting.html +// for details. +func (s *Script) Lang(lang string) *Script { + s.lang = lang + return s +} + +// Param adds a key/value pair to the parameters that this script will be executed with. +func (s *Script) Param(name string, value interface{}) *Script { + if s.params == nil { + s.params = make(map[string]interface{}) + } + s.params[name] = value + return s +} + +// Params sets the map of parameters this script will be executed with. +func (s *Script) Params(params map[string]interface{}) *Script { + s.params = params + return s +} + +// Source returns the JSON serializable data for this Script. +func (s *Script) Source() (interface{}, error) { + if s.typ == "" && s.lang == "" && len(s.params) == 0 { + return s.script, nil + } + source := make(map[string]interface{}) + // Beginning with 6.0, the type can only be "source" or "id" + if s.typ == "" || s.typ == "inline" { + src, err := s.rawScriptSource(s.script) + if err != nil { + return nil, err + } + source["source"] = src + } else { + source["id"] = s.script + } + if s.lang != "" { + source["lang"] = s.lang + } + if len(s.params) > 0 { + source["params"] = s.params + } + return source, nil +} + +// rawScriptSource returns an embeddable script. If it uses a short +// script form, e.g. "ctx._source.likes++" (without the quotes), it +// is quoted. Otherwise it returns the raw script that will be directly +// embedded into the JSON data. +func (s *Script) rawScriptSource(script string) (interface{}, error) { + v := strings.TrimSpace(script) + if !strings.HasPrefix(v, "{") && !strings.HasPrefix(v, `"`) { + v = fmt.Sprintf("%q", v) + } + raw := json.RawMessage(v) + return &raw, nil +} + +// -- Script Field -- + +// ScriptField is a single script field. +type ScriptField struct { + FieldName string // name of the field + + script *Script + ignoreFailure *bool // used in e.g. ScriptSource +} + +// NewScriptField creates and initializes a new ScriptField. +func NewScriptField(fieldName string, script *Script) *ScriptField { + return &ScriptField{FieldName: fieldName, script: script} +} + +// IgnoreFailure indicates whether to ignore failures. It is used +// in e.g. ScriptSource. +func (f *ScriptField) IgnoreFailure(ignore bool) *ScriptField { + f.ignoreFailure = &ignore + return f +} + +// Source returns the serializable JSON for the ScriptField. +func (f *ScriptField) Source() (interface{}, error) { + if f.script == nil { + return nil, errors.New("ScriptField expects script") + } + source := make(map[string]interface{}) + src, err := f.script.Source() + if err != nil { + return nil, err + } + source["script"] = src + if v := f.ignoreFailure; v != nil { + source["ignore_failure"] = *v + } + return source, nil +} diff --git a/pkg/es/search_aggs.go b/pkg/es/search_aggs.go new file mode 100644 index 0000000..2341355 --- /dev/null +++ b/pkg/es/search_aggs.go @@ -0,0 +1,16 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// Aggregations can be seen as a unit-of-work that build +// analytic information over a set of documents. It is +// (in many senses) the follow-up of facets in Elasticsearch. +// For more details about aggregations, visit: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations.html +type Aggregation interface { + // Source returns a JSON-serializable aggregation that is a fragment + // of the request sent to Elasticsearch. + Source() (interface{}, error) +} diff --git a/pkg/es/search_aggs_bucket_terms.go b/pkg/es/search_aggs_bucket_terms.go new file mode 100644 index 0000000..caf2b87 --- /dev/null +++ b/pkg/es/search_aggs_bucket_terms.go @@ -0,0 +1,422 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import "fmt" + +// TermsAggregation is a multi-bucket value source based aggregation +// where buckets are dynamically built - one per unique value. +// +// See: http://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-bucket-terms-aggregation.html +type TermsAggregation struct { + field string + script *Script + missing interface{} + subAggregations map[string]Aggregation + meta map[string]interface{} + + size *int + shardSize *int + requiredSize *int + minDocCount *int + shardMinDocCount *int + valueType string + includeExclude *TermsAggregationIncludeExclude + executionHint string + collectionMode string + showTermDocCountError *bool + order []TermsOrder +} + +func NewTermsAggregation() *TermsAggregation { + return &TermsAggregation{ + subAggregations: make(map[string]Aggregation), + } +} + +func (a *TermsAggregation) Field(field string) *TermsAggregation { + a.field = field + return a +} + +func (a *TermsAggregation) Script(script *Script) *TermsAggregation { + a.script = script + return a +} + +// Missing configures the value to use when documents miss a value. +func (a *TermsAggregation) Missing(missing interface{}) *TermsAggregation { + a.missing = missing + return a +} + +func (a *TermsAggregation) SubAggregation(name string, subAggregation Aggregation) *TermsAggregation { + a.subAggregations[name] = subAggregation + return a +} + +// Meta sets the meta data to be included in the aggregation response. +func (a *TermsAggregation) Meta(metaData map[string]interface{}) *TermsAggregation { + a.meta = metaData + return a +} + +func (a *TermsAggregation) Size(size int) *TermsAggregation { + a.size = &size + return a +} + +func (a *TermsAggregation) RequiredSize(requiredSize int) *TermsAggregation { + a.requiredSize = &requiredSize + return a +} + +func (a *TermsAggregation) ShardSize(shardSize int) *TermsAggregation { + a.shardSize = &shardSize + return a +} + +func (a *TermsAggregation) MinDocCount(minDocCount int) *TermsAggregation { + a.minDocCount = &minDocCount + return a +} + +func (a *TermsAggregation) ShardMinDocCount(shardMinDocCount int) *TermsAggregation { + a.shardMinDocCount = &shardMinDocCount + return a +} + +func (a *TermsAggregation) Include(regexp string) *TermsAggregation { + if a.includeExclude == nil { + a.includeExclude = &TermsAggregationIncludeExclude{} + } + a.includeExclude.Include = regexp + return a +} + +func (a *TermsAggregation) IncludeValues(values ...interface{}) *TermsAggregation { + if a.includeExclude == nil { + a.includeExclude = &TermsAggregationIncludeExclude{} + } + a.includeExclude.IncludeValues = append(a.includeExclude.IncludeValues, values...) + return a +} + +func (a *TermsAggregation) Exclude(regexp string) *TermsAggregation { + if a.includeExclude == nil { + a.includeExclude = &TermsAggregationIncludeExclude{} + } + a.includeExclude.Exclude = regexp + return a +} + +func (a *TermsAggregation) ExcludeValues(values ...interface{}) *TermsAggregation { + if a.includeExclude == nil { + a.includeExclude = &TermsAggregationIncludeExclude{} + } + a.includeExclude.ExcludeValues = append(a.includeExclude.ExcludeValues, values...) + return a +} + +func (a *TermsAggregation) Partition(p int) *TermsAggregation { + if a.includeExclude == nil { + a.includeExclude = &TermsAggregationIncludeExclude{} + } + a.includeExclude.Partition = p + return a +} + +func (a *TermsAggregation) NumPartitions(n int) *TermsAggregation { + if a.includeExclude == nil { + a.includeExclude = &TermsAggregationIncludeExclude{} + } + a.includeExclude.NumPartitions = n + return a +} + +func (a *TermsAggregation) IncludeExclude(includeExclude *TermsAggregationIncludeExclude) *TermsAggregation { + a.includeExclude = includeExclude + return a +} + +// ValueType can be string, long, or double. +func (a *TermsAggregation) ValueType(valueType string) *TermsAggregation { + a.valueType = valueType + return a +} + +func (a *TermsAggregation) Order(order string, asc bool) *TermsAggregation { + a.order = append(a.order, TermsOrder{Field: order, Ascending: asc}) + return a +} + +func (a *TermsAggregation) OrderByCount(asc bool) *TermsAggregation { + // "order" : { "_count" : "asc" } + a.order = append(a.order, TermsOrder{Field: "_count", Ascending: asc}) + return a +} + +func (a *TermsAggregation) OrderByCountAsc() *TermsAggregation { + return a.OrderByCount(true) +} + +func (a *TermsAggregation) OrderByCountDesc() *TermsAggregation { + return a.OrderByCount(false) +} + +// Deprecated: Use OrderByKey instead. +func (a *TermsAggregation) OrderByTerm(asc bool) *TermsAggregation { + // "order" : { "_term" : "asc" } + a.order = append(a.order, TermsOrder{Field: "_term", Ascending: asc}) + return a +} + +// Deprecated: Use OrderByKeyAsc instead. +func (a *TermsAggregation) OrderByTermAsc() *TermsAggregation { + return a.OrderByTerm(true) +} + +// Deprecated: Use OrderByKeyDesc instead. +func (a *TermsAggregation) OrderByTermDesc() *TermsAggregation { + return a.OrderByTerm(false) +} + +func (a *TermsAggregation) OrderByKey(asc bool) *TermsAggregation { + // "order" : { "_term" : "asc" } + a.order = append(a.order, TermsOrder{Field: "_key", Ascending: asc}) + return a +} + +func (a *TermsAggregation) OrderByKeyAsc() *TermsAggregation { + return a.OrderByKey(true) +} + +func (a *TermsAggregation) OrderByKeyDesc() *TermsAggregation { + return a.OrderByKey(false) +} + +// OrderByAggregation creates a bucket ordering strategy which sorts buckets +// based on a single-valued calc get. +func (a *TermsAggregation) OrderByAggregation(aggName string, asc bool) *TermsAggregation { + // { + // "aggs" : { + // "genders" : { + // "terms" : { + // "field" : "gender", + // "order" : { "avg_height" : "desc" } + // }, + // "aggs" : { + // "avg_height" : { "avg" : { "field" : "height" } } + // } + // } + // } + // } + a.order = append(a.order, TermsOrder{Field: aggName, Ascending: asc}) + return a +} + +// OrderByAggregationAndMetric creates a bucket ordering strategy which +// sorts buckets based on a multi-valued calc get. +func (a *TermsAggregation) OrderByAggregationAndMetric(aggName, metric string, asc bool) *TermsAggregation { + // { + // "aggs" : { + // "genders" : { + // "terms" : { + // "field" : "gender", + // "order" : { "height_stats.avg" : "desc" } + // }, + // "aggs" : { + // "height_stats" : { "stats" : { "field" : "height" } } + // } + // } + // } + // } + a.order = append(a.order, TermsOrder{Field: aggName + "." + metric, Ascending: asc}) + return a +} + +func (a *TermsAggregation) ExecutionHint(hint string) *TermsAggregation { + a.executionHint = hint + return a +} + +// Collection mode can be depth_first or breadth_first as of 1.4.0. +func (a *TermsAggregation) CollectionMode(collectionMode string) *TermsAggregation { + a.collectionMode = collectionMode + return a +} + +func (a *TermsAggregation) ShowTermDocCountError(showTermDocCountError bool) *TermsAggregation { + a.showTermDocCountError = &showTermDocCountError + return a +} + +func (a *TermsAggregation) Source() (interface{}, error) { + // Example: + // { + // "aggs" : { + // "genders" : { + // "terms" : { "field" : "gender" } + // } + // } + // } + // This method returns only the { "terms" : { "field" : "gender" } } part. + + source := make(map[string]interface{}) + opts := make(map[string]interface{}) + source["terms"] = opts + + // ValuesSourceAggregationBuilder + if a.field != "" { + opts["field"] = a.field + } + if a.script != nil { + src, err := a.script.Source() + if err != nil { + return nil, err + } + opts["script"] = src + } + if a.missing != nil { + opts["missing"] = a.missing + } + + // TermsBuilder + if a.size != nil && *a.size >= 0 { + opts["size"] = *a.size + } + if a.shardSize != nil && *a.shardSize >= 0 { + opts["shard_size"] = *a.shardSize + } + if a.requiredSize != nil && *a.requiredSize >= 0 { + opts["required_size"] = *a.requiredSize + } + if a.minDocCount != nil && *a.minDocCount >= 0 { + opts["min_doc_count"] = *a.minDocCount + } + if a.shardMinDocCount != nil && *a.shardMinDocCount >= 0 { + opts["shard_min_doc_count"] = *a.shardMinDocCount + } + if a.showTermDocCountError != nil { + opts["show_term_doc_count_error"] = *a.showTermDocCountError + } + if a.collectionMode != "" { + opts["collect_mode"] = a.collectionMode + } + if a.valueType != "" { + opts["value_type"] = a.valueType + } + if len(a.order) > 0 { + var orderSlice []interface{} + for _, order := range a.order { + src, err := order.Source() + if err != nil { + return nil, err + } + orderSlice = append(orderSlice, src) + } + opts["order"] = orderSlice + } + + // Include/Exclude + if ie := a.includeExclude; ie != nil { + if err := ie.MergeInto(opts); err != nil { + return nil, err + } + } + + if a.executionHint != "" { + opts["execution_hint"] = a.executionHint + } + + // AggregationBuilder (SubAggregations) + if len(a.subAggregations) > 0 { + aggsMap := make(map[string]interface{}) + source["aggregations"] = aggsMap + for name, aggregate := range a.subAggregations { + src, err := aggregate.Source() + if err != nil { + return nil, err + } + aggsMap[name] = src + } + } + + // Add Meta data if available + if len(a.meta) > 0 { + source["meta"] = a.meta + } + + return source, nil +} + +// TermsAggregationIncludeExclude allows for include/exclude in a TermsAggregation. +type TermsAggregationIncludeExclude struct { + Include string + Exclude string + IncludeValues []interface{} + ExcludeValues []interface{} + Partition int + NumPartitions int +} + +// Source returns a JSON serializable struct. +func (ie *TermsAggregationIncludeExclude) Source() (interface{}, error) { + source := make(map[string]interface{}) + + // Include + if ie.Include != "" { + source["include"] = ie.Include + } else if len(ie.IncludeValues) > 0 { + source["include"] = ie.IncludeValues + } else if ie.NumPartitions > 0 { + inc := make(map[string]interface{}) + inc["partition"] = ie.Partition + inc["num_partitions"] = ie.NumPartitions + source["include"] = inc + } + + // Exclude + if ie.Exclude != "" { + source["exclude"] = ie.Exclude + } else if len(ie.ExcludeValues) > 0 { + source["exclude"] = ie.ExcludeValues + } + + return source, nil +} + +// MergeInto merges the values of the include/exclude options into source. +func (ie *TermsAggregationIncludeExclude) MergeInto(source map[string]interface{}) error { + values, err := ie.Source() + if err != nil { + return err + } + mv, ok := values.(map[string]interface{}) + if !ok { + return fmt.Errorf("IncludeExclude: expected a map[string]interface{}, got %T", values) + } + for k, v := range mv { + source[k] = v + } + return nil +} + +// TermsOrder specifies a single order field for a terms aggregation. +type TermsOrder struct { + Field string + Ascending bool +} + +// Source returns serializable JSON of the TermsOrder. +func (order *TermsOrder) Source() (interface{}, error) { + source := make(map[string]string) + if order.Ascending { + source[order.Field] = "asc" + } else { + source[order.Field] = "desc" + } + return source, nil +} diff --git a/pkg/es/search_aggs_metrics_avg.go b/pkg/es/search_aggs_metrics_avg.go new file mode 100644 index 0000000..ee6aa0f --- /dev/null +++ b/pkg/es/search_aggs_metrics_avg.go @@ -0,0 +1,112 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// AvgAggregation is a single-value metrics aggregation that computes +// the average of numeric values that are extracted from the +// aggregated documents. These values can be extracted either from +// specific numeric fields in the documents, or be generated by +// a provided script. +// +// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-avg-aggregation.html +type AvgAggregation struct { + field string + script *Script + format string + missing interface{} + subAggregations map[string]Aggregation + meta map[string]interface{} +} + +func NewAvgAggregation() *AvgAggregation { + return &AvgAggregation{ + subAggregations: make(map[string]Aggregation), + } +} + +func (a *AvgAggregation) Field(field string) *AvgAggregation { + a.field = field + return a +} + +func (a *AvgAggregation) Script(script *Script) *AvgAggregation { + a.script = script + return a +} + +func (a *AvgAggregation) Format(format string) *AvgAggregation { + a.format = format + return a +} + +func (a *AvgAggregation) Missing(missing interface{}) *AvgAggregation { + a.missing = missing + return a +} + +func (a *AvgAggregation) SubAggregation(name string, subAggregation Aggregation) *AvgAggregation { + a.subAggregations[name] = subAggregation + return a +} + +// Meta sets the meta data to be included in the aggregation response. +func (a *AvgAggregation) Meta(metaData map[string]interface{}) *AvgAggregation { + a.meta = metaData + return a +} + +func (a *AvgAggregation) Source() (interface{}, error) { + // Example: + // { + // "aggs" : { + // "avg_grade" : { "avg" : { "field" : "grade" } } + // } + // } + // This method returns only the { "avg" : { "field" : "grade" } } part. + + source := make(map[string]interface{}) + opts := make(map[string]interface{}) + source["avg"] = opts + + // ValuesSourceAggregationBuilder + if a.field != "" { + opts["field"] = a.field + } + if a.script != nil { + src, err := a.script.Source() + if err != nil { + return nil, err + } + opts["script"] = src + } + + if a.format != "" { + opts["format"] = a.format + } + + if a.missing != nil { + opts["missing"] = a.missing + } + + // AggregationBuilder (SubAggregations) + if len(a.subAggregations) > 0 { + aggsMap := make(map[string]interface{}) + source["aggregations"] = aggsMap + for name, aggregate := range a.subAggregations { + src, err := aggregate.Source() + if err != nil { + return nil, err + } + aggsMap[name] = src + } + } + + // Add Meta data if available + if len(a.meta) > 0 { + source["meta"] = a.meta + } + + return source, nil +} diff --git a/pkg/es/search_aggs_metrics_cardinality.go b/pkg/es/search_aggs_metrics_cardinality.go new file mode 100644 index 0000000..1556b97 --- /dev/null +++ b/pkg/es/search_aggs_metrics_cardinality.go @@ -0,0 +1,127 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// CardinalityAggregation is a single-value metrics aggregation that +// calculates an approximate count of distinct values. +// Values can be extracted either from specific fields in the document +// or generated by a script. +// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-cardinality-aggregation.html +type CardinalityAggregation struct { + field string + script *Script + format string + missing interface{} + subAggregations map[string]Aggregation + meta map[string]interface{} + precisionThreshold *int64 + rehash *bool +} + +func NewCardinalityAggregation() *CardinalityAggregation { + return &CardinalityAggregation{ + subAggregations: make(map[string]Aggregation), + } +} + +func (a *CardinalityAggregation) Field(field string) *CardinalityAggregation { + a.field = field + return a +} + +func (a *CardinalityAggregation) Script(script *Script) *CardinalityAggregation { + a.script = script + return a +} + +func (a *CardinalityAggregation) Format(format string) *CardinalityAggregation { + a.format = format + return a +} + +func (a *CardinalityAggregation) Missing(missing interface{}) *CardinalityAggregation { + a.missing = missing + return a +} +func (a *CardinalityAggregation) SubAggregation(name string, subAggregation Aggregation) *CardinalityAggregation { + a.subAggregations[name] = subAggregation + return a +} + +// Meta sets the meta data to be included in the aggregation response. +func (a *CardinalityAggregation) Meta(metaData map[string]interface{}) *CardinalityAggregation { + a.meta = metaData + return a +} + +func (a *CardinalityAggregation) PrecisionThreshold(threshold int64) *CardinalityAggregation { + a.precisionThreshold = &threshold + return a +} + +func (a *CardinalityAggregation) Rehash(rehash bool) *CardinalityAggregation { + a.rehash = &rehash + return a +} + +func (a *CardinalityAggregation) Source() (interface{}, error) { + // Example: + // { + // "aggs" : { + // "author_count" : { + // "cardinality" : { "field" : "author" } + // } + // } + // } + // This method returns only the "cardinality" : { "field" : "author" } part. + + source := make(map[string]interface{}) + opts := make(map[string]interface{}) + source["cardinality"] = opts + + // ValuesSourceAggregationBuilder + if a.field != "" { + opts["field"] = a.field + } + if a.script != nil { + src, err := a.script.Source() + if err != nil { + return nil, err + } + opts["script"] = src + } + if a.missing != nil { + opts["missing"] = a.missing + } + if a.format != "" { + opts["format"] = a.format + } + if a.precisionThreshold != nil { + opts["precision_threshold"] = *a.precisionThreshold + } + if a.rehash != nil { + opts["rehash"] = *a.rehash + } + + // AggregationBuilder (SubAggregations) + if len(a.subAggregations) > 0 { + aggsMap := make(map[string]interface{}) + source["aggregations"] = aggsMap + for name, aggregate := range a.subAggregations { + src, err := aggregate.Source() + if err != nil { + return nil, err + } + aggsMap[name] = src + } + } + + // Add Meta data if available + if len(a.meta) > 0 { + source["meta"] = a.meta + } + + return source, nil +} diff --git a/pkg/es/search_aggs_metrics_max.go b/pkg/es/search_aggs_metrics_max.go new file mode 100644 index 0000000..5f52ac4 --- /dev/null +++ b/pkg/es/search_aggs_metrics_max.go @@ -0,0 +1,108 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// MaxAggregation is a single-value metrics aggregation that keeps track and +// returns the maximum value among the numeric values extracted from +// the aggregated documents. These values can be extracted either from +// specific numeric fields in the documents, or be generated by +// a provided script. +// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-max-aggregation.html +type MaxAggregation struct { + field string + script *Script + format string + missing interface{} + subAggregations map[string]Aggregation + meta map[string]interface{} +} + +func NewMaxAggregation() *MaxAggregation { + return &MaxAggregation{ + subAggregations: make(map[string]Aggregation), + } +} + +func (a *MaxAggregation) Field(field string) *MaxAggregation { + a.field = field + return a +} + +func (a *MaxAggregation) Script(script *Script) *MaxAggregation { + a.script = script + return a +} + +func (a *MaxAggregation) Format(format string) *MaxAggregation { + a.format = format + return a +} + +func (a *MaxAggregation) Missing(missing interface{}) *MaxAggregation { + a.missing = missing + return a +} + +func (a *MaxAggregation) SubAggregation(name string, subAggregation Aggregation) *MaxAggregation { + a.subAggregations[name] = subAggregation + return a +} + +// Meta sets the meta data to be included in the aggregation response. +func (a *MaxAggregation) Meta(metaData map[string]interface{}) *MaxAggregation { + a.meta = metaData + return a +} +func (a *MaxAggregation) Source() (interface{}, error) { + // Example: + // { + // "aggs" : { + // "max_price" : { "max" : { "field" : "price" } } + // } + // } + // This method returns only the { "max" : { "field" : "price" } } part. + + source := make(map[string]interface{}) + opts := make(map[string]interface{}) + source["max"] = opts + + // ValuesSourceAggregationBuilder + if a.field != "" { + opts["field"] = a.field + } + if a.script != nil { + src, err := a.script.Source() + if err != nil { + return nil, err + } + opts["script"] = src + } + if a.format != "" { + opts["format"] = a.format + } + if a.missing != nil { + opts["missing"] = a.missing + } + + // AggregationBuilder (SubAggregations) + if len(a.subAggregations) > 0 { + aggsMap := make(map[string]interface{}) + source["aggregations"] = aggsMap + for name, aggregate := range a.subAggregations { + src, err := aggregate.Source() + if err != nil { + return nil, err + } + aggsMap[name] = src + } + } + + // Add Meta data if available + if len(a.meta) > 0 { + source["meta"] = a.meta + } + + return source, nil +} diff --git a/pkg/es/search_aggs_metrics_min.go b/pkg/es/search_aggs_metrics_min.go new file mode 100644 index 0000000..69ac45a --- /dev/null +++ b/pkg/es/search_aggs_metrics_min.go @@ -0,0 +1,109 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// MinAggregation is a single-value metrics aggregation that keeps track and +// returns the minimum value among numeric values extracted from the +// aggregated documents. These values can be extracted either from +// specific numeric fields in the documents, or be generated by a +// provided script. +// See: https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-aggregations-metrics-min-aggregation.html +type MinAggregation struct { + field string + script *Script + format string + missing interface{} + subAggregations map[string]Aggregation + meta map[string]interface{} +} + +func NewMinAggregation() *MinAggregation { + return &MinAggregation{ + subAggregations: make(map[string]Aggregation), + } +} + +func (a *MinAggregation) Field(field string) *MinAggregation { + a.field = field + return a +} + +func (a *MinAggregation) Script(script *Script) *MinAggregation { + a.script = script + return a +} + +func (a *MinAggregation) Format(format string) *MinAggregation { + a.format = format + return a +} + +func (a *MinAggregation) Missing(missing interface{}) *MinAggregation { + a.missing = missing + return a +} + +func (a *MinAggregation) SubAggregation(name string, subAggregation Aggregation) *MinAggregation { + a.subAggregations[name] = subAggregation + return a +} + +// Meta sets the meta data to be included in the aggregation response. +func (a *MinAggregation) Meta(metaData map[string]interface{}) *MinAggregation { + a.meta = metaData + return a +} + +func (a *MinAggregation) Source() (interface{}, error) { + // Example: + // { + // "aggs" : { + // "min_price" : { "min" : { "field" : "price" } } + // } + // } + // This method returns only the { "min" : { "field" : "price" } } part. + + source := make(map[string]interface{}) + opts := make(map[string]interface{}) + source["min"] = opts + + // ValuesSourceAggregationBuilder + if a.field != "" { + opts["field"] = a.field + } + if a.script != nil { + src, err := a.script.Source() + if err != nil { + return nil, err + } + opts["script"] = src + } + if a.format != "" { + opts["format"] = a.format + } + if a.missing != nil { + opts["missing"] = a.missing + } + + // AggregationBuilder (SubAggregations) + if len(a.subAggregations) > 0 { + aggsMap := make(map[string]interface{}) + source["aggregations"] = aggsMap + for name, aggregate := range a.subAggregations { + src, err := aggregate.Source() + if err != nil { + return nil, err + } + aggsMap[name] = src + } + } + + // Add Meta data if available + if len(a.meta) > 0 { + source["meta"] = a.meta + } + + return source, nil +} diff --git a/pkg/es/search_queries_bool.go b/pkg/es/search_queries_bool.go new file mode 100644 index 0000000..1f6a6db --- /dev/null +++ b/pkg/es/search_queries_bool.go @@ -0,0 +1,203 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import "fmt" + +// A bool query matches documents matching boolean +// combinations of other queries. +// For more details, see: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-bool-query.html +type BoolQuery struct { + Query + mustClauses []Query + mustNotClauses []Query + filterClauses []Query + shouldClauses []Query + boost *float64 + minimumShouldMatch string + adjustPureNegative *bool + queryName string +} + +// Creates a new bool query. +func NewBoolQuery() *BoolQuery { + return &BoolQuery{ + mustClauses: make([]Query, 0), + mustNotClauses: make([]Query, 0), + filterClauses: make([]Query, 0), + shouldClauses: make([]Query, 0), + } +} + +func (q *BoolQuery) Must(queries ...Query) *BoolQuery { + q.mustClauses = append(q.mustClauses, queries...) + return q +} + +func (q *BoolQuery) MustNot(queries ...Query) *BoolQuery { + q.mustNotClauses = append(q.mustNotClauses, queries...) + return q +} + +func (q *BoolQuery) Filter(filters ...Query) *BoolQuery { + q.filterClauses = append(q.filterClauses, filters...) + return q +} + +func (q *BoolQuery) Should(queries ...Query) *BoolQuery { + q.shouldClauses = append(q.shouldClauses, queries...) + return q +} + +func (q *BoolQuery) Boost(boost float64) *BoolQuery { + q.boost = &boost + return q +} + +func (q *BoolQuery) MinimumShouldMatch(minimumShouldMatch string) *BoolQuery { + q.minimumShouldMatch = minimumShouldMatch + return q +} + +func (q *BoolQuery) MinimumNumberShouldMatch(minimumNumberShouldMatch int) *BoolQuery { + q.minimumShouldMatch = fmt.Sprintf("%d", minimumNumberShouldMatch) + return q +} + +func (q *BoolQuery) AdjustPureNegative(adjustPureNegative bool) *BoolQuery { + q.adjustPureNegative = &adjustPureNegative + return q +} + +func (q *BoolQuery) QueryName(queryName string) *BoolQuery { + q.queryName = queryName + return q +} + +// Creates the query source for the bool query. +func (q *BoolQuery) Source() (interface{}, error) { + // { + // "bool" : { + // "must" : { + // "term" : { "user" : "kimchy" } + // }, + // "must_not" : { + // "range" : { + // "age" : { "from" : 10, "to" : 20 } + // } + // }, + // "filter" : [ + // ... + // ] + // "should" : [ + // { + // "term" : { "tag" : "wow" } + // }, + // { + // "term" : { "tag" : "elasticsearch" } + // } + // ], + // "minimum_should_match" : 1, + // "boost" : 1.0 + // } + // } + + query := make(map[string]interface{}) + + boolClause := make(map[string]interface{}) + query["bool"] = boolClause + + // must + if len(q.mustClauses) == 1 { + src, err := q.mustClauses[0].Source() + if err != nil { + return nil, err + } + boolClause["must"] = src + } else if len(q.mustClauses) > 1 { + var clauses []interface{} + for _, subQuery := range q.mustClauses { + src, err := subQuery.Source() + if err != nil { + return nil, err + } + clauses = append(clauses, src) + } + boolClause["must"] = clauses + } + + // must_not + if len(q.mustNotClauses) == 1 { + src, err := q.mustNotClauses[0].Source() + if err != nil { + return nil, err + } + boolClause["must_not"] = src + } else if len(q.mustNotClauses) > 1 { + var clauses []interface{} + for _, subQuery := range q.mustNotClauses { + src, err := subQuery.Source() + if err != nil { + return nil, err + } + clauses = append(clauses, src) + } + boolClause["must_not"] = clauses + } + + // filter + if len(q.filterClauses) == 1 { + src, err := q.filterClauses[0].Source() + if err != nil { + return nil, err + } + boolClause["filter"] = src + } else if len(q.filterClauses) > 1 { + var clauses []interface{} + for _, subQuery := range q.filterClauses { + src, err := subQuery.Source() + if err != nil { + return nil, err + } + clauses = append(clauses, src) + } + boolClause["filter"] = clauses + } + + // should + if len(q.shouldClauses) == 1 { + src, err := q.shouldClauses[0].Source() + if err != nil { + return nil, err + } + boolClause["should"] = src + } else if len(q.shouldClauses) > 1 { + var clauses []interface{} + for _, subQuery := range q.shouldClauses { + src, err := subQuery.Source() + if err != nil { + return nil, err + } + clauses = append(clauses, src) + } + boolClause["should"] = clauses + } + + if q.boost != nil { + boolClause["boost"] = *q.boost + } + if q.minimumShouldMatch != "" { + boolClause["minimum_should_match"] = q.minimumShouldMatch + } + if q.adjustPureNegative != nil { + boolClause["adjust_pure_negative"] = *q.adjustPureNegative + } + if q.queryName != "" { + boolClause["_name"] = q.queryName + } + + return query, nil +} diff --git a/pkg/es/search_queries_match.go b/pkg/es/search_queries_match.go new file mode 100644 index 0000000..10f5035 --- /dev/null +++ b/pkg/es/search_queries_match.go @@ -0,0 +1,189 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// MatchQuery is a family of queries that accepts text/numerics/dates, +// analyzes them, and constructs a query. +// +// To create a new MatchQuery, use NewMatchQuery. To create specific types +// of queries, e.g. a match_phrase query, use NewMatchPhrQuery(...).Type("phrase"), +// or use one of the shortcuts e.g. NewMatchPhraseQuery(...). +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-query.html +type MatchQuery struct { + name string + text interface{} + operator string // or / and + analyzer string + boost *float64 + fuzziness string + prefixLength *int + maxExpansions *int + minimumShouldMatch string + fuzzyRewrite string + lenient *bool + fuzzyTranspositions *bool + zeroTermsQuery string + cutoffFrequency *float64 + queryName string +} + +// NewMatchQuery creates and initializes a new MatchQuery. +func NewMatchQuery(name string, text interface{}) *MatchQuery { + return &MatchQuery{name: name, text: text} +} + +// Operator sets the operator to use when using a boolean query. +// Can be "AND" or "OR" (default). +func (q *MatchQuery) Operator(operator string) *MatchQuery { + q.operator = operator + return q +} + +// Analyzer explicitly sets the analyzer to use. It defaults to use explicit +// mapping config for the field, or, if not set, the default search analyzer. +func (q *MatchQuery) Analyzer(analyzer string) *MatchQuery { + q.analyzer = analyzer + return q +} + +// Fuzziness sets the fuzziness when evaluated to a fuzzy query type. +// Defaults to "AUTO". +func (q *MatchQuery) Fuzziness(fuzziness string) *MatchQuery { + q.fuzziness = fuzziness + return q +} + +// PrefixLength sets the length of a length of common (non-fuzzy) +// prefix for fuzzy match queries. It must be non-negative. +func (q *MatchQuery) PrefixLength(prefixLength int) *MatchQuery { + q.prefixLength = &prefixLength + return q +} + +// MaxExpansions is used with fuzzy or prefix type queries. It specifies +// the number of term expansions to use. It defaults to unbounded so that +// its recommended to set it to a reasonable value for faster execution. +func (q *MatchQuery) MaxExpansions(maxExpansions int) *MatchQuery { + q.maxExpansions = &maxExpansions + return q +} + +// CutoffFrequency can be a value in [0..1] (or an absolute number >=1). +// It represents the maximum treshold of a terms document frequency to be +// considered a low frequency term. +func (q *MatchQuery) CutoffFrequency(cutoff float64) *MatchQuery { + q.cutoffFrequency = &cutoff + return q +} + +// MinimumShouldMatch sets the optional minimumShouldMatch value to +// apply to the query. +func (q *MatchQuery) MinimumShouldMatch(minimumShouldMatch string) *MatchQuery { + q.minimumShouldMatch = minimumShouldMatch + return q +} + +// FuzzyRewrite sets the fuzzy_rewrite parameter controlling how the +// fuzzy query will get rewritten. +func (q *MatchQuery) FuzzyRewrite(fuzzyRewrite string) *MatchQuery { + q.fuzzyRewrite = fuzzyRewrite + return q +} + +// FuzzyTranspositions sets whether transpositions are supported in +// fuzzy queries. +// +// The default metric used by fuzzy queries to determine a match is +// the Damerau-Levenshtein distance formula which supports transpositions. +// Setting transposition to false will +// * switch to classic Levenshtein distance. +// * If not set, Damerau-Levenshtein distance metric will be used. +func (q *MatchQuery) FuzzyTranspositions(fuzzyTranspositions bool) *MatchQuery { + q.fuzzyTranspositions = &fuzzyTranspositions + return q +} + +// Lenient specifies whether format based failures will be ignored. +func (q *MatchQuery) Lenient(lenient bool) *MatchQuery { + q.lenient = &lenient + return q +} + +// ZeroTermsQuery can be "all" or "none". +func (q *MatchQuery) ZeroTermsQuery(zeroTermsQuery string) *MatchQuery { + q.zeroTermsQuery = zeroTermsQuery + return q +} + +// Boost sets the boost to apply to this query. +func (q *MatchQuery) Boost(boost float64) *MatchQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched filters per hit. +func (q *MatchQuery) QueryName(queryName string) *MatchQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the function score query. +func (q *MatchQuery) Source() (interface{}, error) { + // {"match":{"name":{"query":"value","type":"boolean/phrase"}}} + source := make(map[string]interface{}) + + match := make(map[string]interface{}) + source["match"] = match + + query := make(map[string]interface{}) + match[q.name] = query + + query["query"] = q.text + + if q.operator != "" { + query["operator"] = q.operator + } + if q.analyzer != "" { + query["analyzer"] = q.analyzer + } + if q.fuzziness != "" { + query["fuzziness"] = q.fuzziness + } + if q.prefixLength != nil { + query["prefix_length"] = *q.prefixLength + } + if q.maxExpansions != nil { + query["max_expansions"] = *q.maxExpansions + } + if q.minimumShouldMatch != "" { + query["minimum_should_match"] = q.minimumShouldMatch + } + if q.fuzzyRewrite != "" { + query["fuzzy_rewrite"] = q.fuzzyRewrite + } + if q.lenient != nil { + query["lenient"] = *q.lenient + } + if q.fuzzyTranspositions != nil { + query["fuzzy_transpositions"] = *q.fuzzyTranspositions + } + if q.zeroTermsQuery != "" { + query["zero_terms_query"] = q.zeroTermsQuery + } + if q.cutoffFrequency != nil { + query["cutoff_frequency"] = *q.cutoffFrequency + } + if q.boost != nil { + query["boost"] = *q.boost + } + if q.queryName != "" { + query["_name"] = q.queryName + } + + return source, nil +} diff --git a/pkg/es/search_queries_match_phrase.go b/pkg/es/search_queries_match_phrase.go new file mode 100644 index 0000000..24c6c0d --- /dev/null +++ b/pkg/es/search_queries_match_phrase.go @@ -0,0 +1,89 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// MatchPhraseQuery analyzes the text and creates a phrase query out of +// the analyzed text. +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-query-phrase.html +type MatchPhraseQuery struct { + name string + value interface{} + analyzer string + slop *int + boost *float64 + queryName string + zeroTermsQuery string +} + +// NewMatchPhraseQuery creates and initializes a new MatchPhraseQuery. +func NewMatchPhraseQuery(name string, value interface{}) *MatchPhraseQuery { + return &MatchPhraseQuery{name: name, value: value} +} + +// Analyzer explicitly sets the analyzer to use. It defaults to use explicit +// mapping config for the field, or, if not set, the default search analyzer. +func (q *MatchPhraseQuery) Analyzer(analyzer string) *MatchPhraseQuery { + q.analyzer = analyzer + return q +} + +// Slop sets the phrase slop if evaluated to a phrase query type. +func (q *MatchPhraseQuery) Slop(slop int) *MatchPhraseQuery { + q.slop = &slop + return q +} + +// ZeroTermsQuery can be "all" or "none". +func (q *MatchPhraseQuery) ZeroTermsQuery(zeroTermsQuery string) *MatchPhraseQuery { + q.zeroTermsQuery = zeroTermsQuery + return q +} + +// Boost sets the boost to apply to this query. +func (q *MatchPhraseQuery) Boost(boost float64) *MatchPhraseQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched filters per hit. +func (q *MatchPhraseQuery) QueryName(queryName string) *MatchPhraseQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the function score query. +func (q *MatchPhraseQuery) Source() (interface{}, error) { + // {"match_phrase":{"name":{"query":"value","analyzer":"my_analyzer"}}} + source := make(map[string]interface{}) + + match := make(map[string]interface{}) + source["match_phrase"] = match + + query := make(map[string]interface{}) + match[q.name] = query + + query["query"] = q.value + + if q.analyzer != "" { + query["analyzer"] = q.analyzer + } + if q.slop != nil { + query["slop"] = *q.slop + } + if q.zeroTermsQuery != "" { + query["zero_terms_query"] = q.zeroTermsQuery + } + if q.boost != nil { + query["boost"] = *q.boost + } + if q.queryName != "" { + query["_name"] = q.queryName + } + + return source, nil +} diff --git a/pkg/es/search_queries_match_phrase_prefix.go b/pkg/es/search_queries_match_phrase_prefix.go new file mode 100644 index 0000000..0116f47 --- /dev/null +++ b/pkg/es/search_queries_match_phrase_prefix.go @@ -0,0 +1,89 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// MatchPhrasePrefixQuery is the same as match_phrase, except that it allows for +// prefix matches on the last term in the text. +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-match-query-phrase-prefix.html +type MatchPhrasePrefixQuery struct { + name string + value interface{} + analyzer string + slop *int + maxExpansions *int + boost *float64 + queryName string +} + +// NewMatchPhrasePrefixQuery creates and initializes a new MatchPhrasePrefixQuery. +func NewMatchPhrasePrefixQuery(name string, value interface{}) *MatchPhrasePrefixQuery { + return &MatchPhrasePrefixQuery{name: name, value: value} +} + +// Analyzer explicitly sets the analyzer to use. It defaults to use explicit +// mapping config for the field, or, if not set, the default search analyzer. +func (q *MatchPhrasePrefixQuery) Analyzer(analyzer string) *MatchPhrasePrefixQuery { + q.analyzer = analyzer + return q +} + +// Slop sets the phrase slop if evaluated to a phrase query type. +func (q *MatchPhrasePrefixQuery) Slop(slop int) *MatchPhrasePrefixQuery { + q.slop = &slop + return q +} + +// MaxExpansions sets the number of term expansions to use. +func (q *MatchPhrasePrefixQuery) MaxExpansions(n int) *MatchPhrasePrefixQuery { + q.maxExpansions = &n + return q +} + +// Boost sets the boost to apply to this query. +func (q *MatchPhrasePrefixQuery) Boost(boost float64) *MatchPhrasePrefixQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched filters per hit. +func (q *MatchPhrasePrefixQuery) QueryName(queryName string) *MatchPhrasePrefixQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the function score query. +func (q *MatchPhrasePrefixQuery) Source() (interface{}, error) { + // {"match_phrase_prefix":{"name":{"query":"value","max_expansions":10}}} + source := make(map[string]interface{}) + + match := make(map[string]interface{}) + source["match_phrase_prefix"] = match + + query := make(map[string]interface{}) + match[q.name] = query + + query["query"] = q.value + + if q.analyzer != "" { + query["analyzer"] = q.analyzer + } + if q.slop != nil { + query["slop"] = *q.slop + } + if q.maxExpansions != nil { + query["max_expansions"] = *q.maxExpansions + } + if q.boost != nil { + query["boost"] = *q.boost + } + if q.queryName != "" { + query["_name"] = q.queryName + } + + return source, nil +} diff --git a/pkg/es/search_queries_prefix.go b/pkg/es/search_queries_prefix.go new file mode 100644 index 0000000..70b8b94 --- /dev/null +++ b/pkg/es/search_queries_prefix.go @@ -0,0 +1,76 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// PrefixQuery matches documents that have fields containing terms +// with a specified prefix (not analyzed). +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-prefix-query.html +type PrefixQuery struct { + name string + prefix string + boost *float64 + rewrite string + caseInsensitive *bool + queryName string +} + +// NewPrefixQuery creates and initializes a new PrefixQuery. +func NewPrefixQuery(name string, prefix string) *PrefixQuery { + return &PrefixQuery{name: name, prefix: prefix} +} + +// Boost sets the boost for this query. +func (q *PrefixQuery) Boost(boost float64) *PrefixQuery { + q.boost = &boost + return q +} + +func (q *PrefixQuery) Rewrite(rewrite string) *PrefixQuery { + q.rewrite = rewrite + return q +} + +func (q *PrefixQuery) CaseInsensitive(caseInsensitive bool) *PrefixQuery { + q.caseInsensitive = &caseInsensitive + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched_filters per hit. +func (q *PrefixQuery) QueryName(queryName string) *PrefixQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the query. +func (q *PrefixQuery) Source() (interface{}, error) { + source := make(map[string]interface{}) + query := make(map[string]interface{}) + source["prefix"] = query + + if q.boost == nil && q.rewrite == "" && q.queryName == "" && q.caseInsensitive == nil { + query[q.name] = q.prefix + } else { + subQuery := make(map[string]interface{}) + subQuery["value"] = q.prefix + if q.boost != nil { + subQuery["boost"] = *q.boost + } + if q.rewrite != "" { + subQuery["rewrite"] = q.rewrite + } + if q.caseInsensitive != nil { + subQuery["case_insensitive"] = *q.caseInsensitive + } + if q.queryName != "" { + subQuery["_name"] = q.queryName + } + query[q.name] = subQuery + } + + return source, nil +} diff --git a/pkg/es/search_queries_query_string.go b/pkg/es/search_queries_query_string.go new file mode 100644 index 0000000..af328bc --- /dev/null +++ b/pkg/es/search_queries_query_string.go @@ -0,0 +1,350 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import ( + "fmt" +) + +// QueryStringQuery uses the query parser in order to parse its content. +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-query-string-query.html +type QueryStringQuery struct { + queryString string + defaultField string + defaultOperator string + analyzer string + quoteAnalyzer string + quoteFieldSuffix string + allowLeadingWildcard *bool + lowercaseExpandedTerms *bool // Deprecated: Decision is now made by the analyzer + enablePositionIncrements *bool + analyzeWildcard *bool + locale string // Deprecated: Decision is now made by the analyzer + boost *float64 + fuzziness string + fuzzyPrefixLength *int + fuzzyMaxExpansions *int + fuzzyRewrite string + phraseSlop *int + fields []string + fieldBoosts map[string]*float64 + tieBreaker *float64 + rewrite string + minimumShouldMatch string + lenient *bool + queryName string + timeZone string + maxDeterminizedStates *int + escape *bool + typ string +} + +// NewQueryStringQuery creates and initializes a new QueryStringQuery. +func NewQueryStringQuery(queryString string) *QueryStringQuery { + return &QueryStringQuery{ + queryString: queryString, + fields: make([]string, 0), + fieldBoosts: make(map[string]*float64), + } +} + +// DefaultField specifies the field to run against when no prefix field +// is specified. Only relevant when not explicitly adding fields the query +// string will run against. +func (q *QueryStringQuery) DefaultField(defaultField string) *QueryStringQuery { + q.defaultField = defaultField + return q +} + +// Field adds a field to run the query string against. +func (q *QueryStringQuery) Field(field string) *QueryStringQuery { + q.fields = append(q.fields, field) + return q +} + +// Type sets how multiple fields should be combined to build textual part queries, +// e.g. "best_fields". +func (q *QueryStringQuery) Type(typ string) *QueryStringQuery { + q.typ = typ + return q +} + +// FieldWithBoost adds a field to run the query string against with a specific boost. +func (q *QueryStringQuery) FieldWithBoost(field string, boost float64) *QueryStringQuery { + q.fields = append(q.fields, field) + q.fieldBoosts[field] = &boost + return q +} + +// TieBreaker is used when more than one field is used with the query string, +// and combined queries are using dismax. +func (q *QueryStringQuery) TieBreaker(tieBreaker float64) *QueryStringQuery { + q.tieBreaker = &tieBreaker + return q +} + +// DefaultOperator sets the boolean operator of the query parser used to +// parse the query string. +// +// In default mode (OR) terms without any modifiers +// are considered optional, e.g. "capital of Hungary" is equal to +// "capital OR of OR Hungary". +// +// In AND mode, terms are considered to be in conjunction. The above mentioned +// query is then parsed as "capital AND of AND Hungary". +func (q *QueryStringQuery) DefaultOperator(operator string) *QueryStringQuery { + q.defaultOperator = operator + return q +} + +// Analyzer is an optional analyzer used to analyze the query string. +// Note, if a field has search analyzer defined for it, then it will be used +// automatically. Defaults to the smart search analyzer. +func (q *QueryStringQuery) Analyzer(analyzer string) *QueryStringQuery { + q.analyzer = analyzer + return q +} + +// QuoteAnalyzer is an optional analyzer to be used to analyze the query string +// for phrase searches. Note, if a field has search analyzer defined for it, +// then it will be used automatically. Defaults to the smart search analyzer. +func (q *QueryStringQuery) QuoteAnalyzer(quoteAnalyzer string) *QueryStringQuery { + q.quoteAnalyzer = quoteAnalyzer + return q +} + +// MaxDeterminizedState protects against too-difficult regular expression queries. +func (q *QueryStringQuery) MaxDeterminizedState(maxDeterminizedStates int) *QueryStringQuery { + q.maxDeterminizedStates = &maxDeterminizedStates + return q +} + +// AllowLeadingWildcard specifies whether leading wildcards should be allowed +// or not (defaults to true). +func (q *QueryStringQuery) AllowLeadingWildcard(allowLeadingWildcard bool) *QueryStringQuery { + q.allowLeadingWildcard = &allowLeadingWildcard + return q +} + +// LowercaseExpandedTerms indicates whether terms of wildcard, prefix, fuzzy +// and range queries are automatically lower-cased or not. Default is true. +// +// Deprecated: Decision is now made by the analyzer. +func (q *QueryStringQuery) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *QueryStringQuery { + q.lowercaseExpandedTerms = &lowercaseExpandedTerms + return q +} + +// EnablePositionIncrements indicates whether to enable position increments +// in result query. Defaults to true. +// +// When set, result phrase and multi-phrase queries will be aware of position +// increments. Useful when e.g. a StopFilter increases the position increment +// of the token that follows an omitted token. +func (q *QueryStringQuery) EnablePositionIncrements(enablePositionIncrements bool) *QueryStringQuery { + q.enablePositionIncrements = &enablePositionIncrements + return q +} + +// Fuzziness sets the edit distance for fuzzy queries. Default is "AUTO". +func (q *QueryStringQuery) Fuzziness(fuzziness string) *QueryStringQuery { + q.fuzziness = fuzziness + return q +} + +// FuzzyPrefixLength sets the minimum prefix length for fuzzy queries. +// Default is 1. +func (q *QueryStringQuery) FuzzyPrefixLength(fuzzyPrefixLength int) *QueryStringQuery { + q.fuzzyPrefixLength = &fuzzyPrefixLength + return q +} + +func (q *QueryStringQuery) FuzzyMaxExpansions(fuzzyMaxExpansions int) *QueryStringQuery { + q.fuzzyMaxExpansions = &fuzzyMaxExpansions + return q +} + +func (q *QueryStringQuery) FuzzyRewrite(fuzzyRewrite string) *QueryStringQuery { + q.fuzzyRewrite = fuzzyRewrite + return q +} + +// PhraseSlop sets the default slop for phrases. If zero, then exact matches +// are required. Default value is zero. +func (q *QueryStringQuery) PhraseSlop(phraseSlop int) *QueryStringQuery { + q.phraseSlop = &phraseSlop + return q +} + +// AnalyzeWildcard indicates whether to enabled analysis on wildcard and prefix queries. +func (q *QueryStringQuery) AnalyzeWildcard(analyzeWildcard bool) *QueryStringQuery { + q.analyzeWildcard = &analyzeWildcard + return q +} + +func (q *QueryStringQuery) Rewrite(rewrite string) *QueryStringQuery { + q.rewrite = rewrite + return q +} + +func (q *QueryStringQuery) MinimumShouldMatch(minimumShouldMatch string) *QueryStringQuery { + q.minimumShouldMatch = minimumShouldMatch + return q +} + +// Boost sets the boost for this query. +func (q *QueryStringQuery) Boost(boost float64) *QueryStringQuery { + q.boost = &boost + return q +} + +// QuoteFieldSuffix is an optional field name suffix to automatically +// try and add to the field searched when using quoted text. +func (q *QueryStringQuery) QuoteFieldSuffix(quoteFieldSuffix string) *QueryStringQuery { + q.quoteFieldSuffix = quoteFieldSuffix + return q +} + +// Lenient indicates whether the query string parser should be lenient +// when parsing field values. It defaults to the index setting and if not +// set, defaults to false. +func (q *QueryStringQuery) Lenient(lenient bool) *QueryStringQuery { + q.lenient = &lenient + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched_filters per hit. +func (q *QueryStringQuery) QueryName(queryName string) *QueryStringQuery { + q.queryName = queryName + return q +} + +// Locale specifies the locale to be used for string conversions. +// +// Deprecated: Decision is now made by the analyzer. +func (q *QueryStringQuery) Locale(locale string) *QueryStringQuery { + q.locale = locale + return q +} + +// TimeZone can be used to automatically adjust to/from fields using a +// timezone. Only used with date fields, of course. +func (q *QueryStringQuery) TimeZone(timeZone string) *QueryStringQuery { + q.timeZone = timeZone + return q +} + +// Escape performs escaping of the query string. +func (q *QueryStringQuery) Escape(escape bool) *QueryStringQuery { + q.escape = &escape + return q +} + +// Source returns JSON for the query. +func (q *QueryStringQuery) Source() (interface{}, error) { + source := make(map[string]interface{}) + query := make(map[string]interface{}) + source["query_string"] = query + + query["query"] = q.queryString + + if q.defaultField != "" { + query["default_field"] = q.defaultField + } + + if len(q.fields) > 0 { + var fields []string + for _, field := range q.fields { + if boost, found := q.fieldBoosts[field]; found { + if boost != nil { + fields = append(fields, fmt.Sprintf("%s^%f", field, *boost)) + } else { + fields = append(fields, field) + } + } else { + fields = append(fields, field) + } + } + query["fields"] = fields + } + + if q.tieBreaker != nil { + query["tie_breaker"] = *q.tieBreaker + } + if q.defaultOperator != "" { + query["default_operator"] = q.defaultOperator + } + if q.analyzer != "" { + query["analyzer"] = q.analyzer + } + if q.quoteAnalyzer != "" { + query["quote_analyzer"] = q.quoteAnalyzer + } + if q.maxDeterminizedStates != nil { + query["max_determinized_states"] = *q.maxDeterminizedStates + } + if q.allowLeadingWildcard != nil { + query["allow_leading_wildcard"] = *q.allowLeadingWildcard + } + if q.lowercaseExpandedTerms != nil { + query["lowercase_expanded_terms"] = *q.lowercaseExpandedTerms + } + if q.enablePositionIncrements != nil { + query["enable_position_increments"] = *q.enablePositionIncrements + } + if q.fuzziness != "" { + query["fuzziness"] = q.fuzziness + } + if q.boost != nil { + query["boost"] = *q.boost + } + if q.fuzzyPrefixLength != nil { + query["fuzzy_prefix_length"] = *q.fuzzyPrefixLength + } + if q.fuzzyMaxExpansions != nil { + query["fuzzy_max_expansions"] = *q.fuzzyMaxExpansions + } + if q.fuzzyRewrite != "" { + query["fuzzy_rewrite"] = q.fuzzyRewrite + } + if q.phraseSlop != nil { + query["phrase_slop"] = *q.phraseSlop + } + if q.analyzeWildcard != nil { + query["analyze_wildcard"] = *q.analyzeWildcard + } + if q.rewrite != "" { + query["rewrite"] = q.rewrite + } + if q.minimumShouldMatch != "" { + query["minimum_should_match"] = q.minimumShouldMatch + } + if q.quoteFieldSuffix != "" { + query["quote_field_suffix"] = q.quoteFieldSuffix + } + if q.lenient != nil { + query["lenient"] = *q.lenient + } + if q.queryName != "" { + query["_name"] = q.queryName + } + if q.locale != "" { + query["locale"] = q.locale + } + if q.timeZone != "" { + query["time_zone"] = q.timeZone + } + if q.escape != nil { + query["escape"] = *q.escape + } + if q.typ != "" { + query["type"] = q.typ + } + + return source, nil +} diff --git a/pkg/es/search_queries_range.go b/pkg/es/search_queries_range.go new file mode 100644 index 0000000..65c2950 --- /dev/null +++ b/pkg/es/search_queries_range.go @@ -0,0 +1,155 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// RangeQuery matches documents with fields that have terms within a certain range. +// +// For details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-range-query.html +type RangeQuery struct { + name string + from interface{} + to interface{} + timeZone string + includeLower bool + includeUpper bool + boost *float64 + queryName string + format string + relation string +} + +// NewRangeQuery creates and initializes a new RangeQuery. +func NewRangeQuery(name string) *RangeQuery { + return &RangeQuery{name: name, includeLower: true, includeUpper: true} +} + +// From indicates the from part of the RangeQuery. +// Use nil to indicate an unbounded from part. +func (q *RangeQuery) From(from interface{}) *RangeQuery { + q.from = from + return q +} + +// Gt indicates a greater-than value for the from part. +// Use nil to indicate an unbounded from part. +func (q *RangeQuery) Gt(from interface{}) *RangeQuery { + q.from = from + q.includeLower = false + return q +} + +// Gte indicates a greater-than-or-equal value for the from part. +// Use nil to indicate an unbounded from part. +func (q *RangeQuery) Gte(from interface{}) *RangeQuery { + q.from = from + q.includeLower = true + return q +} + +// To indicates the to part of the RangeQuery. +// Use nil to indicate an unbounded to part. +func (q *RangeQuery) To(to interface{}) *RangeQuery { + q.to = to + return q +} + +// Lt indicates a less-than value for the to part. +// Use nil to indicate an unbounded to part. +func (q *RangeQuery) Lt(to interface{}) *RangeQuery { + q.to = to + q.includeUpper = false + return q +} + +// Lte indicates a less-than-or-equal value for the to part. +// Use nil to indicate an unbounded to part. +func (q *RangeQuery) Lte(to interface{}) *RangeQuery { + q.to = to + q.includeUpper = true + return q +} + +// IncludeLower indicates whether the lower bound should be included or not. +// Defaults to true. +func (q *RangeQuery) IncludeLower(includeLower bool) *RangeQuery { + q.includeLower = includeLower + return q +} + +// IncludeUpper indicates whether the upper bound should be included or not. +// Defaults to true. +func (q *RangeQuery) IncludeUpper(includeUpper bool) *RangeQuery { + q.includeUpper = includeUpper + return q +} + +// Boost sets the boost for this query. +func (q *RangeQuery) Boost(boost float64) *RangeQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched_filters per hit. +func (q *RangeQuery) QueryName(queryName string) *RangeQuery { + q.queryName = queryName + return q +} + +// TimeZone is used for date fields. In that case, we can adjust the +// from/to fields using a timezone. +func (q *RangeQuery) TimeZone(timeZone string) *RangeQuery { + q.timeZone = timeZone + return q +} + +// Format is used for date fields. In that case, we can set the format +// to be used instead of the mapper format. +func (q *RangeQuery) Format(format string) *RangeQuery { + q.format = format + return q +} + +// Relation is used for range fields. which can be one of +// "within", "contains", "intersects" (default) and "disjoint". +func (q *RangeQuery) Relation(relation string) *RangeQuery { + q.relation = relation + return q +} + +// Source returns JSON for the query. +func (q *RangeQuery) Source() (interface{}, error) { + source := make(map[string]interface{}) + + rangeQ := make(map[string]interface{}) + source["range"] = rangeQ + + params := make(map[string]interface{}) + rangeQ[q.name] = params + + params["from"] = q.from + params["to"] = q.to + if q.timeZone != "" { + params["time_zone"] = q.timeZone + } + if q.format != "" { + params["format"] = q.format + } + if q.relation != "" { + params["relation"] = q.relation + } + if q.boost != nil { + params["boost"] = *q.boost + } + params["include_lower"] = q.includeLower + params["include_upper"] = q.includeUpper + + if q.queryName != "" { + rangeQ["_name"] = q.queryName + } + + return source, nil +} diff --git a/pkg/es/search_queries_rank_feature.go b/pkg/es/search_queries_rank_feature.go new file mode 100644 index 0000000..a90c4b2 --- /dev/null +++ b/pkg/es/search_queries_rank_feature.go @@ -0,0 +1,213 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// RankFeatureQuery boosts the relevance score of documents based on the +// numeric value of a rank_feature or rank_features field. +// +// The RankFeatureQuery is typically used in the should clause of a BoolQuery +// so its relevance scores are added to other scores from the BoolQuery. +// +// For more details, see: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html +type RankFeatureQuery struct { + field string + scoreFunc RankFeatureScoreFunction + boost *float64 + queryName string +} + +// NewRankFeatureQuery creates and initializes a new RankFeatureQuery. +func NewRankFeatureQuery(field string) *RankFeatureQuery { + return &RankFeatureQuery{ + field: field, + } +} + +// Field name. +func (q *RankFeatureQuery) Field(field string) *RankFeatureQuery { + q.field = field + return q +} + +// ScoreFunction specifies the score function for the RankFeatureQuery. +func (q *RankFeatureQuery) ScoreFunction(f RankFeatureScoreFunction) *RankFeatureQuery { + q.scoreFunc = f + return q +} + +// Boost sets the boost for this query. +func (q *RankFeatureQuery) Boost(boost float64) *RankFeatureQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter that can be used when +// searching for matched_filters per hit. +func (q *RankFeatureQuery) QueryName(queryName string) *RankFeatureQuery { + q.queryName = queryName + return q +} + +// Source returns the JSON serializable content for this query. +func (q *RankFeatureQuery) Source() (interface{}, error) { + // { + // "rank_feature": { + // "field": "pagerank", + // "saturation": { + // "pivot": 8 + // } + // } + // } + + query := make(map[string]interface{}) + params := make(map[string]interface{}) + query["rank_feature"] = params + params["field"] = q.field + if q.scoreFunc != nil { + src, err := q.scoreFunc.Source() + if err != nil { + return nil, err + } + params[q.scoreFunc.Name()] = src + } + if q.boost != nil { + params["boost"] = *q.boost + } + if q.queryName != "" { + params["_name"] = q.queryName + } + + return query, nil +} + +// -- Score functions -- + +// RankFeatureScoreFunction specifies the interface for score functions +// in the context of a RankFeatureQuery. +type RankFeatureScoreFunction interface { + Name() string + Source() (interface{}, error) +} + +// -- Log score function -- + +// RankFeatureLogScoreFunction represents a Logarithmic score function for a +// RankFeatureQuery. +// +// See here for details: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-logarithm +type RankFeatureLogScoreFunction struct { + scalingFactor float64 +} + +// NewRankFeatureLogScoreFunction returns a new RankFeatureLogScoreFunction +// with the given scaling factor. +func NewRankFeatureLogScoreFunction(scalingFactor float64) *RankFeatureLogScoreFunction { + return &RankFeatureLogScoreFunction{ + scalingFactor: scalingFactor, + } +} + +// Name of the score function. +func (f *RankFeatureLogScoreFunction) Name() string { return "log" } + +// Source returns a serializable JSON object for building the query. +func (f *RankFeatureLogScoreFunction) Source() (interface{}, error) { + return map[string]interface{}{ + "scaling_factor": f.scalingFactor, + }, nil +} + +// -- Saturation score function -- + +// RankFeatureSaturationScoreFunction represents a Log score function for a +// RankFeatureQuery. +// +// See here for details: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-saturation +type RankFeatureSaturationScoreFunction struct { + pivot *float64 +} + +// NewRankFeatureSaturationScoreFunction initializes a new +// RankFeatureSaturationScoreFunction. +func NewRankFeatureSaturationScoreFunction() *RankFeatureSaturationScoreFunction { + return &RankFeatureSaturationScoreFunction{} +} + +// Pivot specifies the pivot to use. +func (f *RankFeatureSaturationScoreFunction) Pivot(pivot float64) *RankFeatureSaturationScoreFunction { + f.pivot = &pivot + return f +} + +// Name of the score function. +func (f *RankFeatureSaturationScoreFunction) Name() string { return "saturation" } + +// Source returns a serializable JSON object for building the query. +func (f *RankFeatureSaturationScoreFunction) Source() (interface{}, error) { + m := make(map[string]interface{}) + if f.pivot != nil { + m["pivot"] = *f.pivot + } + return m, nil +} + +// -- Sigmoid score function -- + +// RankFeatureSigmoidScoreFunction represents a Sigmoid score function for a +// RankFeatureQuery. +// +// See here for details: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-sigmoid +type RankFeatureSigmoidScoreFunction struct { + pivot float64 + exponent float64 +} + +// NewRankFeatureSigmoidScoreFunction returns a new RankFeatureSigmoidScoreFunction +// with the given scaling factor. +func NewRankFeatureSigmoidScoreFunction(pivot, exponent float64) *RankFeatureSigmoidScoreFunction { + return &RankFeatureSigmoidScoreFunction{ + pivot: pivot, + exponent: exponent, + } +} + +// Name of the score function. +func (f *RankFeatureSigmoidScoreFunction) Name() string { return "sigmoid" } + +// Source returns a serializable JSON object for building the query. +func (f *RankFeatureSigmoidScoreFunction) Source() (interface{}, error) { + return map[string]interface{}{ + "pivot": f.pivot, + "exponent": f.exponent, + }, nil +} + +// -- Linear score function -- + +// RankFeatureLinearScoreFunction represents a Linear score function for a +// RankFeatureQuery. +// +// See here for details: +// https://www.elastic.co/guide/en/elasticsearch/reference/7.14/query-dsl-rank-feature-query.html#rank-feature-query-linear +type RankFeatureLinearScoreFunction struct { +} + +// NewRankFeatureLinearScoreFunction initializes a new +// RankFeatureLinearScoreFunction. +func NewRankFeatureLinearScoreFunction() *RankFeatureLinearScoreFunction { + return &RankFeatureLinearScoreFunction{} +} + +// Name of the score function. +func (f *RankFeatureLinearScoreFunction) Name() string { return "linear" } + +// Source returns a serializable JSON object for building the query. +func (f *RankFeatureLinearScoreFunction) Source() (interface{}, error) { + return map[string]interface{}{}, nil +} diff --git a/pkg/es/search_queries_raw_string.go b/pkg/es/search_queries_raw_string.go new file mode 100644 index 0000000..352ac1a --- /dev/null +++ b/pkg/es/search_queries_raw_string.go @@ -0,0 +1,27 @@ +// Copyright 2012-present Oliver Eilhard, John Stanford. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import "encoding/json" + +// RawStringQuery can be used to treat a string representation of an ES query +// as a Query. Example usage: +// +// q := RawStringQuery("{\"match_all\":{}}") +// db.Search().Query(q).From(1).Size(100).Do() +type RawStringQuery string + +// NewRawStringQuery ininitializes a new RawStringQuery. +// It is the same as RawStringQuery(q). +func NewRawStringQuery(q string) RawStringQuery { + return RawStringQuery(q) +} + +// Source returns the JSON encoded body +func (q RawStringQuery) Source() (interface{}, error) { + var f interface{} + err := json.Unmarshal([]byte(q), &f) + return f, err +} diff --git a/pkg/es/search_queries_regexp.go b/pkg/es/search_queries_regexp.go new file mode 100644 index 0000000..25b2180 --- /dev/null +++ b/pkg/es/search_queries_regexp.go @@ -0,0 +1,91 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// RegexpQuery allows you to use regular expression term queries. +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-regexp-query.html +type RegexpQuery struct { + name string + regexp string + flags string + boost *float64 + rewrite string + caseInsensitive *bool + queryName string + maxDeterminizedStates *int +} + +// NewRegexpQuery creates and initializes a new RegexpQuery. +func NewRegexpQuery(name string, regexp string) *RegexpQuery { + return &RegexpQuery{name: name, regexp: regexp} +} + +// Flags sets the regexp flags. +func (q *RegexpQuery) Flags(flags string) *RegexpQuery { + q.flags = flags + return q +} + +// MaxDeterminizedStates protects against complex regular expressions. +func (q *RegexpQuery) MaxDeterminizedStates(maxDeterminizedStates int) *RegexpQuery { + q.maxDeterminizedStates = &maxDeterminizedStates + return q +} + +// Boost sets the boost for this query. +func (q *RegexpQuery) Boost(boost float64) *RegexpQuery { + q.boost = &boost + return q +} + +func (q *RegexpQuery) Rewrite(rewrite string) *RegexpQuery { + q.rewrite = rewrite + return q +} + +func (q *RegexpQuery) CaseInsensitive(caseInsensitive bool) *RegexpQuery { + q.caseInsensitive = &caseInsensitive + return q +} + +// QueryName sets the query name for the filter that can be used +// when searching for matched_filters per hit +func (q *RegexpQuery) QueryName(queryName string) *RegexpQuery { + q.queryName = queryName + return q +} + +// Source returns the JSON-serializable query data. +func (q *RegexpQuery) Source() (interface{}, error) { + source := make(map[string]interface{}) + query := make(map[string]interface{}) + source["regexp"] = query + + x := make(map[string]interface{}) + x["value"] = q.regexp + if q.flags != "" { + x["flags"] = q.flags + } + if q.maxDeterminizedStates != nil { + x["max_determinized_states"] = *q.maxDeterminizedStates + } + if q.boost != nil { + x["boost"] = *q.boost + } + if q.rewrite != "" { + x["rewrite"] = q.rewrite + } + if q.caseInsensitive != nil { + x["case_insensitive"] = *q.caseInsensitive + } + if q.queryName != "" { + x["name"] = q.queryName + } + query[q.name] = x + + return source, nil +} diff --git a/pkg/es/search_queries_script.go b/pkg/es/search_queries_script.go new file mode 100644 index 0000000..38012fc --- /dev/null +++ b/pkg/es/search_queries_script.go @@ -0,0 +1,51 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import "errors" + +// ScriptQuery allows to define scripts as filters. +// +// For details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-script-query.html +type ScriptQuery struct { + script *Script + queryName string +} + +// NewScriptQuery creates and initializes a new ScriptQuery. +func NewScriptQuery(script *Script) *ScriptQuery { + return &ScriptQuery{ + script: script, + } +} + +// QueryName sets the query name for the filter that can be used +// when searching for matched_filters per hit +func (q *ScriptQuery) QueryName(queryName string) *ScriptQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the query. +func (q *ScriptQuery) Source() (interface{}, error) { + if q.script == nil { + return nil, errors.New("ScriptQuery expected a script") + } + source := make(map[string]interface{}) + params := make(map[string]interface{}) + source["script"] = params + + src, err := q.script.Source() + if err != nil { + return nil, err + } + params["script"] = src + + if q.queryName != "" { + params["_name"] = q.queryName + } + return source, nil +} diff --git a/pkg/es/search_queries_script_score.go b/pkg/es/search_queries_script_score.go new file mode 100644 index 0000000..b5570ca --- /dev/null +++ b/pkg/es/search_queries_script_score.go @@ -0,0 +1,110 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +import "errors" + +// ScriptScoreQuery uses a script to provide a custom score for returned documents. +// +// A ScriptScoreQuery query is useful if, for example, a scoring function is +// expensive and you only need to calculate the score of a filtered set of documents. +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.4/query-dsl-script-score-query.html +type ScriptScoreQuery struct { + query Query + script *Script + minScore *float64 + boost *float64 + queryName string +} + +// NewScriptScoreQuery creates and initializes a new script_score query. +func NewScriptScoreQuery(query Query, script *Script) *ScriptScoreQuery { + return &ScriptScoreQuery{ + query: query, + script: script, + } +} + +// Query to be used in the ScriptScoreQuery. +func (q *ScriptScoreQuery) Query(query Query) *ScriptScoreQuery { + q.query = query + return q +} + +// Script to calculate the score. +func (q *ScriptScoreQuery) Script(script *Script) *ScriptScoreQuery { + q.script = script + return q +} + +// MinScore sets the minimum score. +func (q *ScriptScoreQuery) MinScore(minScore float64) *ScriptScoreQuery { + q.minScore = &minScore + return q +} + +// Boost sets the boost for this query. +func (q *ScriptScoreQuery) Boost(boost float64) *ScriptScoreQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter. +func (q *ScriptScoreQuery) QueryName(queryName string) *ScriptScoreQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the function score query. +func (q *ScriptScoreQuery) Source() (interface{}, error) { + // { + // "script_score" : { + // "query" : { + // "match" : { "message": "elasticsearch" } + // }, + // "script" : { + // "source" : "doc['likes'].value / 10" + // } + // } + // } + + source := make(map[string]interface{}) + query := make(map[string]interface{}) + source["script_score"] = query + + if q.query == nil { + return nil, errors.New("ScriptScoreQuery: Query is missing") + } + if q.script == nil { + return nil, errors.New("ScriptScoreQuery: Script is missing") + } + + if src, err := q.query.Source(); err != nil { + return nil, err + } else { + query["query"] = src + } + + if src, err := q.script.Source(); err != nil { + return nil, err + } else { + query["script"] = src + } + + if v := q.minScore; v != nil { + query["min_score"] = *v + } + + if v := q.boost; v != nil { + query["boost"] = *v + } + if q.queryName != "" { + query["_name"] = q.queryName + } + + return source, nil +} diff --git a/pkg/es/search_queries_slice.go b/pkg/es/search_queries_slice.go new file mode 100644 index 0000000..690145d --- /dev/null +++ b/pkg/es/search_queries_slice.go @@ -0,0 +1,53 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// SliceQuery allows to partition the documents into several slices. +// It is used e.g. to slice scroll operations in Elasticsearch 5.0 or later. +// See https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-scroll.html#sliced-scroll +// for details. +type SliceQuery struct { + field string + id *int + max *int +} + +// NewSliceQuery creates a new SliceQuery. +func NewSliceQuery() *SliceQuery { + return &SliceQuery{} +} + +// Field is the name of the field to slice against (_uid by default). +func (s *SliceQuery) Field(field string) *SliceQuery { + s.field = field + return s +} + +// Id is the id of the slice. +func (s *SliceQuery) Id(id int) *SliceQuery { + s.id = &id + return s +} + +// Max is the maximum number of slices. +func (s *SliceQuery) Max(max int) *SliceQuery { + s.max = &max + return s +} + +// Source returns the JSON body. +func (s *SliceQuery) Source() (interface{}, error) { + m := make(map[string]interface{}) + if s.field != "" { + m["field"] = s.field + } + if s.id != nil { + m["id"] = *s.id + } + if s.max != nil { + m["max"] = *s.max + } + return m, nil +} diff --git a/pkg/es/search_queries_term.go b/pkg/es/search_queries_term.go new file mode 100644 index 0000000..f7c2714 --- /dev/null +++ b/pkg/es/search_queries_term.go @@ -0,0 +1,67 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// TermQuery finds documents that contain the exact term specified +// in the inverted index. +// +// For details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-term-query.html +type TermQuery struct { + name string + value interface{} + boost *float64 + caseInsensitive *bool + queryName string +} + +// NewTermQuery creates and initializes a new TermQuery. +func NewTermQuery(name string, value interface{}) *TermQuery { + return &TermQuery{name: name, value: value} +} + +// Boost sets the boost for this query. +func (q *TermQuery) Boost(boost float64) *TermQuery { + q.boost = &boost + return q +} + +func (q *TermQuery) CaseInsensitive(caseInsensitive bool) *TermQuery { + q.caseInsensitive = &caseInsensitive + return q +} + +// QueryName sets the query name for the filter that can be used +// when searching for matched_filters per hit +func (q *TermQuery) QueryName(queryName string) *TermQuery { + q.queryName = queryName + return q +} + +// Source returns JSON for the query. +func (q *TermQuery) Source() (interface{}, error) { + // {"term":{"name":"value"}} + source := make(map[string]interface{}) + tq := make(map[string]interface{}) + source["term"] = tq + + if q.boost == nil && q.caseInsensitive == nil && q.queryName == "" { + tq[q.name] = q.value + } else { + subQ := make(map[string]interface{}) + subQ["value"] = q.value + if q.boost != nil { + subQ["boost"] = *q.boost + } + if q.caseInsensitive != nil { + subQ["case_insensitive"] = *q.caseInsensitive + } + if q.queryName != "" { + subQ["_name"] = q.queryName + } + tq[q.name] = subQ + } + return source, nil +} diff --git a/pkg/es/search_queries_terms.go b/pkg/es/search_queries_terms.go new file mode 100644 index 0000000..497cf45 --- /dev/null +++ b/pkg/es/search_queries_terms.go @@ -0,0 +1,88 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// TermsQuery filters documents that have fields that match any +// of the provided terms (not analyzed). +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-terms-query.html +type TermsQuery struct { + name string + values []interface{} + termsLookup *TermsLookup + queryName string + boost *float64 +} + +// NewTermsQuery creates and initializes a new TermsQuery. +func NewTermsQuery(name string, values ...interface{}) *TermsQuery { + q := &TermsQuery{ + name: name, + values: make([]interface{}, 0), + } + if len(values) > 0 { + q.values = append(q.values, values...) + } + return q +} + +// NewTermsQueryFromStrings creates and initializes a new TermsQuery +// from strings. +func NewTermsQueryFromStrings(name string, values ...string) *TermsQuery { + q := &TermsQuery{ + name: name, + values: make([]interface{}, 0), + } + for _, v := range values { + q.values = append(q.values, v) + } + return q +} + +// TermsLookup adds terms lookup details to the query. +func (q *TermsQuery) TermsLookup(lookup *TermsLookup) *TermsQuery { + q.termsLookup = lookup + return q +} + +// Boost sets the boost for this query. +func (q *TermsQuery) Boost(boost float64) *TermsQuery { + q.boost = &boost + return q +} + +// QueryName sets the query name for the filter that can be used +// when searching for matched_filters per hit +func (q *TermsQuery) QueryName(queryName string) *TermsQuery { + q.queryName = queryName + return q +} + +// Creates the query source for the term query. +func (q *TermsQuery) Source() (interface{}, error) { + // {"terms":{"name":["value1","value2"]}} + source := make(map[string]interface{}) + params := make(map[string]interface{}) + source["terms"] = params + + if q.termsLookup != nil { + src, err := q.termsLookup.Source() + if err != nil { + return nil, err + } + params[q.name] = src + } else { + params[q.name] = q.values + if q.boost != nil { + params["boost"] = *q.boost + } + if q.queryName != "" { + params["_name"] = q.queryName + } + } + + return source, nil +} diff --git a/pkg/es/search_terms_lookup.go b/pkg/es/search_terms_lookup.go new file mode 100644 index 0000000..61b8abe --- /dev/null +++ b/pkg/es/search_terms_lookup.go @@ -0,0 +1,76 @@ +// Copyright 2012-present Oliver Eilhard. All rights reserved. +// Use of this source code is governed by a MIT-license. +// See http://olivere.mit-license.org/license.txt for details. + +package es + +// TermsLookup encapsulates the parameters needed to fetch terms. +// +// For more details, see +// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/query-dsl-terms-query.html#query-dsl-terms-lookup. +type TermsLookup struct { + index string + typ string + id string + path string + routing string +} + +// NewTermsLookup creates and initializes a new TermsLookup. +func NewTermsLookup() *TermsLookup { + t := &TermsLookup{} + return t +} + +// Index name. +func (t *TermsLookup) Index(index string) *TermsLookup { + t.index = index + return t +} + +// Type name. +// +// Deprecated: Types are in the process of being removed. +func (t *TermsLookup) Type(typ string) *TermsLookup { + t.typ = typ + return t +} + +// Id to look up. +func (t *TermsLookup) Id(id string) *TermsLookup { + t.id = id + return t +} + +// Path to use for lookup. +func (t *TermsLookup) Path(path string) *TermsLookup { + t.path = path + return t +} + +// Routing value. +func (t *TermsLookup) Routing(routing string) *TermsLookup { + t.routing = routing + return t +} + +// Source creates the JSON source of the builder. +func (t *TermsLookup) Source() (interface{}, error) { + src := make(map[string]interface{}) + if t.index != "" { + src["index"] = t.index + } + if t.typ != "" { + src["type"] = t.typ + } + if t.id != "" { + src["id"] = t.id + } + if t.path != "" { + src["path"] = t.path + } + if t.routing != "" { + src["routing"] = t.routing + } + return src, nil +}