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. */
| 1039 | * (e.g. WHERE NOT EXISTS { (f)<-[:CALLS]-() } finds functions with no callers). |
| 1040 | * Multi-hop / nested-WHERE EXISTS is intentionally unsupported. */ |
| 1041 | static cbm_expr_t *parse_exists_predicate(parser_t *p, bool negated) { |
| 1042 | advance(p); /* EXISTS */ |
| 1043 | if (!match(p, TOK_LBRACE)) { |
| 1044 | snprintf(p->error, sizeof(p->error), "expected '{' after EXISTS at pos %d", peek(p)->pos); |
| 1045 | return NULL; |
| 1046 | } |
| 1047 | cbm_node_pattern_t anchor = {0}; |
| 1048 | cbm_rel_pattern_t rel = {0}; |
| 1049 | cbm_node_pattern_t far_node = {0}; |
| 1050 | if (parse_node(p, &anchor) < 0 || parse_rel(p, &rel) < 0 || parse_node(p, &far_node) < 0) { |
| 1051 | free_one_node_pattern(&anchor); |
| 1052 | free_one_rel_pattern(&rel); |
| 1053 | free_one_node_pattern(&far_node); |
| 1054 | snprintf(p->error, sizeof(p->error), |
| 1055 | "unsupported EXISTS pattern — only the single-hop form " |
| 1056 | "'(var)-[:TYPE]->()' is supported"); |
| 1057 | return NULL; |
| 1058 | } |
| 1059 | expect(p, TOK_RBRACE); |
| 1060 | |
| 1061 | cbm_condition_t c = {0}; |
| 1062 | c.negated = negated; |
| 1063 | c.op = heap_strdup("EXISTS"); |
| 1064 | c.variable = heap_strdup(anchor.variable ? anchor.variable : ""); |
| 1065 | c.value = (rel.type_count > 0 && rel.types[0]) ? heap_strdup(rel.types[0]) : NULL; |
| 1066 | c.exists_dir = (rel.direction && strcmp(rel.direction, "inbound") == 0) ? 1 |
| 1067 | : (rel.direction && strcmp(rel.direction, "any") == 0) ? 2 |
| 1068 | : 0; |
| 1069 | free_one_node_pattern(&anchor); |
| 1070 | free_one_rel_pattern(&rel); |
| 1071 | free_one_node_pattern(&far_node); |
| 1072 | return expr_leaf(c); |
| 1073 | } |
| 1074 | |
| 1075 | /* Parse the operator + value tail shared by every condition subject |
| 1076 | * (var[.prop] and multi-arg functions like coalesce(...)): IS [NOT] NULL, |
no test coverage detected