From 4bbffa90225a9295346ae130922b33155031b656 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sat, 31 Jan 2026 15:44:50 +1100 Subject: [PATCH] Fixed merge globbing wildcards in keys #2564 --- pkg/yqlib/lexer_participle.go | 1 + pkg/yqlib/operator_multiply.go | 2 +- pkg/yqlib/operator_multiply_test.go | 19 +++++++++++++++++++ pkg/yqlib/operator_traverse_path.go | 9 +++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/pkg/yqlib/lexer_participle.go b/pkg/yqlib/lexer_participle.go index d4451257..b99f6e37 100644 --- a/pkg/yqlib/lexer_participle.go +++ b/pkg/yqlib/lexer_participle.go @@ -451,6 +451,7 @@ func multiplyWithPrefs(op *operationType) yqAction { prefs.AssignPrefs.ClobberCustomTags = true } prefs.TraversePrefs.DontFollowAlias = true + prefs.TraversePrefs.ExactKeyMatch = true op := &Operation{OperationType: op, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs} return &token{TokenType: operationToken, Operation: op}, nil } diff --git a/pkg/yqlib/operator_multiply.go b/pkg/yqlib/operator_multiply.go index 73e9a6c9..8ea69a18 100644 --- a/pkg/yqlib/operator_multiply.go +++ b/pkg/yqlib/operator_multiply.go @@ -168,7 +168,7 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs // only need to recurse the array if we are doing a deep merge prefs := recursiveDescentPreferences{RecurseArray: preferences.DeepMergeArrays, - TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}} + TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true, ExactKeyMatch: true}} log.Debugf("merge - preferences.DeepMergeArrays %v", preferences.DeepMergeArrays) log.Debugf("merge - preferences.AppendArrays %v", preferences.AppendArrays) err := recursiveDecent(results, context.SingleChildContext(rhs), prefs) diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index 49a3b6eb..1c7eca32 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -86,7 +86,26 @@ c: <<: *cat ` +var mergeWithGlobA = ` +"**cat": things, +"meow**cat": stuff +` + +var mergeWithGlobB = ` +"**cat": newThings, +` + var multiplyOperatorScenarios = []expressionScenario{ + { + description: "glob keys are treated as literals when merging", + skipDoc: true, + document: mergeWithGlobA, + document2: mergeWithGlobB, + expression: `select(fi == 0) * select(fi == 1)`, + expected: []string{ + "D0, P[], (!!map)::\n\"**cat\": newThings,\n\"meow**cat\": stuff\n", + }, + }, { skipDoc: true, document: mergeArrayWithAnchors, diff --git a/pkg/yqlib/operator_traverse_path.go b/pkg/yqlib/operator_traverse_path.go index 3cd9fd51..28ca09a9 100644 --- a/pkg/yqlib/operator_traverse_path.go +++ b/pkg/yqlib/operator_traverse_path.go @@ -14,6 +14,7 @@ type traversePreferences struct { DontAutoCreate bool // by default, we automatically create entries on the fly. DontIncludeMapValues bool OptionalTraverse bool // e.g. .adf? + ExactKeyMatch bool // by default we let wild/glob patterns. Don't do that for merge though. } func splat(context Context, prefs traversePreferences) (Context, error) { @@ -216,7 +217,11 @@ func traverseArrayWithIndices(node *CandidateNode, indices []*CandidateNode, pre return newMatches, nil } -func keyMatches(key *CandidateNode, wantedKey string) bool { +func keyMatches(key *CandidateNode, wantedKey string, exactKeyMatch bool) bool { + if exactKeyMatch { + // this is used for merge + return key.Value == wantedKey + } return matchKey(key.Value, wantedKey) } @@ -303,7 +308,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, node *CandidateNode, wante return err } } - } else if splat || keyMatches(key, wantedKey) { + } else if splat || keyMatches(key, wantedKey, prefs.ExactKeyMatch) { log.Debug("MATCHED") if prefs.IncludeMapKeys { log.Debug("including key")