From 37f3e219707dca065494cd3d9e7f2d751a61b41e Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 9 May 2021 12:44:05 +1000 Subject: [PATCH] Fixed boolean op with empty context issue --- pkg/yqlib/doc/Equals.md | 28 +++++++++++++++++++ pkg/yqlib/operator_booleans.go | 42 ++++++++++++++++++++--------- pkg/yqlib/operator_booleans_test.go | 16 +++++++++++ pkg/yqlib/operator_equals.go | 13 +++++++-- pkg/yqlib/operator_equals_test.go | 16 +++++++++++ pkg/yqlib/operator_select_test.go | 10 +++++++ 6 files changed, 111 insertions(+), 14 deletions(-) diff --git a/pkg/yqlib/doc/Equals.md b/pkg/yqlib/doc/Equals.md index 6d2ebbd..dcdff2b 100644 --- a/pkg/yqlib/doc/Equals.md +++ b/pkg/yqlib/doc/Equals.md @@ -93,3 +93,31 @@ will output true ``` +## Non exisitant key doesn't equal a value +Given a sample.yml file of: +```yaml +a: frog +``` +then +```bash +yq eval 'select(.b != "thing")' sample.yml +``` +will output +```yaml +a: frog +``` + +## Two non existant keys are equal +Given a sample.yml file of: +```yaml +a: frog +``` +then +```bash +yq eval 'select(.b == .c)' sample.yml +``` +will output +```yaml +a: frog +``` + diff --git a/pkg/yqlib/operator_booleans.go b/pkg/yqlib/operator_booleans.go index 084e5f7..f813b9b 100644 --- a/pkg/yqlib/operator_booleans.go +++ b/pkg/yqlib/operator_booleans.go @@ -27,20 +27,37 @@ type boolOp func(bool, bool) bool func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { - lhs.Node = unwrapDoc(lhs.Node) - rhs.Node = unwrapDoc(rhs.Node) + owner := lhs - lhsTrue, errDecoding := isTruthy(lhs) - if errDecoding != nil { - return nil, errDecoding + if lhs == nil && rhs == nil { + owner = &CandidateNode{} + } else if lhs == nil { + owner = rhs } - rhsTrue, errDecoding := isTruthy(rhs) - if errDecoding != nil { - return nil, errDecoding - } + var errDecoding error + lhsTrue := false + if lhs != nil { + lhs.Node = unwrapDoc(lhs.Node) + lhsTrue, errDecoding = isTruthy(lhs) - return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil + if errDecoding != nil { + return nil, errDecoding + } + } + log.Debugf("-- lhsTrue", lhsTrue) + + rhsTrue := false + if rhs != nil { + rhs.Node = unwrapDoc(rhs.Node) + rhsTrue, errDecoding = isTruthy(rhs) + if errDecoding != nil { + return nil, errDecoding + } + } + log.Debugf("-- rhsTrue", rhsTrue) + + return createBooleanCandidate(owner, op(lhsTrue, rhsTrue)), nil } } @@ -48,8 +65,9 @@ func orOperator(d *dataTreeNavigator, context Context, expressionNode *Expressio log.Debugf("-- orOp") return crossFunction(d, context, expressionNode, performBoolOp( func(b1 bool, b2 bool) bool { + log.Debugf("-- peformingOrOp with %v and %v", b1, b2) return b1 || b2 - }), false) + }), true) } func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { @@ -57,7 +75,7 @@ func andOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi return crossFunction(d, context, expressionNode, performBoolOp( func(b1 bool, b2 bool) bool { return b1 && b2 - }), false) + }), true) } func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { diff --git a/pkg/yqlib/operator_booleans_test.go b/pkg/yqlib/operator_booleans_test.go index ba2b6aa..b755409 100644 --- a/pkg/yqlib/operator_booleans_test.go +++ b/pkg/yqlib/operator_booleans_test.go @@ -12,6 +12,22 @@ var booleanOperatorScenarios = []expressionScenario{ "D0, P[], (!!bool)::true\n", }, }, + { + skipDoc: true, + document: "b: hi", + expression: `select(.a or .b)`, + expected: []string{ + "D0, P[], (doc)::b: hi\n", + }, + }, + { + skipDoc: true, + document: "b: hi", + expression: `select((.a and .b) | not)`, + expected: []string{ + "D0, P[], (doc)::b: hi\n", + }, + }, { description: "AND example", expression: `true and false`, diff --git a/pkg/yqlib/operator_equals.go b/pkg/yqlib/operator_equals.go index 1edbccc..fe465ea 100644 --- a/pkg/yqlib/operator_equals.go +++ b/pkg/yqlib/operator_equals.go @@ -4,13 +4,22 @@ import "gopkg.in/yaml.v3" func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("-- equalsOperation") - return crossFunction(d, context, expressionNode, isEquals(false), false) + return crossFunction(d, context, expressionNode, isEquals(false), true) } func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { value := false + if lhs == nil && rhs == nil { + owner := &CandidateNode{} + return createBooleanCandidate(owner, !flip), nil + } else if lhs == nil { + return createBooleanCandidate(rhs, flip), nil + } else if rhs == nil { + return createBooleanCandidate(lhs, flip), nil + } + lhsNode := unwrapDoc(lhs.Node) rhsNode := unwrapDoc(rhs.Node) @@ -29,5 +38,5 @@ func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *Candid func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("-- equalsOperation") - return crossFunction(d, context, expressionNode, isEquals(true), false) + return crossFunction(d, context, expressionNode, isEquals(true), true) } diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go index 291d691..734f087 100644 --- a/pkg/yqlib/operator_equals_test.go +++ b/pkg/yqlib/operator_equals_test.go @@ -87,6 +87,22 @@ var equalsOperatorScenarios = []expressionScenario{ "D0, P[], (!!bool)::true\n", }, }, + { + description: "Non exisitant key doesn't equal a value", + document: "a: frog", + expression: `select(.b != "thing")`, + expected: []string{ + "D0, P[], (doc)::a: frog\n", + }, + }, + { + description: "Two non existant keys are equal", + document: "a: frog", + expression: `select(.b == .c)`, + expected: []string{ + "D0, P[], (doc)::a: frog\n", + }, + }, } func TestEqualOperatorScenarios(t *testing.T) { diff --git a/pkg/yqlib/operator_select_test.go b/pkg/yqlib/operator_select_test.go index 6a08474..4d24e55 100644 --- a/pkg/yqlib/operator_select_test.go +++ b/pkg/yqlib/operator_select_test.go @@ -14,6 +14,16 @@ var selectOperatorScenarios = []expressionScenario{ "D0, P[1], (!!str)::goat\n", }, }, + { + skipDoc: true, + document: "a: hello", + document2: "b: world", + expression: `select(.a == "hello" or .b == "world")`, + expected: []string{ + "D0, P[], (doc)::a: hello\n", + "D0, P[], (doc)::b: world\n", + }, + }, { skipDoc: true, document: `[{animal: cat, legs: {cool: true}}, {animal: fish}]`,