mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-08 08:53:43 +08:00
fix(cypher): reject a non-numeric SKIP/LIMIT operand instead of dropping it
expect() fills p->error and returns NULL when the token after SKIP or LIMIT
is not a number, but both call sites only tested `if (num)` and then fell
through to `*out = r; return 0;`. r->limit kept the -1 "unset" initialiser,
which execution reads as "no LIMIT":
rb_apply_skip_limit(rb, ret->skip, ret->limit >= 0 ? ret->limit : max_rows);
So `RETURN n.name LIMIT $limit` ran unbounded and answered isError:false with
the whole result set. `SKIP $offset LIMIT 10` lost both clauses at once,
because the orphaned operand also blocked the following match(p, TOK_LIMIT).
Propagate the failure the way the ORDER BY branch directly above already
does. This is the failure mode #1334 banned, reached by a different route.
Fixes #1994
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: metehanulusoy <ulusoy.metehan03@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5fbab7bb73
commit
3d9a6cf5cb
+13
-6
@@ -1817,20 +1817,27 @@ tail:
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional SKIP */
|
||||
/* Optional SKIP. A non-numeric operand is a loud parse error: expect() has
|
||||
* already filled p->error, and dropping the clause instead would leave
|
||||
* r->skip at its "none" default while the query still reports success. */
|
||||
if (match(p, TOK_SKIP)) {
|
||||
const cbm_token_t *num = expect(p, TOK_NUMBER);
|
||||
if (num) {
|
||||
r->skip = (int)strtol(num->text, NULL, CBM_DECIMAL_BASE);
|
||||
if (!num) {
|
||||
free_return_clause(r);
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
r->skip = (int)strtol(num->text, NULL, CBM_DECIMAL_BASE);
|
||||
}
|
||||
|
||||
/* Optional LIMIT */
|
||||
/* Optional LIMIT. Same rule: a dropped LIMIT is the failure mode #1334
|
||||
* banned - the caller would silently receive the whole result set. */
|
||||
if (match(p, TOK_LIMIT)) {
|
||||
const cbm_token_t *num = expect(p, TOK_NUMBER);
|
||||
if (num) {
|
||||
r->limit = (int)strtol(num->text, NULL, CBM_DECIMAL_BASE);
|
||||
if (!num) {
|
||||
free_return_clause(r);
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
r->limit = (int)strtol(num->text, NULL, CBM_DECIMAL_BASE);
|
||||
}
|
||||
|
||||
*out = r;
|
||||
|
||||
@@ -420,6 +420,53 @@ TEST(cypher_parse_order_by_over_cap_rejected_issue1334) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1994: a non-numeric SKIP/LIMIT operand must be a loud parse error too. The old
|
||||
* failure mode was the same one #1334 banned by a different route: expect()
|
||||
* returned NULL, the clause was dropped, and the query still reported success
|
||||
* with limit left at its -1 "no LIMIT" sentinel - so query_graph answered a
|
||||
* bounded query with the entire result set. Cypher parameters ($limit) are the
|
||||
* everyday trigger: they are what a Neo4j-shaped client writes by default. */
|
||||
TEST(cypher_parse_nonnumeric_limit_rejected_issue1994) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
int rc = cbm_cypher_parse("MATCH (f:Function) RETURN f.name LIMIT $limit", &q, &err);
|
||||
ASSERT(rc != 0);
|
||||
free(err);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cypher_parse_nonnumeric_skip_rejected_issue1994) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
/* The orphaned operand also swallowed the LIMIT that followed it. */
|
||||
int rc = cbm_cypher_parse("MATCH (f:Function) RETURN f.name SKIP $offset LIMIT 10", &q, &err);
|
||||
ASSERT(rc != 0);
|
||||
free(err);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cypher_parse_word_limit_operand_rejected_issue1994) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
int rc = cbm_cypher_parse("MATCH (f:Function) RETURN f.name LIMIT abc", &q, &err);
|
||||
ASSERT(rc != 0);
|
||||
free(err);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Control: a well-formed SKIP/LIMIT still parses and still carries its values. */
|
||||
TEST(cypher_parse_numeric_skip_limit_still_accepted) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
int rc = cbm_cypher_parse("MATCH (f:Function) RETURN f.name SKIP 2 LIMIT 10", &q, &err);
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_EQ(q->ret->skip, 2);
|
||||
ASSERT_EQ(q->ret->limit, 10);
|
||||
|
||||
cbm_query_free(q);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cypher_parse_return_distinct) {
|
||||
cbm_query_t *q = NULL;
|
||||
char *err = NULL;
|
||||
@@ -4116,6 +4163,10 @@ SUITE(cypher) {
|
||||
RUN_TEST(cypher_parse_return_order_limit);
|
||||
RUN_TEST(cypher_parse_multikey_order_by_issue1334);
|
||||
RUN_TEST(cypher_parse_order_by_over_cap_rejected_issue1334);
|
||||
RUN_TEST(cypher_parse_nonnumeric_limit_rejected_issue1994);
|
||||
RUN_TEST(cypher_parse_nonnumeric_skip_rejected_issue1994);
|
||||
RUN_TEST(cypher_parse_word_limit_operand_rejected_issue1994);
|
||||
RUN_TEST(cypher_parse_numeric_skip_limit_still_accepted);
|
||||
RUN_TEST(cypher_parse_return_distinct);
|
||||
RUN_TEST(cypher_parse_inline_props);
|
||||
RUN_TEST(cypher_parse_error);
|
||||
|
||||
Reference in New Issue
Block a user