From 676c143fe97c212cb4364a00e350059f69e5b1c3 Mon Sep 17 00:00:00 2001 From: Tom Wright Date: Sun, 8 Aug 2021 22:12:40 +0100 Subject: [PATCH 1/3] Add != comparison operator support in dynamic and search selectors --- CHANGELOG.md | 4 +++- condition_equal.go | 14 +++++++++++-- condition_key_equal.go | 13 +++++++++++- go.sum | 3 --- node_test.go | 46 +++++++++++++++++++++++++++++++++++++++++- parse_selector.go | 28 +++++++++++++++++++++++++ 6 files changed, 100 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2de4ff9b..bccede4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing +### Added + +- Support for `!=` comparison operator in dynamic and search selectors. ## [v1.16.1] - 2021-08-02 diff --git a/condition_equal.go b/condition_equal.go index a92efb38..7b0c4c37 100644 --- a/condition_equal.go +++ b/condition_equal.go @@ -12,6 +12,16 @@ type EqualCondition struct { Key string // Value is the value we are looking for. Value string + // Not is true if this is a not equal check. + Not bool +} + +func (c EqualCondition) check(a interface{}, b interface{}) (bool, error) { + var res = fmt.Sprint(a) == b + if c.Not { + res = !res + } + return res, nil } // Check checks to see if other contains the required key value pair. @@ -23,7 +33,7 @@ func (c EqualCondition) Check(other reflect.Value) (bool, error) { value := unwrapValue(other) if c.Key == "value" || c.Key == "." { - return fmt.Sprint(value.Interface()) == c.Value, nil + return c.check(value.Interface(), c.Value) } switch value.Kind() { @@ -39,7 +49,7 @@ func (c EqualCondition) Check(other reflect.Value) (bool, error) { return false, fmt.Errorf("subquery failed: %w", err) } - return fmt.Sprint(foundNode.InterfaceValue()) == c.Value, nil + return c.check(foundNode.InterfaceValue(), c.Value) } return false, &UnhandledCheckType{Value: value.String()} diff --git a/condition_key_equal.go b/condition_key_equal.go index 65de611c..6e59ddc0 100644 --- a/condition_key_equal.go +++ b/condition_key_equal.go @@ -1,6 +1,7 @@ package dasel import ( + "fmt" "reflect" ) @@ -8,6 +9,16 @@ import ( type KeyEqualCondition struct { // Value is the value we are looking for. Value string + // Not is true if this is a not equal check. + Not bool +} + +func (c KeyEqualCondition) check(a interface{}, b interface{}) (bool, error) { + var res = fmt.Sprint(a) == b + if c.Not { + res = !res + } + return res, nil } // Check checks to see if other contains the required key value pair. @@ -18,5 +29,5 @@ func (c KeyEqualCondition) Check(other reflect.Value) (bool, error) { value := unwrapValue(other) - return c.Value == value.String(), nil + return c.check(c.Value, value.String()) } diff --git a/go.sum b/go.sum index b948cde2..297f70c7 100644 --- a/go.sum +++ b/go.sum @@ -34,7 +34,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -101,7 +100,6 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -121,7 +119,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/node_test.go b/node_test.go index 36204398..61e549b1 100644 --- a/node_test.go +++ b/node_test.go @@ -181,6 +181,19 @@ func TestParseSelector(t *testing.T) { }, }, })) + t.Run("SearchNotEqual", testParseSelector(".(?:name!=asd)", dasel.Selector{ + Raw: ".(?:name!=asd)", + Current: ".(?:name!=asd)", + Remaining: "", + Type: "SEARCH", + Conditions: []dasel.Condition{ + &dasel.EqualCondition{ + Key: "name", + Value: "asd", + Not: true, + }, + }, + })) t.Run("SearchMoreThan", testParseSelector(".(?:name.[#]>3)", dasel.Selector{ Raw: ".(?:name.[#]>3)", Current: ".(?:name.[#]>3)", @@ -244,6 +257,18 @@ func TestParseSelector(t *testing.T) { }, }, })) + t.Run("SearchKeyNotEqual", testParseSelector(".(?:-!=asd)", dasel.Selector{ + Raw: ".(?:-!=asd)", + Current: ".(?:-!=asd)", + Remaining: "", + Type: "SEARCH", + Conditions: []dasel.Condition{ + &dasel.KeyEqualCondition{ + Value: "asd", + Not: true, + }, + }, + })) t.Run("DynamicEqual", testParseSelector(".(name=asd)", dasel.Selector{ Raw: ".(name=asd)", Current: ".(name=asd)", @@ -256,6 +281,19 @@ func TestParseSelector(t *testing.T) { }, }, })) + t.Run("DynamicNotEqual", testParseSelector(".(name!=asd)", dasel.Selector{ + Raw: ".(name!=asd)", + Current: ".(name!=asd)", + Remaining: "", + Type: "DYNAMIC", + Conditions: []dasel.Condition{ + &dasel.EqualCondition{ + Key: "name", + Value: "asd", + Not: true, + }, + }, + })) t.Run("DynamicMoreThan", testParseSelector(".(name.[#]>3)", dasel.Selector{ Raw: ".(name.[#]>3)", Current: ".(name.[#]>3)", @@ -383,7 +421,13 @@ func TestNode_QueryMultiple(t *testing.T) { t.Run("SingleResultDynamic", testNodeQueryMultipleArray(".(age=25).name", []interface{}{ "Amelia", })) - t.Run("SingleResultDynamic", testNodeQueryMultipleArray(".(age=27).name", []interface{}{ + t.Run("SingleResultDynamic", testNodeQueryMultipleArray(".(age!=27).name", []interface{}{ + "Amelia", + })) + t.Run("MultipleResultSearchKeyNotEqual", testNodeQueryMultipleArray(".[*].(?:-!=name)", []interface{}{ + "27", "27", "25", + })) + t.Run("MultipleResultDynamic", testNodeQueryMultipleArray(".(age=27).name", []interface{}{ "Tom", "Jim", })) diff --git a/parse_selector.go b/parse_selector.go index 077b07c9..c97f0a8b 100644 --- a/parse_selector.go +++ b/parse_selector.go @@ -54,15 +54,32 @@ func processParseSelectorDynamic(selector string, sel Selector) (Selector, error } for _, g := range dynamicGroups { + // evaluable, err := gvalInstance.NewEvaluable(g) + // if err != nil { + // return sel, fmt.Errorf("could not parse dynamic expression: %w", err) + // } + // + // todo : how do we execute sub queries? + // sel.Conditions = append(sel.Conditions, &GvalCondition{ + // Evaluable: evaluable, + // }) + m := FindDynamicSelectorParts(g) var cond Condition + switch m.Comparison { case "=": cond = &EqualCondition{ Key: m.Key, Value: m.Value, } + case "!=": + cond = &EqualCondition{ + Key: m.Key, + Value: m.Value, + Not: true, + } case ">=": cond = &SortedComparisonCondition{ Key: m.Key, @@ -121,6 +138,11 @@ func processParseSelectorSearch(selector string, sel Selector) (Selector, error) cond = &KeyEqualCondition{ Value: m.Value, } + case "!=": + cond = &KeyEqualCondition{ + Value: m.Value, + Not: true, + } default: return sel, &UnknownComparisonOperatorErr{Operator: m.Comparison} } @@ -131,6 +153,12 @@ func processParseSelectorSearch(selector string, sel Selector) (Selector, error) Key: m.Key, Value: m.Value, } + case "!=": + cond = &EqualCondition{ + Key: m.Key, + Value: m.Value, + Not: true, + } case ">=": cond = &SortedComparisonCondition{ Key: m.Key, From b3e0bfefded4c90fa3464b842fcb3abd5cf48215 Mon Sep 17 00:00:00 2001 From: Tom Wright Date: Sun, 8 Aug 2021 22:48:54 +0100 Subject: [PATCH 2/3] Remove duplicate code and add support for key name matches in dynamic selectors --- node_query.go | 17 +++- node_query_multiple.go | 17 +++- node_test.go | 33 +++++++- parse_selector.go | 179 ++++++++++++++++------------------------- 4 files changed, 126 insertions(+), 120 deletions(-) diff --git a/node_query.go b/node_query.go index b7cca6a4..0b60e1b6 100644 --- a/node_query.go +++ b/node_query.go @@ -119,12 +119,21 @@ func findNextAvailableIndex(n *Node, createIfNotExists bool) (reflect.Value, err } // processFindDynamicItem is used by findValueDynamic. -func processFindDynamicItem(n *Node, object reflect.Value) (bool, error) { +func processFindDynamicItem(n *Node, object reflect.Value, key string) (bool, error) { // Loop through each condition. allConditionsMatched := true for _, c := range n.Selector.Conditions { // If the object doesn't match any checks, return a ValueNotFound. - found, err := c.Check(object) + + var found bool + var err error + switch cond := c.(type) { + case *KeyEqualCondition: + found, err = cond.Check(reflect.ValueOf(key)) + default: + found, err = cond.Check(object) + } + if err != nil { return false, err } @@ -152,7 +161,7 @@ func findValueDynamic(n *Node, createIfNotExists bool) (reflect.Value, error) { case reflect.Slice: for i := 0; i < value.Len(); i++ { object := value.Index(i) - found, err := processFindDynamicItem(n, object) + found, err := processFindDynamicItem(n, object, fmt.Sprint(i)) if err != nil { return nilValue(), err } @@ -171,7 +180,7 @@ func findValueDynamic(n *Node, createIfNotExists bool) (reflect.Value, error) { case reflect.Map: for _, key := range value.MapKeys() { object := value.MapIndex(key) - found, err := processFindDynamicItem(n, object) + found, err := processFindDynamicItem(n, object, key.String()) if err != nil { return nilValue(), err } diff --git a/node_query_multiple.go b/node_query_multiple.go index ed2defd1..02b8d789 100644 --- a/node_query_multiple.go +++ b/node_query_multiple.go @@ -182,12 +182,21 @@ func findNextAvailableIndexNodes(selector Selector, previousValue reflect.Value, } // processFindDynamicItems is used by findNodesDynamic. -func processFindDynamicItems(selector Selector, object reflect.Value) (bool, error) { +func processFindDynamicItems(selector Selector, object reflect.Value, key string) (bool, error) { // Loop through each condition. allConditionsMatched := true for _, c := range selector.Conditions { // If the object doesn't match any checks, return a ValueNotFound. - found, err := c.Check(object) + + var found bool + var err error + switch cond := c.(type) { + case *KeyEqualCondition: + found, err = cond.Check(reflect.ValueOf(key)) + default: + found, err = cond.Check(object) + } + if err != nil { return false, err } @@ -215,7 +224,7 @@ func findNodesDynamic(selector Selector, previousValue reflect.Value, createIfNo results := make([]*Node, 0) for i := 0; i < value.Len(); i++ { object := value.Index(i) - found, err := processFindDynamicItems(selector, object) + found, err := processFindDynamicItems(selector, object, fmt.Sprint(i)) if err != nil { return nil, err } @@ -246,7 +255,7 @@ func findNodesDynamic(selector Selector, previousValue reflect.Value, createIfNo results := make([]*Node, 0) for _, key := range value.MapKeys() { object := value.MapIndex(key) - found, err := processFindDynamicItems(selector, object) + found, err := processFindDynamicItems(selector, object, key.String()) if err != nil { return nil, err } diff --git a/node_test.go b/node_test.go index 61e549b1..2a1f6537 100644 --- a/node_test.go +++ b/node_test.go @@ -269,6 +269,29 @@ func TestParseSelector(t *testing.T) { }, }, })) + t.Run("DynamicKey", testParseSelector(".(-=asd)", dasel.Selector{ + Raw: ".(-=asd)", + Current: ".(-=asd)", + Remaining: "", + Type: "DYNAMIC", + Conditions: []dasel.Condition{ + &dasel.KeyEqualCondition{ + Value: "asd", + }, + }, + })) + t.Run("DynamicKeyNotEqual", testParseSelector(".(-!=asd)", dasel.Selector{ + Raw: ".(-!=asd)", + Current: ".(-!=asd)", + Remaining: "", + Type: "DYNAMIC", + Conditions: []dasel.Condition{ + &dasel.KeyEqualCondition{ + Value: "asd", + Not: true, + }, + }, + })) t.Run("DynamicEqual", testParseSelector(".(name=asd)", dasel.Selector{ Raw: ".(name=asd)", Current: ".(name=asd)", @@ -418,12 +441,18 @@ func TestNode_QueryMultiple(t *testing.T) { t.Run("SingleResult", testNodeQueryMultipleArray(".[0].name", []interface{}{ "Tom", })) - t.Run("SingleResultDynamic", testNodeQueryMultipleArray(".(age=25).name", []interface{}{ + t.Run("SingleResultDynamicEqual", testNodeQueryMultipleArray(".(age=25).name", []interface{}{ "Amelia", })) - t.Run("SingleResultDynamic", testNodeQueryMultipleArray(".(age!=27).name", []interface{}{ + t.Run("SingleResultDynamicNotEqual", testNodeQueryMultipleArray(".(age!=27).name", []interface{}{ "Amelia", })) + t.Run("SingleResultDynamicKeyEqual", testNodeQueryMultipleArray(".(-=0).name", []interface{}{ + "Tom", + })) + t.Run("MultipleResultDynamicKeyNotEqual", testNodeQueryMultipleArray(".(-!=0).name", []interface{}{ + "Jim", "Amelia", + })) t.Run("MultipleResultSearchKeyNotEqual", testNodeQueryMultipleArray(".[*].(?:-!=name)", []interface{}{ "27", "27", "25", })) diff --git a/parse_selector.go b/parse_selector.go index c97f0a8b..7196031a 100644 --- a/parse_selector.go +++ b/parse_selector.go @@ -46,69 +46,82 @@ func ParseSelector(selector string) (Selector, error) { return sel, err } -func processParseSelectorDynamic(selector string, sel Selector) (Selector, error) { - sel.Type = "DYNAMIC" - dynamicGroups, err := DynamicSelectorToGroups(selector) - if err != nil { - return sel, err - } - - for _, g := range dynamicGroups { - // evaluable, err := gvalInstance.NewEvaluable(g) - // if err != nil { - // return sel, fmt.Errorf("could not parse dynamic expression: %w", err) - // } - // - // todo : how do we execute sub queries? - // sel.Conditions = append(sel.Conditions, &GvalCondition{ - // Evaluable: evaluable, - // }) - - m := FindDynamicSelectorParts(g) - - var cond Condition +func getCondition(parts DynamicSelectorParts) (Condition, error) { + switch parts.Key { + case "-", "keyValue": + switch parts.Comparison { + case "=": + return &KeyEqualCondition{ + Value: parts.Value, + }, nil + case "!=": + return &KeyEqualCondition{ + Value: parts.Value, + Not: true, + }, nil + default: + return nil, &UnknownComparisonOperatorErr{Operator: parts.Comparison} + } + default: - switch m.Comparison { + switch parts.Comparison { case "=": - cond = &EqualCondition{ - Key: m.Key, - Value: m.Value, - } + return &EqualCondition{ + Key: parts.Key, + Value: parts.Value, + }, nil case "!=": - cond = &EqualCondition{ - Key: m.Key, - Value: m.Value, + return &EqualCondition{ + Key: parts.Key, + Value: parts.Value, Not: true, - } + }, nil case ">=": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, + return &SortedComparisonCondition{ + Key: parts.Key, + Value: parts.Value, Equal: true, After: true, - } + }, nil case ">": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, + return &SortedComparisonCondition{ + Key: parts.Key, + Value: parts.Value, After: true, - } + }, nil case "<=": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, + return &SortedComparisonCondition{ + Key: parts.Key, + Value: parts.Value, Equal: true, - } + }, nil case "<": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, - } + return &SortedComparisonCondition{ + Key: parts.Key, + Value: parts.Value, + }, nil default: - return sel, &UnknownComparisonOperatorErr{Operator: m.Comparison} + return nil, &UnknownComparisonOperatorErr{Operator: parts.Comparison} } + } +} - sel.Conditions = append(sel.Conditions, cond) +func processParseSelectorDynamic(selector string, sel Selector) (Selector, error) { + sel.Type = "DYNAMIC" + dynamicGroups, err := DynamicSelectorToGroups(selector) + if err != nil { + return sel, err + } + + for _, g := range dynamicGroups { + parts := FindDynamicSelectorParts(g) + cond, err := getCondition(parts) + if err != nil { + return sel, err + } + if cond != nil { + sel.Conditions = append(sel.Conditions, cond) + } } return sel, nil @@ -126,69 +139,15 @@ func processParseSelectorSearch(selector string, sel Selector) (Selector, error) } for _, g := range dynamicGroups { - m := FindDynamicSelectorParts(g) - - m.Key = strings.TrimPrefix(m.Key, "?:") - - var cond Condition - switch m.Key { - case "-", "keyValue": - switch m.Comparison { - case "=": - cond = &KeyEqualCondition{ - Value: m.Value, - } - case "!=": - cond = &KeyEqualCondition{ - Value: m.Value, - Not: true, - } - default: - return sel, &UnknownComparisonOperatorErr{Operator: m.Comparison} - } - default: - switch m.Comparison { - case "=": - cond = &EqualCondition{ - Key: m.Key, - Value: m.Value, - } - case "!=": - cond = &EqualCondition{ - Key: m.Key, - Value: m.Value, - Not: true, - } - case ">=": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, - Equal: true, - After: true, - } - case ">": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, - After: true, - } - case "<=": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, - Equal: true, - } - case "<": - cond = &SortedComparisonCondition{ - Key: m.Key, - Value: m.Value, - } - default: - return sel, &UnknownComparisonOperatorErr{Operator: m.Comparison} - } + parts := FindDynamicSelectorParts(g) + parts.Key = strings.TrimPrefix(parts.Key, "?:") + cond, err := getCondition(parts) + if err != nil { + return sel, err + } + if cond != nil { + sel.Conditions = append(sel.Conditions, cond) } - - sel.Conditions = append(sel.Conditions, cond) } return sel, nil From 873fa85624583f5644a37f7a716f256d5574ec91 Mon Sep 17 00:00:00 2001 From: Tom Wright Date: Sun, 8 Aug 2021 22:51:29 +0100 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bccede4f..f1725284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for `!=` comparison operator in dynamic and search selectors. +- Support for `-`/`keyValue` key in dynamic selectors. ## [v1.16.1] - 2021-08-02