From 85f50eb9d5c5e882eb7b26b18295f3ae50e2f50d Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 17 Feb 2023 16:59:05 +0100 Subject: [PATCH] fix(storage,sqlite): in operator sqlite translation --- pkg/storage/filter/sql/operator.go | 15 +++++++ pkg/storage/filter/sql/option.go | 6 +++ pkg/storage/filter/sql/sql.go | 24 +++++------ pkg/storage/filter/sql/transform.go | 2 +- pkg/storage/sqlite/document_store.go | 1 + pkg/storage/sqlite/filter.go | 24 +++++++++++ pkg/storage/testsuite/document_store_query.go | 43 +++++++++++++++++++ 7 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 pkg/storage/filter/sql/operator.go create mode 100644 pkg/storage/sqlite/filter.go diff --git a/pkg/storage/filter/sql/operator.go b/pkg/storage/filter/sql/operator.go new file mode 100644 index 0000000..37e0a04 --- /dev/null +++ b/pkg/storage/filter/sql/operator.go @@ -0,0 +1,15 @@ +package sql + +const ( + OpIn = "IN" + OpLesserThan = "<" + OpLesserThanEqual = "<=" + OpEqual = "=" + OpNotEqual = "!=" + OpSuperiorThan = ">" + OpSuperiorThanEqual = ">=" + OpAnd = "AND" + OpOr = "OR" + OpLike = "LIKE" + OpNot = "NOT" +) diff --git a/pkg/storage/filter/sql/option.go b/pkg/storage/filter/sql/option.go index a115335..c7eb1ae 100644 --- a/pkg/storage/filter/sql/option.go +++ b/pkg/storage/filter/sql/option.go @@ -71,6 +71,12 @@ func WithDefaultTransform() OptionFunc { } } +func WithTransform(transform TransformFunc) OptionFunc { + return func(opt *Option) { + opt.Transform = transform + } +} + func WithNoOpValueTransform() OptionFunc { return WithValueTransform(func(value interface{}) interface{} { return value diff --git a/pkg/storage/filter/sql/sql.go b/pkg/storage/filter/sql/sql.go index 34b3e8b..19795c6 100644 --- a/pkg/storage/filter/sql/sql.go +++ b/pkg/storage/filter/sql/sql.go @@ -60,7 +60,7 @@ func transformAndOperator(op filter.Operator, option *Option) (string, []interfa return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenAnd, op.Token()) } - return aggregatorToSQL("AND", option, andOp.Children()...) + return aggregatorToSQL(OpAnd, option, andOp.Children()...) } func transformOrOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -69,7 +69,7 @@ func transformOrOperator(op filter.Operator, option *Option) (string, []interfac return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenOr, op.Token()) } - return aggregatorToSQL("OR", option, orOp.Children()...) + return aggregatorToSQL(OpOr, option, orOp.Children()...) } func transformEqOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -78,7 +78,7 @@ func transformEqOperator(op filter.Operator, option *Option) (string, []interfac return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenEq, op.Token()) } - return fieldsToSQL("=", false, eqOp.Fields(), option) + return fieldsToSQL(OpEqual, false, eqOp.Fields(), option) } func transformNeqOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -87,7 +87,7 @@ func transformNeqOperator(op filter.Operator, option *Option) (string, []interfa return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenNeq, op.Token()) } - return fieldsToSQL("!=", false, eqOp.Fields(), option) + return fieldsToSQL(OpNotEqual, false, eqOp.Fields(), option) } func transformGtOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -96,7 +96,7 @@ func transformGtOperator(op filter.Operator, option *Option) (string, []interfac return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenGt, op.Token()) } - return fieldsToSQL(">", false, gtOp.Fields(), option) + return fieldsToSQL(OpSuperiorThan, false, gtOp.Fields(), option) } func transformGteOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -105,7 +105,7 @@ func transformGteOperator(op filter.Operator, option *Option) (string, []interfa return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenGte, op.Token()) } - return fieldsToSQL(">=", false, gteOp.Fields(), option) + return fieldsToSQL(OpSuperiorThanEqual, false, gteOp.Fields(), option) } func transformLtOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -114,7 +114,7 @@ func transformLtOperator(op filter.Operator, option *Option) (string, []interfac return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenLt, op.Token()) } - return fieldsToSQL("<", false, ltOp.Fields(), option) + return fieldsToSQL(OpLesserThan, false, ltOp.Fields(), option) } func transformLteOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -123,7 +123,7 @@ func transformLteOperator(op filter.Operator, option *Option) (string, []interfa return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenLte, op.Token()) } - return fieldsToSQL("<=", false, lteOp.Fields(), option) + return fieldsToSQL(OpLesserThanEqual, false, lteOp.Fields(), option) } func transformInOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -132,7 +132,7 @@ func transformInOperator(op filter.Operator, option *Option) (string, []interfac return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenIn, op.Token()) } - return fieldsToSQL("IN", true, inOp.Fields(), option) + return fieldsToSQL(OpIn, true, inOp.Fields(), option) } func transformLikeOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -141,7 +141,7 @@ func transformLikeOperator(op filter.Operator, option *Option) (string, []interf return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenLike, op.Token()) } - return fieldsToSQL("LIKE", false, likeOp.Fields(), option) + return fieldsToSQL(OpLike, false, likeOp.Fields(), option) } func transformNotOperator(op filter.Operator, option *Option) (string, []interface{}, error) { @@ -150,10 +150,10 @@ func transformNotOperator(op filter.Operator, option *Option) (string, []interfa return "", nil, errors.Wrapf(filter.ErrUnexpectedOperator, "expected '%s', got '%s'", filter.TokenNot, op.Token()) } - sql, args, err := aggregatorToSQL("AND", option, notOp.Children()...) + sql, args, err := aggregatorToSQL(OpAnd, option, notOp.Children()...) if err != nil { return "", nil, errors.WithStack(err) } - return "NOT " + sql, args, nil + return OpNot + " " + sql, args, nil } diff --git a/pkg/storage/filter/sql/transform.go b/pkg/storage/filter/sql/transform.go index 10d174d..f31dd72 100644 --- a/pkg/storage/filter/sql/transform.go +++ b/pkg/storage/filter/sql/transform.go @@ -32,7 +32,7 @@ func DefaultTransform(operator string, invert bool, key string, value interface{ return "", nil, errors.WithStack(err) } - if _, err := sb.WriteString(key); err != nil { + if _, err := sb.WriteString(option.KeyTransform(key)); err != nil { return "", nil, errors.WithStack(err) } } else { diff --git a/pkg/storage/sqlite/document_store.go b/pkg/storage/sqlite/document_store.go index 69f76c4..ac28e6e 100644 --- a/pkg/storage/sqlite/document_store.go +++ b/pkg/storage/sqlite/document_store.go @@ -102,6 +102,7 @@ func (s *DocumentStore) Query(ctx context.Context, collection string, filter *fi criteria, args, err = filterSQL.ToSQL( filter.Root(), filterSQL.WithPreparedParameter("$", 2), + filterSQL.WithTransform(transformOperator), filterSQL.WithKeyTransform(func(key string) string { return fmt.Sprintf("json_extract(data, '$.%s')", key) }), diff --git a/pkg/storage/sqlite/filter.go b/pkg/storage/sqlite/filter.go new file mode 100644 index 0000000..25524e8 --- /dev/null +++ b/pkg/storage/sqlite/filter.go @@ -0,0 +1,24 @@ +package sqlite + +import ( + "fmt" + + "forge.cadoles.com/arcad/edge/pkg/storage/filter/sql" +) + +func transformOperator(operator string, invert bool, key string, value any, option *sql.Option) (string, any, error) { + switch operator { + case sql.OpIn: + return transformInOperator(key, value, option) + default: + return sql.DefaultTransform(operator, invert, key, value, option) + } +} + +func transformInOperator(key string, value any, option *sql.Option) (string, any, error) { + return fmt.Sprintf( + "EXISTS (SELECT 1 FROM json_each(json_extract(data, \"$.%v\")) WHERE value = %v)", + key, + option.PreparedParameter(), + ), option.ValueTransform(value), nil +} diff --git a/pkg/storage/testsuite/document_store_query.go b/pkg/storage/testsuite/document_store_query.go index c5ed901..61a3fce 100644 --- a/pkg/storage/testsuite/document_store_query.go +++ b/pkg/storage/testsuite/document_store_query.go @@ -60,6 +60,49 @@ var documentStoreQueryTestCases = []documentStoreQueryTestCase{ } }, }, + { + Name: "IN Operator", + Before: func(ctx context.Context, store storage.DocumentStore) error { + docs := []storage.Document{ + { + "counter": 1, + "tags": []string{"foo", "bar"}, + }, + { + "counter": 1, + "tags": []string{"nope"}, + }, + } + + for _, doc := range docs { + if _, err := store.Upsert(ctx, "in_operator", doc); err != nil { + return errors.WithStack(err) + } + } + + return nil + }, + Collection: "in_operator", + Filter: filter.New( + filter.NewAndOperator( + filter.NewEqOperator(map[string]any{ + "counter": 1, + }), + filter.NewInOperator(map[string]any{ + "tags": "foo", + }), + ), + ), + After: func(t *testing.T, results []storage.Document, err error) { + if err != nil { + t.Fatalf("%+v", errors.WithStack(err)) + } + + if e, g := 1, len(results); e != g { + t.Errorf("len(results): expected '%v', got '%v'", e, g) + } + }, + }, } func testDocumentStoreQuery(t *testing.T, store storage.DocumentStore) {