Skip to content

Commit 506ec0e

Browse files
author
Dylan Bobby Storey
committed
fix(transform): WITH detects nested aggregates for implicit GROUP BY
Companion to the iter-33 transform_return.c fix. The WITH clause has its own aggregate-detection at line ~419 that only set has_aggregate when the projection item's TOP-LEVEL expression was a function call. Aggregates buried in binary ops, list/map literals, or subscripts were missed. Symptom (With6 [6]): MATCH ... WITH me.age AS age, you WITH age, age + count(you.age) AS agg RETURN * -- Empty-input result was 1 all-null row (no GROUP BY → ungrouped agg). -- Spec: 0 rows. Two changes: 1. Extended find_aggregating_call() to walk MAP and SUBSCRIPT nodes in addition to FUNCTION_CALL/BINARY_OP/NOT/PROPERTY/NULL_CHECK/LIST. 2. The else-branch (BINARY_OP / map / etc.) now calls find_aggregating_call() after the projection emits and sets has_aggregate = true if any aggregate is found nested inside — this triggers the existing GROUP BY emission on the non-aggregate columns. TCK delta: pass=3456 -> 3458 (+2), fails -2. With6 [6]/[7]. 944/944 unit, functional clean.
1 parent 22197bd commit 506ec0e

1 file changed

Lines changed: 24 additions & 0 deletions

File tree

src/backend/transform/transform_with.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ static const char *find_aggregating_call(ast_node *expr)
7777
if (r) return r;
7878
}
7979
}
80+
} else if (expr->type == AST_NODE_MAP) {
81+
cypher_map *m = (cypher_map*)expr;
82+
if (m->pairs) {
83+
for (int i = 0; i < m->pairs->count; i++) {
84+
cypher_map_pair *mp = (cypher_map_pair*)m->pairs->items[i];
85+
if (mp) {
86+
const char *r = find_aggregating_call(mp->value);
87+
if (r) return r;
88+
}
89+
}
90+
}
91+
} else if (expr->type == AST_NODE_SUBSCRIPT) {
92+
cypher_subscript *s = (cypher_subscript*)expr;
93+
const char *r = find_aggregating_call(s->expr);
94+
if (r) return r;
95+
return find_aggregating_call(s->index);
8096
}
8197
return NULL;
8298
}
@@ -464,6 +480,14 @@ int transform_with_clause(cypher_transform_context *ctx, cypher_with *with)
464480
ctx->sql_size = saved_size;
465481
strcpy(ctx->sql_buffer, saved_buffer);
466482
free(saved_buffer);
483+
484+
/* Check for nested aggregates (e.g. `age + count(x)` or
485+
* `{kids: collect(x)}`). Without this, the implicit GROUP BY
486+
* isn't emitted and a grouped-aggregate-on-empty produces 1
487+
* all-null row instead of 0 rows. (With6 [6]/[7].) */
488+
if (find_aggregating_call(item->expr) != NULL) {
489+
has_aggregate = true;
490+
}
467491
}
468492
}
469493

0 commit comments

Comments
 (0)