fix: apply ireduce per pipeline input like jq reduce (#2869)

match(...) | (.captures[] as $c ireduce (...)) folded every match's
captures into one accumulator, so only the last object remained. jq
reduce runs once per input. Use the same EvaluateTogether split as
`as` and collect; eval-all file merges are unchanged.

Fixes #2859
This commit is contained in:
00200200
2026-10-01 11:15:22 +10:00
committed by GitHub
parent 78dc444c15
commit 247492ffba
2 changed files with 34 additions and 0 deletions
+26
View File
@@ -21,6 +21,32 @@ func reduceOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
return Context{}, fmt.Errorf("reduce must be given a block, got %v instead", expressionNode.RHS.Operation.OperationType.Type)
}
// Same EvaluateTogether split as `as` / collect / crossFunction:
// file documents are reduced together (eval-all merge), while
// pipeline results such as match(...) are reduced per input, like jq.
var evaluateAllTogether = true
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
if !evaluateAllTogether {
break
}
}
if evaluateAllTogether {
return reduceOperatorSingle(d, context, expressionNode)
}
results := list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
result, err := reduceOperatorSingle(d, context.SingleChildContext(el.Value.(*CandidateNode)), expressionNode)
if err != nil {
return Context{}, err
}
results.PushBackList(result.MatchingNodes)
}
return context.ChildContext(results), nil
}
func reduceOperatorSingle(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
arrayExpNode := expressionNode.LHS.LHS
array, err := d.GetMatchingNodes(context, arrayExpNode)
+8
View File
@@ -30,6 +30,14 @@ var reduceOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::Cathy: apples\nBob: bananas\n",
},
},
{
skipDoc: true,
description: "Issue 2859: match then ireduce .captures[] matches jq reduce / capture",
expression: `"\"a\"=\"b\" \"x\"=\"y\"" | [match("\"(?<key>.*?)\"=\"(?<value>.*?)\""; "g") | (.captures[] as $capture ireduce ({}; .[$capture.name] = $capture.string))]`,
expected: []string{
"D0, P[], (!!seq)::- key: a\n value: b\n- key: x\n value: y\n",
},
},
}
func TestReduceOperatorScenarios(t *testing.T) {