diff --git a/contactql/es/query.go b/contactql/es/query.go index bc0097e69..d8ad36beb 100644 --- a/contactql/es/query.go +++ b/contactql/es/query.go @@ -8,8 +8,7 @@ import ( "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/contactql" "github.com/nyaruka/goflow/envs" - - "github.com/olivere/elastic/v7" + "github.com/nyaruka/goflow/utils/elastic" ) // AssetMapper is used to map engine assets to however ES identifies them @@ -38,57 +37,52 @@ func ToElasticQuery(env envs.Environment, mapper AssetMapper, query *contactql.C func nodeToElastic(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, node contactql.QueryNode) elastic.Query { switch n := node.(type) { case *contactql.BoolCombination: - return boolCombinationToElastic(env, resolver, mapper, n) + return boolCombination(env, resolver, mapper, n) case *contactql.Condition: - return conditionToElastic(env, resolver, mapper, n) + return condition(env, resolver, mapper, n) default: panic(fmt.Sprintf("unsupported node type: %T", n)) } } -func boolCombinationToElastic(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, combination *contactql.BoolCombination) elastic.Query { +func boolCombination(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, combination *contactql.BoolCombination) elastic.Query { queries := make([]elastic.Query, len(combination.Children())) for i, child := range combination.Children() { queries[i] = nodeToElastic(env, resolver, mapper, child) } if combination.Operator() == contactql.BoolOperatorAnd { - return elastic.NewBoolQuery().Must(queries...) + return elastic.All(queries...) } - return elastic.NewBoolQuery().Should(queries...) + return elastic.Any(queries...) } -func conditionToElastic(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, c *contactql.Condition) elastic.Query { +func condition(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, c *contactql.Condition) elastic.Query { switch c.PropertyType() { case contactql.PropertyTypeField: - return fieldConditionToElastic(env, resolver, c) + return fieldCondition(env, resolver, c) case contactql.PropertyTypeAttribute: - return attributeConditionToElastic(env, resolver, mapper, c) + return attributeCondition(env, resolver, mapper, c) case contactql.PropertyTypeURN: - return schemeConditionToElastic(env, c) + return schemeCondition(c) default: panic(fmt.Sprintf("unsupported property type: %s", c.PropertyType())) } } -func fieldConditionToElastic(env envs.Environment, resolver contactql.Resolver, c *contactql.Condition) elastic.Query { - var query elastic.Query - +func fieldCondition(env envs.Environment, resolver contactql.Resolver, c *contactql.Condition) elastic.Query { field := resolver.ResolveField(c.PropertyKey()) fieldType := field.Type() - fieldQuery := elastic.NewTermQuery("fields.field", field.UUID()) + fieldQuery := elastic.Term("fields.field", field.UUID()) // special cases for set/unset if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && c.Value() == "" { - query = elastic.NewNestedQuery("fields", elastic.NewBoolQuery().Must( - fieldQuery, - elastic.NewExistsQuery("fields."+string(fieldType)), - )) + query := elastic.Nested("fields", elastic.All(fieldQuery, elastic.Exists("fields."+string(fieldType)))) // if we are looking for unset, inverse our query if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query } @@ -98,77 +92,68 @@ func fieldConditionToElastic(env envs.Environment, resolver contactql.Resolver, switch c.Operator() { case contactql.OpEqual: - query = elastic.NewTermQuery("fields.text", value) - return elastic.NewNestedQuery("fields", elastic.NewBoolQuery().Must(fieldQuery, query)) + return elastic.Nested("fields", elastic.All(fieldQuery, elastic.Term("fields.text", value))) case contactql.OpNotEqual: - query = elastic.NewBoolQuery().Must( - fieldQuery, - elastic.NewTermQuery("fields.text", value), - elastic.NewExistsQuery("fields.text"), - ) - return not(elastic.NewNestedQuery("fields", query)) + query := elastic.All(fieldQuery, elastic.Term("fields.text", value), elastic.Exists("fields.text")) + return elastic.Not(elastic.Nested("fields", query)) default: panic(fmt.Sprintf("unsupported text field operator: %s", c.Operator())) } } else if fieldType == assets.FieldTypeNumber { value, _ := c.ValueAsNumber() + var query elastic.Query switch c.Operator() { case contactql.OpEqual: - query = elastic.NewMatchQuery("fields.number", value) + query = elastic.Match("fields.number", value) case contactql.OpNotEqual: - return not( - elastic.NewNestedQuery("fields", - elastic.NewBoolQuery().Must( - fieldQuery, - elastic.NewMatchQuery("fields.number", value), - ), + return elastic.Not( + elastic.Nested("fields", + elastic.All(fieldQuery, elastic.Match("fields.number", value)), ), ) case contactql.OpGreaterThan: - query = elastic.NewRangeQuery("fields.number").Gt(value) + query = elastic.GreaterThan("fields.number", value) case contactql.OpGreaterThanOrEqual: - query = elastic.NewRangeQuery("fields.number").Gte(value) + query = elastic.GreaterThanOrEqual("fields.number", value) case contactql.OpLessThan: - query = elastic.NewRangeQuery("fields.number").Lt(value) + query = elastic.LessThan("fields.number", value) case contactql.OpLessThanOrEqual: - query = elastic.NewRangeQuery("fields.number").Lte(value) + query = elastic.LessThanOrEqual("fields.number", value) default: panic(fmt.Sprintf("unsupported number field operator: %s", c.Operator())) } - return elastic.NewNestedQuery("fields", elastic.NewBoolQuery().Must(fieldQuery, query)) + return elastic.Nested("fields", elastic.All(fieldQuery, query)) } else if fieldType == assets.FieldTypeDatetime { value, _ := c.ValueAsDate(env) start, end := dates.DayToUTCRange(value, value.Location()) + var query elastic.Query switch c.Operator() { case contactql.OpEqual: - query = elastic.NewRangeQuery("fields.datetime").Gte(start).Lt(end) + query = elastic.Between("fields.datetime", start, end) case contactql.OpNotEqual: - return not( - elastic.NewNestedQuery("fields", - elastic.NewBoolQuery().Must( - fieldQuery, - elastic.NewRangeQuery("fields.datetime").Gte(start).Lt(end), - ), + return elastic.Not( + elastic.Nested("fields", + elastic.All(fieldQuery, elastic.Between("fields.datetime", start, end)), ), ) case contactql.OpGreaterThan: - query = elastic.NewRangeQuery("fields.datetime").Gte(end) + query = elastic.GreaterThanOrEqual("fields.datetime", end) case contactql.OpGreaterThanOrEqual: - query = elastic.NewRangeQuery("fields.datetime").Gte(start) + query = elastic.GreaterThanOrEqual("fields.datetime", start) case contactql.OpLessThan: - query = elastic.NewRangeQuery("fields.datetime").Lt(start) + query = elastic.LessThan("fields.datetime", start) case contactql.OpLessThanOrEqual: - query = elastic.NewRangeQuery("fields.datetime").Lt(end) + query = elastic.LessThan("fields.datetime", end) default: panic(fmt.Sprintf("unsupported datetime field operator: %s", c.Operator())) } - return elastic.NewNestedQuery("fields", elastic.NewBoolQuery().Must(fieldQuery, query)) + return elastic.Nested("fields", elastic.All(fieldQuery, query)) } else if fieldType == assets.FieldTypeState || fieldType == assets.FieldTypeDistrict || fieldType == assets.FieldTypeWard { value := strings.ToLower(c.Value()) @@ -176,15 +161,11 @@ func fieldConditionToElastic(env envs.Environment, resolver contactql.Resolver, switch c.Operator() { case contactql.OpEqual: - query = elastic.NewTermQuery(name, value) - return elastic.NewNestedQuery("fields", elastic.NewBoolQuery().Must(fieldQuery, query)) + return elastic.Nested("fields", elastic.All(fieldQuery, elastic.Term(name, value))) case contactql.OpNotEqual: - return not( - elastic.NewNestedQuery("fields", - elastic.NewBoolQuery().Must( - elastic.NewTermQuery(name, value), - elastic.NewExistsQuery(name), - ), + return elastic.Not( + elastic.Nested("fields", + elastic.All(elastic.Term(name, value), elastic.Exists(name)), ), ) default: @@ -195,22 +176,18 @@ func fieldConditionToElastic(env envs.Environment, resolver contactql.Resolver, panic(fmt.Sprintf("unsupported field type: %s", fieldType)) } -func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, c *contactql.Condition) elastic.Query { +func attributeCondition(env envs.Environment, resolver contactql.Resolver, mapper AssetMapper, c *contactql.Condition) elastic.Query { key := c.PropertyKey() value := strings.ToLower(c.Value()) - var query elastic.Query // special case for set/unset for name and language if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && value == "" && (key == contactql.AttributeName || key == contactql.AttributeLanguage) { - query = elastic.NewBoolQuery().Must( - elastic.NewExistsQuery(key), - not(elastic.NewTermQuery(fmt.Sprintf("%s.keyword", key), "")), - ) + query := elastic.All(elastic.Exists(key), elastic.Not(elastic.Term(fmt.Sprintf("%s.keyword", key), ""))) if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query @@ -222,20 +199,20 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv case contactql.AttributeID: switch c.Operator() { case contactql.OpEqual: - return elastic.NewIdsQuery().Ids(value) + return elastic.Ids(value) case contactql.OpNotEqual: - return not(elastic.NewIdsQuery().Ids(value)) + return elastic.Not(elastic.Ids(value)) default: panic(fmt.Sprintf("unsupported ID attribute operator: %s", c.Operator())) } case contactql.AttributeName: switch c.Operator() { case contactql.OpEqual: - return elastic.NewTermQuery("name.keyword", c.Value()) + return elastic.Term("name.keyword", c.Value()) case contactql.OpNotEqual: - return not(elastic.NewTermQuery("name.keyword", c.Value())) + return elastic.Not(elastic.Term("name.keyword", c.Value())) case contactql.OpContains: - return elastic.NewMatchQuery("name", value) + return elastic.Match("name", value) default: panic(fmt.Sprintf("unsupported name attribute operator: %s", c.Operator())) } @@ -251,26 +228,26 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv switch c.Operator() { case contactql.OpEqual: - return elastic.NewRangeQuery("created_on").Gte(start).Lt(end) + return elastic.Between("created_on", start, end) case contactql.OpNotEqual: - return not(elastic.NewRangeQuery("created_on").Gte(start).Lt(end)) + return elastic.Not(elastic.Between("created_on", start, end)) case contactql.OpGreaterThan: - return elastic.NewRangeQuery("created_on").Gte(end) + return elastic.GreaterThanOrEqual("created_on", end) case contactql.OpGreaterThanOrEqual: - return elastic.NewRangeQuery("created_on").Gte(start) + return elastic.GreaterThanOrEqual("created_on", start) case contactql.OpLessThan: - return elastic.NewRangeQuery("created_on").Lt(start) + return elastic.LessThan("created_on", start) case contactql.OpLessThanOrEqual: - return elastic.NewRangeQuery("created_on").Lt(end) + return elastic.LessThan("created_on", end) default: panic(fmt.Sprintf("unsupported created_on attribute operator: %s", c.Operator())) } case contactql.AttributeLastSeenOn: // special case for set/unset if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && value == "" { - query = elastic.NewExistsQuery("last_seen_on") + query := elastic.Exists("last_seen_on") if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query } @@ -280,17 +257,17 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv switch c.Operator() { case contactql.OpEqual: - return elastic.NewRangeQuery("last_seen_on").Gte(start).Lt(end) + return elastic.Between("last_seen_on", start, end) case contactql.OpNotEqual: - return not(elastic.NewRangeQuery("last_seen_on").Gte(start).Lt(end)) + return elastic.Not(elastic.Between("last_seen_on", start, end)) case contactql.OpGreaterThan: - return elastic.NewRangeQuery("last_seen_on").Gte(end) + return elastic.GreaterThanOrEqual("last_seen_on", end) case contactql.OpGreaterThanOrEqual: - return elastic.NewRangeQuery("last_seen_on").Gte(start) + return elastic.GreaterThanOrEqual("last_seen_on", start) case contactql.OpLessThan: - return elastic.NewRangeQuery("last_seen_on").Lt(start) + return elastic.LessThan("last_seen_on", start) case contactql.OpLessThanOrEqual: - return elastic.NewRangeQuery("last_seen_on").Lt(end) + return elastic.LessThan("last_seen_on", end) default: panic(fmt.Sprintf("unsupported last_seen_on attribute operator: %s", c.Operator())) } @@ -299,29 +276,29 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv // special case for set/unset if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && value == "" { - query = elastic.NewNestedQuery("urns", elastic.NewExistsQuery("urns.path")) + query := elastic.Nested("urns", elastic.Exists("urns.path")) if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query } switch c.Operator() { case contactql.OpEqual: - return elastic.NewNestedQuery("urns", elastic.NewTermQuery("urns.path.keyword", value)) + return elastic.Nested("urns", elastic.Term("urns.path.keyword", value)) case contactql.OpNotEqual: - return not(elastic.NewNestedQuery("urns", elastic.NewTermQuery("urns.path.keyword", value))) + return elastic.Not(elastic.Nested("urns", elastic.Term("urns.path.keyword", value))) case contactql.OpContains: - return elastic.NewNestedQuery("urns", elastic.NewMatchPhraseQuery("urns.path", value)) + return elastic.Nested("urns", elastic.MatchPhrase("urns.path", value)) default: panic(fmt.Sprintf("unsupported URN attribute operator: %s", c.Operator())) } case contactql.AttributeGroup: // special case for set/unset if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && value == "" { - query = elastic.NewExistsQuery("group_ids") + query := elastic.Exists("group_ids") if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query } @@ -330,9 +307,9 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv switch c.Operator() { case contactql.OpEqual: - return elastic.NewTermQuery("group_ids", mapper.Group(group)) + return elastic.Term("group_ids", mapper.Group(group)) case contactql.OpNotEqual: - return not(elastic.NewTermQuery("group_ids", mapper.Group(group))) + return elastic.Not(elastic.Term("group_ids", mapper.Group(group))) default: panic(fmt.Sprintf("unsupported group attribute operator: %s", c.Operator())) } @@ -344,9 +321,9 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv // special case for set/unset if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && value == "" { - query = elastic.NewExistsQuery(fieldName) + query := elastic.Exists(fieldName) if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query } @@ -355,9 +332,9 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv switch c.Operator() { case contactql.OpEqual: - return elastic.NewTermQuery(fieldName, mapper.Flow(flow)) + return elastic.Term(fieldName, mapper.Flow(flow)) case contactql.OpNotEqual: - return not(elastic.NewTermQuery(fieldName, mapper.Flow(flow))) + return elastic.Not(elastic.Term(fieldName, mapper.Flow(flow))) default: panic(fmt.Sprintf("unsupported flow attribute operator: %s", c.Operator())) } @@ -368,39 +345,26 @@ func attributeConditionToElastic(env envs.Environment, resolver contactql.Resolv } } -func schemeConditionToElastic(env envs.Environment, c *contactql.Condition) elastic.Query { +func schemeCondition(c *contactql.Condition) elastic.Query { key := c.PropertyKey() value := strings.ToLower(c.Value()) // special case for set/unset if (c.Operator() == contactql.OpEqual || c.Operator() == contactql.OpNotEqual) && value == "" { - var query elastic.Query - query = elastic.NewNestedQuery("urns", elastic.NewBoolQuery().Must( - elastic.NewTermQuery("urns.scheme", key), - elastic.NewExistsQuery("urns.path"), - )) + query := elastic.Nested("urns", elastic.All(elastic.Term("urns.scheme", key), elastic.Exists("urns.path"))) if c.Operator() == contactql.OpEqual { - query = not(query) + query = elastic.Not(query) } return query } switch c.Operator() { case contactql.OpEqual: - return elastic.NewNestedQuery("urns", elastic.NewBoolQuery().Must( - elastic.NewTermQuery("urns.path.keyword", value), - elastic.NewTermQuery("urns.scheme", key)), - ) + return elastic.Nested("urns", elastic.All(elastic.Term("urns.path.keyword", value), elastic.Term("urns.scheme", key))) case contactql.OpNotEqual: - return not(elastic.NewNestedQuery("urns", elastic.NewBoolQuery().Must( - elastic.NewTermQuery("urns.path.keyword", value), - elastic.NewTermQuery("urns.scheme", key)), - )) + return elastic.Not(elastic.Nested("urns", elastic.All(elastic.Term("urns.path.keyword", value), elastic.Term("urns.scheme", key)))) case contactql.OpContains: - return elastic.NewNestedQuery("urns", elastic.NewBoolQuery().Must( - elastic.NewMatchPhraseQuery("urns.path", value), - elastic.NewTermQuery("urns.scheme", key)), - ) + return elastic.Nested("urns", elastic.All(elastic.MatchPhrase("urns.path", value), elastic.Term("urns.scheme", key))) default: panic(fmt.Sprintf("unsupported scheme operator: %s", c.Operator())) } @@ -411,9 +375,9 @@ func textAttributeQuery(c *contactql.Condition, name string, tx func(string) str switch c.Operator() { case contactql.OpEqual: - return elastic.NewTermQuery(name, value) + return elastic.Term(name, value) case contactql.OpNotEqual: - return not(elastic.NewTermQuery(name, value)) + return elastic.Not(elastic.Term(name, value)) default: panic(fmt.Sprintf("unsupported %s attribute operator: %s", name, c.Operator())) } @@ -424,23 +388,18 @@ func numericalAttributeQuery(c *contactql.Condition, name string) elastic.Query switch c.Operator() { case contactql.OpEqual: - return elastic.NewMatchQuery(name, value) + return elastic.Match(name, value) case contactql.OpNotEqual: - return not(elastic.NewMatchQuery(name, value)) + return elastic.Not(elastic.Match(name, value)) case contactql.OpGreaterThan: - return elastic.NewRangeQuery(name).Gt(value) + return elastic.GreaterThan(name, value) case contactql.OpGreaterThanOrEqual: - return elastic.NewRangeQuery(name).Gte(value) + return elastic.GreaterThanOrEqual(name, value) case contactql.OpLessThan: - return elastic.NewRangeQuery(name).Lt(value) + return elastic.LessThan(name, value) case contactql.OpLessThanOrEqual: - return elastic.NewRangeQuery(name).Lte(value) + return elastic.LessThanOrEqual(name, value) default: panic(fmt.Sprintf("unsupported %s attribute operator: %s", name, c.Operator())) } } - -// convenience utility to create a not boolean query -func not(queries ...elastic.Query) *elastic.BoolQuery { - return elastic.NewBoolQuery().MustNot(queries...) -} diff --git a/contactql/es/query_test.go b/contactql/es/query_test.go index 3e8bbc408..eec2b9d88 100644 --- a/contactql/es/query_test.go +++ b/contactql/es/query_test.go @@ -96,10 +96,7 @@ func TestElasticQuery(t *testing.T) { query := es.ToElasticQuery(env, mapper, parsed) assert.NotNil(t, query, tc.Description) - source, err := query.Source() - require.NoError(t, err, "error requesting source for elastic query in ", testName) - - asJSON, err := jsonx.Marshal(source) + asJSON, err := jsonx.Marshal(query) require.NoError(t, err) test.AssertEqualJSON(t, tc.Elastic, asJSON, "elastic mismatch in %s", testName) diff --git a/contactql/es/sort.go b/contactql/es/sort.go index 33e24c581..02ecaa544 100644 --- a/contactql/es/sort.go +++ b/contactql/es/sort.go @@ -6,16 +6,15 @@ import ( "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/contactql" - - "github.com/olivere/elastic/v7" + "github.com/nyaruka/goflow/utils/elastic" "github.com/pkg/errors" ) // ToElasticFieldSort returns the elastic FieldSort for the passed in sort by string -func ToElasticFieldSort(sortBy string, resolver contactql.Resolver) (*elastic.FieldSort, error) { +func ToElasticFieldSort(sortBy string, resolver contactql.Resolver) (elastic.Sort, error) { // default to most recent first by id if sortBy == "" { - return elastic.NewFieldSort("id").Desc(), nil + return elastic.SortBy("id", false), nil } // figure out if we are ascending or descending (default is ascending, can be changed with leading -) @@ -30,12 +29,12 @@ func ToElasticFieldSort(sortBy string, resolver contactql.Resolver) (*elastic.Fi // name needs to be sorted by keyword field if property == contactql.AttributeName { - return elastic.NewFieldSort("name.keyword").Order(ascending), nil + return elastic.SortBy("name.keyword", ascending), nil } // other attributes are straight sorts if property == contactql.AttributeID || property == contactql.AttributeCreatedOn || property == contactql.AttributeLastSeenOn || property == contactql.AttributeLanguage { - return elastic.NewFieldSort(property).Order(ascending), nil + return elastic.SortBy(property, ascending), nil } // we are sorting by a custom field @@ -52,8 +51,5 @@ func ToElasticFieldSort(sortBy string, resolver contactql.Resolver) (*elastic.Fi key = fmt.Sprintf("fields.%s", field.Type()) } - sort := elastic.NewFieldSort(key) - sort = sort.Nested(elastic.NewNestedSort("fields").Filter(elastic.NewTermQuery("fields.field", field.UUID()))) - sort = sort.Order(ascending) - return sort, nil + return elastic.SortNested(key, elastic.Term("fields.field", field.UUID()), "fields", ascending), nil } diff --git a/contactql/es/sort_test.go b/contactql/es/sort_test.go index 98e0fc152..58de6b1da 100644 --- a/contactql/es/sort_test.go +++ b/contactql/es/sort_test.go @@ -35,8 +35,7 @@ func TestElasticSort(t *testing.T) { if tc.Error != "" { assert.EqualError(t, err, tc.Error) } else { - src, _ := sort.Source() - encoded := jsonx.MustMarshal(src) + encoded := jsonx.MustMarshal(sort) test.AssertEqualJSON(t, []byte(tc.Elastic), encoded, "field sort mismatch for %s", tc.Description) } } diff --git a/go.mod b/go.mod index 6ca43b76f..58d4ca26b 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/buger/jsonparser v1.1.1 github.com/go-playground/validator/v10 v10.20.0 github.com/nyaruka/gocommon v1.54.9 - github.com/olivere/elastic/v7 v7.0.32 github.com/pkg/errors v0.9.1 github.com/sergi/go-diff v1.3.1 github.com/shopspring/decimal v1.4.0 @@ -28,9 +27,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.3.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 879a554d6..714627e5d 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= @@ -33,8 +31,6 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -44,16 +40,12 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/nyaruka/gocommon v1.54.9 h1:BvWgXQc7h9Qlhj347N56wPZiik64SGmv+qSLmar4pVo= github.com/nyaruka/gocommon v1.54.9/go.mod h1:rWkEIpYIK98zL9Qm6PeMXJ+84WcWlArf01RfuWWCYvQ= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/phonenumbers v1.3.5 h1:WZLbQn61j2E1OFnvpUTYbK/6hViUgl6tppJ55/E2iQM= github.com/nyaruka/phonenumbers v1.3.5/go.mod h1:Ut+eFwikULbmCenH6InMKL9csUNLyxHuBLyfkpum11s= -github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= -github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/utils/elastic/query.go b/utils/elastic/query.go new file mode 100644 index 000000000..dca9048c3 --- /dev/null +++ b/utils/elastic/query.go @@ -0,0 +1,98 @@ +package elastic + +type Query map[string]any + +// Any is a shortcut for a bool query with a should clause +func Any(queries ...Query) Query { + return Query{"bool": Query{"should": queries}} +} + +// All is a shortcut for a bool query with a must clause +func All(queries ...Query) Query { + return Query{"bool": Query{"must": queries}} +} + +// Not is a shortcut for a bool query with a must_not clause +func Not(query Query) Query { + return Query{"bool": Query{"must_not": query}} +} + +// Not is a shortcut for an ids query +func Ids(values ...string) Query { + return Query{"ids": Query{"values": values}} +} + +// Term is a shortcut for a term query +func Term(field string, value any) Query { + return Query{"term": Query{field: value}} +} + +// Exists is a shortcut for an exists query +func Exists(field string) Query { + return Query{"exists": Query{"field": field}} +} + +// Nested is a shortcut for a nested query +func Nested(path string, query Query) Query { + return Query{"nested": Query{"path": path, "query": query}} +} + +// Match is a shortcut for a match query +func Match(field string, value any) Query { + return Query{"match": Query{field: Query{"query": value}}} +} + +// MatchPhrase is a shortcut for a match_phrase query +func MatchPhrase(field, value string) Query { + return Query{"match_phrase": Query{field: Query{"query": value}}} +} + +// GreaterThan is a shortcut for a range query where x > value +func GreaterThan(field string, value any) Query { + return Query{"range": Query{field: Query{ + "from": value, + "include_lower": false, + "include_upper": true, + "to": nil, + }}} +} + +// GreaterThanOrEqual is a shortcut for a range query where x >= value +func GreaterThanOrEqual(field string, value any) Query { + return Query{"range": Query{field: Query{ + "from": value, + "include_lower": true, + "include_upper": true, + "to": nil, + }}} +} + +// LessThan is a shortcut for a range query where x < value +func LessThan(field string, value any) Query { + return Query{"range": Query{field: Query{ + "from": nil, + "include_lower": true, + "include_upper": false, + "to": value, + }}} +} + +// LessThanOrEqual is a shortcut for a range query where x <= value +func LessThanOrEqual(field string, value any) Query { + return Query{"range": Query{field: Query{ + "from": nil, + "include_lower": true, + "include_upper": true, + "to": value, + }}} +} + +// Between is a shortcut for a range query where from <= x < to +func Between(field string, from, to any) Query { + return Query{"range": Query{field: Query{ + "from": from, + "include_lower": true, + "include_upper": false, + "to": to, + }}} +} diff --git a/utils/elastic/sort.go b/utils/elastic/sort.go new file mode 100644 index 000000000..c4c04f225 --- /dev/null +++ b/utils/elastic/sort.go @@ -0,0 +1,23 @@ +package elastic + +type Sort map[string]any + +// SortBy is a shortcut for a simple field sort +func SortBy(field string, ascending bool) Sort { + return Sort{field: map[string]any{"order": order(ascending)}} +} + +// SortNested is a shortcut for a nested field sort +func SortNested(field string, filter Query, path string, ascending bool) Sort { + return Sort{field: map[string]any{ + "nested": map[string]any{"filter": filter, "path": path}, + "order": order(ascending), + }} +} + +func order(asc bool) string { + if asc { + return "asc" + } + return "desc" +}