Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/testing/semantic-coverage-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,3 +682,20 @@ consistent with comparisons where comparisons are defined", all 10 type examples
`cmp_temporal_strings()` parses each operand to `(epoch_seconds, sub_second_ns)`
and compares componentwise (no overflow across the full Cypher year range),
falling back to lexical compare on parse failure.

## Coverage update (2026-06-03) — implicit GROUP BY on a computed key (+2)

`transform_with.c`. Verified via the TCK harness (rigorous full pass-set diff:
**zero regressions, +2**, 3786→3788; unit 944/944; functional clean). Fixes
WithOrderBy4 [12] ("Sort by an aliased aggregate projection") and Pattern2 [8]
("Use a pattern comprehension in WITH").

- **A non-aggregate *computed* WITH projection is now a GROUP BY key.**
In `WITH a.num2 % 3 AS mod, sum(a.num + a.num2) AS sum`, the grouping key
`a.num2 % 3` is a general expression (BINARY_OP), handled by the catch-all
projection branch — which emitted the column but never added it to GROUP BY.
Simple identifier and property keys were grouped, but the computed key wasn't,
so every row collapsed into a single group and the aggregate summed across the
whole table. The catch-all branch now appends the (non-aggregate) transformed
expression to GROUP BY, mirroring the identifier/property branches. Aggregate
projections (`find_aggregating_call` non-null) are still excluded from GROUP BY.
15 changes: 15 additions & 0 deletions src/backend/transform/transform_with.c
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,11 @@ int transform_with_clause(cypher_transform_context *ctx, cypher_with *with)
return -1;
}

/* Capture the raw transformed expression so it can serve as both
* the projected column and (when it's a non-aggregate key) a
* GROUP BY term. */
char *grp_expr = strdup(ctx->sql_buffer);

/* Boolean-valued projections (comparisons, AND/OR/NOT, quantifiers,
* IS NULL, ...) must carry the boolean subtype so a downstream
* RETURN of this column renders as a JSON boolean, not 1/0. Wrap the
Expand Down Expand Up @@ -656,7 +661,17 @@ int transform_with_clause(cypher_transform_context *ctx, cypher_with *with)
* all-null row instead of 0 rows. (With6 [6]/[7].) */
if (find_aggregating_call(item->expr) != NULL) {
has_aggregate = true;
} else if (grp_expr && grp_expr[0]) {
/* A non-aggregate COMPUTED key (e.g. `a.num2 % 3 AS mod`) is a
* grouping key — emit it into GROUP BY just like simple
* identifier/property keys. Without this the key was projected
* but never grouped, so an aggregating WITH collapsed all rows
* into one group (WithOrderBy4 [11]/[12]). */
if (group_count > 0) dbuf_append(&group_buf, ", ");
dbuf_append(&group_buf, grp_expr);
group_count++;
}
free(grp_expr);
}
}

Expand Down
Loading