@@ -1802,8 +1802,16 @@ static int parse_return_or_with(parser_t *p, cbm_return_clause_t **out, bool is_
18021802 /* Projection is materialized per row into fixed-width stack arrays sized at
18031803 * CBM_SZ_32 columns (execute_return_simple and its siblings). Bound the
18041804 * parsed item count to that width so an over-wide RETURN is rejected here
1805- * instead of writing past those arrays downstream. */
1806- if (r -> count > CBM_SZ_32 ) {
1805+ * instead of writing past those arrays downstream.
1806+ *
1807+ * WITH is bounded tighter, by CYP_MAX_VARS. Every item a WITH projects
1808+ * becomes one variable of the binding that carries the rest of the query,
1809+ * and binding_t holds exactly CYP_MAX_VARS variables. A wider WITH used to
1810+ * parse, then lose every alias past the 16th in with_add_vbinding_var and
1811+ * answer with silently blank columns. Refuse it here, the same way an
1812+ * over-wide RETURN is refused, so the caller sees an error instead of a
1813+ * short or empty result. */
1814+ if (r -> count > (is_with ? CYP_MAX_VARS : CBM_SZ_32 )) {
18071815 free_return_clause (r );
18081816 return CBM_NOT_FOUND ;
18091817 }
@@ -4193,17 +4201,34 @@ static void execute_with_clause(cbm_query_t *q, binding_t **bindings_ptr, int *b
41934201
41944202/* Project RETURN * — all bound variable properties */
41954203/* Collect all variable names from query patterns */
4204+ /* Has this variable already been collected? A query may name the same variable
4205+ * in more than one pattern, and RETURN * must give it one set of columns. */
4206+ static bool star_var_seen (const char * * vars , int vc , const char * name ) {
4207+ for (int i = 0 ; i < vc ; i ++ ) {
4208+ if (strcmp (vars [i ], name ) == 0 ) {
4209+ return true;
4210+ }
4211+ }
4212+ return false;
4213+ }
4214+
4215+ /* Collect the variables a RETURN * projects, in the order the query names them
4216+ * and with no repeats. Without the repeat check, `MATCH (f) OPTIONAL MATCH
4217+ * (f)-[:CALLS]->(g)` names f in two patterns and f gets its four columns
4218+ * twice. */
41964219static int collect_pattern_vars (cbm_query_t * q , const char * * vars , int max_vars ) {
41974220 int vc = 0 ;
41984221 for (int pi = 0 ; pi < q -> pattern_count ; pi ++ ) {
41994222 for (int ni = 0 ; ni < q -> patterns [pi ].node_count && vc < max_vars ; ni ++ ) {
4200- if (q -> patterns [pi ].nodes [ni ].variable ) {
4201- vars [vc ++ ] = q -> patterns [pi ].nodes [ni ].variable ;
4223+ const char * var = q -> patterns [pi ].nodes [ni ].variable ;
4224+ if (var && !star_var_seen (vars , vc , var )) {
4225+ vars [vc ++ ] = var ;
42024226 }
42034227 }
42044228 for (int ri = 0 ; ri < q -> patterns [pi ].rel_count && vc < max_vars ; ri ++ ) {
4205- if (q -> patterns [pi ].rels [ri ].variable ) {
4206- vars [vc ++ ] = q -> patterns [pi ].rels [ri ].variable ;
4229+ const char * var = q -> patterns [pi ].rels [ri ].variable ;
4230+ if (var && !star_var_seen (vars , vc , var )) {
4231+ vars [vc ++ ] = var ;
42074232 }
42084233 }
42094234 }
@@ -4255,8 +4280,43 @@ static void project_star_row(binding_t *b, const char **vars, int vc, const char
42554280 }
42564281}
42574282
4283+ /* RETURN * after a WITH.
4284+ *
4285+ * The pattern's variables are out of scope by this point — the WITH replaced
4286+ * them with the names it made. Each of those names holds one value, not a
4287+ * node, so each is ONE column rather than the four a node variable gets.
4288+ *
4289+ * Reading the pattern here instead is the fault this function exists to avoid:
4290+ * it named variables the bindings no longer hold, found nothing for every one
4291+ * of them, and answered a full result of empty strings with no error. */
4292+ static void execute_return_star_after_with (cbm_query_t * q , binding_t * bindings , int bind_count ,
4293+ int max_rows , result_builder_t * rb ) {
4294+ cbm_return_clause_t * wc = q -> with_clause ;
4295+ char name_bufs [CYP_MAX_VARS ][CBM_SZ_128 ];
4296+ const char * cols [CYP_MAX_VARS ];
4297+ /* parse_return_or_with refuses a WITH wider than CYP_MAX_VARS, so this
4298+ * clamp cannot fire. It stays as the bound this function relies on. */
4299+ int col_n = wc -> count < CYP_MAX_VARS ? wc -> count : CYP_MAX_VARS ;
4300+ for (int i = 0 ; i < col_n ; i ++ ) {
4301+ cols [i ] = resolve_item_alias (& wc -> items [i ], name_bufs [i ], sizeof (name_bufs [i ]));
4302+ }
4303+ rb_set_columns (rb , cols , col_n );
4304+ for (int bi = 0 ; bi < bind_count && rb -> row_count < max_rows ; bi ++ ) {
4305+ const char * vals [CYP_MAX_VARS ];
4306+ for (int i = 0 ; i < col_n ; i ++ ) {
4307+ cbm_node_t * vn = binding_get (& bindings [bi ], cols [i ]);
4308+ vals [i ] = vn && vn -> name ? vn -> name : "" ;
4309+ }
4310+ rb_add_row (rb , vals );
4311+ }
4312+ }
4313+
42584314static void execute_return_star (cbm_query_t * q , binding_t * bindings , int bind_count , int max_rows ,
42594315 result_builder_t * rb ) {
4316+ if (q -> with_clause ) {
4317+ execute_return_star_after_with (q , bindings , bind_count , max_rows , rb );
4318+ return ;
4319+ }
42604320 const char * vars [CBM_SZ_32 ];
42614321 int vc = collect_pattern_vars (q , vars , CBM_SZ_32 );
42624322 build_star_columns (rb , vars , vc );
0 commit comments