Parse UNWIND [...] AS var clause into query */
| 1879 | |
| 1880 | /* Parse UNWIND [...] AS var clause into query */ |
| 1881 | static void parse_unwind_clause(parser_t *p, cbm_query_t *q) { |
| 1882 | advance(p); |
| 1883 | if (check(p, TOK_LBRACKET)) { |
| 1884 | /* Literal list: [1, 2, 3] — collect as JSON array string */ |
| 1885 | advance(p); |
| 1886 | char buf[CBM_SZ_2K] = "["; |
| 1887 | int blen = SKIP_ONE; |
| 1888 | /* snprintf returns the length it WOULD have written, so a single |
| 1889 | * oversized token (string literals lex up to CBM_SZ_4K-1) can push |
| 1890 | * blen past sizeof(buf). Clamp after every write and guard the raw |
| 1891 | * buf[blen++] stores, mirroring format_collect_list(). */ |
| 1892 | const int cap = (int)sizeof(buf); |
| 1893 | while (!check(p, TOK_RBRACKET) && !check(p, TOK_EOF)) { |
| 1894 | if (blen > SKIP_ONE && blen < cap - SKIP_ONE) { |
| 1895 | buf[blen++] = ','; |
| 1896 | } |
| 1897 | if (check(p, TOK_STRING)) { |
| 1898 | blen += snprintf(buf + blen, (size_t)(cap - blen), "\"%s\"", peek(p)->text); |
| 1899 | advance(p); |
| 1900 | } else if (check(p, TOK_NUMBER)) { |
| 1901 | blen += snprintf(buf + blen, (size_t)(cap - blen), "%s", peek(p)->text); |
| 1902 | advance(p); |
| 1903 | } else { |
| 1904 | advance(p); |
| 1905 | } |
| 1906 | if (blen >= cap) { |
| 1907 | blen = cap - SKIP_ONE; |
| 1908 | } |
| 1909 | match(p, TOK_COMMA); |
| 1910 | } |
| 1911 | expect(p, TOK_RBRACKET); |
| 1912 | if (blen < cap - SKIP_ONE) { |
| 1913 | buf[blen++] = ']'; |
| 1914 | } |
| 1915 | buf[blen] = '\0'; |
| 1916 | q->unwind_expr = heap_strdup(buf); |
| 1917 | } else if (check(p, TOK_IDENT)) { |
| 1918 | q->unwind_expr = heap_strdup(advance(p)->text); |
| 1919 | } |
| 1920 | expect(p, TOK_AS); |
| 1921 | const cbm_token_t *alias = expect(p, TOK_IDENT); |
| 1922 | if (alias) { |
| 1923 | q->unwind_alias = heap_strdup(alias->text); |
| 1924 | } |
| 1925 | } |
| 1926 | |
| 1927 | /* Parse a chain of MATCH / OPTIONAL MATCH patterns into query. |
| 1928 | * Returns -1 on error (fills p->error). */ |