enhancement(search): implement os dsl range field
This commit is contained in:
@@ -11,6 +11,7 @@ type TableTest[G any, W any] struct {
|
||||
Name string
|
||||
Got G
|
||||
Want W
|
||||
Err error
|
||||
}
|
||||
|
||||
func ToJSON(t *testing.T, data any) string {
|
||||
|
||||
@@ -43,6 +43,24 @@ func (f BuilderFunc) String() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func applyValue[T any](target map[string]any, key string, v T) {
|
||||
if target == nil || isEmpty(key) || isEmpty(v) {
|
||||
return
|
||||
}
|
||||
|
||||
target[key] = v
|
||||
}
|
||||
|
||||
func applyValues[T any](target map[string]any, values map[string]T) {
|
||||
if target == nil || isEmpty(values) {
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range values {
|
||||
applyValue[T](target, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func applyBuilder(target map[string]any, key string, builder Builder) error {
|
||||
if target == nil || isEmpty(key) || isEmpty(builder) {
|
||||
return nil
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestBoolQuery(t *testing.T) {
|
||||
Want: nil,
|
||||
},
|
||||
{
|
||||
Name: "naked",
|
||||
Name: "with-options",
|
||||
Got: opensearch.NewBoolQuery(opensearch.BoolQueryOptions{
|
||||
MinimumShouldMatch: 10,
|
||||
Boost: 10,
|
||||
|
||||
@@ -31,9 +31,7 @@ func (q *MatchPhraseQuery) Map() (map[string]any, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEmpty(q.query) {
|
||||
data["query"] = q.query
|
||||
}
|
||||
applyValue(data, "query", q.query)
|
||||
|
||||
if isEmpty(data) {
|
||||
return nil, nil
|
||||
|
||||
@@ -24,9 +24,7 @@ func (q *IDsQuery) Map() (map[string]any, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEmpty(q.values) {
|
||||
data["values"] = q.values
|
||||
}
|
||||
applyValue(data, "values", q.values)
|
||||
|
||||
if isEmpty(data) {
|
||||
return nil, nil
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package opensearch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RangeQuery[T time.Time | string] struct {
|
||||
field string
|
||||
gt T
|
||||
gte T
|
||||
lt T
|
||||
lte T
|
||||
options RangeQueryOptions
|
||||
}
|
||||
|
||||
type RangeQueryOptions struct {
|
||||
Format string `json:"format,omitempty"`
|
||||
Relation string `json:"relation,omitempty"`
|
||||
Boost float32 `json:"boost,omitempty"`
|
||||
TimeZone string `json:"time_zone,omitempty"`
|
||||
}
|
||||
|
||||
func NewRangeQuery[T time.Time | string](field string, o ...RangeQueryOptions) *RangeQuery[T] {
|
||||
return &RangeQuery[T]{field: field, options: merge(o...)}
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) Gt(v T) *RangeQuery[T] {
|
||||
q.gt = v
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) Gte(v T) *RangeQuery[T] {
|
||||
q.gte = v
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) Lt(v T) *RangeQuery[T] {
|
||||
q.lt = v
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) Lte(v T) *RangeQuery[T] {
|
||||
q.lte = v
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) Map() (map[string]any, error) {
|
||||
data, err := convert[map[string]any](q.options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEmpty(q.gt) && !isEmpty(q.gte) {
|
||||
return nil, errors.New("cannot set both gt and gte in RangeQuery")
|
||||
}
|
||||
|
||||
if !isEmpty(q.lt) && !isEmpty(q.lte) {
|
||||
return nil, errors.New("cannot set both lt and lte in RangeQuery")
|
||||
}
|
||||
|
||||
applyValues(data, map[string]T{
|
||||
"gt": q.gt,
|
||||
"gte": q.gte,
|
||||
"lt": q.lt,
|
||||
"lte": q.lte,
|
||||
})
|
||||
|
||||
if isEmpty(data) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"range": map[string]any{
|
||||
q.field: data,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) MarshalJSON() ([]byte, error) {
|
||||
data, err := q.Map()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
func (q *RangeQuery[T]) String() string {
|
||||
b, _ := q.MarshalJSON()
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package opensearch_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/opensearch"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/opensearch/internal/test"
|
||||
)
|
||||
|
||||
func TestRangeQuery(t *testing.T) {
|
||||
now := time.Now()
|
||||
tests := []opensearchtest.TableTest[opensearch.Builder, map[string]any]{
|
||||
{
|
||||
Name: "empty",
|
||||
Got: opensearch.NewRangeQuery[string]("empty"),
|
||||
Want: nil,
|
||||
},
|
||||
{
|
||||
Name: "gt string",
|
||||
Got: opensearch.NewRangeQuery[string]("created").Gt("2023-01-01T00:00:00Z"),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"gt": "2023-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gt time",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created").Gt(now),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"gt": now,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gte string",
|
||||
Got: opensearch.NewRangeQuery[string]("created").Gte("2023-01-01T00:00:00Z"),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"gte": "2023-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gte time",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created").Gte(now),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"gte": now,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "gt & gte",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created").Gt(now).Gte(now),
|
||||
Want: nil,
|
||||
Err: errors.New(""),
|
||||
},
|
||||
{
|
||||
Name: "gt string",
|
||||
Got: opensearch.NewRangeQuery[string]("created").Lt("2023-01-01T00:00:00Z"),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"lt": "2023-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lt time",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created").Lt(now),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"lt": now,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lte string",
|
||||
Got: opensearch.NewRangeQuery[string]("created").Lte("2023-01-01T00:00:00Z"),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"lte": "2023-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lte time",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created").Lte(now),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"lte": now,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lt & lte",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created").Lt(now).Lte(now),
|
||||
Want: nil,
|
||||
Err: errors.New(""),
|
||||
},
|
||||
{
|
||||
Name: "options",
|
||||
Got: opensearch.NewRangeQuery[time.Time]("created", opensearch.RangeQueryOptions{
|
||||
Format: "strict_date_optional_time",
|
||||
Relation: "within",
|
||||
Boost: 1.0,
|
||||
TimeZone: "UTC",
|
||||
}).Lte(now).Gte(now),
|
||||
Want: map[string]any{
|
||||
"range": map[string]any{
|
||||
"created": map[string]any{
|
||||
"lte": now,
|
||||
"gte": now,
|
||||
"format": "strict_date_optional_time",
|
||||
"relation": "within",
|
||||
"boost": 1.0,
|
||||
"time_zone": "UTC",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
got, err := test.Got.MarshalJSON()
|
||||
switch {
|
||||
case test.Err != nil && test.Err.Error() == "": // Expecting any error
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, got)
|
||||
return
|
||||
case test.Err != nil && test.Err.Error() != "": // Expecting a specific error
|
||||
assert.ErrorIs(t, test.Err, err)
|
||||
assert.Nil(t, got)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, opensearchtest.JSONMustMarshal(t, test.Want), string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,7 @@ func (q *TermQuery[T]) Map() (map[string]any, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEmpty(q.value) {
|
||||
data["value"] = q.value
|
||||
}
|
||||
applyValue(data, "value", q.value)
|
||||
|
||||
if isEmpty(data) {
|
||||
return nil, nil
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestTermQuery(t *testing.T) {
|
||||
Want: nil,
|
||||
},
|
||||
{
|
||||
Name: "naked",
|
||||
Name: "op-options",
|
||||
Got: opensearch.NewTermQuery[bool]("deleted").Value(false),
|
||||
Want: map[string]any{
|
||||
"term": map[string]any{
|
||||
@@ -28,7 +28,7 @@ func TestTermQuery(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "term",
|
||||
Name: "with-options",
|
||||
Got: opensearch.NewTermQuery[bool]("deleted", opensearch.TermQueryOptions{
|
||||
Boost: 1.0,
|
||||
CaseInsensitive: true,
|
||||
|
||||
@@ -31,9 +31,7 @@ func (q *WildcardQuery) Map() (map[string]any, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEmpty(q.value) {
|
||||
data["value"] = q.value
|
||||
}
|
||||
applyValue(data, "value", q.value)
|
||||
|
||||
if isEmpty(data) {
|
||||
return nil, nil
|
||||
|
||||
Reference in New Issue
Block a user