Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
85f50eb9d5 | |||
673586c2f7 | |||
4606d7a08d |
@ -12,26 +12,26 @@ import (
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
UUID string `goja:"uuid"`
|
||||
Host net.IP `goja:"host"`
|
||||
Port int `goja:"port"`
|
||||
Name string `goja:"name"`
|
||||
UUID string `goja:"uuid" json:"uuid"`
|
||||
Host net.IP `goja:"host" json:"host"`
|
||||
Port int `goja:"port" json:"port"`
|
||||
Name string `goja:"name" json:"name"`
|
||||
}
|
||||
|
||||
type DeviceStatus struct {
|
||||
CurrentApp DeviceStatusCurrentApp `goja:"currentApp"`
|
||||
Volume DeviceStatusVolume `goja:"volume"`
|
||||
CurrentApp DeviceStatusCurrentApp `goja:"currentApp" json:"currentApp"`
|
||||
Volume DeviceStatusVolume `goja:"volume" json:"volume"`
|
||||
}
|
||||
|
||||
type DeviceStatusCurrentApp struct {
|
||||
ID string `goja:"id"`
|
||||
DisplayName string `goja:"displayName"`
|
||||
StatusText string `goja:"statusText"`
|
||||
ID string `goja:"id" json:"id"`
|
||||
DisplayName string `goja:"displayName" json:"displayName"`
|
||||
StatusText string `goja:"statusText" json:"statusText"`
|
||||
}
|
||||
|
||||
type DeviceStatusVolume struct {
|
||||
Level float64 `goja:"level"`
|
||||
Muted bool `goja:"muted"`
|
||||
Level float64 `goja:"level" json:"level"`
|
||||
Muted bool `goja:"muted" json:"muted"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -91,22 +91,24 @@ func (m *StoreModule) query(call goja.FunctionCall, rt *goja.Runtime) goja.Value
|
||||
|
||||
queryOptionsFuncs := make([]storage.QueryOptionFunc, 0)
|
||||
|
||||
if queryOptions.Limit != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithLimit(*queryOptions.Limit))
|
||||
}
|
||||
if queryOptions != nil {
|
||||
if queryOptions.Limit != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithLimit(*queryOptions.Limit))
|
||||
}
|
||||
|
||||
if queryOptions.OrderBy != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOrderBy(*queryOptions.OrderBy))
|
||||
}
|
||||
if queryOptions.OrderBy != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOrderBy(*queryOptions.OrderBy))
|
||||
}
|
||||
|
||||
if queryOptions.Offset != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOffset(*queryOptions.Limit))
|
||||
}
|
||||
if queryOptions.Offset != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOffset(*queryOptions.Limit))
|
||||
}
|
||||
|
||||
if queryOptions.OrderDirection != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOrderDirection(
|
||||
storage.OrderDirection(*queryOptions.OrderDirection),
|
||||
))
|
||||
if queryOptions.OrderDirection != nil {
|
||||
queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOrderDirection(
|
||||
storage.OrderDirection(*queryOptions.OrderDirection),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
documents, err := m.store.Query(ctx, collection, filter, queryOptionsFuncs...)
|
||||
@ -144,6 +146,10 @@ func (m *StoreModule) assertCollection(value goja.Value, rt *goja.Runtime) strin
|
||||
}
|
||||
|
||||
func (m *StoreModule) assertFilter(value goja.Value, rt *goja.Runtime) *filter.Filter {
|
||||
if value.Export() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawFilter, ok := value.Export().(map[string]interface{})
|
||||
if !ok {
|
||||
panic(rt.NewTypeError(fmt.Sprintf("filter must be an object, got '%T'", value.Export())))
|
||||
@ -172,6 +178,10 @@ func (m *StoreModule) assertDocumentID(value goja.Value, rt *goja.Runtime) stora
|
||||
}
|
||||
|
||||
func (m *StoreModule) assertQueryOptions(value goja.Value, rt *goja.Runtime) *queryOptions {
|
||||
if value.Export() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawQueryOptions, ok := value.Export().(map[string]interface{})
|
||||
if !ok {
|
||||
panic(rt.NewTypeError(fmt.Sprintf("query options must be an object, got '%T'", value.Export())))
|
||||
|
15
pkg/storage/filter/sql/operator.go
Normal file
15
pkg/storage/filter/sql/operator.go
Normal file
@ -0,0 +1,15 @@
|
||||
package sql
|
||||
|
||||
const (
|
||||
OpIn = "IN"
|
||||
OpLesserThan = "<"
|
||||
OpLesserThanEqual = "<="
|
||||
OpEqual = "="
|
||||
OpNotEqual = "!="
|
||||
OpSuperiorThan = ">"
|
||||
OpSuperiorThanEqual = ">="
|
||||
OpAnd = "AND"
|
||||
OpOr = "OR"
|
||||
OpLike = "LIKE"
|
||||
OpNot = "NOT"
|
||||
)
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -93,15 +93,23 @@ func (s *DocumentStore) Query(ctx context.Context, collection string, filter *fi
|
||||
var documents []storage.Document
|
||||
|
||||
err := s.withTx(ctx, func(tx *sql.Tx) error {
|
||||
criteria, args, err := filterSQL.ToSQL(
|
||||
filter.Root(),
|
||||
filterSQL.WithPreparedParameter("$", 2),
|
||||
filterSQL.WithKeyTransform(func(key string) string {
|
||||
return fmt.Sprintf("json_extract(data, '$.%s')", key)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
criteria := "1 = 1"
|
||||
args := make([]any, 0)
|
||||
|
||||
var err error
|
||||
|
||||
if filter != nil {
|
||||
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)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
query := `
|
||||
|
24
pkg/storage/sqlite/filter.go
Normal file
24
pkg/storage/sqlite/filter.go
Normal file
@ -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
|
||||
}
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user