Skip to content

Commit f07d3ad

Browse files
fix(cypher): support coalesce() and other multi-arg scalar functions in WHERE (#874)
Signed-off-by: Harshita Joshi <j.harshitaa06@gmail.com>
1 parent 2dca0a3 commit f07d3ad

3 files changed

Lines changed: 266 additions & 28 deletions

File tree

src/cypher/cypher.c

Lines changed: 132 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,13 @@ static void expr_free(cbm_expr_t *e) {
759759
safe_str_free(&cur->cond.in_values[i]);
760760
}
761761
free(cur->cond.in_values);
762+
safe_str_free(&cur->cond.func);
763+
for (int i = 0; i < cur->cond.arg_count; i++) {
764+
safe_str_free(&cur->cond.args[i].variable);
765+
safe_str_free(&cur->cond.args[i].property);
766+
safe_str_free(&cur->cond.args[i].literal);
767+
}
768+
free(cur->cond.args);
762769
}
763770
if (cur->right) {
764771
if (top < EXPR_FREE_STACK) {
@@ -837,6 +844,37 @@ static const char *unsupported_clause_error(cbm_token_type_t type) {
837844

838845
/* Forward declarations for recursive descent */
839846
static cbm_expr_t *parse_or_expr(parser_t *p);
847+
/* Multi-arg scalar function support, shared with the RETURN-item parser (#874) */
848+
static bool is_multiarg_func_call(parser_t *p);
849+
static int parse_multiarg_func_item(parser_t *p, cbm_return_item_t *item);
850+
851+
/* Free a multi-arg function argument array. */
852+
static void func_args_free(cbm_func_arg_t *args, int count) {
853+
for (int i = 0; i < count; i++) {
854+
safe_str_free(&args[i].variable);
855+
safe_str_free(&args[i].property);
856+
safe_str_free(&args[i].literal);
857+
}
858+
free(args);
859+
}
860+
861+
/* Free the fields of a partially-parsed multi-arg function item. */
862+
static void func_item_fields_free(cbm_return_item_t *item) {
863+
safe_str_free(&item->variable);
864+
safe_str_free(&item->property);
865+
safe_str_free(&item->func);
866+
func_args_free(item->args, item->arg_count);
867+
item->args = NULL;
868+
item->arg_count = 0;
869+
}
870+
871+
/* Free the function-call fields of a WHERE condition (#874). */
872+
static void cond_func_fields_free(cbm_condition_t *c) {
873+
safe_str_free(&c->func);
874+
func_args_free(c->args, c->arg_count);
875+
c->args = NULL;
876+
c->arg_count = 0;
877+
}
840878

841879
/* Parse IN [val, val, ...] list. Returns expr_leaf or NULL on error. */
842880
static cbm_expr_t *parse_in_list(parser_t *p, cbm_condition_t *c) {
@@ -1007,48 +1045,94 @@ static cbm_expr_t *parse_exists_predicate(parser_t *p, bool negated) {
10071045
return expr_leaf(c);
10081046
}
10091047

1010-
static cbm_expr_t *parse_condition_expr(parser_t *p) {
1011-
/* Check for NOT prefix at condition level (e.g. NOT n.name CONTAINS "x") */
1012-
bool negated = match(p, TOK_NOT);
1048+
/* parse_condition_lhs result: the label-test form is a complete condition. */
1049+
enum { COND_LHS_COMPLETE = 1 };
1050+
1051+
/* Parse the left-hand side of a WHERE condition into c.
1052+
* Returns CBM_NOT_FOUND on error, 0 when an operator/value should follow, and
1053+
* COND_LHS_COMPLETE when the condition is already complete (label test). */
1054+
static int parse_condition_lhs(parser_t *p, cbm_condition_t *c) {
1055+
if (is_multiarg_func_call(p)) {
1056+
/* Multi-arg scalar function LHS: coalesce(f.depth, 0) >= 2 (#874).
1057+
* Reuse the RETURN-item parser, then move ownership into the condition. */
1058+
cbm_return_item_t fitem;
1059+
memset(&fitem, 0, sizeof(fitem));
1060+
if (parse_multiarg_func_item(p, &fitem) < 0) {
1061+
func_item_fields_free(&fitem);
1062+
return CBM_NOT_FOUND;
1063+
}
1064+
c->variable = fitem.variable;
1065+
c->property = fitem.property;
1066+
c->func = fitem.func;
1067+
c->args = fitem.args;
1068+
c->arg_count = fitem.arg_count;
1069+
return 0;
1070+
}
10131071

1014-
/* EXISTS { pattern } predicate (anchored single-hop existence). */
1015-
if (check(p, TOK_EXISTS)) {
1016-
return parse_exists_predicate(p, negated);
1072+
if (check(p, TOK_IDENT) && p->pos + SKIP_ONE < p->count &&
1073+
p->tokens[p->pos + SKIP_ONE].type == TOK_LPAREN) {
1074+
/* Unrecognised function call in WHERE — fail loudly with the supported
1075+
* set instead of the misleading "unexpected operator" (#874). */
1076+
snprintf(p->error, sizeof(p->error),
1077+
"unsupported function '%s' in WHERE (supported: coalesce, substring, replace, "
1078+
"left, right)",
1079+
peek(p)->text);
1080+
return CBM_NOT_FOUND;
10171081
}
10181082

10191083
const cbm_token_t *var = expect(p, TOK_IDENT);
10201084
if (!var) {
1021-
return NULL;
1085+
return CBM_NOT_FOUND;
10221086
}
10231087

1024-
cbm_condition_t c = {0};
1025-
c.negated = negated;
1026-
10271088
/* Label test: WHERE n:Label (openCypher, #241). Modelled as a leaf with
10281089
* op="HAS_LABEL" and value=Label, evaluated against the bound node's label. */
10291090
if (check(p, TOK_COLON)) {
10301091
advance(p);
10311092
const cbm_token_t *lbl = expect(p, TOK_IDENT);
10321093
if (!lbl) {
1033-
return NULL;
1094+
return CBM_NOT_FOUND;
10341095
}
1035-
c.variable = heap_strdup(var->text);
1036-
c.op = heap_strdup("HAS_LABEL");
1037-
c.value = heap_strdup(lbl->text);
1038-
return expr_leaf(c);
1096+
c->variable = heap_strdup(var->text);
1097+
c->op = heap_strdup("HAS_LABEL");
1098+
c->value = heap_strdup(lbl->text);
1099+
return COND_LHS_COMPLETE;
10391100
}
10401101

10411102
if (match(p, TOK_DOT)) {
10421103
const cbm_token_t *prop = expect(p, TOK_IDENT);
10431104
if (!prop) {
1044-
return NULL;
1105+
return CBM_NOT_FOUND;
10451106
}
1046-
c.variable = heap_strdup(var->text);
1047-
c.property = heap_strdup(prop->text);
1107+
c->variable = heap_strdup(var->text);
1108+
c->property = heap_strdup(prop->text);
10481109
} else {
10491110
/* No dot: bare alias (e.g. post-WITH variable like "cnt") */
1050-
c.variable = heap_strdup(var->text);
1051-
c.property = NULL;
1111+
c->variable = heap_strdup(var->text);
1112+
c->property = NULL;
1113+
}
1114+
return 0;
1115+
}
1116+
1117+
static cbm_expr_t *parse_condition_expr(parser_t *p) {
1118+
/* Check for NOT prefix at condition level (e.g. NOT n.name CONTAINS "x") */
1119+
bool negated = match(p, TOK_NOT);
1120+
1121+
/* EXISTS { pattern } predicate (anchored single-hop existence). */
1122+
if (check(p, TOK_EXISTS)) {
1123+
return parse_exists_predicate(p, negated);
1124+
}
1125+
1126+
cbm_condition_t c = {0};
1127+
c.negated = negated;
1128+
1129+
int lhs_rc = parse_condition_lhs(p, &c);
1130+
if (lhs_rc < 0) {
1131+
return NULL;
1132+
}
1133+
if (lhs_rc > 0) {
1134+
/* HAS_LABEL leaf — complete condition, no operator follows */
1135+
return expr_leaf(c);
10521136
}
10531137

10541138
/* IS NULL / IS NOT NULL */
@@ -1066,13 +1150,19 @@ static cbm_expr_t *parse_condition_expr(parser_t *p) {
10661150

10671151
/* IN [...] */
10681152
if (check(p, TOK_IN)) {
1069-
return parse_in_list(p, &c);
1153+
cbm_expr_t *e = parse_in_list(p, &c);
1154+
if (!e) {
1155+
/* parse_in_list frees variable/property/op; func fields are ours */
1156+
cond_func_fields_free(&c);
1157+
}
1158+
return e;
10701159
}
10711160

10721161
/* Standard operators */
10731162
c.op = parse_comparison_op(p);
10741163
if (!c.op) {
10751164
snprintf(p->error, sizeof(p->error), "unexpected operator at pos %d", peek(p)->pos);
1165+
cond_func_fields_free(&c);
10761166
safe_str_free(&c.variable);
10771167
safe_str_free(&c.property);
10781168
return NULL;
@@ -1089,6 +1179,7 @@ static cbm_expr_t *parse_condition_expr(parser_t *p) {
10891179
c.value = heap_strdup("false");
10901180
} else {
10911181
snprintf(p->error, sizeof(p->error), "expected value at pos %d", peek(p)->pos);
1182+
cond_func_fields_free(&c);
10921183
safe_str_free(&c.variable);
10931184
safe_str_free(&c.property);
10941185
safe_str_free(&c.op);
@@ -1949,6 +2040,8 @@ static void free_where(cbm_where_clause_t *w) {
19492040
safe_str_free(&w->conditions[i].in_values[j]);
19502041
}
19512042
free(w->conditions[i].in_values);
2043+
safe_str_free(&w->conditions[i].func);
2044+
func_args_free(w->conditions[i].args, w->conditions[i].arg_count);
19522045
}
19532046
free(w->conditions);
19542047
safe_str_free(&w->op);
@@ -2338,8 +2431,26 @@ static void binding_set(binding_t *b, const char *var, const cbm_node_t *node) {
23382431
b->var_count++;
23392432
}
23402433

2434+
static const char *eval_multiarg_func(binding_t *b, const cbm_return_item_t *item, char *buf,
2435+
size_t bufsz);
2436+
23412437
/* Resolve the actual property value for a condition from a binding */
23422438
static const char *resolve_condition_value(const cbm_condition_t *c, binding_t *b) {
2439+
/* Multi-arg scalar function LHS: coalesce(f.depth, 0) >= 2 (#874).
2440+
* Evaluated through the same code path as RETURN projections. The value is
2441+
* consumed by eval_condition before any other condition is resolved, so a
2442+
* single thread-local buffer per call is safe. */
2443+
if (c->func) {
2444+
static _Thread_local char func_buf[CBM_SZ_512];
2445+
cbm_return_item_t item = {0};
2446+
item.variable = c->variable;
2447+
item.property = c->property;
2448+
item.func = c->func;
2449+
item.args = c->args;
2450+
item.arg_count = c->arg_count;
2451+
return eval_multiarg_func(b, &item, func_buf, sizeof(func_buf));
2452+
}
2453+
23432454
cbm_edge_t *e = binding_get_edge(b, c->variable);
23442455
if (e) {
23452456
return edge_prop(e, c->property);

src/cypher/cypher.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ typedef struct {
181181
int rel_count;
182182
} cbm_pattern_t;
183183

184+
/* One argument to a multi-argument scalar function (coalesce, substring, ...). */
185+
typedef struct {
186+
const char *variable; /* variable reference (NULL if a literal) */
187+
const char *property; /* property of the variable (NULL if whole var / literal) */
188+
const char *literal; /* literal string/number text (NULL if a variable ref) */
189+
} cbm_func_arg_t;
190+
184191
/* WHERE condition */
185192
typedef struct {
186193
const char *variable;
@@ -195,6 +202,11 @@ typedef struct {
195202
* anchor, `value` the edge type (NULL = any), `exists_dir` the direction
196203
* (0 = outbound, 1 = inbound, 2 = any). */
197204
int exists_dir;
205+
/* Multi-arg scalar function on the LHS, e.g. coalesce(f.depth, 0) >= 2
206+
* (#874). NULL func = plain variable/property LHS. */
207+
const char *func;
208+
cbm_func_arg_t *args;
209+
int arg_count;
198210
} cbm_condition_t;
199211

200212
/* Expression tree for WHERE clause */
@@ -234,13 +246,6 @@ typedef struct {
234246
const char *else_val; /* NULL if no ELSE */
235247
} cbm_case_expr_t;
236248

237-
/* One argument to a multi-argument scalar function (coalesce, substring, ...). */
238-
typedef struct {
239-
const char *variable; /* variable reference (NULL if a literal) */
240-
const char *property; /* property of the variable (NULL if whole var / literal) */
241-
const char *literal; /* literal string/number text (NULL if a variable ref) */
242-
} cbm_func_arg_t;
243-
244249
/* RETURN item */
245250
typedef struct {
246251
const char *variable;

0 commit comments

Comments
 (0)