This commit is contained in:
Joeseph Grey 2026-06-27 01:46:13 -04:00 committed by GitHub
commit 755dd15055
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 11 deletions

View File

@ -111,10 +111,10 @@ func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode
return Context{}, err return Context{}, err
} }
// rhs is a collect expression that will yield indices to retrieve of the arrays // rhs is a collect expression that yields the indices to retrieve. It is
// evaluated over the whole context, producing one index set per incoming
// candidate.
rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.RHS) rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.RHS)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
@ -123,16 +123,37 @@ func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode
if expressionNode.Operation.Preferences != nil { if expressionNode.Operation.Preferences != nil {
prefs = expressionNode.Operation.Preferences.(traversePreferences) prefs = expressionNode.Operation.Preferences.(traversePreferences)
} }
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Content
log.Debugf("indicesToTraverse %v", len(indicesToTraverse)) results := list.New()
if lhs.MatchingNodes.Len() == rhs.MatchingNodes.Len() {
//now we traverse the result of the lhs against the indices we found // One index set per LHS node (both derive from the same context):
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, prefs) // traverse each LHS node with its own index set. Previously only the
if err != nil { // first index set was used, so a context-dependent index like `$o[.]`
return Context{}, err // over a `keys[]` stream dropped every match but the first (#2593).
rhsEl := rhs.MatchingNodes.Front()
for lhsEl := lhs.MatchingNodes.Front(); lhsEl != nil; lhsEl = lhsEl.Next() {
indicesToTraverse := rhsEl.Value.(*CandidateNode).Content
result, err := traverseNodesWithArrayIndices(context.SingleChildContext(lhsEl.Value.(*CandidateNode)), indicesToTraverse, prefs)
if err != nil {
return Context{}, err
}
results.PushBackList(result.MatchingNodes)
rhsEl = rhsEl.Next()
}
} else {
// LHS collapsed to a single node (e.g. a variable) while the index
// varies per candidate: traverse the LHS against every index set.
for rhsEl := rhs.MatchingNodes.Front(); rhsEl != nil; rhsEl = rhsEl.Next() {
indicesToTraverse := rhsEl.Value.(*CandidateNode).Content
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, prefs)
if err != nil {
return Context{}, err
}
results.PushBackList(result.MatchingNodes)
}
} }
return context.ChildContext(result.MatchingNodes), nil
return context.ChildContext(results), nil
} }
func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*CandidateNode, prefs traversePreferences) (Context, error) { func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*CandidateNode, prefs traversePreferences) (Context, error) {

View File

@ -675,6 +675,27 @@ var traversePathOperatorScenarios = []expressionScenario{
expression: ". = (.x = 1)", expression: ". = (.x = 1)",
expectedError: "alias cycle detected", expectedError: "alias cycle detected",
}, },
{
// Regression test for https://github.com/mikefarah/yq/issues/2593
// A context-dependent index (here the streamed key) must be applied
// per candidate; previously only the first index was used.
skipDoc: true,
document: `["a","b"]`,
expression: `. as $o | keys[] | $o[.]`,
expected: []string{
"D0, P[0], (!!str)::a\n",
"D0, P[1], (!!str)::b\n",
},
},
{
skipDoc: true,
document: `{"x": 1, "y": 2}`,
expression: `. as $o | keys[] | $o[.]`,
expected: []string{
"D0, P[x], (!!int)::1\n",
"D0, P[y], (!!int)::2\n",
},
},
} }
func TestTraversePathOperatorScenarios(t *testing.T) { func TestTraversePathOperatorScenarios(t *testing.T) {