Refuse duplicate aggregate columns and page aggregates after the fold

A repeated extract function names one response column twice, which openvoxdb
aliases as <name>_2: unknown to the merge spec, it froze at the first backend's
value. A limit pushed upstream truncated each backend's groups before the
cross-backend fold, so a group could be partly counted or missed.

- Refuses any extract projecting one response column twice, naming the clash
- Fetches every group and applies limit/offset after the fold
- Documents the float64 avg divergence from Postgres numeric
This commit is contained in:
2026-09-06 23:58:29 +10:00
parent 66ed7b615c
commit b499e962af
7 changed files with 230 additions and 20 deletions
+38
View File
@@ -371,6 +371,44 @@ func TestParseAggregate_TwoAvgColumnsAreRefused(t *testing.T) {
}
}
// Every extract function names its column after itself, so a second one of the
// same name clashes whatever the function is — not only for avg.
func TestParseAggregate_RepeatedFunctionNameIsRefused(t *testing.T) {
for _, fn := range []string{"count", "sum", "min", "max", "to_string", "jsonb_typeof", "avg"} {
q := `["extract",[["function","` + fn + `","line"],["function","` + fn + `","type"]],["group_by","line","type"]]`
spec, err := parseAggregate(q)
if err == nil {
t.Errorf("parseAggregate(%s) = %+v, want a refusal", q, spec)
continue
}
if !strings.Contains(err.Error(), fn) {
t.Errorf("error %q does not name the clashing column %q", err, fn)
}
}
}
// The same clash the other way round: a plain field takes the response key a
// later function column would name.
func TestParseAggregate_FieldClashingWithAFunctionIsRefused(t *testing.T) {
const q = `["extract",["count",["function","count","certname"]],["group_by","count"]]`
spec, err := parseAggregate(q)
if err == nil {
t.Fatalf("parseAggregate(%s) = %+v, want a refusal", q, spec)
}
if !strings.Contains(err.Error(), "count") {
t.Errorf("error %q does not name the clashing column", err)
}
}
// A repeated plain field projects the same value twice, so it is no clash: the
// duplicate carries nothing the grouping key has not already got.
func TestParseAggregate_RepeatedPlainFieldIsKept(t *testing.T) {
spec := mustAggregate(t, `["extract",["type","type",["function","count","certname"]],["group_by","type"]]`)
if !slices.Equal(spec.keys, []string{"type"}) {
t.Errorf("keys = %v, want [type]", spec.keys)
}
}
func TestCombineRows_DisjointKeysAreKept(t *testing.T) {
spec := mustAggregate(t, `["extract",[["function","count"],"status"],["=","certname","h1"],["group_by","status"]]`)
merged := combineRows([]backendResult{