fix(cypher): LIMIT 0 returns 0 rows instead of all rows

The return-clause `limit` was zero-initialized (calloc) and every guard tested
`limit > 0`, so an explicit `LIMIT 0` was indistinguishable from "no LIMIT
clause" and returned ALL rows. Standard Cypher treats `LIMIT N` as an upper
bound, so `LIMIT 0` must return zero rows.

Default `limit` to the sentinel -1 ("no limit"); the parser still sets 0 for an
explicit `LIMIT 0`. Change the truncation guards (rb_apply_skip_limit,
bindings_skip_limit) and the apply call to `limit >= 0`, so limit 0 truncates to
0 while -1 returns all. The proj_cap optimization stays `> 0` (LIMIT 0 falls
through to full projection, then the final truncation yields 0).

Reproduced by repro_new_cypher_limit_zero (was RED): `RETURN ... LIMIT 0` must
yield row_count == 0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-06-27 02:20:47 +02:00
parent a044a71d59
commit 9ebddb718a
+7 -3
View File
@@ -1615,6 +1615,10 @@ static int parse_return_or_with(parser_t *p, cbm_return_clause_t **out, bool is_
}
cbm_return_clause_t *r = calloc(CBM_ALLOC_ONE, sizeof(cbm_return_clause_t));
/* -1 = no LIMIT clause (return all). An explicit `LIMIT 0` parses to 0 below
* and must return 0 rows — distinguishing the two requires a sentinel, since
* calloc zeroes limit and `limit > 0` would treat LIMIT 0 as "no limit". */
r->limit = -1;
int cap = CYP_INIT_CAP8;
r->items = malloc(cap * sizeof(cbm_return_item_t));
@@ -3092,7 +3096,7 @@ static void rb_apply_skip_limit(result_builder_t *rb, int skip_n, int limit) {
rb->row_count = 0;
}
/* Limit */
if (limit > 0 && rb->row_count > limit) {
if (limit >= 0 && rb->row_count > limit) {
for (int i = limit; i < rb->row_count; i++) {
for (int c = 0; c < rb->col_count; c++) {
safe_str_free(&rb->rows[i][c]);
@@ -3406,7 +3410,7 @@ static void bindings_skip_limit(binding_t *vbindings, int *count, int skip, int
}
*count = 0;
}
if (limit > 0 && *count > limit) {
if (limit >= 0 && *count > limit) {
for (int i = limit; i < *count; i++) {
binding_free(&vbindings[i]);
}
@@ -4246,7 +4250,7 @@ static void execute_return_clause(cbm_query_t *q, cbm_return_clause_t *ret, bind
}
rb_apply_order_by(rb, ret);
rb_apply_skip_limit(rb, ret->skip, ret->limit > 0 ? ret->limit : max_rows);
rb_apply_skip_limit(rb, ret->skip, ret->limit >= 0 ? ret->limit : max_rows);
if (ret->distinct) {
rb_apply_distinct(rb);
}