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