mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-03 02:51:40 +00:00
fix: apply per-candidate index when traversing arrays/maps by a streamed index
`$o[.]` over a streamed context (e.g. `keys[] | $o[.]`) only returned the first match. The index expression yields one index set per incoming candidate, but traverseArrayOperator used only the first set (rhs.MatchingNodes.Front()), dropping the rest. Pair each index set with its candidate: when the LHS has one node per candidate (e.g. `.[] | .[idx]`) each node is traversed with its own index set; when the LHS collapses to a single node (a variable) it is traversed against every index set. Covers both arrays and maps. Fixes #2593.
This commit is contained in:
parent
5cf0adcc5b
commit
0cc5c19843
@ -111,10 +111,10 @@ func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
@ -123,16 +123,37 @@ func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode
|
||||
if expressionNode.Operation.Preferences != nil {
|
||||
prefs = expressionNode.Operation.Preferences.(traversePreferences)
|
||||
}
|
||||
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Content
|
||||
|
||||
log.Debugf("indicesToTraverse %v", len(indicesToTraverse))
|
||||
|
||||
//now we traverse the result of the lhs against the indices we found
|
||||
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, prefs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
results := list.New()
|
||||
if lhs.MatchingNodes.Len() == rhs.MatchingNodes.Len() {
|
||||
// One index set per LHS node (both derive from the same context):
|
||||
// traverse each LHS node with its own index set. Previously only the
|
||||
// first index set was used, so a context-dependent index like `$o[.]`
|
||||
// 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) {
|
||||
|
||||
@ -675,6 +675,27 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
expression: ". = (.x = 1)",
|
||||
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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user