@@ -138,20 +138,160 @@ convert_out_generic(ch_convert_output_state * state, Datum val)
138138 return val ;
139139}
140140
141+ /*
142+ * Walk nested ch_binary_array_t into a flat datum buffer, verifying each
143+ * level matches the dims taken from the first child. Returns false if the
144+ * shape is jagged so the caller can fall back to a slower path.
145+ */
146+ static bool
147+ flatten_nested_array (ch_binary_array_t * slot , int * dims , int level ,
148+ Datum * values , bool * nulls , size_t * idx )
149+ {
150+ if ((int ) slot -> len != dims [level ])
151+ return false;
152+
153+ if (slot -> ndim == 1 )
154+ {
155+ for (size_t i = 0 ; i < slot -> len ; i ++ )
156+ {
157+ values [* idx ] = slot -> datums [i ];
158+ nulls [* idx ] = slot -> nulls [i ];
159+ (* idx )++ ;
160+ }
161+ }
162+ else
163+ {
164+ for (size_t i = 0 ; i < slot -> len ; i ++ )
165+ {
166+ ch_binary_array_t * child = (ch_binary_array_t * ) DatumGetPointer (slot -> datums [i ]);
167+
168+ if (!flatten_nested_array (child , dims , level + 1 , values , nulls , idx ))
169+ return false;
170+ }
171+ }
172+ return true;
173+ }
174+
175+ /*
176+ * Emit a nested ch_binary_array_t as a postgres array text literal, quoting
177+ * each leaf and escaping `\` and `"`. Used as the jagged fallback so binary
178+ * surfaces the same array_in malformed-literal error as the http path.
179+ */
180+ static void
181+ emit_nested_array_text (ch_binary_array_t * slot , FmgrInfo * outfn , StringInfo buf )
182+ {
183+ appendStringInfoChar (buf , '{' );
184+ for (size_t i = 0 ; i < slot -> len ; i ++ )
185+ {
186+ if (i > 0 )
187+ appendStringInfoChar (buf , ',' );
188+
189+ if (slot -> ndim > 1 )
190+ {
191+ ch_binary_array_t * child = (ch_binary_array_t * ) DatumGetPointer (slot -> datums [i ]);
192+
193+ emit_nested_array_text (child , outfn , buf );
194+ }
195+ else if (slot -> nulls [i ])
196+ appendStringInfoString (buf , "NULL" );
197+ else
198+ {
199+ char * s = OutputFunctionCall (outfn , slot -> datums [i ]);
200+
201+ appendStringInfoChar (buf , '"' );
202+ for (char * p = s ; * p ; p ++ )
203+ {
204+ if (* p == '"' || * p == '\\' )
205+ appendStringInfoChar (buf , '\\' );
206+ appendStringInfoChar (buf , * p );
207+ }
208+ appendStringInfoChar (buf , '"' );
209+ pfree (s );
210+ }
211+ }
212+ appendStringInfoChar (buf , '}' );
213+ }
214+
141215static Datum
142216convert_array (ch_convert_state * state , Datum val )
143217{
144218 ch_binary_array_t * slot = (ch_binary_array_t * ) DatumGetPointer (val );
145219
146220 if (slot -> len == 0 )
147- val = PointerGetDatum (construct_empty_array (state -> intype ));
148- else
221+ val = PointerGetDatum (construct_empty_array (slot -> item_type ));
222+ else if ( slot -> ndim == 1 )
149223 {
150224 void * arrout = construct_array (slot -> datums , slot -> len , slot -> item_type ,
151225 state -> typlen , state -> typbyval , state -> typalign );
152226
153227 val = PointerGetDatum (arrout );
154228 }
229+ else
230+ {
231+ int dims [MAXDIM ];
232+ int lbs [MAXDIM ];
233+ size_t total = 1 ;
234+ size_t idx = 0 ;
235+ Datum * flat ;
236+ bool * flatnulls ;
237+ ch_binary_array_t * probe = slot ;
238+
239+ if (slot -> ndim > MAXDIM )
240+ ereport (ERROR ,
241+ (errcode (ERRCODE_PROGRAM_LIMIT_EXCEEDED ),
242+ errmsg ("pg_clickhouse: nested array depth %d exceeds maximum %d" ,
243+ slot -> ndim , MAXDIM )));
244+
245+ for (int d = 0 ; d < slot -> ndim ; d ++ )
246+ {
247+ dims [d ] = (int ) probe -> len ;
248+ lbs [d ] = 1 ;
249+ total *= probe -> len ;
250+ if (probe -> ndim > 1 && probe -> len > 0 )
251+ probe = (ch_binary_array_t * ) DatumGetPointer (probe -> datums [0 ]);
252+ }
253+
254+ if (total == 0 )
255+ val = PointerGetDatum (construct_empty_array (slot -> item_type ));
256+ else
257+ {
258+ flat = palloc (sizeof (Datum ) * total );
259+ flatnulls = palloc0 (sizeof (bool ) * total );
260+
261+ if (flatten_nested_array (slot , dims , 0 , flat , flatnulls , & idx ))
262+ val = PointerGetDatum (construct_md_array (flat , flatnulls , slot -> ndim ,
263+ dims , lbs , slot -> item_type ,
264+ state -> typlen , state -> typbyval ,
265+ state -> typalign ));
266+ else
267+ {
268+ /*
269+ * Jagged shape: format as text and route through array_in so
270+ * binary surfaces the same malformed-literal error as http.
271+ */
272+ StringInfoData buf ;
273+ FmgrInfo outfn ;
274+ Oid out_func ;
275+ Oid in_func ;
276+ Oid ioparam ;
277+ bool varlena ;
278+
279+ pfree (flat );
280+ pfree (flatnulls );
281+
282+ getTypeOutputInfo (slot -> item_type , & out_func , & varlena );
283+ fmgr_info (out_func , & outfn );
284+
285+ initStringInfo (& buf );
286+ emit_nested_array_text (slot , & outfn , & buf );
287+
288+ getTypeInputInfo (state -> intype , & in_func , & ioparam );
289+ val = OidInputFunctionCall (in_func , buf .data , ioparam , -1 );
290+
291+ pfree (buf .data );
292+ }
293+ }
294+ }
155295
156296 return convert_generic (state , val );
157297}
@@ -501,6 +641,7 @@ ch_binary_do_output_conversion(ch_binary_insert_state * insert_state,
501641
502642 arr = palloc (sizeof (ch_binary_array_t ));
503643 arr -> len = ArrayGetNItems (AARR_NDIM (v ), AARR_DIMS (v ));
644+ arr -> ndim = 1 ;
504645 arr -> datums = palloc (sizeof (Datum ) * arr -> len );
505646 arr -> nulls = palloc (sizeof (bool ) * arr -> len );
506647 arr -> item_type = cstate -> innertype ;
0 commit comments