Parse a bounded EXISTS predicate: EXISTS { (anchor)-[:TYPE]->() } — a * single-hop, edge-type-specific existence check anchored on a bound variable * (e.g. WHERE NOT EXISTS { (f)<-[:CALLS]-() } finds functions with no callers). * Multi-hop / nested-WHERE EXISTS is intentionally unsupported. */
| 974 | * (e.g. WHERE NOT EXISTS { (f)<-[:CALLS]-() } finds functions with no callers). |
| 975 | * Multi-hop / nested-WHERE EXISTS is intentionally unsupported. */ |
| 976 | static cbm_expr_t *parse_exists_predicate(parser_t *p, bool negated) { |
| 977 | advance(p); /* EXISTS */ |
| 978 | if (!match(p, TOK_LBRACE)) { |
| 979 | snprintf(p->error, sizeof(p->error), "expected '{' after EXISTS at pos %d", peek(p)->pos); |
| 980 | return NULL; |
| 981 | } |
| 982 | cbm_node_pattern_t anchor = {0}; |
| 983 | cbm_rel_pattern_t rel = {0}; |
| 984 | cbm_node_pattern_t far = {0}; |
| 985 | if (parse_node(p, &anchor) < 0 || parse_rel(p, &rel) < 0 || parse_node(p, &far) < 0) { |
| 986 | free_one_node_pattern(&anchor); |
| 987 | free_one_rel_pattern(&rel); |
| 988 | free_one_node_pattern(&far); |
| 989 | snprintf(p->error, sizeof(p->error), |
| 990 | "unsupported EXISTS pattern — only the single-hop form " |
| 991 | "'(var)-[:TYPE]->()' is supported"); |
| 992 | return NULL; |
| 993 | } |
| 994 | expect(p, TOK_RBRACE); |
| 995 | |
| 996 | cbm_condition_t c = {0}; |
| 997 | c.negated = negated; |
| 998 | c.op = heap_strdup("EXISTS"); |
| 999 | c.variable = heap_strdup(anchor.variable ? anchor.variable : ""); |
| 1000 | c.value = (rel.type_count > 0 && rel.types[0]) ? heap_strdup(rel.types[0]) : NULL; |
| 1001 | c.exists_dir = (rel.direction && strcmp(rel.direction, "inbound") == 0) ? 1 |
| 1002 | : (rel.direction && strcmp(rel.direction, "any") == 0) ? 2 |
| 1003 | : 0; |
| 1004 | free_one_node_pattern(&anchor); |
| 1005 | free_one_rel_pattern(&rel); |
| 1006 | free_one_node_pattern(&far); |
| 1007 | return expr_leaf(c); |
| 1008 | } |
| 1009 | |
| 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") */ |
no test coverage detected