|
35 | 35 |
|
36 | 36 | #include <ctype.h> |
37 | 37 | #include "foundation/compat_regex.h" |
| 38 | +#include <stddef.h> |
38 | 39 | #include <stdint.h> // int64_t |
39 | 40 | #include <stdio.h> |
40 | 41 | #include <stdlib.h> |
@@ -326,6 +327,13 @@ static bool lex_skip_whitespace_comments(const char *input, int len, int *i) { |
326 | 327 | } |
327 | 328 | return true; |
328 | 329 | } |
| 330 | + /* SQL-style -- single-line comment */ |
| 331 | + if (*i + SKIP_ONE < len && input[*i] == '-' && input[*i + SKIP_ONE] == '-') { |
| 332 | + while (*i < len && input[*i] != '\n') { |
| 333 | + (*i)++; |
| 334 | + } |
| 335 | + return true; |
| 336 | + } |
329 | 337 | if (*i + SKIP_ONE < len && input[*i] == '/' && input[*i + SKIP_ONE] == '*') { |
330 | 338 | *i += PAIR_LEN; |
331 | 339 | while (*i + SKIP_ONE < len && !(input[*i] == '*' && input[*i + SKIP_ONE] == '/')) { |
@@ -1639,35 +1647,58 @@ typedef struct { |
1639 | 1647 | const char *edge_var_names[CYP_MAX_EDGE_VARS]; /* variable names (edges) */ |
1640 | 1648 | cbm_edge_t edge_vars[CYP_MAX_EDGE_VARS]; /* edge data */ |
1641 | 1649 | int edge_var_count; |
| 1650 | + cbm_store_t *store; /* for computing in_degree/out_degree on demand */ |
1642 | 1651 | } binding_t; |
1643 | 1652 |
|
1644 | | -/* Get node property by name */ |
1645 | | -static const char *node_prop(const cbm_node_t *n, const char *prop) { |
| 1653 | +/* Return a string field from a node by property name. NULL-safe. */ |
| 1654 | +static const char *node_string_field(const cbm_node_t *n, const char *prop) { |
| 1655 | + static const struct { |
| 1656 | + const char *key; |
| 1657 | + size_t offset; |
| 1658 | + } fields[] = { |
| 1659 | + {"name", offsetof(cbm_node_t, name)}, |
| 1660 | + {"qualified_name", offsetof(cbm_node_t, qualified_name)}, |
| 1661 | + {"label", offsetof(cbm_node_t, label)}, |
| 1662 | + {"file_path", offsetof(cbm_node_t, file_path)}, |
| 1663 | + }; |
| 1664 | + for (size_t i = 0; i < sizeof(fields) / sizeof(fields[0]); i++) { |
| 1665 | + if (strcmp(prop, fields[i].key) == 0) { |
| 1666 | + const char *val = *(const char **)((const char *)n + fields[i].offset); |
| 1667 | + return val ? val : ""; |
| 1668 | + } |
| 1669 | + } |
| 1670 | + return NULL; |
| 1671 | +} |
| 1672 | + |
| 1673 | +/* Get node property by name. |
| 1674 | + * store may be NULL; only needed for virtual degree properties. */ |
| 1675 | +static const char *node_prop(const cbm_node_t *n, const char *prop, cbm_store_t *store) { |
1646 | 1676 | if (!n || !prop) { |
1647 | 1677 | return ""; |
1648 | 1678 | } |
1649 | | - if (strcmp(prop, "name") == 0) { |
1650 | | - return n->name ? n->name : ""; |
1651 | | - } |
1652 | | - if (strcmp(prop, "qualified_name") == 0) { |
1653 | | - return n->qualified_name ? n->qualified_name : ""; |
1654 | | - } |
1655 | | - if (strcmp(prop, "label") == 0) { |
1656 | | - return n->label ? n->label : ""; |
1657 | | - } |
1658 | | - if (strcmp(prop, "file_path") == 0) { |
1659 | | - return n->file_path ? n->file_path : ""; |
| 1679 | + const char *str = node_string_field(n, prop); |
| 1680 | + if (str) { |
| 1681 | + return str; |
1660 | 1682 | } |
| 1683 | + /* Integer properties returned as strings. */ |
| 1684 | + static _Thread_local char int_buf[CBM_SZ_32]; |
1661 | 1685 | if (strcmp(prop, "start_line") == 0) { |
1662 | | - /* Return as string */ |
1663 | | - static char buf[CBM_SZ_32]; |
1664 | | - snprintf(buf, sizeof(buf), "%d", n->start_line); |
1665 | | - return buf; |
| 1686 | + snprintf(int_buf, sizeof(int_buf), "%d", n->start_line); |
| 1687 | + return int_buf; |
1666 | 1688 | } |
1667 | 1689 | if (strcmp(prop, "end_line") == 0) { |
1668 | | - static char buf[CBM_SZ_32]; |
1669 | | - snprintf(buf, sizeof(buf), "%d", n->end_line); |
1670 | | - return buf; |
| 1690 | + snprintf(int_buf, sizeof(int_buf), "%d", n->end_line); |
| 1691 | + return int_buf; |
| 1692 | + } |
| 1693 | + /* Virtual computed properties: in_degree/out_degree via CALLS edges. |
| 1694 | + * Enables Cypher dead-code detection: WHERE n.in_degree = '0'. */ |
| 1695 | + if (store && (strcmp(prop, "in_degree") == 0 || strcmp(prop, "out_degree") == 0)) { |
| 1696 | + int in_deg = 0; |
| 1697 | + int out_deg = 0; |
| 1698 | + cbm_store_node_degree(store, n->id, &in_deg, &out_deg); |
| 1699 | + int val = (strcmp(prop, "in_degree") == 0) ? in_deg : out_deg; |
| 1700 | + snprintf(int_buf, sizeof(int_buf), "%d", val); |
| 1701 | + return int_buf; |
1671 | 1702 | } |
1672 | 1703 | return ""; |
1673 | 1704 | } |
@@ -1827,6 +1858,7 @@ static void binding_copy(binding_t *dst, const binding_t *src) { |
1827 | 1858 | dst->edge_var_names[i] = src->edge_var_names[i]; /* AST-owned */ |
1828 | 1859 | edge_deep_copy(&dst->edge_vars[i], &src->edge_vars[i]); |
1829 | 1860 | } |
| 1861 | + dst->store = src->store; |
1830 | 1862 | } |
1831 | 1863 |
|
1832 | 1864 | /* Deep-copy a node into a binding (binding owns the strings) */ |
@@ -1858,7 +1890,7 @@ static const char *resolve_condition_value(const cbm_condition_t *c, binding_t * |
1858 | 1890 | return NULL; /* unbound variable */ |
1859 | 1891 | } |
1860 | 1892 | if (c->property) { |
1861 | | - return node_prop(n, c->property); |
| 1893 | + return node_prop(n, c->property, b->store); |
1862 | 1894 | } |
1863 | 1895 | /* Bare alias (e.g. post-WITH virtual var) — use node name directly */ |
1864 | 1896 | return n->name ? n->name : ""; |
@@ -1991,11 +2023,34 @@ static bool eval_where(const cbm_where_clause_t *w, binding_t *b) { |
1991 | 2023 | return is_and; |
1992 | 2024 | } |
1993 | 2025 |
|
1994 | | -/* Check inline property filters */ |
1995 | | -static bool check_inline_props(const cbm_node_t *n, const cbm_prop_filter_t *props, int count) { |
| 2026 | +/* Check if a string value looks like a regex pattern. */ |
| 2027 | +static bool looks_like_regex(const char *s) { |
| 2028 | + if (!s) { |
| 2029 | + return false; |
| 2030 | + } |
| 2031 | + return strstr(s, ".*") || strstr(s, ".+") || strchr(s, '[') || strchr(s, '(') || |
| 2032 | + strchr(s, '|') || strchr(s, '^') || strchr(s, '$'); |
| 2033 | +} |
| 2034 | + |
| 2035 | +/* Check inline property filters. |
| 2036 | + * Values that look like regex patterns are matched with POSIX ERE; |
| 2037 | + * plain values use exact strcmp. */ |
| 2038 | +static bool check_inline_props(const cbm_node_t *n, const cbm_prop_filter_t *props, int count, |
| 2039 | + cbm_store_t *store) { |
1996 | 2040 | for (int i = 0; i < count; i++) { |
1997 | | - const char *actual = node_prop(n, props[i].key); |
1998 | | - if (strcmp(actual, props[i].value) != 0) { |
| 2041 | + const char *actual = node_prop(n, props[i].key, store); |
| 2042 | + if (looks_like_regex(props[i].value)) { |
| 2043 | + cbm_regex_t re; |
| 2044 | + if (cbm_regcomp(&re, props[i].value, CBM_REG_EXTENDED | CBM_REG_NOSUB) == 0) { |
| 2045 | + bool matched = cbm_regexec(&re, actual, 0, NULL, 0) == 0; |
| 2046 | + cbm_regfree(&re); |
| 2047 | + if (!matched) { |
| 2048 | + return false; |
| 2049 | + } |
| 2050 | + } else if (strcmp(actual, props[i].value) != 0) { |
| 2051 | + return false; |
| 2052 | + } |
| 2053 | + } else if (strcmp(actual, props[i].value) != 0) { |
1999 | 2054 | return false; |
2000 | 2055 | } |
2001 | 2056 | } |
@@ -2068,7 +2123,7 @@ static const char *binding_get_virtual(binding_t *b, const char *var, const char |
2068 | 2123 | cbm_node_t *n = binding_get(b, var); |
2069 | 2124 | if (n) { |
2070 | 2125 | if (prop) { |
2071 | | - return node_prop(n, prop); |
| 2126 | + return node_prop(n, prop, b->store); |
2072 | 2127 | } |
2073 | 2128 | return n->name ? n->name : ""; |
2074 | 2129 | } |
@@ -2147,7 +2202,7 @@ static void scan_pattern_nodes(cbm_store_t *store, const char *project, int max_ |
2147 | 2202 | if (first->prop_count > 0) { |
2148 | 2203 | int kept = 0; |
2149 | 2204 | for (int i = 0; i < *out_count; i++) { |
2150 | | - if (check_inline_props(&(*out_nodes)[i], first->props, first->prop_count)) { |
| 2205 | + if (check_inline_props(&(*out_nodes)[i], first->props, first->prop_count, store)) { |
2151 | 2206 | if (kept != i) { |
2152 | 2207 | (*out_nodes)[kept] = (*out_nodes)[i]; |
2153 | 2208 | } |
@@ -2178,7 +2233,7 @@ static void process_edges(cbm_store_t *store, cbm_edge_t *edges, int edge_count, |
2178 | 2233 | node_fields_free(&found); |
2179 | 2234 | continue; |
2180 | 2235 | } |
2181 | | - if (!check_inline_props(&found, target_node->props, target_node->prop_count)) { |
| 2236 | + if (!check_inline_props(&found, target_node->props, target_node->prop_count, store)) { |
2182 | 2237 | node_fields_free(&found); |
2183 | 2238 | continue; |
2184 | 2239 | } |
@@ -2211,7 +2266,7 @@ static void expand_var_length(cbm_store_t *store, cbm_rel_pattern_t *rel, |
2211 | 2266 | if (target_node->label && strcmp(hop->node.label, target_node->label) != 0) { |
2212 | 2267 | continue; |
2213 | 2268 | } |
2214 | | - if (!check_inline_props(&hop->node, target_node->props, target_node->prop_count)) { |
| 2269 | + if (!check_inline_props(&hop->node, target_node->props, target_node->prop_count, store)) { |
2215 | 2270 | continue; |
2216 | 2271 | } |
2217 | 2272 | binding_t nb = {0}; |
@@ -3287,6 +3342,7 @@ static int execute_single(cbm_store_t *store, cbm_query_t *q, const char *projec |
3287 | 3342 |
|
3288 | 3343 | for (int i = 0; i < scan_count && bind_count < bind_cap; i++) { |
3289 | 3344 | binding_t b = {0}; |
| 3345 | + b.store = store; |
3290 | 3346 | binding_set(&b, var_name, &scanned[i]); |
3291 | 3347 | bool pass = !q->where || eval_where(q->where, &b); |
3292 | 3348 | if (pass) { |
|
0 commit comments