| 4896 | /* ── Main entry point ─────────────────────────────────────────── */ |
| 4897 | |
| 4898 | int cbm_cypher_execute(cbm_store_t *store, const char *query, const char *project, int max_rows, |
| 4899 | cbm_cypher_result_t *out) { |
| 4900 | memset(out, 0, sizeof(*out)); |
| 4901 | g_cypher_depth_clamped = 0; |
| 4902 | cypher_deadline_arm(); /* #601: start the wall-clock budget for this query */ |
| 4903 | if (max_rows <= 0) { |
| 4904 | max_rows = CYPHER_RESULT_CEILING; |
| 4905 | } |
| 4906 | |
| 4907 | cbm_query_t *q = NULL; |
| 4908 | char *err = NULL; |
| 4909 | if (cbm_cypher_parse(query, &q, &err) < 0) { |
| 4910 | out->error = err; |
| 4911 | return CBM_NOT_FOUND; |
| 4912 | } |
| 4913 | |
| 4914 | result_builder_t rb = {0}; |
| 4915 | if (execute_single(store, q, project, max_rows, &rb) < 0) { |
| 4916 | rb_free(&rb); |
| 4917 | cbm_query_free(q); |
| 4918 | out->error = heap_strdup("query aborted: out of memory or an allocation limit was reached"); |
| 4919 | return CBM_NOT_FOUND; |
| 4920 | } |
| 4921 | |
| 4922 | /* UNION chain */ |
| 4923 | cbm_query_t *uq = q->union_next; |
| 4924 | while (uq) { |
| 4925 | result_builder_t rb2 = {0}; |
| 4926 | if (execute_single(store, uq, project, max_rows, &rb2) < 0) { |
| 4927 | rb_free(&rb); |
| 4928 | rb_free(&rb2); |
| 4929 | cbm_query_free(q); |
| 4930 | out->error = |
| 4931 | heap_strdup("query aborted: out of memory or an allocation limit was reached"); |
| 4932 | return CBM_NOT_FOUND; |
| 4933 | } |
| 4934 | /* Concatenate rows from rb2 into rb */ |
| 4935 | for (int i = 0; i < rb2.row_count; i++) { |
| 4936 | rb_add_row(&rb, rb2.rows[i]); |
| 4937 | } |
| 4938 | rb_free(&rb2); |
| 4939 | |
| 4940 | uq = uq->union_next; |
| 4941 | } |
| 4942 | |
| 4943 | /* UNION (not ALL) deduplication */ |
| 4944 | if (q->union_next && !q->union_all) { |
| 4945 | rb_apply_distinct(&rb); |
| 4946 | } |
| 4947 | |
| 4948 | /* #601: abort a runaway query that blew the wall-clock budget before it can |
| 4949 | * return a misleading partial result. Checked before the row ceiling. */ |
| 4950 | if (g_cypher_timed_out) { |
| 4951 | rb_free(&rb); |
| 4952 | cbm_query_free(q); |
| 4953 | out->error = |
| 4954 | heap_strdup("query exceeded the execution time limit — narrow the pattern with a WHERE " |
| 4955 | "filter, use a directed MATCH instead of an unbounded OPTIONAL MATCH, or " |