From b9be9a6184a159d4cbd0f14fefe0db27aaf195e4 Mon Sep 17 00:00:00 2001 From: upstream Date: Thu, 3 Sep 2026 13:29:22 +0200 Subject: [PATCH] Upstream mikefarah/yq state of the touched files before 253c991996 --- pkg/yqlib/doc/operators/equals.md | 133 ++++++++++++++++++++ pkg/yqlib/operator_equals.go | 48 ++++++++ pkg/yqlib/operator_equals_test.go | 197 ++++++++++++++++++++++++++++++ 3 files changed, 378 insertions(+) create mode 100644 pkg/yqlib/doc/operators/equals.md create mode 100644 pkg/yqlib/operator_equals.go create mode 100644 pkg/yqlib/operator_equals_test.go diff --git a/pkg/yqlib/doc/operators/equals.md b/pkg/yqlib/doc/operators/equals.md new file mode 100644 index 0000000..ca5099b --- /dev/null +++ b/pkg/yqlib/doc/operators/equals.md @@ -0,0 +1,133 @@ +# Equals / Not Equals + +This is a boolean operator that will return `true` if the LHS is equal to the RHS and `false` otherwise. + +``` +.a == .b +``` + +It is most often used with the select operator to find particular nodes: + +``` +select(.a == .b) +``` + +The not equals `!=` operator returns `false` if the LHS is equal to the RHS. + +## Related Operators + +- comparison (`>=`, `<` etc) operators [here](https://mikefarah.gitbook.io/yq/operators/compare) +- boolean operators (`and`, `or`, `any` etc) [here](https://mikefarah.gitbook.io/yq/operators/boolean-operators) +- select operator [here](https://mikefarah.gitbook.io/yq/operators/select) + + +## Match string +Given a sample.yml file of: +```yaml +- cat +- goat +- dog +``` +then +```bash +yq '.[] | (. == "*at")' sample.yml +``` +will output +```yaml +true +true +false +``` + +## Don't match string +Given a sample.yml file of: +```yaml +- cat +- goat +- dog +``` +then +```bash +yq '.[] | (. != "*at")' sample.yml +``` +will output +```yaml +false +false +true +``` + +## Match number +Given a sample.yml file of: +```yaml +- 3 +- 4 +- 5 +``` +then +```bash +yq '.[] | (. == 4)' sample.yml +``` +will output +```yaml +false +true +false +``` + +## Don't match number +Given a sample.yml file of: +```yaml +- 3 +- 4 +- 5 +``` +then +```bash +yq '.[] | (. != 4)' sample.yml +``` +will output +```yaml +true +false +true +``` + +## Match nulls +Running +```bash +yq --null-input 'null == ~' +``` +will output +```yaml +true +``` + +## Non existent key doesn't equal a value +Given a sample.yml file of: +```yaml +a: frog +``` +then +```bash +yq 'select(.b != "thing")' sample.yml +``` +will output +```yaml +a: frog +``` + +## Two non existent keys are equal +Given a sample.yml file of: +```yaml +a: frog +``` +then +```bash +yq 'select(.b == .c)' sample.yml +``` +will output +```yaml +a: frog +``` + diff --git a/pkg/yqlib/operator_equals.go b/pkg/yqlib/operator_equals.go new file mode 100644 index 0000000..4bcf28d --- /dev/null +++ b/pkg/yqlib/operator_equals.go @@ -0,0 +1,48 @@ +package yqlib + +func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("equalsOperation") + 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(_ *dataTreeNavigator, _ Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { + value := false + log.Debugf("isEquals cross function") + if lhs == nil && rhs == nil { + log.Debugf("both are nil") + owner := &CandidateNode{} + return createBooleanCandidate(owner, !flip), nil + } else if lhs == nil { + log.Debugf("lhs nil, but rhs is not") + value := rhs.Tag == "!!null" + if flip { + value = !value + } + return createBooleanCandidate(rhs, value), nil + } else if rhs == nil { + log.Debugf("lhs not nil, but rhs is") + value := lhs.Tag == "!!null" + if flip { + value = !value + } + return createBooleanCandidate(lhs, value), nil + } + + if lhs.Tag == "!!null" { + value = (rhs.Tag == "!!null") + } else if lhs.Kind == ScalarNode && rhs.Kind == ScalarNode { + value = matchKey(lhs.Value, rhs.Value) + } + log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value) + if flip { + value = !value + } + return createBooleanCandidate(lhs, value), nil + } +} + +func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + log.Debugf("notEqualsOperator") + return crossFunction(d, context.ReadOnlyClone(), expressionNode, isEquals(true), true) +} diff --git a/pkg/yqlib/operator_equals_test.go b/pkg/yqlib/operator_equals_test.go new file mode 100644 index 0000000..43a2fa8 --- /dev/null +++ b/pkg/yqlib/operator_equals_test.go @@ -0,0 +1,197 @@ +package yqlib + +import ( + "testing" +) + +var equalsOperatorScenarios = []expressionScenario{ + { + skipDoc: true, + expression: ".a == .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + expression: `(.k | length) == 0`, + skipDoc: true, + expected: []string{ + "D0, P[k], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: `a: cat`, + expression: ".a == .b", + expected: []string{ + "D0, P[a], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: `a: cat`, + expression: ".b == .a", + expected: []string{ + "D0, P[b], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "cat", + document2: "dog", + expression: "select(fi==0) == select(fi==1)", + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "{}", + expression: "(.a == .b) as $x | .", + expected: []string{ + "D0, P[], (!!map)::{}\n", + }, + }, + { + skipDoc: true, + document: "{}", + expression: ".a == .b", + expected: []string{ + "D0, P[a], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: "{}", + expression: "(.a != .b) as $x | .", + expected: []string{ + "D0, P[], (!!map)::{}\n", + }, + }, + { + skipDoc: true, + document: "{}", + expression: ".a != .b", + expected: []string{ + "D0, P[], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: "{a: {b: 10}}", + expression: "select(.c != null)", + expected: []string{}, + }, + { + skipDoc: true, + document: "{a: {b: 10}}", + expression: "select(.d == .c)", + expected: []string{ + "D0, P[], (!!map)::{a: {b: 10}}\n", + }, + }, + { + skipDoc: true, + document: "{a: {b: 10}}", + expression: "select(null == .c)", + expected: []string{ + "D0, P[], (!!map)::{a: {b: 10}}\n", + }, + }, + { + skipDoc: true, + document: "{a: { b: {things: \"\"}, f: [1], g: [] }}", + expression: ".. | select(. == \"\")", + expected: []string{ + "D0, P[a b things], (!!str)::\n", + }, + }, + { + description: "Match string", + document: `[cat,goat,dog]`, + expression: `.[] | (. == "*at")`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, + { + description: "Don't match string", + document: `[cat,goat,dog]`, + expression: `.[] | (. != "*at")`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::false\n", + "D0, P[2], (!!bool)::true\n", + }, + }, + { + description: "Match number", + document: `[3, 4, 5]`, + expression: `.[] | (. == 4)`, + expected: []string{ + "D0, P[0], (!!bool)::false\n", + "D0, P[1], (!!bool)::true\n", + "D0, P[2], (!!bool)::false\n", + }, + }, + { + description: "Don't match number", + document: `[3, 4, 5]`, + expression: `.[] | (. != 4)`, + expected: []string{ + "D0, P[0], (!!bool)::true\n", + "D0, P[1], (!!bool)::false\n", + "D0, P[2], (!!bool)::true\n", + }, + }, + { + skipDoc: true, + document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, + expression: `.a | (.[].b == "apple")`, + expected: []string{ + "D0, P[a cat b], (!!bool)::true\n", + "D0, P[a pat b], (!!bool)::false\n", + }, + }, + { + skipDoc: true, + document: ``, + expression: `null == null`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + description: "Match nulls", + document: ``, + expression: `null == ~`, + expected: []string{ + "D0, P[], (!!bool)::true\n", + }, + }, + { + description: "Non existent key doesn't equal a value", + document: "a: frog", + expression: `select(.b != "thing")`, + expected: []string{ + "D0, P[], (!!map)::a: frog\n", + }, + }, + { + description: "Two non existent keys are equal", + document: "a: frog", + expression: `select(.b == .c)`, + expected: []string{ + "D0, P[], (!!map)::a: frog\n", + }, + }, +} + +func TestEqualOperatorScenarios(t *testing.T) { + for _, tt := range equalsOperatorScenarios { + testScenario(t, &tt) + } + documentOperatorScenarios(t, "equals", equalsOperatorScenarios) +}