File Coverage

ClickHouse.xs
Criterion Covered Total %
statement 5 3803 0.1
branch 3 3140 0.1
condition n/a
subroutine n/a
pod n/a
total 8 6943 0.1


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4              
5             #include "EVAPI.h"
6             #include
7             #include
8             #include
9             #include
10             #include
11             #include
12             #include
13             #include
14             #include
15             #include
16             #include
17              
18             #define CH_MAX_DECOMPRESS_SIZE (128 * 1024 * 1024) /* 128 MB safety limit */
19              
20             #ifdef HAVE_LZ4
21             #include
22             #include "cityhash.h"
23              
24             #define CH_LZ4_METHOD 0x82
25             #define CH_CHECKSUM_SIZE 16
26             #define CH_COMPRESS_HEADER_SIZE 9 /* 1 (method) + 4 (compressed_size) + 4 (uncompressed_size) */
27             #endif
28              
29             #ifdef HAVE_OPENSSL
30             #include
31             #include
32             #include
33             #endif
34              
35             #include "ngx_queue.h"
36              
37             typedef struct ev_clickhouse_s ev_clickhouse_t;
38             typedef struct ev_ch_cb_s ev_ch_cb_t;
39             typedef struct ev_ch_send_s ev_ch_send_t;
40              
41             typedef ev_clickhouse_t* EV__ClickHouse;
42             typedef struct ev_loop* EV__Loop;
43              
44             #define EV_CH_MAGIC 0xC11C4011
45             #define EV_CH_FREED 0xFEEDFACE
46              
47             #define PROTO_HTTP 0
48             #define PROTO_NATIVE 1
49              
50             #define RECV_BUF_INIT 8192
51             #define SEND_BUF_INIT 4096
52              
53             /* ClickHouse native protocol client info */
54             #define CH_CLIENT_NAME "EV::ClickHouse"
55             #define CH_CLIENT_VERSION_MAJOR 0
56             #define CH_CLIENT_VERSION_MINOR 1
57             #define CH_CLIENT_REVISION 54459
58              
59             /* Protocol revision thresholds */
60             #define DBMS_MIN_REVISION_WITH_BLOCK_INFO 51903
61             #define DBMS_MIN_REVISION_WITH_SERVER_DISPLAY_NAME 54372
62             #define DBMS_MIN_REVISION_WITH_VERSION_PATCH 54401
63             #define DBMS_MIN_REVISION_WITH_PROGRESS_WRITES 54420
64             #define DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE 54423
65             #define DBMS_MIN_REVISION_WITH_OPENTELEMETRY 54442
66             #define DBMS_MIN_REVISION_WITH_CUSTOM_SERIALIZATION 54454
67             #define DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME 54449
68             #define DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM 54458
69             #define DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS 54459
70              
71             /* Client packet types */
72             #define CLIENT_HELLO 0
73             #define CLIENT_QUERY 1
74             #define CLIENT_DATA 2
75             #define CLIENT_CANCEL 3
76             #define CLIENT_PING 4
77              
78             /* Server packet types */
79             #define SERVER_HELLO 0
80             #define SERVER_DATA 1
81             #define SERVER_EXCEPTION 2
82             #define SERVER_PROGRESS 3
83             #define SERVER_PONG 4
84             #define SERVER_END_OF_STREAM 5
85             #define SERVER_PROFILE_INFO 6
86             #define SERVER_TOTALS 7
87             #define SERVER_EXTREMES 8
88             #define SERVER_LOG 10
89             #define SERVER_TABLE_COLUMNS 11
90             #define SERVER_PROFILE_EVENTS 14
91              
92             /* Query kind */
93             #define QUERY_INITIAL 1
94              
95             /* Query stage */
96             #define STAGE_COMPLETE 2
97              
98             /* Native protocol states */
99             #define NATIVE_IDLE 0
100             #define NATIVE_WAIT_HELLO 1
101             #define NATIVE_WAIT_RESULT 2
102             #define NATIVE_WAIT_INSERT_META 3
103              
104             /* Decode flags for column value formatting (opt-in) */
105             #define DECODE_DT_STR (1 << 0) /* Date/DateTime/DateTime64 → string */
106             #define DECODE_DEC_SCALE (1 << 1) /* Decimal → scaled NV */
107             #define DECODE_ENUM_STR (1 << 2) /* Enum → string label */
108             #define DECODE_NAMED_ROWS (1 << 3) /* results as arrayref of hashrefs */
109              
110             struct ev_clickhouse_s {
111             unsigned int magic;
112             struct ev_loop *loop;
113              
114             int fd;
115             ev_io rio, wio;
116             ev_timer timer;
117             int reading, writing, timing;
118             int connected, connecting;
119             int protocol; /* PROTO_HTTP or PROTO_NATIVE */
120              
121             #ifdef HAVE_OPENSSL
122             SSL_CTX *ssl_ctx;
123             SSL *ssl;
124             #endif
125             int tls_enabled;
126             char *tls_ca_file;
127              
128             /* connection params */
129             char *host, *user, *password, *database;
130             unsigned int port;
131              
132             /* send/recv buffers */
133             char *send_buf;
134             size_t send_len, send_pos, send_cap;
135             char *recv_buf;
136             size_t recv_len, recv_cap;
137              
138             /* native protocol state */
139             char *server_name;
140             char *server_display_name;
141             char *server_timezone;
142             unsigned int server_version_major, server_version_minor, server_revision;
143             unsigned int server_version_patch;
144             int native_state; /* NATIVE_IDLE, NATIVE_WAIT_HELLO, NATIVE_WAIT_RESULT, ... */
145             AV *native_rows; /* accumulate rows across Data blocks */
146             char *insert_data; /* pending TabSeparated data for two-phase INSERT */
147             size_t insert_data_len;
148             SV *insert_av; /* pending AV* of AV*s for arrayref INSERT */
149             char *insert_err; /* deferred error from unsupported INSERT encoding */
150              
151             /* queues */
152             ngx_queue_t cb_queue;
153             ngx_queue_t send_queue;
154             int pending_count;
155             int send_count;
156              
157             /* options */
158             char *session_id;
159             int compress;
160             double connect_timeout;
161             HV *default_settings; /* connection-level ClickHouse settings */
162              
163             SV *on_connect;
164             SV *on_error;
165             SV *on_progress;
166             SV *on_disconnect;
167             int tls_skip_verify;
168             double query_timeout;
169             int auto_reconnect;
170             uint32_t decode_flags;
171             AV *native_col_names; /* column names from last native result */
172             AV *native_col_types; /* column type strings from last native result */
173             SV *on_drain; /* callback fired when pending_count drops to 0 */
174             char *last_query_id; /* query_id of the last dispatched query */
175             SV *on_trace; /* debug trace callback */
176             ev_timer ka_timer; /* keepalive timer */
177             double keepalive; /* keepalive interval (0 = disabled) */
178             int ka_timing;
179             unsigned int ka_in_flight; /* keepalive pings sent but not yet ack'd */
180             int callback_depth;
181             /* error info from last SERVER_EXCEPTION or HTTP error */
182             int32_t last_error_code;
183             /* profile info from last SERVER_PROFILE_INFO */
184             uint64_t profile_rows;
185             uint64_t profile_bytes;
186             uint64_t profile_rows_before_limit;
187             /* totals / extremes from last native query */
188             AV *native_totals;
189             AV *native_extremes;
190             /* reconnect backoff */
191             double reconnect_delay;
192             double reconnect_max_delay;
193             int reconnect_attempts;
194             ev_timer reconnect_timer;
195             int reconnect_timing;
196             /* LowCardinality cross-block dictionary state */
197             SV ***lc_dicts; /* array of dictionaries, one per column */
198             uint64_t *lc_dict_sizes; /* size of each dictionary */
199             int lc_num_cols; /* number of columns with LC state */
200             };
201              
202             struct ev_ch_cb_s {
203             SV *cb;
204             int raw; /* return raw response body instead of parsed rows */
205             SV *on_data; /* per-query streaming callback (fires per block) */
206             double query_timeout; /* per-query timeout (0=use default) */
207             ngx_queue_t queue;
208             };
209              
210             struct ev_ch_send_s {
211             char *data; /* full HTTP request or native packet */
212             size_t data_len;
213             SV *cb;
214             char *insert_data; /* deferred TSV data for native INSERT */
215             size_t insert_data_len;
216             SV *insert_av; /* deferred AV* data for native INSERT */
217             int raw; /* return raw response body */
218             SV *on_data; /* per-query streaming callback */
219             double query_timeout; /* per-query timeout */
220             char *query_id; /* query_id for tracking */
221             ngx_queue_t queue;
222             };
223              
224             /* forward declarations */
225             static void io_cb(EV_P_ ev_io *w, int revents);
226             static void timer_cb(EV_P_ ev_timer *w, int revents);
227             static void ka_timer_cb(EV_P_ ev_timer *w, int revents);
228             static void start_keepalive(ev_clickhouse_t *self);
229             static void stop_keepalive(ev_clickhouse_t *self);
230             static void schedule_reconnect(ev_clickhouse_t *self);
231             static void lc_free_dicts(ev_clickhouse_t *self);
232             static void start_reading(ev_clickhouse_t *self);
233             static void stop_reading(ev_clickhouse_t *self);
234             static void start_writing(ev_clickhouse_t *self);
235             static void stop_writing(ev_clickhouse_t *self);
236             static void emit_error(ev_clickhouse_t *self, const char *msg);
237             static void emit_trace(ev_clickhouse_t *self, const char *fmt, ...);
238             static void cleanup_connection(ev_clickhouse_t *self);
239             static int cancel_pending(ev_clickhouse_t *self, const char *errmsg);
240             static int check_destroyed(ev_clickhouse_t *self);
241             static void on_connect_done(ev_clickhouse_t *self);
242             static void process_http_response(ev_clickhouse_t *self);
243             static int try_write(ev_clickhouse_t *self);
244             static int pipeline_advance(ev_clickhouse_t *self);
245             static void on_readable(ev_clickhouse_t *self);
246              
247             /* --- freelist for cb_queue entries --- */
248              
249             static ev_ch_cb_t *cbt_freelist = NULL;
250              
251 0           static ev_ch_cb_t* alloc_cbt(void) {
252             ev_ch_cb_t *cbt;
253 0 0         if (cbt_freelist) {
254 0           cbt = cbt_freelist;
255 0           cbt_freelist = *(ev_ch_cb_t **)cbt;
256             } else {
257 0           Newx(cbt, 1, ev_ch_cb_t);
258             }
259             /* Fully initialise so callers can never observe a stale field from a
260             * previous user of this freelist node. */
261 0           cbt->cb = NULL;
262 0           cbt->raw = 0;
263 0           cbt->on_data = NULL;
264 0           cbt->query_timeout = 0;
265 0           return cbt;
266             }
267              
268 0           static void release_cbt(ev_ch_cb_t *cbt) {
269 0           *(ev_ch_cb_t **)cbt = cbt_freelist;
270 0           cbt_freelist = cbt;
271 0           }
272              
273             /* --- freelist for send_queue entries --- */
274              
275             static ev_ch_send_t *send_freelist = NULL;
276              
277 0           static ev_ch_send_t* alloc_send(void) {
278             ev_ch_send_t *s;
279 0 0         if (send_freelist) {
280 0           s = send_freelist;
281 0           send_freelist = *(ev_ch_send_t **)s;
282             } else {
283 0           Newx(s, 1, ev_ch_send_t);
284             }
285             /* Fully initialise so callers can never observe a stale field from a
286             * previous user of this freelist node. */
287 0           s->data = NULL;
288 0           s->data_len = 0;
289 0           s->cb = NULL;
290 0           s->insert_data = NULL;
291 0           s->insert_data_len = 0;
292 0           s->insert_av = NULL;
293 0           s->raw = 0;
294 0           s->on_data = NULL;
295 0           s->query_timeout = 0;
296 0           s->query_id = NULL;
297 0           return s;
298             }
299              
300 0           static void release_send(ev_ch_send_t *s) {
301 0 0         if (s->query_id) { Safefree(s->query_id); s->query_id = NULL; }
302 0           *(ev_ch_send_t **)s = send_freelist;
303 0           send_freelist = s;
304 0           }
305              
306             /* --- watcher helpers --- */
307              
308 0           static void start_reading(ev_clickhouse_t *self) {
309 0 0         if (!self->reading && self->fd >= 0) {
    0          
310 0           ev_io_start(self->loop, &self->rio);
311 0           self->reading = 1;
312             }
313 0           }
314              
315 0           static void stop_reading(ev_clickhouse_t *self) {
316 0 0         if (self->reading) {
317 0           ev_io_stop(self->loop, &self->rio);
318 0           self->reading = 0;
319             }
320 0           }
321              
322 0           static void start_writing(ev_clickhouse_t *self) {
323 0 0         if (!self->writing && self->fd >= 0) {
    0          
324 0           ev_io_start(self->loop, &self->wio);
325 0           self->writing = 1;
326             }
327 0           }
328              
329 0           static void stop_writing(ev_clickhouse_t *self) {
330 0 0         if (self->writing) {
331 0           ev_io_stop(self->loop, &self->wio);
332 0           self->writing = 0;
333             }
334 0           }
335              
336 0           static int check_destroyed(ev_clickhouse_t *self) {
337 0 0         if (self->magic == EV_CH_FREED && self->callback_depth == 0) {
    0          
338 0           Safefree(self);
339 0           return 1;
340             }
341 0           return 0;
342             }
343              
344 0           static void emit_error(ev_clickhouse_t *self, const char *msg) {
345 0 0         if (NULL == self->on_error) return;
346              
347             /* Self-guard callback_depth so on_error handlers may drop the last
348             * reference to the connection without freeing self while we're still
349             * running. Caller must still invoke check_destroyed() afterwards to
350             * pick up a deferred Safefree. */
351 0           self->callback_depth++;
352             {
353 0           dSP;
354 0           ENTER;
355 0           SAVETMPS;
356 0 0         PUSHMARK(SP);
357 0 0         XPUSHs(sv_2mortal(newSVpv(msg, 0)));
358 0           PUTBACK;
359              
360 0           call_sv(self->on_error, G_DISCARD | G_EVAL);
361 0 0         if (SvTRUE(ERRSV)) {
    0          
362 0 0         warn("EV::ClickHouse: exception in error handler: %s", SvPV_nolen(ERRSV));
363             }
364              
365 0 0         FREETMPS;
366 0           LEAVE;
367             }
368 0           self->callback_depth--;
369             }
370              
371             /* emit_error + cancel_pending + cleanup_connection. Returns 1 if self was
372             * freed (caller must not access self after). Caller should return regardless
373             * of the result — the connection is gone. */
374 0           static int fail_connection(ev_clickhouse_t *self, const char *msg) {
375 0           emit_error(self, msg);
376 0 0         if (check_destroyed(self)) return 1;
377 0 0         if (cancel_pending(self, msg)) return 1;
378 0           cleanup_connection(self);
379 0           return 0;
380             }
381              
382             /* Invoke a zero-argument callback (on_connect, on_disconnect, on_drain).
383             * Self-guards callback_depth and consumes ERRSV; caller decides whether to
384             * SvREFCNT_dec the captured cb. Returns 1 if self was freed. */
385 0           static int fire_zero_arg_cb(ev_clickhouse_t *self, SV *cb, const char *what) {
386 0           self->callback_depth++;
387             {
388 0           dSP;
389 0           ENTER;
390 0           SAVETMPS;
391 0 0         PUSHMARK(SP);
392 0           PUTBACK;
393 0           call_sv(cb, G_DISCARD | G_EVAL);
394 0 0         if (SvTRUE(ERRSV))
    0          
395 0 0         warn("EV::ClickHouse: exception in %s handler: %s",
396             what, SvPV_nolen(ERRSV));
397 0 0         FREETMPS;
398 0           LEAVE;
399             }
400 0           self->callback_depth--;
401 0           return check_destroyed(self);
402             }
403              
404 0           static void emit_trace(ev_clickhouse_t *self, const char *fmt, ...) {
405             char buf[512];
406             va_list ap;
407 0 0         if (NULL == self->on_trace) return;
408 0           va_start(ap, fmt);
409 0           vsnprintf(buf, sizeof(buf), fmt, ap);
410 0           va_end(ap);
411              
412             /* Self-guard callback_depth so on_trace may drop the last reference to
413             * the connection without freeing self mid-call. We deliberately do NOT
414             * call check_destroyed here: most callers (cleanup_connection, start_connect,
415             * pipeline_advance) continue to access self after emit_trace returns, so
416             * an immediate Safefree would UAF. The struct is in EV_CH_FREED state
417             * after such a callback and the next outer check_destroyed picks it up;
418             * worst case is a tiny leak when on_trace destroys the connection. */
419 0           self->callback_depth++;
420             {
421 0           dSP;
422 0           ENTER;
423 0           SAVETMPS;
424 0 0         PUSHMARK(SP);
425 0 0         XPUSHs(sv_2mortal(newSVpv(buf, 0)));
426 0           PUTBACK;
427 0           call_sv(self->on_trace, G_DISCARD | G_EVAL);
428 0 0         if (SvTRUE(ERRSV))
    0          
429 0 0         warn("EV::ClickHouse: exception in trace handler: %s", SvPV_nolen(ERRSV));
430 0 0         FREETMPS;
431 0           LEAVE;
432             }
433 0           self->callback_depth--;
434             }
435              
436 0           static SV* pop_cb(ev_clickhouse_t *self) {
437             ngx_queue_t *q;
438             ev_ch_cb_t *cbt;
439             SV *cb;
440              
441 0 0         if (ngx_queue_empty(&self->cb_queue)) return NULL;
442              
443 0           q = ngx_queue_head(&self->cb_queue);
444 0           cbt = ngx_queue_data(q, ev_ch_cb_t, queue);
445              
446 0           cb = cbt->cb;
447 0 0         if (cbt->on_data) { SvREFCNT_dec(cbt->on_data); cbt->on_data = NULL; }
448 0           ngx_queue_remove(q);
449 0           self->pending_count--;
450 0           release_cbt(cbt);
451              
452 0           return cb;
453             }
454              
455             /* Peek the on_data callback from front of cb_queue (NULL if none) */
456 0           static SV* peek_cb_on_data(ev_clickhouse_t *self) {
457             ngx_queue_t *q;
458             ev_ch_cb_t *cbt;
459 0 0         if (ngx_queue_empty(&self->cb_queue)) return NULL;
460 0           q = ngx_queue_head(&self->cb_queue);
461 0           cbt = ngx_queue_data(q, ev_ch_cb_t, queue);
462 0           return cbt->on_data;
463             }
464              
465 0           static int peek_cb_raw(ev_clickhouse_t *self) {
466             ngx_queue_t *q;
467             ev_ch_cb_t *cbt;
468 0 0         if (ngx_queue_empty(&self->cb_queue)) return 0;
469 0           q = ngx_queue_head(&self->cb_queue);
470 0           cbt = ngx_queue_data(q, ev_ch_cb_t, queue);
471 0           return cbt->raw;
472             }
473              
474 0           static void invoke_cb(SV *cb) {
475 0           call_sv(cb, G_DISCARD | G_EVAL);
476 0 0         if (SvTRUE(ERRSV)) {
    0          
477 0 0         warn("EV::ClickHouse: exception in callback: %s", SvPV_nolen(ERRSV));
478             }
479 0           SvREFCNT_dec(cb);
480 0           }
481              
482             /* Returns 1 if self was freed. */
483 0           static int deliver_error(ev_clickhouse_t *self, const char *errmsg) {
484 0           SV *cb = pop_cb(self);
485 0 0         if (cb == NULL) return 0;
486              
487 0           self->callback_depth++;
488             {
489 0           dSP;
490 0           ENTER;
491 0           SAVETMPS;
492 0 0         PUSHMARK(SP);
493 0           PUSHs(&PL_sv_undef);
494 0           PUSHs(sv_2mortal(newSVpv(errmsg, 0)));
495 0           PUTBACK;
496 0           invoke_cb(cb);
497 0 0         FREETMPS;
498 0           LEAVE;
499             }
500 0           self->callback_depth--;
501 0           return check_destroyed(self);
502             }
503              
504             /* Returns 1 if self was freed. */
505 0           static int deliver_rows(ev_clickhouse_t *self, AV *rows) {
506 0           SV *cb = pop_cb(self);
507 0 0         if (cb == NULL) {
508 0 0         if (rows) SvREFCNT_dec((SV*)rows);
509 0           return 0;
510             }
511              
512 0           self->callback_depth++;
513             {
514 0           dSP;
515 0           ENTER;
516 0           SAVETMPS;
517 0 0         PUSHMARK(SP);
518 0 0         if (rows) {
519 0           PUSHs(sv_2mortal(newRV_noinc((SV*)rows)));
520             } else {
521 0           PUSHs(&PL_sv_undef);
522             }
523 0           PUTBACK;
524 0           invoke_cb(cb);
525 0 0         FREETMPS;
526 0           LEAVE;
527             }
528 0           self->callback_depth--;
529 0           return check_destroyed(self);
530             }
531              
532             /* Deliver raw response body as scalar string. Returns 1 if self was freed. */
533 0           static int deliver_raw_body(ev_clickhouse_t *self, const char *data, size_t len) {
534 0           SV *cb = pop_cb(self);
535 0 0         if (cb == NULL) return 0;
536              
537 0           self->callback_depth++;
538             {
539 0           dSP;
540 0           ENTER;
541 0           SAVETMPS;
542 0 0         PUSHMARK(SP);
543 0           PUSHs(sv_2mortal(newSVpvn(data, len)));
544 0           PUTBACK;
545 0           invoke_cb(cb);
546 0 0         FREETMPS;
547 0           LEAVE;
548             }
549 0           self->callback_depth--;
550 0           return check_destroyed(self);
551             }
552              
553 0           static void push_cb_owned_ex(ev_clickhouse_t *self, SV *cb, int raw,
554             SV *on_data, double query_timeout) {
555 0           ev_ch_cb_t *cbt = alloc_cbt();
556 0           cbt->cb = cb;
557 0           cbt->raw = raw;
558 0 0         cbt->on_data = on_data ? SvREFCNT_inc(on_data) : NULL;
559 0           cbt->query_timeout = query_timeout;
560 0           ngx_queue_insert_tail(&self->cb_queue, &cbt->queue);
561             /* pending_count already counted */
562 0           }
563              
564 0           static SV* handler_accessor(SV **slot, SV *handler, int has_arg) {
565 0 0         if (has_arg) {
566 0 0         if (NULL != *slot) {
567 0           SvREFCNT_dec(*slot);
568 0           *slot = NULL;
569             }
570 0 0         if (NULL != handler && SvOK(handler) &&
    0          
571 0 0         SvROK(handler) && SvTYPE(SvRV(handler)) == SVt_PVCV) {
    0          
572 0           *slot = SvREFCNT_inc(handler);
573             }
574             }
575 0 0         return (NULL != *slot) ? SvREFCNT_inc(*slot) : &PL_sv_undef;
576             }
577              
578 0           static char* safe_strdup(const char *s) {
579             char *d;
580             size_t len;
581 0 0         if (!s) return NULL;
582 0           len = strlen(s);
583 0           Newx(d, len + 1, char);
584 0           Copy(s, d, len + 1, char);
585 0           return d;
586             }
587              
588 0           static int has_http_unsafe_chars(const char *s) {
589             /* The XS layer hands us a NUL-terminated C string, so embedded NULs
590             * have already been truncated; we only need to reject CR/LF here. */
591 0 0         if (!s) return 0;
592 0 0         for (; *s; s++)
593 0 0         if (*s == '\r' || *s == '\n') return 1;
    0          
594 0           return 0;
595             }
596              
597             /* Common cleanup idioms for fields owned by the connection. */
598             #define CLEAR_STR(p) do { if (p) { Safefree(p); (p) = NULL; } } while (0)
599             #define CLEAR_SV(p) do { if (p) { SvREFCNT_dec((SV*)(p)); (p) = NULL; } } while (0)
600              
601             /* Drop the first `n` bytes from recv_buf, shifting any remaining bytes left. */
602 0           static inline void recv_consume(struct ev_clickhouse_s *self, size_t n) {
603 0 0         if (n < self->recv_len)
604 0           memmove(self->recv_buf, self->recv_buf + n, self->recv_len - n);
605 0           self->recv_len -= n;
606 0           }
607              
608             /* Forward decl: defined further down with the other buffer helpers. */
609             static void ensure_send_cap(struct ev_clickhouse_s *self, size_t need);
610              
611             /* Replace send_buf content with `src` (heap-allocated, freed here). */
612 0           static inline void send_replace(struct ev_clickhouse_s *self, char *src, size_t len) {
613 0           ensure_send_cap(self, len);
614 0           Copy(src, self->send_buf, len, char);
615 0           self->send_len = len;
616 0           self->send_pos = 0;
617 0           Safefree(src);
618 0           }
619              
620 0           static int is_ip_literal(const char *s) {
621             struct in_addr a4;
622             struct in6_addr a6;
623 0           return (inet_pton(AF_INET, s, &a4) == 1 ||
624 0           inet_pton(AF_INET6, s, &a6) == 1);
625             }
626              
627 0           static void cleanup_connection(ev_clickhouse_t *self) {
628 0           int was_connected = self->connected;
629              
630 0 0         if (was_connected) emit_trace(self, "disconnect");
631 0           stop_reading(self);
632 0           stop_writing(self);
633 0           stop_keepalive(self);
634 0 0         if (self->timing) {
635 0           ev_timer_stop(self->loop, &self->timer);
636 0           self->timing = 0;
637             }
638              
639             #ifdef HAVE_OPENSSL
640 0 0         if (self->ssl) {
641 0           SSL_shutdown(self->ssl);
642 0           SSL_free(self->ssl);
643 0           self->ssl = NULL;
644             }
645 0 0         if (self->ssl_ctx) {
646 0           SSL_CTX_free(self->ssl_ctx);
647 0           self->ssl_ctx = NULL;
648             }
649             #endif
650              
651 0 0         if (self->fd >= 0) {
652 0           close(self->fd);
653 0           self->fd = -1;
654             }
655              
656 0           self->connected = 0;
657 0           self->connecting = 0;
658 0           self->send_len = 0;
659 0           self->send_pos = 0;
660 0           self->recv_len = 0;
661 0           self->send_count = 0;
662 0           self->ka_in_flight = 0;
663 0           self->native_state = NATIVE_IDLE;
664 0 0         CLEAR_SV(self->native_rows);
665 0 0         CLEAR_SV(self->native_col_names);
666 0 0         CLEAR_SV(self->native_col_types);
667 0 0         CLEAR_SV(self->native_totals);
668 0 0         CLEAR_SV(self->native_extremes);
669 0           lc_free_dicts(self);
670 0 0         CLEAR_STR(self->insert_data);
671 0           self->insert_data_len = 0;
672 0 0         CLEAR_SV(self->insert_av);
673 0 0         CLEAR_STR(self->insert_err);
674              
675             /* Fire on_disconnect AFTER state is reset, so a handler that queues
676             * new queries or calls reconnect sees clean state. */
677 0 0         if (was_connected && NULL != self->on_disconnect)
    0          
678 0           (void)fire_zero_arg_cb(self, self->on_disconnect, "disconnect");
679 0           }
680              
681             /* Returns 1 if self was freed. */
682 0           static int cancel_pending(ev_clickhouse_t *self, const char *errmsg) {
683 0           self->callback_depth++;
684              
685 0 0         while (!ngx_queue_empty(&self->send_queue)) {
686 0           ngx_queue_t *q = ngx_queue_head(&self->send_queue);
687 0           ev_ch_send_t *send = ngx_queue_data(q, ev_ch_send_t, queue);
688 0           SV *cb = send->cb;
689 0           ngx_queue_remove(q);
690 0           Safefree(send->data);
691 0 0         if (send->insert_data) Safefree(send->insert_data);
692 0 0         if (send->insert_av) { SvREFCNT_dec(send->insert_av); send->insert_av = NULL; }
693 0 0         if (send->on_data) { SvREFCNT_dec(send->on_data); send->on_data = NULL; }
694 0           release_send(send);
695 0           self->pending_count--;
696              
697             {
698 0           dSP;
699 0           ENTER;
700 0           SAVETMPS;
701 0 0         PUSHMARK(SP);
702 0           PUSHs(&PL_sv_undef);
703 0           PUSHs(sv_2mortal(newSVpv(errmsg, 0)));
704 0           PUTBACK;
705 0           invoke_cb(cb);
706 0 0         FREETMPS;
707 0           LEAVE;
708             }
709 0 0         if (self->magic != EV_CH_MAGIC) break;
710             }
711              
712 0 0         while (!ngx_queue_empty(&self->cb_queue)) {
713 0           SV *cb = pop_cb(self);
714 0 0         if (cb == NULL) break;
715              
716             {
717 0           dSP;
718 0           ENTER;
719 0           SAVETMPS;
720 0 0         PUSHMARK(SP);
721 0           PUSHs(&PL_sv_undef);
722 0           PUSHs(sv_2mortal(newSVpv(errmsg, 0)));
723 0           PUTBACK;
724 0           invoke_cb(cb);
725 0 0         FREETMPS;
726 0           LEAVE;
727             }
728 0 0         if (self->magic != EV_CH_MAGIC) break;
729             }
730              
731 0           self->send_count = 0;
732 0           self->callback_depth--;
733 0           return check_destroyed(self);
734             }
735              
736             /* --- I/O helpers (with optional TLS) --- */
737              
738 0           static ssize_t ch_read(ev_clickhouse_t *self, void *buf, size_t len) {
739             #ifdef HAVE_OPENSSL
740 0 0         if (self->ssl) {
741 0 0         int ssl_len = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
742 0           int ret = SSL_read(self->ssl, buf, ssl_len);
743 0 0         if (ret <= 0) {
744 0           int err = SSL_get_error(self->ssl, ret);
745 0 0         if (err == SSL_ERROR_WANT_READ) {
746 0           errno = EAGAIN;
747 0           return -1;
748             }
749 0 0         if (err == SSL_ERROR_WANT_WRITE) {
750 0           start_writing(self);
751 0           errno = EAGAIN;
752 0           return -1;
753             }
754 0 0         if (err == SSL_ERROR_ZERO_RETURN) return 0;
755 0           errno = EIO;
756 0           return -1;
757             }
758 0           return ret;
759             }
760             #endif
761 0           return read(self->fd, buf, len);
762             }
763              
764 0           static ssize_t ch_write(ev_clickhouse_t *self, const void *buf, size_t len) {
765             #ifdef HAVE_OPENSSL
766 0 0         if (self->ssl) {
767 0 0         int ssl_len = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
768 0           int ret = SSL_write(self->ssl, buf, ssl_len);
769 0 0         if (ret <= 0) {
770 0           int err = SSL_get_error(self->ssl, ret);
771 0 0         if (err == SSL_ERROR_WANT_WRITE) {
772 0           errno = EAGAIN;
773 0           return -1;
774             }
775 0 0         if (err == SSL_ERROR_WANT_READ) {
776 0           start_reading(self);
777 0           errno = EAGAIN;
778 0           return -1;
779             }
780 0           errno = EIO;
781 0           return -1;
782             }
783 0           return ret;
784             }
785             #endif
786 0           return write(self->fd, buf, len);
787             }
788              
789             /* --- Buffer management --- */
790              
791 0           static void ensure_recv_cap(ev_clickhouse_t *self, size_t need) {
792 0 0         if (self->recv_cap >= need) return;
793 0 0         if (need > SIZE_MAX / 2) croak("recv buffer overflow");
794 0           size_t newcap = self->recv_cap * 2;
795 0 0         if (newcap < need) newcap = need;
796 0           Renew(self->recv_buf, newcap, char);
797 0           self->recv_cap = newcap;
798             }
799              
800 0           static void ensure_send_cap(ev_clickhouse_t *self, size_t need) {
801 0 0         if (self->send_cap >= need) return;
802 0 0         if (need > SIZE_MAX / 2) croak("send buffer overflow");
803 0           size_t newcap = self->send_cap * 2;
804 0 0         if (newcap < need) newcap = need;
805 0           Renew(self->send_buf, newcap, char);
806 0           self->send_cap = newcap;
807             }
808              
809             /* --- Native protocol buffer (for building packets) --- */
810              
811             typedef struct {
812             char *data;
813             size_t len;
814             size_t cap;
815             } native_buf_t;
816              
817 0           static void nbuf_init(native_buf_t *b) {
818 0           b->cap = 256;
819 0           b->len = 0;
820 0           Newx(b->data, b->cap, char);
821 0           }
822              
823 0           static void nbuf_grow(native_buf_t *b, size_t need) {
824 0 0         if (b->len + need > b->cap) {
825 0 0         while (b->len + need > b->cap) {
826 0 0         if (b->cap > SIZE_MAX / 2) croak("native buffer overflow");
827 0           b->cap *= 2;
828             }
829 0           Renew(b->data, b->cap, char);
830             }
831 0           }
832              
833 0           static void nbuf_append(native_buf_t *b, const char *data, size_t len) {
834 0           nbuf_grow(b, len);
835 0           memcpy(b->data + b->len, data, len);
836 0           b->len += len;
837 0           }
838              
839 0           static void nbuf_varuint(native_buf_t *b, uint64_t n) {
840 0           nbuf_grow(b, 10);
841 0 0         while (n >= 0x80) {
842 0           b->data[b->len++] = (char)((n & 0x7F) | 0x80);
843 0           n >>= 7;
844             }
845 0           b->data[b->len++] = (char)n;
846 0           }
847              
848 0           static void nbuf_string(native_buf_t *b, const char *s, size_t len) {
849 0           nbuf_varuint(b, (uint64_t)len);
850 0           nbuf_append(b, s, len);
851 0           }
852              
853 0           static void nbuf_cstring(native_buf_t *b, const char *s) {
854 0 0         nbuf_string(b, s, s ? strlen(s) : 0);
855 0           }
856              
857 0           static void nbuf_u8(native_buf_t *b, uint8_t v) {
858 0           nbuf_grow(b, 1);
859 0           b->data[b->len++] = (char)v;
860 0           }
861              
862             /* --- Native protocol read helpers (from recv_buf) --- */
863              
864             /* Returns 1=success, 0=need more data, -1=overflow */
865 0           static int read_varuint(const char *buf, size_t len, size_t *pos, uint64_t *out) {
866 0           uint64_t val = 0;
867 0           unsigned shift = 0;
868 0           size_t p = *pos;
869 0 0         while (p < len) {
870 0           uint8_t byte = (uint8_t)buf[p++];
871 0           val |= (uint64_t)(byte & 0x7F) << shift;
872 0 0         if (!(byte & 0x80)) {
873 0           *out = val;
874 0           *pos = p;
875 0           return 1;
876             }
877 0           shift += 7;
878 0 0         if (shift >= 64) return -1;
879             }
880 0           return 0;
881             }
882              
883 0           static int read_native_string_alloc(const char *buf, size_t len, size_t *pos,
884             char **out, size_t *out_len) {
885             uint64_t slen;
886 0           size_t saved = *pos;
887 0           int rc = read_varuint(buf, len, pos, &slen);
888 0 0         if (rc <= 0) { *pos = saved; return rc; }
889 0 0         if (slen > len - *pos) { *pos = saved; return 0; }
890 0           Newx(*out, slen + 1, char);
891 0           Copy(buf + *pos, *out, slen, char);
892 0           (*out)[slen] = '\0';
893 0 0         if (out_len) *out_len = (size_t)slen;
894 0           *pos += slen;
895 0           return 1;
896             }
897              
898             /* Read a string without allocating — returns pointer into buf */
899 0           static int read_native_string_ref(const char *buf, size_t len, size_t *pos,
900             const char **out, size_t *out_len) {
901             uint64_t slen;
902 0           size_t saved = *pos;
903 0           int rc = read_varuint(buf, len, pos, &slen);
904 0 0         if (rc <= 0) { *pos = saved; return rc; }
905 0 0         if (slen > len - *pos) { *pos = saved; return 0; }
906 0           *out = buf + *pos;
907 0           *out_len = (size_t)slen;
908 0           *pos += slen;
909 0           return 1;
910             }
911              
912 0           static int read_u8(const char *buf, size_t len, size_t *pos, uint8_t *out) {
913 0 0         if (*pos + 1 > len) return 0;
914 0           *out = (uint8_t)buf[*pos];
915 0           (*pos)++;
916 0           return 1;
917             }
918              
919 0           static int read_i32(const char *buf, size_t len, size_t *pos, int32_t *out) {
920 0 0         if (*pos + 4 > len) return 0;
921 0           memcpy(out, buf + *pos, 4);
922 0           *pos += 4;
923 0           return 1;
924             }
925              
926             /* Skip VarUInt without storing */
927 0           static int skip_varuint(const char *buf, size_t len, size_t *pos) {
928             uint64_t dummy;
929 0           return read_varuint(buf, len, pos, &dummy);
930             }
931              
932             /* Skip native string */
933 0           static int skip_native_string(const char *buf, size_t len, size_t *pos) {
934             uint64_t slen;
935 0           size_t saved = *pos;
936 0           int rc = read_varuint(buf, len, pos, &slen);
937 0 0         if (rc <= 0) { *pos = saved; return rc; }
938 0 0         if (slen > len - *pos) { *pos = saved; return 0; }
939 0           *pos += slen;
940 0           return 1;
941             }
942              
943             /* --- URL encoding --- */
944              
945 0           static size_t url_encode(const char *src, size_t src_len, char *dst) {
946             static const char hex[] = "0123456789ABCDEF";
947 0           size_t j = 0;
948             size_t i;
949 0 0         for (i = 0; i < src_len; i++) {
950 0           unsigned char c = (unsigned char)src[i];
951 0 0         if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
    0          
    0          
    0          
    0          
952 0 0         (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') {
    0          
    0          
    0          
    0          
953 0           dst[j++] = c;
954             } else {
955 0           dst[j++] = '%';
956 0           dst[j++] = hex[c >> 4];
957 0           dst[j++] = hex[c & 0x0F];
958             }
959             }
960 0           return j;
961             }
962              
963             /* --- Per-query settings helpers --- */
964              
965             /* Compute buffer space needed for URL-encoded settings params.
966             * Returns bytes needed for "&key=value" pairs (with URL encoding). */
967 0           static int is_client_only_key(const char *key, I32 klen) {
968 0 0         return (klen == 3 && memcmp(key, "raw", 3) == 0)
969 0 0         || (klen == 8 && memcmp(key, "query_id", 8) == 0)
    0          
970 0 0         || (klen == 7 && memcmp(key, "on_data", 7) == 0)
    0          
971 0 0         || (klen == 13 && memcmp(key, "query_timeout", 13) == 0)
    0          
972 0 0         || (klen == 6 && memcmp(key, "params", 6) == 0);
    0          
    0          
973             }
974              
975 0           static size_t settings_url_params_size(HV *defaults, HV *overrides) {
976 0           size_t total = 0;
977             HE *entry;
978 0 0         if (overrides) {
979 0           hv_iterinit(overrides);
980 0 0         while ((entry = hv_iternext(overrides))) {
981             I32 klen;
982             STRLEN vlen;
983 0           char *key = hv_iterkey(entry, &klen);
984 0 0         if (is_client_only_key(key, klen)) continue;
985 0           (void)SvPV(hv_iterval(overrides, entry), vlen);
986 0           total += 2 + (size_t)klen * 3 + (size_t)vlen * 3;
987             }
988             }
989 0 0         if (defaults) {
990 0           hv_iterinit(defaults);
991 0 0         while ((entry = hv_iternext(defaults))) {
992             I32 klen;
993             STRLEN vlen;
994 0           char *key = hv_iterkey(entry, &klen);
995 0 0         if (overrides && hv_exists(overrides, key, klen))
    0          
996 0           continue; /* overridden */
997 0 0         if (is_client_only_key(key, klen)) continue;
998 0           (void)SvPV(hv_iterval(defaults, entry), vlen);
999 0           total += 2 + (size_t)klen * 3 + (size_t)vlen * 3;
1000             }
1001             }
1002 0           return total;
1003             }
1004              
1005             /* Append merged settings as URL params (&key=encoded_value).
1006             * Extracts query_id into *query_id_out (caller must not free — points into HV).
1007             * Per-query overrides take precedence over connection defaults.
1008             * Returns new position in params buffer. */
1009 0           static size_t append_settings_url_params(char *params, size_t plen,
1010             HV *defaults, HV *overrides,
1011             const char **query_id_out, STRLEN *query_id_len_out) {
1012             HE *entry;
1013 0           *query_id_out = NULL;
1014 0           *query_id_len_out = 0;
1015              
1016             /* Write overrides first */
1017 0 0         if (overrides) {
1018 0           hv_iterinit(overrides);
1019 0 0         while ((entry = hv_iternext(overrides))) {
1020             I32 klen;
1021             STRLEN vlen;
1022 0           char *key = hv_iterkey(entry, &klen);
1023 0           char *val = SvPV(hv_iterval(overrides, entry), vlen);
1024 0 0         if (klen == 8 && memcmp(key, "query_id", 8) == 0) {
    0          
1025 0           *query_id_out = val;
1026 0           *query_id_len_out = vlen;
1027 0           continue;
1028             }
1029 0 0         if (is_client_only_key(key, klen)) continue;
1030 0           params[plen++] = '&';
1031 0           plen += url_encode(key, (size_t)klen, params + plen);
1032 0           params[plen++] = '=';
1033 0           plen += url_encode(val, vlen, params + plen);
1034             }
1035             }
1036             /* Write defaults, skipping keys present in overrides */
1037 0 0         if (defaults) {
1038 0           hv_iterinit(defaults);
1039 0 0         while ((entry = hv_iternext(defaults))) {
1040             I32 klen;
1041             STRLEN vlen;
1042 0           char *key = hv_iterkey(entry, &klen);
1043 0           char *val = SvPV(hv_iterval(defaults, entry), vlen);
1044 0 0         if (overrides && hv_exists(overrides, key, klen))
    0          
1045 0           continue;
1046 0 0         if (klen == 8 && memcmp(key, "query_id", 8) == 0) {
    0          
1047 0 0         if (!*query_id_out) {
1048 0           *query_id_out = val;
1049 0           *query_id_len_out = vlen;
1050             }
1051 0           continue;
1052             }
1053 0 0         if (is_client_only_key(key, klen)) continue;
1054 0           params[plen++] = '&';
1055 0           plen += url_encode(key, (size_t)klen, params + plen);
1056 0           params[plen++] = '=';
1057 0           plen += url_encode(val, vlen, params + plen);
1058             }
1059             }
1060 0           return plen;
1061             }
1062              
1063             /* Write merged settings in native protocol wire format.
1064             * Format per setting: String name, UInt8 is_important(0), String value.
1065             * Terminated by empty name string (written by caller). */
1066 0           static void write_native_settings(native_buf_t *b, HV *defaults, HV *overrides,
1067             const char **query_id_out, STRLEN *query_id_len_out) {
1068             HE *entry;
1069 0 0         if (query_id_out) *query_id_out = NULL;
1070 0 0         if (query_id_len_out) *query_id_len_out = 0;
1071              
1072 0 0         if (overrides) {
1073 0           hv_iterinit(overrides);
1074 0 0         while ((entry = hv_iternext(overrides))) {
1075             I32 klen;
1076             STRLEN vlen;
1077 0           char *key = hv_iterkey(entry, &klen);
1078 0           char *val = SvPV(hv_iterval(overrides, entry), vlen);
1079 0 0         if (klen == 8 && memcmp(key, "query_id", 8) == 0) {
    0          
1080 0 0         if (query_id_out) {
1081 0           *query_id_out = val;
1082 0           *query_id_len_out = vlen;
1083             }
1084 0           continue;
1085             }
1086 0 0         if (is_client_only_key(key, klen)) continue;
1087             /* param_* keys go in the parameters block, not settings */
1088 0 0         if (klen > 6 && memcmp(key, "param_", 6) == 0) continue;
    0          
1089 0           nbuf_varuint(b, (uint64_t)klen);
1090 0           nbuf_append(b, key, (size_t)klen);
1091 0           nbuf_u8(b, 0); /* is_important = 0 */
1092 0           nbuf_varuint(b, (uint64_t)vlen);
1093 0           nbuf_append(b, val, vlen);
1094             }
1095             }
1096 0 0         if (defaults) {
1097 0           hv_iterinit(defaults);
1098 0 0         while ((entry = hv_iternext(defaults))) {
1099             I32 klen;
1100             STRLEN vlen;
1101 0           char *key = hv_iterkey(entry, &klen);
1102 0           char *val = SvPV(hv_iterval(defaults, entry), vlen);
1103 0 0         if (overrides && hv_exists(overrides, key, klen))
    0          
1104 0           continue;
1105 0 0         if (klen == 8 && memcmp(key, "query_id", 8) == 0) {
    0          
1106 0 0         if (query_id_out && !*query_id_out) {
    0          
1107 0           *query_id_out = val;
1108 0           *query_id_len_out = vlen;
1109             }
1110 0           continue;
1111             }
1112 0 0         if (is_client_only_key(key, klen)) continue;
1113 0 0         if (klen > 6 && memcmp(key, "param_", 6) == 0) continue;
    0          
1114 0           nbuf_varuint(b, (uint64_t)klen);
1115 0           nbuf_append(b, key, (size_t)klen);
1116 0           nbuf_u8(b, 0); /* is_important = 0 */
1117 0           nbuf_varuint(b, (uint64_t)vlen);
1118 0           nbuf_append(b, val, vlen);
1119             }
1120             }
1121 0           }
1122              
1123             /* --- Gzip compression/decompression --- */
1124              
1125             /* Compress data with gzip. Returns malloc'd buffer, sets *out_len. NULL on error. */
1126 0           static char* gzip_compress(const char *data, size_t data_len, size_t *out_len) {
1127             z_stream strm;
1128             char *out;
1129             size_t out_cap;
1130             int ret;
1131              
1132 0 0         if (data_len > (size_t)UINT_MAX) return NULL;
1133              
1134 0           Zero(&strm, 1, z_stream);
1135 0           ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
1136 0 0         if (ret != Z_OK) return NULL;
1137              
1138 0           out_cap = deflateBound(&strm, (uLong)data_len);
1139 0 0         if (out_cap > (size_t)UINT_MAX) { deflateEnd(&strm); return NULL; }
1140 0           Newx(out, out_cap, char);
1141              
1142 0           strm.next_in = (Bytef *)data;
1143 0           strm.avail_in = (uInt)data_len;
1144 0           strm.next_out = (Bytef *)out;
1145 0           strm.avail_out = (uInt)out_cap;
1146              
1147 0           ret = deflate(&strm, Z_FINISH);
1148 0 0         if (ret != Z_STREAM_END) {
1149 0           Safefree(out);
1150 0           deflateEnd(&strm);
1151 0           return NULL;
1152             }
1153              
1154 0           *out_len = strm.total_out;
1155 0           deflateEnd(&strm);
1156 0           return out;
1157             }
1158              
1159             /* Decompress gzip data. Returns malloc'd buffer, sets *out_len. NULL on error. */
1160 0           static char* gzip_decompress(const char *data, size_t data_len, size_t *out_len) {
1161             z_stream strm;
1162             char *out;
1163             size_t out_cap;
1164             int ret;
1165              
1166 0 0         if (data_len > (size_t)UINT_MAX) return NULL;
1167              
1168 0           Zero(&strm, 1, z_stream);
1169 0           ret = inflateInit2(&strm, 15 + 16); /* auto-detect gzip */
1170 0 0         if (ret != Z_OK) return NULL;
1171              
1172 0           out_cap = data_len * 4;
1173 0 0         if (out_cap < 4096) out_cap = 4096;
1174 0           Newx(out, out_cap, char);
1175              
1176 0           strm.next_in = (Bytef *)data;
1177 0           strm.avail_in = (uInt)data_len;
1178              
1179 0           *out_len = 0;
1180             do {
1181 0 0         if (*out_len + 4096 > out_cap) {
1182 0           out_cap *= 2;
1183 0 0         if (out_cap > CH_MAX_DECOMPRESS_SIZE) {
1184 0           Safefree(out);
1185 0           inflateEnd(&strm);
1186 0           return NULL;
1187             }
1188 0           Renew(out, out_cap, char);
1189             }
1190 0           strm.next_out = (Bytef *)(out + *out_len);
1191 0           strm.avail_out = (uInt)(out_cap - *out_len);
1192              
1193 0           ret = inflate(&strm, Z_NO_FLUSH);
1194 0 0         if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR ||
    0          
    0          
1195 0 0         ret == Z_MEM_ERROR || ret == Z_BUF_ERROR) {
1196 0           Safefree(out);
1197 0           inflateEnd(&strm);
1198 0           return NULL;
1199             }
1200 0           *out_len = strm.total_out;
1201 0 0         } while (ret != Z_STREAM_END);
1202              
1203 0           inflateEnd(&strm);
1204 0           return out;
1205             }
1206              
1207             #ifdef HAVE_LZ4
1208              
1209             /*
1210             * Decompress a ClickHouse LZ4 compressed block.
1211             * Input: compressed block starting at checksum (16 + 9 + payload bytes).
1212             * Returns malloc'd buffer with decompressed data, sets *out_len.
1213             * Returns NULL on error or if need more data (sets *need_more=1).
1214             */
1215             static char* ch_lz4_decompress(const char *data, size_t data_len,
1216             size_t *out_len, size_t *consumed,
1217             int *need_more, const char **err_reason) {
1218             uint32_t compressed_with_header, uncompressed_size;
1219             uint32_t payload_size;
1220             uint8_t method;
1221             char *out;
1222             int ret;
1223              
1224             *need_more = 0;
1225             *consumed = 0;
1226             if (err_reason) *err_reason = NULL;
1227              
1228             /* Need at least checksum (16) + header (9) */
1229             if (data_len < CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE) {
1230             *need_more = 1;
1231             return NULL;
1232             }
1233              
1234             /* Read header fields (after 16-byte checksum) */
1235             method = (uint8_t)data[CH_CHECKSUM_SIZE];
1236             if (method != CH_LZ4_METHOD) {
1237             if (err_reason) *err_reason = "unsupported compression method";
1238             return NULL;
1239             }
1240             memcpy(&compressed_with_header, data + CH_CHECKSUM_SIZE + 1, 4);
1241             memcpy(&uncompressed_size, data + CH_CHECKSUM_SIZE + 5, 4);
1242              
1243             if (uncompressed_size > CH_MAX_DECOMPRESS_SIZE) {
1244             if (err_reason) *err_reason = "decompressed size exceeds 128 MB limit";
1245             return NULL;
1246             }
1247              
1248             if (compressed_with_header < CH_COMPRESS_HEADER_SIZE) {
1249             if (err_reason) *err_reason = "compressed_with_header too small";
1250             return NULL;
1251             }
1252              
1253             payload_size = compressed_with_header - CH_COMPRESS_HEADER_SIZE;
1254              
1255             /* Need full block */
1256             if (data_len < CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE + payload_size) {
1257             *need_more = 1;
1258             return NULL;
1259             }
1260              
1261             /* Verify checksum */
1262             {
1263             ch_uint128_t expected, actual;
1264             memcpy(&expected.lo, data, 8);
1265             memcpy(&expected.hi, data + 8, 8);
1266             actual = ch_city_hash128(data + CH_CHECKSUM_SIZE, compressed_with_header);
1267             if (actual.lo != expected.lo || actual.hi != expected.hi) {
1268             if (err_reason) *err_reason = "CityHash128 checksum mismatch";
1269             return NULL;
1270             }
1271             }
1272              
1273             Newx(out, uncompressed_size, char);
1274             ret = LZ4_decompress_safe(data + CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE,
1275             out, (int)payload_size, (int)uncompressed_size);
1276             if (ret < 0 || (uint32_t)ret != uncompressed_size) {
1277             Safefree(out);
1278             if (err_reason) *err_reason = "LZ4 decompression failed";
1279             return NULL;
1280             }
1281              
1282             *out_len = uncompressed_size;
1283             *consumed = CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE + payload_size;
1284             return out;
1285             }
1286              
1287             /*
1288             * Compress data into a ClickHouse LZ4 compressed block.
1289             * Returns malloc'd buffer (checksum + header + LZ4 payload), sets *out_len.
1290             */
1291             static char* ch_lz4_compress(const char *data, size_t data_len, size_t *out_len) {
1292             int max_compressed;
1293             if (data_len > (size_t)INT_MAX) return NULL;
1294             max_compressed = LZ4_compressBound((int)data_len);
1295             char *out;
1296             int compressed_size;
1297             uint32_t compressed_with_header;
1298             ch_uint128_t checksum;
1299              
1300             Newx(out, CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE + max_compressed, char);
1301              
1302             compressed_size = LZ4_compress_default(
1303             data, out + CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE,
1304             (int)data_len, max_compressed);
1305              
1306             if (compressed_size <= 0) {
1307             Safefree(out);
1308             return NULL;
1309             }
1310              
1311             compressed_with_header = (uint32_t)compressed_size + CH_COMPRESS_HEADER_SIZE;
1312              
1313             /* Write header */
1314             out[CH_CHECKSUM_SIZE] = (char)CH_LZ4_METHOD;
1315             memcpy(out + CH_CHECKSUM_SIZE + 1, &compressed_with_header, 4);
1316             { uint32_t uncomp = (uint32_t)data_len;
1317             memcpy(out + CH_CHECKSUM_SIZE + 5, &uncomp, 4);
1318             }
1319              
1320             /* Compute checksum over header + compressed data */
1321             checksum = ch_city_hash128(out + CH_CHECKSUM_SIZE, compressed_with_header);
1322             memcpy(out, &checksum.lo, 8);
1323             memcpy(out + 8, &checksum.hi, 8);
1324              
1325             *out_len = CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE + compressed_size;
1326             return out;
1327             }
1328              
1329             /* Decompress one or more consecutive LZ4 sub-blocks starting at buf[*pos].
1330             * Advances *pos past every consumed sub-block; *out_len is total decompressed
1331             * size. Returns malloc'd buffer (caller Safefrees) on success, NULL on
1332             * not-enough-data (sets *need_more=1) or on hard error (sets *err). */
1333             static char* ch_lz4_decompress_chain(const char *buf, size_t len, size_t *pos,
1334             size_t *out_len, int *need_more,
1335             const char **err) {
1336             size_t comp_consumed;
1337             char *out;
1338              
1339             *need_more = 0;
1340             *err = NULL;
1341              
1342             out = ch_lz4_decompress(buf + *pos, len - *pos, out_len,
1343             &comp_consumed, need_more, err);
1344             if (!out) return NULL;
1345             *pos += comp_consumed;
1346              
1347             while (len - *pos >= CH_CHECKSUM_SIZE + CH_COMPRESS_HEADER_SIZE
1348             && (uint8_t)buf[*pos + CH_CHECKSUM_SIZE] == CH_LZ4_METHOD) {
1349             size_t extra_len, extra_consumed;
1350             int extra_need_more = 0;
1351             const char *extra_err = NULL;
1352             char *extra = ch_lz4_decompress(buf + *pos, len - *pos, &extra_len,
1353             &extra_consumed,
1354             &extra_need_more, &extra_err);
1355             if (!extra) {
1356             Safefree(out);
1357             if (extra_need_more) { *need_more = 1; return NULL; }
1358             *err = extra_err;
1359             return NULL;
1360             }
1361             Renew(out, *out_len + extra_len, char);
1362             Copy(extra, out + *out_len, extra_len, char);
1363             *out_len += extra_len;
1364             *pos += extra_consumed;
1365             Safefree(extra);
1366             }
1367             return out;
1368             }
1369              
1370             #endif /* HAVE_LZ4 */
1371              
1372             /* --- Days-since-epoch calculation for Date encoding --- */
1373              
1374 0           static int32_t date_string_to_days(const char *s, size_t len) {
1375             int year, month, day;
1376 0 0         if (len >= 10 && s[4] == '-' && s[7] == '-') {
    0          
    0          
1377 0           year = atoi(s);
1378 0           month = atoi(s + 5);
1379 0           day = atoi(s + 8);
1380             /* civil_from_days algorithm (Howard Hinnant) */
1381 0 0         if (month <= 2) { year--; month += 9; } else { month -= 3; }
1382             {
1383 0 0         int era = (year >= 0 ? year : year - 399) / 400;
1384 0           unsigned yoe = (unsigned)(year - era * 400);
1385 0           unsigned doy = (153 * (unsigned)month + 2) / 5 + (unsigned)day - 1;
1386 0           unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;
1387 0           return (int32_t)(era * 146097 + (int)doe - 719468);
1388             }
1389             }
1390             /* fallback: numeric value */
1391 0           return (int32_t)strtol(s, NULL, 10);
1392             }
1393              
1394 0           static uint32_t datetime_string_to_epoch(const char *s, size_t len) {
1395 0           int hour = 0, min = 0, sec = 0;
1396 0 0         if (len >= 10 && s[4] == '-' && s[7] == '-') {
    0          
    0          
1397 0 0         if (len >= 19) {
1398 0           hour = atoi(s + 11);
1399 0           min = atoi(s + 14);
1400 0           sec = atoi(s + 17);
1401             }
1402             {
1403 0           int32_t days = date_string_to_days(s, 10);
1404 0           return (uint32_t)((int64_t)days * 86400 + hour * 3600 + min * 60 + sec);
1405             }
1406             }
1407 0           return (uint32_t)strtoul(s, NULL, 10);
1408             }
1409              
1410             /* --- TabSeparated parser --- */
1411              
1412             /* Parse TabSeparated body into AV of AV. Handles \N -> undef, backslash escapes. */
1413 0           static AV* parse_tab_separated(const char *data, size_t len) {
1414 0           AV *rows = newAV();
1415 0           const char *p = data;
1416 0           const char *end = data + len;
1417             const char *line_start;
1418             AV *row;
1419             char *buf;
1420             size_t buf_len;
1421              
1422             /* pre-allocate scratch buffer for unescaping */
1423 0           Newx(buf, len + 1, char);
1424              
1425 0 0         while (p < end) {
1426             /* skip trailing empty line */
1427 0 0         if (p + 1 == end && *p == '\n') break;
    0          
1428              
1429 0           row = newAV();
1430 0           line_start = p;
1431              
1432 0 0         while (p <= end) {
1433 0 0         int is_end_of_line = (p == end || *p == '\n');
    0          
1434 0 0         int is_tab = (!is_end_of_line && *p == '\t');
    0          
1435              
1436 0 0         if (is_end_of_line || is_tab) {
    0          
1437 0           const char *field_start = line_start;
1438 0           size_t field_len = p - field_start;
1439              
1440             /* check for \N (NULL) */
1441 0 0         if (field_len == 2 && field_start[0] == '\\' && field_start[1] == 'N') {
    0          
    0          
1442 0           av_push(row, newSV(0));
1443             } else {
1444             /* unescape */
1445 0           buf_len = 0;
1446 0           const char *s = field_start;
1447 0           const char *s_end = field_start + field_len;
1448 0 0         while (s < s_end) {
1449 0 0         if (*s == '\\' && s + 1 < s_end) {
    0          
1450 0           s++;
1451 0           switch (*s) {
1452 0           case 'n': buf[buf_len++] = '\n'; break;
1453 0           case 't': buf[buf_len++] = '\t'; break;
1454 0           case '\\': buf[buf_len++] = '\\'; break;
1455 0           case '\'': buf[buf_len++] = '\''; break;
1456 0           case '0': buf[buf_len++] = '\0'; break;
1457 0           case 'a': buf[buf_len++] = '\a'; break;
1458 0           case 'b': buf[buf_len++] = '\b'; break;
1459 0           case 'f': buf[buf_len++] = '\f'; break;
1460 0           case 'r': buf[buf_len++] = '\r'; break;
1461 0           default: buf[buf_len++] = '\\'; buf[buf_len++] = *s; break;
1462             }
1463 0           s++;
1464             } else {
1465 0           buf[buf_len++] = *s++;
1466             }
1467             }
1468 0           av_push(row, newSVpvn(buf, buf_len));
1469             }
1470              
1471 0 0         if (is_tab) {
1472 0           p++;
1473 0           line_start = p;
1474             } else {
1475 0 0         if (p < end) p++; /* skip \n */
1476 0           break;
1477             }
1478             } else {
1479 0           p++;
1480             }
1481             }
1482 0           av_push(rows, newRV_noinc((SV*)row));
1483             }
1484              
1485 0           Safefree(buf);
1486 0           return rows;
1487             }
1488              
1489             /* --- HTTP request building --- */
1490              
1491             /*
1492             * Build HTTP POST request for a query.
1493             * SQL goes in body. Returns malloc'd buffer with full request.
1494             */
1495 0           static char* build_http_query_request(ev_clickhouse_t *self, const char *sql,
1496             size_t sql_len, int do_compress,
1497             HV *defaults, HV *overrides,
1498             size_t *req_len) {
1499             char *req;
1500             size_t req_cap;
1501 0           size_t pos = 0;
1502 0           char *body = NULL;
1503 0           size_t body_len = sql_len;
1504 0           const char *content_encoding = NULL;
1505              
1506             /* compress body if requested */
1507 0 0         if (do_compress && sql_len > 0) {
    0          
1508             size_t gz_len;
1509 0           body = gzip_compress(sql, sql_len, &gz_len);
1510 0 0         if (body) {
1511 0           body_len = gz_len;
1512 0           content_encoding = "Content-Encoding: gzip\r\n";
1513             }
1514             }
1515              
1516             /* build URL params (dynamically allocated) */
1517 0           const char *query_id = NULL;
1518 0           STRLEN query_id_len = 0;
1519 0           size_t params_cap = 128
1520 0 0         + (self->database ? strlen(self->database) * 3 : 0)
1521 0 0         + (self->session_id ? strlen(self->session_id) * 3 : 0)
1522 0           + settings_url_params_size(defaults, overrides);
1523             char *params;
1524 0           size_t plen = 0;
1525 0           Newx(params, params_cap, char);
1526 0 0         if (self->database) {
1527 0           size_t db_len = strlen(self->database);
1528             char *enc_db;
1529 0           Newx(enc_db, db_len * 3 + 1, char);
1530 0           size_t enc_len = url_encode(self->database, db_len, enc_db);
1531 0           plen = (size_t)snprintf(params, params_cap, "?database=%.*s&wait_end_of_query=1",
1532             (int)enc_len, enc_db);
1533 0           Safefree(enc_db);
1534             } else {
1535 0           plen = (size_t)snprintf(params, params_cap, "?wait_end_of_query=1");
1536             }
1537 0 0         if (self->session_id) {
1538 0           size_t sid_len = strlen(self->session_id);
1539             char *enc_sid;
1540 0           Newx(enc_sid, sid_len * 3 + 1, char);
1541 0           size_t enc_len = url_encode(self->session_id, sid_len, enc_sid);
1542 0           plen += (size_t)snprintf(params + plen, params_cap - plen,
1543             "&session_id=%.*s", (int)enc_len, enc_sid);
1544 0           Safefree(enc_sid);
1545             }
1546 0           plen = append_settings_url_params(params, plen,
1547             defaults, overrides,
1548             &query_id, &query_id_len);
1549 0 0         if (query_id) {
1550 0           size_t need = plen + 10 + query_id_len * 3 + 1;
1551 0 0         if (need > params_cap) {
1552 0           params_cap = need;
1553 0           Renew(params, params_cap, char);
1554             }
1555 0           plen += (size_t)snprintf(params + plen, params_cap - plen, "&query_id=");
1556 0           plen += url_encode(query_id, query_id_len, params + plen);
1557             }
1558 0           params[plen] = '\0';
1559              
1560 0           req_cap = 512 + body_len + plen
1561 0 0         + (self->host ? strlen(self->host) : 0)
1562 0 0         + (self->user ? strlen(self->user) : 0)
1563 0 0         + (self->password ? strlen(self->password) : 0);
1564 0           Newx(req, req_cap, char);
1565              
1566             /* request line */
1567 0           pos += snprintf(req + pos, req_cap - pos,
1568             "POST /%s HTTP/1.1\r\n", params);
1569 0           Safefree(params);
1570              
1571             /* headers */
1572 0           pos += snprintf(req + pos, req_cap - pos,
1573             "Host: %s:%u\r\n", self->host, self->port);
1574 0 0         if (self->user) {
1575 0           pos += snprintf(req + pos, req_cap - pos,
1576             "X-ClickHouse-User: %s\r\n", self->user);
1577             }
1578 0 0         if (self->password && self->password[0]) {
    0          
1579 0           pos += snprintf(req + pos, req_cap - pos,
1580             "X-ClickHouse-Key: %s\r\n", self->password);
1581             }
1582 0           pos += snprintf(req + pos, req_cap - pos, "Connection: keep-alive\r\n");
1583              
1584 0 0         if (content_encoding)
1585 0           pos += snprintf(req + pos, req_cap - pos, "%s", content_encoding);
1586              
1587 0 0         if (self->compress)
1588 0           pos += snprintf(req + pos, req_cap - pos, "Accept-Encoding: gzip\r\n");
1589              
1590 0           pos += snprintf(req + pos, req_cap - pos,
1591             "Content-Length: %lu\r\n\r\n", (unsigned long)body_len);
1592              
1593             /* body */
1594 0 0         if (body_len > 0) {
1595 0 0         if (pos + body_len > req_cap) {
1596 0           req_cap = pos + body_len + 1;
1597 0           Renew(req, req_cap, char);
1598             }
1599 0 0         Copy(body ? body : sql, req + pos, body_len, char);
1600 0           pos += body_len;
1601             }
1602              
1603 0 0         if (body) Safefree(body);
1604              
1605 0           *req_len = pos;
1606 0           return req;
1607             }
1608              
1609             /*
1610             * Build HTTP POST request for INSERT with data.
1611             * Query goes in URL param, data in body.
1612             */
1613 0           static char* build_http_insert_request(ev_clickhouse_t *self, const char *table,
1614             size_t table_len, const char *data,
1615             size_t data_len, int do_compress,
1616             HV *defaults, HV *overrides,
1617             size_t *req_len) {
1618             char *req;
1619             size_t req_cap;
1620 0           size_t pos = 0;
1621 0           char *body = NULL;
1622 0           size_t body_len = data_len;
1623 0           const char *content_encoding = NULL;
1624              
1625 0 0         if (do_compress && data_len > 0) {
    0          
1626             size_t gz_len;
1627 0           body = gzip_compress(data, data_len, &gz_len);
1628 0 0         if (body) {
1629 0           body_len = gz_len;
1630 0           content_encoding = "Content-Encoding: gzip\r\n";
1631             }
1632             }
1633              
1634             /* build query string: INSERT INTO FORMAT TabSeparated */
1635 0           size_t isql_cap = table_len + 64;
1636             char *insert_sql;
1637 0           Newx(insert_sql, isql_cap, char);
1638 0           int isql_len = snprintf(insert_sql, isql_cap,
1639             "INSERT INTO %.*s FORMAT TabSeparated",
1640             (int)table_len, table);
1641              
1642 0           const char *query_id = NULL;
1643 0           STRLEN query_id_len = 0;
1644 0           size_t params_cap = 128
1645 0 0         + (self->database ? strlen(self->database) * 3 : 0)
1646 0 0         + (self->session_id ? strlen(self->session_id) * 3 : 0)
1647 0           + (size_t)isql_len * 3
1648 0           + settings_url_params_size(defaults, overrides);
1649             char *params;
1650 0           size_t plen = 0;
1651 0           Newx(params, params_cap, char);
1652 0 0         if (self->database) {
1653 0           size_t db_len = strlen(self->database);
1654             char *enc_db;
1655 0           Newx(enc_db, db_len * 3 + 1, char);
1656 0           size_t enc_len = url_encode(self->database, db_len, enc_db);
1657 0           plen = (size_t)snprintf(params, params_cap, "?database=%.*s&wait_end_of_query=1",
1658             (int)enc_len, enc_db);
1659 0           Safefree(enc_db);
1660             } else {
1661 0           plen = (size_t)snprintf(params, params_cap, "?wait_end_of_query=1");
1662             }
1663 0 0         if (self->session_id) {
1664 0           size_t sid_len = strlen(self->session_id);
1665             char *enc_sid;
1666 0           Newx(enc_sid, sid_len * 3 + 1, char);
1667 0           size_t enc_len = url_encode(self->session_id, sid_len, enc_sid);
1668 0           plen += (size_t)snprintf(params + plen, params_cap - plen,
1669             "&session_id=%.*s", (int)enc_len, enc_sid);
1670 0           Safefree(enc_sid);
1671             }
1672             {
1673             char *enc_q;
1674 0           Newx(enc_q, isql_len * 3 + 1, char);
1675 0           size_t enc_len = url_encode(insert_sql, isql_len, enc_q);
1676 0           Safefree(insert_sql);
1677 0           plen += (size_t)snprintf(params + plen, params_cap - plen,
1678             "&query=%.*s", (int)enc_len, enc_q);
1679 0           Safefree(enc_q);
1680             }
1681 0           plen = append_settings_url_params(params, plen,
1682             defaults, overrides,
1683             &query_id, &query_id_len);
1684 0 0         if (query_id) {
1685 0           size_t need = plen + 10 + query_id_len * 3 + 1;
1686 0 0         if (need > params_cap) {
1687 0           params_cap = need;
1688 0           Renew(params, params_cap, char);
1689             }
1690 0           plen += (size_t)snprintf(params + plen, params_cap - plen, "&query_id=");
1691 0           plen += url_encode(query_id, query_id_len, params + plen);
1692             }
1693 0           params[plen] = '\0';
1694              
1695 0           req_cap = 512 + body_len + plen
1696 0 0         + (self->host ? strlen(self->host) : 0)
1697 0 0         + (self->user ? strlen(self->user) : 0)
1698 0 0         + (self->password ? strlen(self->password) : 0);
1699 0           Newx(req, req_cap, char);
1700              
1701 0           pos += snprintf(req + pos, req_cap - pos,
1702             "POST /%s HTTP/1.1\r\n", params);
1703 0           Safefree(params);
1704 0           pos += snprintf(req + pos, req_cap - pos,
1705             "Host: %s:%u\r\n", self->host, self->port);
1706 0 0         if (self->user) {
1707 0           pos += snprintf(req + pos, req_cap - pos,
1708             "X-ClickHouse-User: %s\r\n", self->user);
1709             }
1710 0 0         if (self->password && self->password[0]) {
    0          
1711 0           pos += snprintf(req + pos, req_cap - pos,
1712             "X-ClickHouse-Key: %s\r\n", self->password);
1713             }
1714 0           pos += snprintf(req + pos, req_cap - pos, "Connection: keep-alive\r\n");
1715              
1716 0 0         if (do_compress)
1717 0           pos += snprintf(req + pos, req_cap - pos, "Accept-Encoding: gzip\r\n");
1718              
1719 0 0         if (content_encoding)
1720 0           pos += snprintf(req + pos, req_cap - pos, "%s", content_encoding);
1721              
1722 0           pos += snprintf(req + pos, req_cap - pos,
1723             "Content-Length: %lu\r\n\r\n", (unsigned long)body_len);
1724              
1725 0 0         if (body_len > 0) {
1726 0 0         if (pos + body_len > req_cap) {
1727 0           req_cap = pos + body_len + 1;
1728 0           Renew(req, req_cap, char);
1729             }
1730 0 0         Copy(body ? body : data, req + pos, body_len, char);
1731 0           pos += body_len;
1732             }
1733              
1734 0 0         if (body) Safefree(body);
1735              
1736 0           *req_len = pos;
1737 0           return req;
1738             }
1739              
1740             /* Build HTTP GET /ping request */
1741 0           static char* build_http_ping_request(ev_clickhouse_t *self, size_t *req_len) {
1742             char *req;
1743 0 0         size_t req_cap = 128 + (self->host ? strlen(self->host) : 0);
1744 0           size_t pos = 0;
1745              
1746 0           Newx(req, req_cap, char);
1747 0           pos = snprintf(req, req_cap,
1748             "GET /ping HTTP/1.1\r\n"
1749             "Host: %s:%u\r\n"
1750             "Connection: keep-alive\r\n\r\n",
1751             self->host, self->port);
1752 0 0         if (pos >= req_cap) pos = req_cap - 1;
1753 0           *req_len = pos;
1754 0           return req;
1755             }
1756              
1757             /* --- HTTP response parsing --- */
1758              
1759             /* Find \r\n\r\n in recv_buf. Returns offset past it, or 0 if not found. */
1760 0           static size_t find_header_end(const char *buf, size_t len) {
1761             size_t i;
1762 0 0         if (len < 4) return 0;
1763 0 0         for (i = 0; i <= len - 4; i++) {
1764 0 0         if (buf[i] == '\r' && buf[i+1] == '\n' &&
    0          
1765 0 0         buf[i+2] == '\r' && buf[i+3] == '\n') {
    0          
1766 0           return i + 4;
1767             }
1768             }
1769 0           return 0;
1770             }
1771              
1772             /* Extract ClickHouse error code from HTTP error body ("Code: NNN. ...") */
1773 0           static int32_t parse_ch_error_code(const char *body, size_t len) {
1774 0 0         if (len > 6 && memcmp(body, "Code: ", 6) == 0)
    0          
1775 0           return (int32_t)atoi(body + 6);
1776 0           return 0;
1777             }
1778              
1779             /* Parse HTTP status line, extract status code */
1780 0           static int parse_http_status(const char *buf, size_t len) {
1781             /* HTTP/1.1 200 OK\r\n */
1782 0           const char *p = buf;
1783 0           const char *end = buf + len;
1784             int status;
1785              
1786             /* skip "HTTP/1.x " */
1787 0 0         while (p < end && *p != ' ') p++;
    0          
1788 0 0         if (p >= end) return 0;
1789 0           p++;
1790              
1791 0           status = atoi(p);
1792 0 0         if (status < 100 || status > 599) return 500; /* treat malformed as server error */
    0          
1793 0           return status;
1794             }
1795              
1796             /* Find header value (case-insensitive). Returns pointer into buf or NULL. */
1797 0           static const char* find_header(const char *headers, size_t headers_len,
1798             const char *name, size_t *value_len) {
1799 0           size_t name_len = strlen(name);
1800 0           const char *p = headers;
1801 0           const char *end = headers + headers_len;
1802              
1803 0 0         while (p < end) {
1804 0           const char *line_end = p;
1805 0 0         while (line_end < end && *line_end != '\r') line_end++;
    0          
1806              
1807 0 0         if ((size_t)(line_end - p) > name_len + 1 && p[name_len] == ':') {
    0          
1808 0           int match = 1;
1809             size_t i;
1810 0 0         for (i = 0; i < name_len; i++) {
1811 0 0         if (tolower((unsigned char)p[i]) != tolower((unsigned char)name[i])) {
1812 0           match = 0;
1813 0           break;
1814             }
1815             }
1816 0 0         if (match) {
1817 0           const char *val = p + name_len + 1;
1818 0 0         while (val < line_end && *val == ' ') val++;
    0          
1819 0           *value_len = line_end - val;
1820 0           return val;
1821             }
1822             }
1823              
1824             /* advance past \r\n */
1825 0 0         if (line_end + 2 <= end) p = line_end + 2;
1826 0           else break;
1827             }
1828 0           return NULL;
1829             }
1830              
1831             /* Parse a complete HTTP response from recv_buf. */
1832 0           static void process_http_response(ev_clickhouse_t *self) {
1833             size_t hdr_end;
1834             int status;
1835             const char *val;
1836             size_t val_len;
1837 0           size_t content_length = 0;
1838 0           int chunked = 0;
1839 0           int is_gzip = 0;
1840             const char *body;
1841             size_t body_len;
1842 0           char *decoded = NULL;
1843 0           size_t decoded_len = 0;
1844 0           size_t decoded_cap = 0;
1845              
1846 0 0         if (self->recv_len == 0 || self->send_count == 0) return;
    0          
1847              
1848             /* find headers end */
1849 0           hdr_end = find_header_end(self->recv_buf, self->recv_len);
1850 0 0         if (hdr_end == 0) return; /* need more data */
1851              
1852             /* parse status */
1853 0           status = parse_http_status(self->recv_buf, hdr_end);
1854              
1855             /* parse Content-Length */
1856 0           val = find_header(self->recv_buf, hdr_end, "Content-Length", &val_len);
1857 0 0         if (val) {
1858 0           content_length = (size_t)strtoul(val, NULL, 10);
1859             }
1860              
1861             /* check Transfer-Encoding: chunked */
1862 0           val = find_header(self->recv_buf, hdr_end, "Transfer-Encoding", &val_len);
1863 0 0         if (val && val_len >= 7 && strncasecmp(val, "chunked", 7) == 0) {
    0          
    0          
1864 0           chunked = 1;
1865             }
1866              
1867             /* check Content-Encoding: gzip */
1868 0           val = find_header(self->recv_buf, hdr_end, "Content-Encoding", &val_len);
1869 0 0         if (val && val_len >= 4 && strncasecmp(val, "gzip", 4) == 0) {
    0          
    0          
1870 0           is_gzip = 1;
1871             }
1872              
1873 0 0         if (chunked) {
1874             /* decode chunked transfer encoding */
1875 0           const char *cp = self->recv_buf + hdr_end;
1876 0           const char *cp_end = self->recv_buf + self->recv_len;
1877              
1878             {
1879 0           int chunked_complete = 0;
1880 0 0         while (cp < cp_end) {
1881             /* read chunk size */
1882 0           const char *nl = cp;
1883             unsigned long chunk_size;
1884 0 0         while (nl < cp_end && *nl != '\r') nl++;
    0          
1885 0 0         if (nl + 2 > cp_end) goto need_more; /* need more data */
1886              
1887 0           chunk_size = strtoul(cp, NULL, 16);
1888 0           cp = nl + 2; /* skip \r\n */
1889              
1890 0 0         if (chunk_size == 0) {
1891             /* terminal chunk; skip trailing \r\n */
1892 0 0         if (cp + 2 > cp_end) goto need_more;
1893 0           cp += 2;
1894 0           chunked_complete = 1;
1895 0           break;
1896             }
1897              
1898 0 0         if ((size_t)(cp_end - cp) < 2
1899 0 0         || chunk_size > (size_t)(cp_end - cp) - 2) goto need_more;
1900              
1901             /* guard against overflow and unbounded growth —
1902             * close connection since remaining chunks would
1903             * corrupt the stream for subsequent requests */
1904 0 0         if (decoded_len + chunk_size < decoded_len
1905 0 0         || decoded_len + chunk_size > CH_MAX_DECOMPRESS_SIZE) {
1906 0 0         if (decoded) Safefree(decoded);
1907 0           self->send_count--;
1908 0           int destroyed = deliver_error(self, "chunked response too large");
1909 0 0         if (destroyed) return;
1910 0 0         if (cancel_pending(self, "connection closed")) return;
1911 0           cleanup_connection(self);
1912 0           return;
1913             }
1914 0 0         if (decoded == NULL) {
1915 0           decoded_cap = chunk_size + 256;
1916 0           Newx(decoded, decoded_cap, char);
1917 0 0         } else if (decoded_len + chunk_size > decoded_cap) {
1918 0           decoded_cap = (decoded_len + chunk_size) * 2;
1919 0           Renew(decoded, decoded_cap, char);
1920             }
1921 0           Copy(cp, decoded + decoded_len, chunk_size, char);
1922 0           decoded_len += chunk_size;
1923 0           cp += chunk_size + 2; /* skip chunk data + \r\n */
1924             }
1925              
1926 0 0         if (!chunked_complete) goto need_more;
1927             }
1928              
1929 0           body = decoded;
1930 0           body_len = decoded_len;
1931              
1932             /* deliver response */
1933 0           self->send_count--;
1934 0 0         if (status == 200) {
1935 0           char *final_body = (char *)body;
1936 0           size_t final_len = body_len;
1937              
1938 0 0         if (is_gzip && body_len > 0) {
    0          
1939             size_t dec_len;
1940 0           char *dec = gzip_decompress(body, body_len, &dec_len);
1941 0 0         if (dec) {
1942 0           final_body = dec;
1943 0           final_len = dec_len;
1944             } else {
1945 0 0         if (decoded) Safefree(decoded);
1946 0           size_t consumed = cp - self->recv_buf;
1947 0           recv_consume(self, consumed);
1948 0           int destroyed = deliver_error(self, "gzip decompression failed");
1949 0 0         if (destroyed) return;
1950 0           goto done;
1951             }
1952             }
1953              
1954             {
1955 0           int is_raw = peek_cb_raw(self);
1956 0           size_t consumed = cp - self->recv_buf;
1957              
1958 0 0         if (is_raw) {
1959             /* raw mode — deliver body as scalar, skip TSV parsing */
1960 0           int destroyed = deliver_raw_body(self, final_body, final_len);
1961 0 0         if (final_body != body) Safefree(final_body);
1962 0 0         if (decoded) Safefree(decoded);
1963 0           recv_consume(self, consumed);
1964 0 0         if (destroyed) return;
1965             } else {
1966 0           AV *rows = NULL;
1967 0 0         if (final_len > 0)
1968 0           rows = parse_tab_separated(final_body, final_len);
1969 0 0         if (final_body != body) Safefree(final_body);
1970 0 0         if (decoded) Safefree(decoded);
1971 0           recv_consume(self, consumed);
1972 0 0         if (deliver_rows(self, rows)) return;
1973             }
1974             }
1975             } else {
1976             /* error */
1977             char *errmsg;
1978 0           char *err_body = (char *)body;
1979 0           size_t err_len = body_len;
1980              
1981 0 0         if (is_gzip && body_len > 0) {
    0          
1982             size_t dec_len;
1983 0           char *dec = gzip_decompress(body, body_len, &dec_len);
1984 0 0         if (dec) {
1985 0           err_body = dec;
1986 0           err_len = dec_len;
1987             }
1988             }
1989              
1990 0 0         while (err_len > 0 && (err_body[err_len-1] == '\n' || err_body[err_len-1] == '\r'))
    0          
    0          
1991 0           err_len--;
1992 0           self->last_error_code = parse_ch_error_code(err_body, err_len);
1993 0           Newx(errmsg, err_len + 64, char);
1994 0           snprintf(errmsg, err_len + 64, "HTTP %d: %.*s",
1995             status, (int)err_len, err_body);
1996 0 0         if (err_body != body) Safefree(err_body);
1997 0 0         if (decoded) Safefree(decoded);
1998              
1999 0           size_t consumed = cp - self->recv_buf;
2000 0           recv_consume(self, consumed);
2001              
2002 0           int destroyed = deliver_error(self, errmsg);
2003 0           Safefree(errmsg);
2004 0 0         if (destroyed) return;
2005             }
2006             } else {
2007             /* Content-Length based */
2008 0 0         if (self->recv_len < hdr_end + content_length) return; /* need more data */
2009              
2010 0           body = self->recv_buf + hdr_end;
2011 0           body_len = content_length;
2012              
2013 0           self->send_count--;
2014 0 0         if (status == 200) {
2015 0           char *final_body = (char *)body;
2016 0           size_t final_len = body_len;
2017              
2018 0 0         if (is_gzip && body_len > 0) {
    0          
2019             size_t dec_len;
2020 0           char *dec = gzip_decompress(body, body_len, &dec_len);
2021 0 0         if (dec) {
2022 0           final_body = dec;
2023 0           final_len = dec_len;
2024             } else {
2025 0           size_t consumed = hdr_end + content_length;
2026 0           recv_consume(self, consumed);
2027 0           int destroyed = deliver_error(self, "gzip decompression failed");
2028 0 0         if (destroyed) return;
2029 0           goto done;
2030             }
2031             }
2032              
2033             {
2034 0           int is_raw = peek_cb_raw(self);
2035 0           size_t consumed = hdr_end + content_length;
2036              
2037 0 0         if (is_raw) {
2038 0           int destroyed = deliver_raw_body(self, final_body, final_len);
2039 0 0         if (final_body != body) Safefree(final_body);
2040 0           recv_consume(self, consumed);
2041 0 0         if (destroyed) return;
2042             } else {
2043 0           AV *rows = NULL;
2044 0 0         if (final_len > 0)
2045 0           rows = parse_tab_separated(final_body, final_len);
2046 0 0         if (final_body != body) Safefree(final_body);
2047 0           recv_consume(self, consumed);
2048 0 0         if (deliver_rows(self, rows)) return;
2049             }
2050             }
2051             } else {
2052             char *errmsg;
2053 0           char *err_body = (char *)body;
2054 0           size_t err_len = body_len;
2055              
2056 0 0         if (is_gzip && body_len > 0) {
    0          
2057             size_t dec_len;
2058 0           char *dec = gzip_decompress(body, body_len, &dec_len);
2059 0 0         if (dec) {
2060 0           err_body = dec;
2061 0           err_len = dec_len;
2062             }
2063             }
2064              
2065 0 0         while (err_len > 0 && (err_body[err_len-1] == '\n' || err_body[err_len-1] == '\r'))
    0          
    0          
2066 0           err_len--;
2067 0           self->last_error_code = parse_ch_error_code(err_body, err_len);
2068 0           Newx(errmsg, err_len + 64, char);
2069 0           snprintf(errmsg, err_len + 64, "HTTP %d: %.*s",
2070             status, (int)err_len, err_body);
2071              
2072 0 0         if (err_body != body) Safefree(err_body);
2073              
2074 0           size_t consumed = hdr_end + content_length;
2075 0           recv_consume(self, consumed);
2076              
2077 0           int destroyed = deliver_error(self, errmsg);
2078 0           Safefree(errmsg);
2079 0 0         if (destroyed) return;
2080             }
2081             }
2082              
2083 0 0         if (self->magic != EV_CH_MAGIC) return;
2084              
2085 0           done:
2086             /* Stop query timeout timer on response */
2087 0 0         if (self->timing) {
2088 0           ev_timer_stop(self->loop, &self->timer);
2089 0           self->timing = 0;
2090             }
2091 0           pipeline_advance(self);
2092 0           return;
2093              
2094 0           need_more:
2095             /* incomplete response — keep reading */
2096 0 0         if (decoded) Safefree(decoded);
2097 0           return;
2098             }
2099              
2100             /* --- Native protocol packet builders --- */
2101              
2102 0           static char* build_native_hello(ev_clickhouse_t *self, size_t *out_len) {
2103             native_buf_t b;
2104 0           nbuf_init(&b);
2105              
2106 0           nbuf_varuint(&b, CLIENT_HELLO);
2107 0           nbuf_cstring(&b, CH_CLIENT_NAME);
2108 0           nbuf_varuint(&b, CH_CLIENT_VERSION_MAJOR);
2109 0           nbuf_varuint(&b, CH_CLIENT_VERSION_MINOR);
2110 0           nbuf_varuint(&b, CH_CLIENT_REVISION);
2111 0 0         nbuf_cstring(&b, self->database ? self->database : "default");
2112 0 0         nbuf_cstring(&b, self->user ? self->user : "default");
2113 0 0         nbuf_cstring(&b, self->password ? self->password : "");
2114              
2115 0           *out_len = b.len;
2116 0           return b.data;
2117             }
2118              
2119 0           static char* build_native_ping(size_t *out_len) {
2120             native_buf_t b;
2121 0           nbuf_init(&b);
2122 0           nbuf_varuint(&b, CLIENT_PING);
2123 0           *out_len = b.len;
2124 0           return b.data;
2125             }
2126              
2127             /* Build an empty Data block (signals end of client data after Query) */
2128 0           static void nbuf_empty_data_block(native_buf_t *b, int do_compress) {
2129 0           nbuf_varuint(b, CLIENT_DATA);
2130 0           nbuf_cstring(b, ""); /* table name — outside compression */
2131              
2132             /* block body: block info + num_cols + num_rows */
2133             #ifdef HAVE_LZ4
2134             if (do_compress) {
2135             native_buf_t body;
2136             char *compressed;
2137             size_t comp_len;
2138              
2139             nbuf_init(&body);
2140             nbuf_varuint(&body, 1); /* field_num = 1 */
2141             nbuf_u8(&body, 0); /* is_overflows = false */
2142             nbuf_varuint(&body, 2); /* field_num = 2 */
2143             {
2144             int32_t bucket = -1;
2145             nbuf_append(&body, (const char *)&bucket, 4);
2146             }
2147             nbuf_varuint(&body, 0); /* end of block info */
2148             nbuf_varuint(&body, 0); /* num_columns = 0 */
2149             nbuf_varuint(&body, 0); /* num_rows = 0 */
2150              
2151             compressed = ch_lz4_compress(body.data, body.len, &comp_len);
2152             Safefree(body.data);
2153             if (compressed) {
2154             nbuf_append(b, compressed, comp_len);
2155             Safefree(compressed);
2156             return;
2157             }
2158             /* LZ4 failed (should never happen) — fall through to uncompressed */
2159             }
2160             #else
2161             (void)do_compress;
2162             #endif
2163              
2164             /* block info (revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO) */
2165 0           nbuf_varuint(b, 1); /* field_num = 1 */
2166 0           nbuf_u8(b, 0); /* is_overflows = false */
2167 0           nbuf_varuint(b, 2); /* field_num = 2 */
2168             {
2169 0           int32_t bucket = -1;
2170 0           nbuf_append(b, (const char *)&bucket, 4); /* bucket_num = -1 */
2171             }
2172 0           nbuf_varuint(b, 0); /* end of block info */
2173 0           nbuf_varuint(b, 0); /* num_columns = 0 */
2174 0           nbuf_varuint(b, 0); /* num_rows = 0 */
2175 0           }
2176              
2177 0           static char* build_native_query(ev_clickhouse_t *self, const char *sql,
2178             size_t sql_len, HV *defaults,
2179             HV *overrides, size_t *out_len) {
2180             native_buf_t b;
2181 0           const char *query_id = NULL;
2182 0           STRLEN query_id_len = 0;
2183 0           nbuf_init(&b);
2184              
2185             /* Pre-scan settings for query_id (needed before settings block) */
2186             {
2187             SV **svp;
2188 0 0         if (overrides && (svp = hv_fetch(overrides, "query_id", 8, 0)))
    0          
2189 0           query_id = SvPV(*svp, query_id_len);
2190 0 0         else if (defaults && (svp = hv_fetch(defaults, "query_id", 8, 0)))
    0          
2191 0           query_id = SvPV(*svp, query_id_len);
2192             }
2193              
2194             /* Query packet */
2195 0           nbuf_varuint(&b, CLIENT_QUERY);
2196 0 0         if (query_id) {
2197 0           nbuf_varuint(&b, (uint64_t)query_id_len);
2198 0           nbuf_append(&b, query_id, query_id_len);
2199             } else {
2200 0           nbuf_cstring(&b, ""); /* query_id (empty = auto) */
2201             }
2202              
2203             /* Client info — field order must match ClientInfo::read() */
2204 0           nbuf_u8(&b, QUERY_INITIAL);
2205 0           nbuf_cstring(&b, ""); /* initial_user */
2206 0           nbuf_cstring(&b, ""); /* initial_query_id */
2207 0           nbuf_cstring(&b, "[::ffff:127.0.0.1]:0"); /* initial_address */
2208              
2209             /* initial_query_start_time_microseconds (revision >= 54449) */
2210             {
2211 0           uint64_t zero64 = 0;
2212 0           nbuf_append(&b, (const char *)&zero64, 8);
2213             }
2214              
2215             /* iface_type: 1=TCP, os_user, client_hostname, client_name */
2216 0           nbuf_u8(&b, 1);
2217 0           nbuf_cstring(&b, ""); /* os_user */
2218 0           nbuf_cstring(&b, ""); /* client_hostname */
2219 0           nbuf_cstring(&b, CH_CLIENT_NAME);
2220 0           nbuf_varuint(&b, CH_CLIENT_VERSION_MAJOR);
2221 0           nbuf_varuint(&b, CH_CLIENT_VERSION_MINOR);
2222 0           nbuf_varuint(&b, CH_CLIENT_REVISION);
2223              
2224             /* quota_key_in_client_info (always present, revision >= ~54060) */
2225 0           nbuf_cstring(&b, "");
2226              
2227             /* distributed_depth (revision >= 54448) */
2228 0           nbuf_varuint(&b, 0);
2229              
2230             /* version_patch (revision >= 54401) */
2231 0           nbuf_varuint(&b, 0);
2232              
2233             /* OpenTelemetry trace context (revision >= 54442): no trace */
2234 0           nbuf_u8(&b, 0);
2235              
2236             /* parallel_replicas (revision >= 54453) */
2237 0           nbuf_varuint(&b, 0); /* collaborate_with_initiator */
2238 0           nbuf_varuint(&b, 0); /* count_participating_replicas */
2239 0           nbuf_varuint(&b, 0); /* number_of_current_replica */
2240              
2241             /* Settings (serialized as strings: revision >= 54429)
2242             * Format: repeated (String name, UInt8 is_important, String value),
2243             * terminated by empty name. */
2244 0           write_native_settings(&b, defaults, overrides, NULL, NULL);
2245 0           nbuf_cstring(&b, ""); /* empty name = end of settings */
2246              
2247             /* interserver_secret: empty string (revision >= 54441) */
2248 0           nbuf_cstring(&b, "");
2249              
2250             /* state (stage), compression, query */
2251 0           nbuf_varuint(&b, STAGE_COMPLETE);
2252             #ifdef HAVE_LZ4
2253             nbuf_varuint(&b, self->compress ? 1 : 0);
2254             #else
2255 0           nbuf_varuint(&b, 0);
2256             #endif
2257 0           nbuf_string(&b, sql, sql_len);
2258              
2259             /* Parameters block (revision >= 54459):
2260             * Format: repeated (String name, VarUInt flags, String value),
2261             * terminated by empty name. Values are wrapped in single quotes
2262             * (ClickHouse Field::dump format for strings — the server uses
2263             * Field::restoreFromDump to parse them). */
2264 0 0         if (overrides) {
2265             HE *pe;
2266 0           hv_iterinit(overrides);
2267 0 0         while ((pe = hv_iternext(overrides))) {
2268             I32 klen;
2269             STRLEN vlen;
2270 0           char *key = hv_iterkey(pe, &klen);
2271 0           char *val = SvPV(hv_iterval(overrides, pe), vlen);
2272 0 0         if (klen > 6 && memcmp(key, "param_", 6) == 0) {
    0          
2273             /* strip param_ prefix: native protocol uses bare names */
2274 0           nbuf_varuint(&b, (uint64_t)(klen - 6));
2275 0           nbuf_append(&b, key + 6, (size_t)(klen - 6));
2276 0           nbuf_varuint(&b, 2); /* flags: CUSTOM = 0x02 */
2277             /* value: wrap in single quotes for Field::dump format */
2278 0           nbuf_varuint(&b, (uint64_t)(vlen + 2));
2279 0           nbuf_append(&b, "'", 1);
2280 0           nbuf_append(&b, val, vlen);
2281 0           nbuf_append(&b, "'", 1);
2282             }
2283             }
2284             }
2285 0 0         if (defaults) {
2286             HE *pe;
2287 0           hv_iterinit(defaults);
2288 0 0         while ((pe = hv_iternext(defaults))) {
2289             I32 klen;
2290 0           char *key = hv_iterkey(pe, &klen);
2291 0 0         if (klen > 6 && memcmp(key, "param_", 6) == 0) {
    0          
2292 0 0         if (overrides && hv_exists(overrides, key, klen))
    0          
2293 0           continue;
2294             STRLEN vlen;
2295 0           char *val = SvPV(hv_iterval(defaults, pe), vlen);
2296             /* strip param_ prefix: native protocol uses bare names */
2297 0           nbuf_varuint(&b, (uint64_t)(klen - 6));
2298 0           nbuf_append(&b, key + 6, (size_t)(klen - 6));
2299 0           nbuf_varuint(&b, 2); /* flags: CUSTOM = 0x02 */
2300 0           nbuf_varuint(&b, (uint64_t)(vlen + 2));
2301 0           nbuf_append(&b, "'", 1);
2302 0           nbuf_append(&b, val, vlen);
2303 0           nbuf_append(&b, "'", 1);
2304             }
2305             }
2306             }
2307 0           nbuf_cstring(&b, ""); /* empty name = end of parameters */
2308              
2309             /* Append empty Data block */
2310 0           nbuf_empty_data_block(&b, self->compress);
2311              
2312 0           *out_len = b.len;
2313 0           return b.data;
2314             }
2315              
2316             /* --- Native protocol column decoder --- */
2317              
2318             /* Column type codes for decoding. */
2319             enum {
2320             CT_INT8, CT_INT16, CT_INT32, CT_INT64,
2321             CT_UINT8, CT_UINT16, CT_UINT32, CT_UINT64,
2322             CT_FLOAT32, CT_FLOAT64,
2323             CT_STRING, CT_FIXEDSTRING,
2324             CT_ARRAY, CT_NULLABLE,
2325             CT_DATE, CT_DATE32, CT_DATETIME, CT_DATETIME64,
2326             CT_UUID, CT_ENUM8, CT_ENUM16,
2327             CT_DECIMAL32, CT_DECIMAL64, CT_DECIMAL128,
2328             CT_LOWCARDINALITY, CT_NOTHING,
2329             CT_BOOL, CT_IPV4, CT_IPV6,
2330             CT_INT128, CT_UINT128,
2331             CT_INT256, CT_UINT256,
2332             CT_TUPLE, CT_MAP,
2333             CT_UNKNOWN
2334             };
2335              
2336             typedef struct col_type_s col_type_t;
2337             struct col_type_s {
2338             int code;
2339             int param; /* FixedString(N), DateTime64 precision, Decimal scale */
2340             col_type_t *inner; /* Nullable, Array, LowCardinality */
2341             col_type_t **inners; /* Tuple elements, Map key+value */
2342             int num_inners;
2343             char *type_str; /* full type string (for Enum label lookup) */
2344             size_t type_str_len;
2345             char *tz; /* timezone for DateTime/DateTime64 (NULL = UTC) */
2346             };
2347              
2348 0           static void free_col_type(col_type_t *t) {
2349             int i;
2350 0 0         if (!t) return;
2351 0 0         if (t->inner) free_col_type(t->inner);
2352 0 0         if (t->inners) {
2353 0 0         for (i = 0; i < t->num_inners; i++)
2354 0           free_col_type(t->inners[i]);
2355 0           Safefree(t->inners);
2356             }
2357 0 0         if (t->type_str) Safefree(t->type_str);
2358 0 0         if (t->tz) Safefree(t->tz);
2359 0           Safefree(t);
2360             }
2361              
2362             /* Forward declaration */
2363             static col_type_t* parse_col_type(const char *type, size_t len);
2364              
2365             /*
2366             * Parse comma-separated type list inside Tuple(...) or Map(...).
2367             * Handles nested parentheses correctly.
2368             * Sets t->inners and t->num_inners.
2369             */
2370 0           static void parse_type_list(col_type_t *t, const char *inner, size_t inner_len) {
2371 0           int depth = 0, count = 0;
2372 0           size_t i, start = 0;
2373              
2374             /* Count elements */
2375 0 0         for (i = 0; i <= inner_len; i++) {
2376 0 0         if (i < inner_len && inner[i] == '(') depth++;
    0          
2377 0 0         else if (i < inner_len && inner[i] == ')') depth--;
    0          
2378 0 0         else if (i == inner_len || (inner[i] == ',' && depth == 0))
    0          
    0          
2379 0           count++;
2380             }
2381              
2382 0           Newxz(t->inners, count, col_type_t*);
2383 0           t->num_inners = count;
2384              
2385             /* Parse each element */
2386 0           count = 0;
2387 0           depth = 0;
2388 0           start = 0;
2389 0 0         for (i = 0; i <= inner_len; i++) {
2390 0 0         if (i < inner_len && inner[i] == '(') depth++;
    0          
2391 0 0         else if (i < inner_len && inner[i] == ')') depth--;
    0          
2392 0 0         else if (i == inner_len || (inner[i] == ',' && depth == 0)) {
    0          
    0          
2393 0           size_t s = start, e = i;
2394 0 0         while (s < e && inner[s] == ' ') s++;
    0          
2395 0 0         while (e > s && inner[e-1] == ' ') e--;
    0          
2396             /* Strip named tuple field prefix: "name Type" -> "Type" */
2397             {
2398             size_t sp;
2399 0 0         for (sp = s; sp < e; sp++) {
2400 0 0         if (inner[sp] == '(') break; /* type with parens, stop */
2401 0 0         if (inner[sp] == ' ') { s = sp + 1; break; }
2402             }
2403             }
2404 0           t->inners[count++] = parse_col_type(inner + s, e - s);
2405 0           start = i + 1;
2406             }
2407             }
2408 0           }
2409              
2410 0           static col_type_t* parse_col_type(const char *type, size_t len) {
2411             col_type_t *t;
2412 0           Newxz(t, 1, col_type_t);
2413              
2414 0 0         if (len == 4 && memcmp(type, "Int8", 4) == 0) t->code = CT_INT8;
    0          
2415 0 0         else if (len == 5 && memcmp(type, "Int16", 5) == 0) t->code = CT_INT16;
    0          
2416 0 0         else if (len == 5 && memcmp(type, "Int32", 5) == 0) t->code = CT_INT32;
    0          
2417 0 0         else if (len == 5 && memcmp(type, "Int64", 5) == 0) t->code = CT_INT64;
    0          
2418 0 0         else if (len == 5 && memcmp(type, "UInt8", 5) == 0) t->code = CT_UINT8;
    0          
2419 0 0         else if (len == 6 && memcmp(type, "UInt16", 6) == 0) t->code = CT_UINT16;
    0          
2420 0 0         else if (len == 6 && memcmp(type, "UInt32", 6) == 0) t->code = CT_UINT32;
    0          
2421 0 0         else if (len == 6 && memcmp(type, "UInt64", 6) == 0) t->code = CT_UINT64;
    0          
2422 0 0         else if (len == 7 && memcmp(type, "Float32", 7) == 0) t->code = CT_FLOAT32;
    0          
2423 0 0         else if (len == 7 && memcmp(type, "Float64", 7) == 0) t->code = CT_FLOAT64;
    0          
2424 0 0         else if (len == 6 && memcmp(type, "String", 6) == 0) t->code = CT_STRING;
    0          
2425 0 0         else if (len > 12 && memcmp(type, "FixedString(", 12) == 0) {
    0          
2426 0           t->code = CT_FIXEDSTRING;
2427 0           t->param = atoi(type + 12);
2428             }
2429 0 0         else if (len > 6 && memcmp(type, "Array(", 6) == 0) {
    0          
2430 0           t->code = CT_ARRAY;
2431 0           t->inner = parse_col_type(type + 6, len - 7);
2432             }
2433 0 0         else if (len > 9 && memcmp(type, "Nullable(", 9) == 0) {
    0          
2434 0           t->code = CT_NULLABLE;
2435 0           t->inner = parse_col_type(type + 9, len - 10);
2436             }
2437 0 0         else if (len > 15 && memcmp(type, "LowCardinality(", 15) == 0) {
    0          
2438 0           t->code = CT_LOWCARDINALITY;
2439 0           t->inner = parse_col_type(type + 15, len - 16);
2440             }
2441 0 0         else if (len == 4 && memcmp(type, "Date", 4) == 0) t->code = CT_DATE;
    0          
2442 0 0         else if (len == 6 && memcmp(type, "Date32", 6) == 0) t->code = CT_DATE32;
    0          
2443 0 0         else if (len == 8 && memcmp(type, "DateTime", 8) == 0) t->code = CT_DATETIME;
    0          
2444 0 0         else if (len > 9 && memcmp(type, "DateTime(", 9) == 0) {
    0          
2445 0           t->code = CT_DATETIME;
2446             /* DateTime('timezone') — extract timezone */
2447 0           {
2448 0           const char *q = memchr(type + 9, '\'', len - 9);
2449 0 0         if (q) {
2450 0           const char *qe = memchr(q + 1, '\'', type + len - q - 1);
2451 0 0         if (qe && qe > q + 1) {
    0          
2452 0           size_t tzlen = qe - q - 1;
2453 0           Newx(t->tz, tzlen + 1, char);
2454 0           Copy(q + 1, t->tz, tzlen, char);
2455 0           t->tz[tzlen] = '\0';
2456             }
2457             }
2458             }
2459             }
2460 0 0         else if (len > 11 && memcmp(type, "DateTime64(", 11) == 0) {
    0          
2461 0           t->code = CT_DATETIME64;
2462 0           t->param = atoi(type + 11);
2463             /* DateTime64(N, 'timezone') — extract timezone */
2464 0           {
2465 0           const char *comma = memchr(type + 11, ',', len - 11);
2466 0 0         if (comma) {
2467 0           const char *q = memchr(comma, '\'', type + len - comma);
2468 0 0         if (q) {
2469 0           const char *qe = memchr(q + 1, '\'', type + len - q - 1);
2470 0 0         if (qe && qe > q + 1) {
    0          
2471 0           size_t tzlen = qe - q - 1;
2472 0           Newx(t->tz, tzlen + 1, char);
2473 0           Copy(q + 1, t->tz, tzlen, char);
2474 0           t->tz[tzlen] = '\0';
2475             }
2476             }
2477             }
2478             }
2479             }
2480 0 0         else if (len == 4 && memcmp(type, "UUID", 4) == 0) t->code = CT_UUID;
    0          
2481 0 0         else if (len > 6 && memcmp(type, "Enum8(", 6) == 0) {
    0          
2482 0           t->code = CT_ENUM8;
2483 0           Newx(t->type_str, len + 1, char);
2484 0           Copy(type, t->type_str, len, char);
2485 0           t->type_str[len] = '\0';
2486 0           t->type_str_len = len;
2487             }
2488 0 0         else if (len > 7 && memcmp(type, "Enum16(", 7) == 0) {
    0          
2489 0           t->code = CT_ENUM16;
2490 0           Newx(t->type_str, len + 1, char);
2491 0           Copy(type, t->type_str, len, char);
2492 0           t->type_str[len] = '\0';
2493 0           t->type_str_len = len;
2494             }
2495 0 0         else if (len > 10 && memcmp(type, "Decimal32(", 10) == 0) {
    0          
2496 0           t->code = CT_DECIMAL32;
2497 0           t->param = atoi(type + 10);
2498             }
2499 0 0         else if (len > 10 && memcmp(type, "Decimal64(", 10) == 0) {
    0          
2500 0           t->code = CT_DECIMAL64;
2501 0           t->param = atoi(type + 10);
2502             }
2503 0 0         else if (len > 11 && memcmp(type, "Decimal128(", 11) == 0) {
    0          
2504 0           t->code = CT_DECIMAL128;
2505 0           t->param = atoi(type + 11);
2506             }
2507 0 0         else if (len > 8 && memcmp(type, "Decimal(", 8) == 0) {
    0          
2508 0           int precision = atoi(type + 8);
2509 0           const char *comma = memchr(type + 8, ',', len - 8);
2510 0 0         t->param = comma ? atoi(comma + 1) : 0;
2511 0 0         if (precision <= 9) t->code = CT_DECIMAL32;
2512 0 0         else if (precision <= 18) t->code = CT_DECIMAL64;
2513 0           else t->code = CT_DECIMAL128;
2514             }
2515 0 0         else if (len == 7 && memcmp(type, "Nothing", 7) == 0) t->code = CT_NOTHING;
    0          
2516 0 0         else if (len == 4 && memcmp(type, "Bool", 4) == 0) t->code = CT_BOOL;
    0          
2517 0 0         else if (len == 4 && memcmp(type, "IPv4", 4) == 0) t->code = CT_IPV4;
    0          
2518 0 0         else if (len == 4 && memcmp(type, "IPv6", 4) == 0) t->code = CT_IPV6;
    0          
2519 0 0         else if (len == 6 && memcmp(type, "Int128", 6) == 0) t->code = CT_INT128;
    0          
2520 0 0         else if (len == 7 && memcmp(type, "UInt128", 7) == 0) t->code = CT_UINT128;
    0          
2521 0 0         else if (len == 6 && memcmp(type, "Int256", 6) == 0) t->code = CT_INT256;
    0          
2522 0 0         else if (len == 7 && memcmp(type, "UInt256", 7) == 0) t->code = CT_UINT256;
    0          
2523 0 0         else if (len > 6 && memcmp(type, "Tuple(", 6) == 0) {
    0          
2524 0           t->code = CT_TUPLE;
2525 0           parse_type_list(t, type + 6, len - 7);
2526             }
2527 0 0         else if (len > 4 && memcmp(type, "Map(", 4) == 0) {
    0          
2528 0           t->code = CT_MAP;
2529 0           parse_type_list(t, type + 4, len - 5);
2530             }
2531 0 0         else if (len > 7 && memcmp(type, "Nested(", 7) == 0) {
    0          
2532             /* Nested(name1 Type1, name2 Type2) = Array(Tuple(Type1, Type2)) */
2533             col_type_t *tuple;
2534 0           Newxz(tuple, 1, col_type_t);
2535 0           tuple->code = CT_TUPLE;
2536 0           parse_type_list(tuple, type + 7, len - 8);
2537 0           t->code = CT_ARRAY;
2538 0           t->inner = tuple;
2539             }
2540 0 0         else if (len > 25 && memcmp(type, "SimpleAggregateFunction(", 24) == 0) {
    0          
2541             /* SimpleAggregateFunction(func, Type...) — skip func, parse inner type(s) */
2542 0           const char *inner = type + 24;
2543 0           size_t inner_len = len - 25;
2544             /* Find first comma at depth 0 — that separates func from type */
2545             size_t ci;
2546 0           int depth = 0;
2547 0 0         for (ci = 0; ci < inner_len; ci++) {
2548 0 0         if (inner[ci] == '(') depth++;
2549 0 0         else if (inner[ci] == ')') depth--;
2550 0 0         else if (inner[ci] == ',' && depth == 0) break;
    0          
2551             }
2552 0 0         if (ci < inner_len) {
2553             /* Skip comma and whitespace */
2554 0           ci++;
2555 0 0         while (ci < inner_len && inner[ci] == ' ') ci++;
    0          
2556 0           Safefree(t);
2557 0           t = parse_col_type(inner + ci, inner_len - ci);
2558             } else {
2559 0           t->code = CT_UNKNOWN;
2560             }
2561             }
2562             else {
2563             /* Unknown type — treat as String (read raw bytes) */
2564 0           t->code = CT_UNKNOWN;
2565             }
2566              
2567 0           return t;
2568             }
2569              
2570             /* Size in bytes for fixed-width types. Returns 0 for variable-width. */
2571 0           static size_t col_type_fixed_size(col_type_t *t) {
2572 0           switch (t->code) {
2573 0           case CT_INT8: case CT_UINT8: case CT_ENUM8: case CT_BOOL: return 1;
2574 0           case CT_INT16: case CT_UINT16: case CT_ENUM16: case CT_DATE: return 2;
2575 0           case CT_INT32: case CT_UINT32: case CT_FLOAT32:
2576             case CT_DECIMAL32: case CT_DATE32: case CT_DATETIME:
2577 0           case CT_IPV4: return 4;
2578 0           case CT_INT64: case CT_UINT64: case CT_FLOAT64:
2579 0           case CT_DECIMAL64: case CT_DATETIME64: return 8;
2580 0           case CT_UUID: case CT_DECIMAL128:
2581 0           case CT_INT128: case CT_UINT128: case CT_IPV6: return 16;
2582 0           case CT_INT256: case CT_UINT256: return 32;
2583 0           case CT_FIXEDSTRING: return (size_t)t->param;
2584 0           default: return 0;
2585             }
2586             }
2587              
2588             /* --- Decode helper functions for opt-in type formatting --- */
2589              
2590             /* Convert days since Unix epoch to "YYYY-MM-DD" */
2591 0           static SV* days_to_date_sv(int32_t days) {
2592 0           time_t t = (time_t)days * 86400;
2593             struct tm tm;
2594             char buf[11];
2595 0 0         if (!gmtime_r(&t, &tm)) return newSVpvn("0000-00-00", 10);
2596 0           snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
2597 0           tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
2598 0           return newSVpvn(buf, 10);
2599             }
2600              
2601             /* Convert epoch seconds to "YYYY-MM-DD HH:MM:SS" in UTC. */
2602 0           static SV* epoch_to_datetime_sv(uint32_t epoch) {
2603 0           time_t t = (time_t)epoch;
2604             struct tm tm;
2605             char buf[20];
2606 0 0         if (!gmtime_r(&t, &tm)) return newSVpvn("0000-00-00 00:00:00", 19);
2607 0           snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
2608 0           tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
2609             tm.tm_hour, tm.tm_min, tm.tm_sec);
2610 0           return newSVpvn(buf, 19);
2611             }
2612              
2613             /* Format epoch in the caller's currently-active TZ (set via set_tz). */
2614 0           static SV* epoch_to_datetime_sv_local(uint32_t epoch) {
2615 0           time_t t = (time_t)epoch;
2616             struct tm tm;
2617             char buf[20];
2618 0 0         if (!localtime_r(&t, &tm)) return newSVpvn("0000-00-00 00:00:00", 19);
2619 0           snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
2620 0           tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
2621             tm.tm_hour, tm.tm_min, tm.tm_sec);
2622 0           return newSVpvn(buf, 19);
2623             }
2624              
2625             /* Convert DateTime64 to "YYYY-MM-DD HH:MM:SS.fff...", use_local=1 for localtime */
2626 0           static SV* dt64_to_datetime_sv_ex(int64_t val, int precision, int use_local) {
2627 0           int64_t scale = 1;
2628             int p;
2629             int64_t epoch, frac;
2630             time_t t;
2631             struct tm tm;
2632             char buf[32];
2633             int n;
2634              
2635 0 0         for (p = 0; p < precision; p++) scale *= 10;
2636 0           epoch = val / scale;
2637 0           frac = val % scale;
2638 0 0         if (frac < 0) { epoch--; frac += scale; }
2639              
2640 0           t = (time_t)epoch;
2641 0 0         if (use_local) {
2642 0 0         if (!localtime_r(&t, &tm)) return newSVpvn("0000-00-00 00:00:00", 19);
2643             } else {
2644 0 0         if (!gmtime_r(&t, &tm)) return newSVpvn("0000-00-00 00:00:00", 19);
2645             }
2646 0           n = snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d",
2647 0           tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
2648             tm.tm_hour, tm.tm_min, tm.tm_sec);
2649 0 0         if (precision > 0 && n < 30) {
    0          
2650             char fracbuf[16];
2651             int fi;
2652 0           snprintf(fracbuf, sizeof(fracbuf), "%0*lld", precision, (long long)frac);
2653 0           buf[n++] = '.';
2654 0 0         for (fi = 0; fi < precision && n < 31; fi++)
    0          
2655 0           buf[n++] = fracbuf[fi];
2656             }
2657 0           return newSVpvn(buf, n);
2658             }
2659              
2660              
2661             /* Set TZ environment variable and call tzset(); returns saved old TZ
2662             * (caller must free).
2663             *
2664             * Design note: this mutates process-global state via setenv()+tzset().
2665             * That is safe in this module because (1) EV runs single-threaded, (2)
2666             * the entire set_tz / loop / restore_tz sequence in decode_column_ex
2667             * is pure C with no Perl callback dispatch in between, and (3) Perl
2668             * callbacks (on_data, query callbacks) only fire AFTER the block decode
2669             * completes and the TZ is already restored. Code reading the TZ from a
2670             * C-level signal handler during the window would see the column's TZ;
2671             * such handlers are inherently async-signal-unsafe with libc time
2672             * functions and out of scope here. */
2673 0           static char* set_tz(const char *tz) {
2674 0           char *old_tz = getenv("TZ");
2675 0           char *saved = NULL;
2676 0 0         if (old_tz) {
2677 0           size_t l = strlen(old_tz);
2678 0           Newx(saved, l + 1, char);
2679 0           Copy(old_tz, saved, l + 1, char);
2680             }
2681 0           setenv("TZ", tz, 1);
2682 0           tzset();
2683 0           return saved;
2684             }
2685              
2686             /* Restore TZ from saved value (which may be NULL), then free saved */
2687 0           static void restore_tz(char *saved) {
2688 0 0         if (saved) {
2689 0           setenv("TZ", saved, 1);
2690 0           Safefree(saved);
2691             } else {
2692 0           unsetenv("TZ");
2693             }
2694 0           tzset();
2695 0           }
2696              
2697             /* Compute 10^n as double */
2698 0           static double pow10_int(int n) {
2699 0           double r = 1.0;
2700             int i;
2701 0 0         for (i = 0; i < n; i++) r *= 10.0;
2702 0           return r;
2703             }
2704              
2705             /* Parse enum label for a given code from type string like "Enum8('a'=1,'b'=2)" */
2706 0           static SV* enum_label_for_code(const char *type_str, size_t type_str_len, int code) {
2707             /* Find the opening '(' */
2708 0           const char *p = memchr(type_str, '(', type_str_len);
2709             const char *end;
2710 0 0         if (!p) return newSViv(code);
2711 0           p++;
2712 0           end = type_str + type_str_len - 1; /* skip closing ')' */
2713              
2714 0 0         while (p < end) {
2715             /* Skip whitespace */
2716 0 0         while (p < end && *p == ' ') p++;
    0          
2717 0 0         if (p >= end || *p != '\'') break;
    0          
2718 0           p++; /* skip opening quote */
2719              
2720             /* Read label (handle escaped quotes) */
2721             {
2722 0           const char *label_start = p;
2723             size_t label_len;
2724             int val;
2725              
2726 0 0         while (p < end && !(*p == '\'' && (p + 1 >= end || *(p+1) != '\''))) {
    0          
    0          
    0          
2727 0 0         if (*p == '\'' && p + 1 < end && *(p+1) == '\'') { p += 2; continue; }
    0          
    0          
2728 0           p++;
2729             }
2730 0           label_len = p - label_start;
2731 0 0         if (p < end) p++; /* skip closing quote */
2732              
2733             /* Skip ' = ' */
2734 0 0         while (p < end && (*p == ' ' || *p == '=')) p++;
    0          
    0          
2735              
2736             /* Read integer value */
2737 0           val = (int)strtol(p, NULL, 10);
2738              
2739 0 0         if (val == code) return newSVpvn(label_start, label_len);
2740              
2741             /* Skip to next entry */
2742 0 0         while (p < end && *p != ',') p++;
    0          
2743 0 0         if (p < end) p++; /* skip comma */
2744             }
2745             }
2746             /* Not found — return numeric code */
2747 0           return newSViv(code);
2748             }
2749              
2750             /*
2751             * Decode a column of `nrows` values from the native binary format.
2752             * Returns an array of SVs (one per row). Returns NULL on failure.
2753             * Sets *decode_err=1 on definitive errors (vs needing more data).
2754             * Advances *pos past the consumed bytes.
2755             */
2756              
2757             #ifdef __SIZEOF_INT128__
2758 0           static SV* int128_to_sv(const char *p, int is_signed) {
2759             unsigned __int128 uv;
2760             char dbuf[42];
2761 0           int dlen = 0, neg = 0, k;
2762 0 0         if (is_signed) {
2763             __int128 sv;
2764 0           memcpy(&sv, p, 16);
2765 0           neg = sv < 0;
2766 0 0         uv = neg ? -(unsigned __int128)sv : (unsigned __int128)sv;
2767             } else {
2768 0           memcpy(&uv, p, 16);
2769             }
2770             do {
2771 0           dbuf[dlen++] = '0' + (int)(uv % 10);
2772 0           uv /= 10;
2773 0 0         } while (uv);
2774 0 0         if (neg) dbuf[dlen++] = '-';
2775 0 0         for (k = 0; k < dlen/2; k++) {
2776 0           char tmp = dbuf[k]; dbuf[k] = dbuf[dlen-1-k]; dbuf[dlen-1-k] = tmp;
2777             }
2778 0           return newSVpvn(dbuf, dlen);
2779             }
2780             #endif
2781              
2782             /* Convert a 256-bit LE unsigned integer (as 4 x uint64_t) to decimal string.
2783             * Works on all platforms (no __int128 required). */
2784 0           static SV* uint256_to_sv(const char *p) {
2785             /* Copy into 4 x uint64_t LE limbs: v[0] = lowest */
2786             uint64_t v[4];
2787             char dbuf[80];
2788 0           int dlen = 0, k;
2789              
2790 0           memcpy(v, p, 32);
2791              
2792             /* Handle zero */
2793 0 0         if (v[0] == 0 && v[1] == 0 && v[2] == 0 && v[3] == 0)
    0          
    0          
    0          
2794 0           return newSVpvn("0", 1);
2795              
2796             /* Repeatedly divide by 10, collecting remainders */
2797 0 0         while (v[0] || v[1] || v[2] || v[3]) {
    0          
    0          
    0          
2798 0           uint64_t rem = 0;
2799             int i;
2800 0 0         for (i = 3; i >= 0; i--) {
2801             #ifdef __SIZEOF_INT128__
2802 0           unsigned __int128 cur = ((unsigned __int128)rem << 64) | v[i];
2803 0           v[i] = (uint64_t)(cur / 10);
2804 0           rem = (uint64_t)(cur % 10);
2805             #else
2806             /* Without 128-bit: split each 64-bit limb into hi32:lo32 */
2807             uint64_t hi = (rem << 32) | (v[i] >> 32);
2808             uint64_t q_hi = hi / 10;
2809             uint64_t r_hi = hi % 10;
2810             uint64_t lo = (r_hi << 32) | (v[i] & 0xFFFFFFFFULL);
2811             uint64_t q_lo = lo / 10;
2812             rem = lo % 10;
2813             v[i] = (q_hi << 32) | q_lo;
2814             #endif
2815             }
2816 0           dbuf[dlen++] = '0' + (int)rem;
2817             }
2818 0 0         for (k = 0; k < dlen/2; k++) {
2819 0           char tmp = dbuf[k]; dbuf[k] = dbuf[dlen-1-k]; dbuf[dlen-1-k] = tmp;
2820             }
2821 0           return newSVpvn(dbuf, dlen);
2822             }
2823              
2824 0           static SV* int256_to_sv(const char *p, int is_signed) {
2825 0 0         if (is_signed && ((unsigned char)p[31] & 0x80)) {
    0          
2826             /* Negative: two's complement negate, format, prepend '-' */
2827             unsigned char neg[32];
2828 0           int i, carry = 1;
2829             SV *sv;
2830             STRLEN svlen;
2831             char *s;
2832 0 0         for (i = 0; i < 32; i++) {
2833 0           int b = (unsigned char)(~((unsigned char)p[i])) + carry;
2834 0           neg[i] = (unsigned char)(b & 0xFF);
2835 0           carry = b >> 8;
2836             }
2837 0           sv = uint256_to_sv((const char *)neg);
2838             /* Prepend '-' */
2839 0           s = SvPV(sv, svlen);
2840             {
2841 0           SV *result = newSV(svlen + 1);
2842 0           SvPOK_on(result);
2843 0           SvCUR_set(result, svlen + 1);
2844 0           *SvPVX(result) = '-';
2845 0           Copy(s, SvPVX(result) + 1, svlen, char);
2846 0           SvPVX(result)[svlen + 1] = '\0';
2847 0           SvREFCNT_dec(sv);
2848 0           return result;
2849             }
2850             }
2851 0           return uint256_to_sv(p);
2852             }
2853              
2854             static SV** decode_column_ex(const char *buf, size_t len, size_t *pos,
2855             uint64_t nrows, col_type_t *ct, int *decode_err,
2856             uint32_t decode_flags, ev_clickhouse_t *lc_self,
2857             int lc_col_idx);
2858              
2859 0           static SV** decode_column(const char *buf, size_t len, size_t *pos,
2860             uint64_t nrows, col_type_t *ct, int *decode_err,
2861             uint32_t decode_flags) {
2862 0           return decode_column_ex(buf, len, pos, nrows, ct, decode_err, decode_flags, NULL, -1);
2863             }
2864              
2865 0           static SV** decode_column_ex(const char *buf, size_t len, size_t *pos,
2866             uint64_t nrows, col_type_t *ct, int *decode_err,
2867             uint32_t decode_flags, ev_clickhouse_t *lc_self,
2868             int lc_col_idx) {
2869             SV **out;
2870             uint64_t i;
2871             size_t fsz;
2872              
2873 0 0         Newxz(out, nrows ? nrows : 1, SV*);
    0          
    0          
2874              
2875 0 0         if (ct->code == CT_NOTHING) {
2876             /* Nothing type: 1 placeholder byte ('0') per row */
2877 0 0         if (*pos > len || nrows > len - *pos) goto fail;
    0          
2878 0           *pos += nrows;
2879 0 0         for (i = 0; i < nrows; i++)
2880 0           out[i] = newSV(0);
2881 0           return out;
2882             }
2883              
2884 0 0         if (ct->code == CT_NULLABLE) {
2885             /* null bitmap: nrows bytes of UInt8 */
2886             uint8_t *nulls;
2887             SV **inner;
2888 0 0         if (*pos > len || nrows > len - *pos) goto fail;
    0          
2889 0           Newx(nulls, nrows, uint8_t);
2890 0           Copy(buf + *pos, nulls, nrows, uint8_t);
2891 0           *pos += nrows;
2892              
2893             /* decode inner column */
2894 0           inner = decode_column(buf, len, pos, nrows, ct->inner, decode_err, decode_flags);
2895 0 0         if (!inner) { Safefree(nulls); goto fail; }
2896              
2897 0 0         for (i = 0; i < nrows; i++) {
2898 0 0         if (nulls[i]) {
2899 0           SvREFCNT_dec(inner[i]);
2900 0           out[i] = newSV(0); /* undef */
2901             } else {
2902 0           out[i] = inner[i];
2903             }
2904             }
2905 0           Safefree(nulls);
2906 0           Safefree(inner);
2907 0           return out;
2908             }
2909              
2910 0 0         if (ct->code == CT_LOWCARDINALITY) {
2911             /*
2912             * LowCardinality wire format (all multi-byte integers are UInt64 LE):
2913             * PREFIX: UInt64 key_version (1=SharedDicts, 2=SingleDict)
2914             * DATA: UInt64 serialization_type (bits 0-7: index type,
2915             * bit 8: NeedGlobalDictionary, bit 9: HasAdditionalKeys,
2916             * bit 10: NeedUpdateDictionary)
2917             * if NeedUpdateDictionary: UInt64 num_keys + dictionary data
2918             * UInt64 num_indices + index data
2919             */
2920             uint64_t version, ser_type, num_keys, num_indices;
2921 0           size_t saved = *pos;
2922             int key_type;
2923             size_t idx_size;
2924 0           SV **dict = NULL;
2925 0           int dict_borrowed = 0; /* 1 if dict points to lc_self storage */
2926              
2927             /* key_version: UInt64 (from serializeBinaryBulkStatePrefix) */
2928 0 0         if (*pos + 8 > len) goto fail;
2929 0           memcpy(&version, buf + *pos, 8); *pos += 8;
2930              
2931             /* serialization_type: UInt64 */
2932 0 0         if (*pos + 8 > len) { *pos = saved; goto fail; }
2933 0           memcpy(&ser_type, buf + *pos, 8); *pos += 8;
2934              
2935 0           key_type = (int)(ser_type & 0xFF);
2936             /* key_type: 0=UInt8, 1=UInt16, 2=UInt32, 3=UInt64 */
2937              
2938             /* Read dictionary if NeedUpdateDictionary (bit 10) */
2939 0 0         if (ser_type & (1ULL << 10)) {
2940 0 0         if (*pos + 8 > len) { *pos = saved; goto fail; }
2941 0           memcpy(&num_keys, buf + *pos, 8); *pos += 8;
2942              
2943 0           dict = decode_column(buf, len, pos, num_keys, ct->inner, decode_err, decode_flags);
2944 0 0         if (!dict) { *pos = saved; goto fail; }
2945             } else {
2946             /* NeedUpdateDictionary=0: reuse dictionary from prior block */
2947 0 0         if (lc_self && lc_col_idx >= 0 && lc_col_idx < lc_self->lc_num_cols
    0          
    0          
2948 0 0         && lc_self->lc_dicts[lc_col_idx]) {
2949 0           dict = lc_self->lc_dicts[lc_col_idx];
2950 0           num_keys = lc_self->lc_dict_sizes[lc_col_idx];
2951 0           dict_borrowed = 1;
2952             } else {
2953 0 0         if (decode_err) *decode_err = 1;
2954 0           *pos = saved;
2955 0           goto fail;
2956             }
2957             }
2958              
2959             /* Read indices: UInt64 num_indices + index data */
2960 0 0         if (*pos + 8 > len) {
2961 0 0         if (dict && !dict_borrowed) { for (i = 0; i < num_keys; i++) SvREFCNT_dec(dict[i]); Safefree(dict); }
    0          
    0          
2962 0           *pos = saved; goto fail;
2963             }
2964 0           memcpy(&num_indices, buf + *pos, 8); *pos += 8;
2965              
2966 0 0         idx_size = (key_type == 0) ? 1 : (key_type == 1) ? 2 :
    0          
    0          
2967             (key_type == 2) ? 4 : 8;
2968 0 0         if (num_indices != nrows) {
2969 0 0         if (dict && !dict_borrowed) { for (i = 0; i < num_keys; i++) SvREFCNT_dec(dict[i]); Safefree(dict); }
    0          
    0          
2970 0 0         *pos = saved; if (decode_err) *decode_err = 1; goto fail;
2971             }
2972 0 0         if (*pos > len || num_indices > (len - *pos) / idx_size) {
    0          
2973 0 0         if (dict && !dict_borrowed) { for (i = 0; i < num_keys; i++) SvREFCNT_dec(dict[i]); Safefree(dict); }
    0          
    0          
2974 0           *pos = saved; goto fail;
2975             }
2976              
2977             /* Store new dictionary for cross-block reuse (after validation) */
2978 0 0         if (!dict_borrowed && lc_self && lc_col_idx >= 0 && lc_col_idx < lc_self->lc_num_cols) {
    0          
    0          
    0          
2979 0 0         if (lc_self->lc_dicts[lc_col_idx]) {
2980             uint64_t di;
2981 0 0         for (di = 0; di < lc_self->lc_dict_sizes[lc_col_idx]; di++)
2982 0           SvREFCNT_dec(lc_self->lc_dicts[lc_col_idx][di]);
2983 0           Safefree(lc_self->lc_dicts[lc_col_idx]);
2984             }
2985             SV **dcopy;
2986 0 0         Newx(dcopy, num_keys > 0 ? num_keys : 1, SV*);
    0          
    0          
2987 0 0         for (i = 0; i < num_keys; i++)
2988 0           dcopy[i] = SvREFCNT_inc(dict[i]);
2989 0           lc_self->lc_dicts[lc_col_idx] = dcopy;
2990 0           lc_self->lc_dict_sizes[lc_col_idx] = num_keys;
2991             }
2992              
2993 0 0         for (i = 0; i < nrows; i++) {
2994 0           uint64_t idx = 0;
2995 0           memcpy(&idx, buf + *pos + i * idx_size, idx_size);
2996 0 0         if (dict && idx < num_keys) {
    0          
2997 0           out[i] = SvREFCNT_inc(dict[idx]);
2998             } else {
2999 0           out[i] = newSV(0); /* undef for missing dict entry */
3000             }
3001             }
3002 0           *pos += num_indices * idx_size;
3003              
3004 0 0         if (dict && !dict_borrowed) {
    0          
3005 0 0         for (i = 0; i < num_keys; i++) SvREFCNT_dec(dict[i]);
3006 0           Safefree(dict);
3007             }
3008 0           return out;
3009             }
3010              
3011 0 0         if (ct->code == CT_STRING) {
3012 0 0         for (i = 0; i < nrows; i++) {
3013             const char *s;
3014             size_t slen;
3015 0 0         if (read_native_string_ref(buf, len, pos, &s, &slen) <= 0) {
3016             /* clean up already-created SVs */
3017             uint64_t j;
3018 0 0         for (j = 0; j < i; j++) SvREFCNT_dec(out[j]);
3019 0           goto fail;
3020             }
3021 0           out[i] = newSVpvn(s, slen);
3022             }
3023 0           return out;
3024             }
3025              
3026 0 0         if (ct->code == CT_ARRAY) {
3027             /* offsets: nrows x UInt64 */
3028             uint64_t *offsets;
3029             SV **elems;
3030             uint64_t total, prev;
3031              
3032 0 0         if (*pos > len || nrows > (len - *pos) / 8) goto fail;
    0          
3033 0 0         Newx(offsets, nrows, uint64_t);
3034 0 0         Copy(buf + *pos, offsets, nrows, uint64_t);
3035 0           *pos += nrows * 8;
3036              
3037             /* validate offset monotonicity */
3038 0           prev = 0;
3039 0 0         for (i = 0; i < nrows; i++) {
3040 0 0         if (offsets[i] < prev) { Safefree(offsets); goto fail; }
3041 0           prev = offsets[i];
3042             }
3043              
3044 0 0         total = nrows > 0 ? offsets[nrows - 1] : 0;
3045              
3046             /* decode all inner elements */
3047 0           elems = decode_column(buf, len, pos, total, ct->inner, decode_err, decode_flags);
3048 0 0         if (!elems) { Safefree(offsets); goto fail; }
3049              
3050             /* build AV for each row */
3051 0           prev = 0;
3052 0 0         for (i = 0; i < nrows; i++) {
3053 0           uint64_t count = offsets[i] - prev;
3054 0           AV *av = newAV();
3055             uint64_t j;
3056 0 0         if (count > 0) av_extend(av, count - 1);
3057 0 0         for (j = 0; j < count; j++) {
3058 0           av_push(av, elems[prev + j]);
3059             }
3060 0           out[i] = newRV_noinc((SV*)av);
3061 0           prev = offsets[i];
3062             }
3063              
3064 0           Safefree(offsets);
3065 0           Safefree(elems);
3066 0           return out;
3067             }
3068              
3069 0 0         if (ct->code == CT_TUPLE) {
3070             /* Tuple: each element is a separate column, transpose to row arrays */
3071             SV ***cols;
3072             int j;
3073              
3074 0           Newxz(cols, ct->num_inners, SV**);
3075 0 0         for (j = 0; j < ct->num_inners; j++) {
3076 0           cols[j] = decode_column(buf, len, pos, nrows, ct->inners[j], decode_err, decode_flags);
3077 0 0         if (!cols[j]) {
3078             int k;
3079 0 0         for (k = 0; k < j; k++) {
3080 0 0         for (i = 0; i < nrows; i++) SvREFCNT_dec(cols[k][i]);
3081 0           Safefree(cols[k]);
3082             }
3083 0           Safefree(cols);
3084 0           goto fail;
3085             }
3086             }
3087              
3088 0 0         for (i = 0; i < nrows; i++) {
3089 0           AV *av = newAV();
3090 0           av_extend(av, ct->num_inners - 1);
3091 0 0         for (j = 0; j < ct->num_inners; j++)
3092 0           av_push(av, cols[j][i]);
3093 0           out[i] = newRV_noinc((SV*)av);
3094             }
3095              
3096 0 0         for (j = 0; j < ct->num_inners; j++) Safefree(cols[j]);
3097 0           Safefree(cols);
3098 0           return out;
3099             }
3100              
3101 0 0         if (ct->code == CT_MAP) {
3102 0 0         if (ct->num_inners != 2) { if (decode_err) *decode_err = 1; goto fail; }
    0          
3103             /* Map(K,V): wire format same as Array — offsets + keys column + values column */
3104             uint64_t *offsets, total, prev;
3105             SV **keys_col, **vals_col;
3106              
3107 0 0         if (*pos > len || nrows > (len - *pos) / 8) goto fail;
    0          
3108 0 0         Newx(offsets, nrows, uint64_t);
3109 0 0         Copy(buf + *pos, offsets, nrows, uint64_t);
3110 0           *pos += nrows * 8;
3111              
3112             /* validate offset monotonicity */
3113 0           prev = 0;
3114 0 0         for (i = 0; i < nrows; i++) {
3115 0 0         if (offsets[i] < prev) { Safefree(offsets); goto fail; }
3116 0           prev = offsets[i];
3117             }
3118              
3119 0 0         total = nrows > 0 ? offsets[nrows - 1] : 0;
3120              
3121 0           keys_col = decode_column(buf, len, pos, total, ct->inners[0], decode_err, decode_flags);
3122 0 0         if (!keys_col) { Safefree(offsets); goto fail; }
3123              
3124 0           vals_col = decode_column(buf, len, pos, total, ct->inners[1], decode_err, decode_flags);
3125 0 0         if (!vals_col) {
3126 0 0         for (i = 0; i < total; i++) SvREFCNT_dec(keys_col[i]);
3127 0           Safefree(keys_col);
3128 0           Safefree(offsets);
3129 0           goto fail;
3130             }
3131              
3132 0           prev = 0;
3133 0 0         for (i = 0; i < nrows; i++) {
3134 0           uint64_t count = offsets[i] - prev;
3135 0           HV *hv = newHV();
3136             uint64_t j;
3137 0 0         for (j = 0; j < count; j++) {
3138             STRLEN klen;
3139 0           const char *kstr = SvPV(keys_col[prev + j], klen);
3140             {
3141 0           SV *val_sv = SvREFCNT_inc(vals_col[prev + j]);
3142 0 0         if (!hv_store(hv, kstr, klen, val_sv, 0))
3143 0           SvREFCNT_dec(val_sv);
3144             }
3145             }
3146 0           out[i] = newRV_noinc((SV*)hv);
3147 0           prev = offsets[i];
3148             }
3149              
3150 0 0         for (i = 0; i < total; i++) {
3151 0           SvREFCNT_dec(keys_col[i]);
3152 0           SvREFCNT_dec(vals_col[i]);
3153             }
3154 0           Safefree(keys_col);
3155 0           Safefree(vals_col);
3156 0           Safefree(offsets);
3157 0           return out;
3158             }
3159              
3160             /* Fixed-width types */
3161 0           fsz = col_type_fixed_size(ct);
3162 0 0         if (ct->code == CT_FIXEDSTRING && fsz == 0) {
    0          
3163             /* FixedString(0): 0 bytes per row, produce empty strings */
3164 0 0         for (i = 0; i < nrows; i++)
3165 0           out[i] = newSVpvn("", 0);
3166 0           return out;
3167             }
3168 0 0         if (fsz > 0) {
3169 0           char *saved_tz = NULL;
3170 0           int tz_set = 0;
3171              
3172 0 0         if (*pos > len || nrows > (len - *pos) / fsz) goto fail;
    0          
3173              
3174             /* Set timezone for DateTime/DateTime64 columns with explicit tz */
3175 0 0         if (ct->tz && (decode_flags & DECODE_DT_STR) &&
    0          
3176 0 0         (ct->code == CT_DATETIME || ct->code == CT_DATETIME64)) {
    0          
3177 0           saved_tz = set_tz(ct->tz);
3178 0           tz_set = 1;
3179             }
3180              
3181 0 0         for (i = 0; i < nrows; i++) {
3182 0           const char *p = buf + *pos + i * fsz;
3183 0           switch (ct->code) {
3184 0           case CT_INT8: out[i] = newSViv(*(int8_t*)p); break;
3185 0           case CT_INT16: { int16_t v; memcpy(&v, p, 2); out[i] = newSViv(v); break; }
3186 0           case CT_INT32: { int32_t v; memcpy(&v, p, 4); out[i] = newSViv(v); break; }
3187 0           case CT_INT64: { int64_t v; memcpy(&v, p, 8); out[i] = newSViv((IV)v); break; }
3188 0           case CT_UINT8: case CT_BOOL:
3189 0           out[i] = newSVuv(*(uint8_t*)p); break;
3190 0           case CT_UINT16: { uint16_t v; memcpy(&v, p, 2); out[i] = newSVuv(v); break; }
3191 0           case CT_UINT32: { uint32_t v; memcpy(&v, p, 4); out[i] = newSVuv(v); break; }
3192 0           case CT_UINT64: { uint64_t v; memcpy(&v, p, 8); out[i] = newSVuv((UV)v); break; }
3193 0           case CT_FLOAT32: { float v; memcpy(&v, p, 4); out[i] = newSVnv(v); break; }
3194 0           case CT_FLOAT64: { double v; memcpy(&v, p, 8); out[i] = newSVnv(v); break; }
3195 0           case CT_ENUM8:
3196 0 0         if (decode_flags & DECODE_ENUM_STR)
3197 0           out[i] = enum_label_for_code(ct->type_str, ct->type_str_len, *(int8_t*)p);
3198             else
3199 0           out[i] = newSViv(*(int8_t*)p);
3200 0           break;
3201 0           case CT_ENUM16: {
3202 0           int16_t v; memcpy(&v, p, 2);
3203 0 0         if (decode_flags & DECODE_ENUM_STR)
3204 0           out[i] = enum_label_for_code(ct->type_str, ct->type_str_len, v);
3205             else
3206 0           out[i] = newSViv(v);
3207 0           break;
3208             }
3209 0           case CT_DATE: {
3210 0           uint16_t v; memcpy(&v, p, 2);
3211 0 0         if (decode_flags & DECODE_DT_STR)
3212 0           out[i] = days_to_date_sv((int32_t)v);
3213             else
3214 0           out[i] = newSVuv(v);
3215 0           break;
3216             }
3217 0           case CT_DATE32: {
3218 0           int32_t v; memcpy(&v, p, 4);
3219 0 0         if (decode_flags & DECODE_DT_STR)
3220 0           out[i] = days_to_date_sv(v);
3221             else
3222 0           out[i] = newSViv(v);
3223 0           break;
3224             }
3225 0           case CT_DATETIME: {
3226 0           uint32_t v; memcpy(&v, p, 4);
3227 0 0         if (decode_flags & DECODE_DT_STR)
3228 0           out[i] = tz_set ? epoch_to_datetime_sv_local(v)
3229 0 0         : epoch_to_datetime_sv(v);
3230             else
3231 0           out[i] = newSVuv(v);
3232 0           break;
3233             }
3234 0           case CT_DATETIME64: {
3235 0           int64_t v; memcpy(&v, p, 8);
3236 0 0         if (decode_flags & DECODE_DT_STR)
3237 0           out[i] = dt64_to_datetime_sv_ex(v, ct->param, tz_set);
3238             else
3239 0           out[i] = newSViv((IV)v);
3240 0           break;
3241             }
3242 0           case CT_DECIMAL32: {
3243 0           int32_t v; memcpy(&v, p, 4);
3244 0 0         if (decode_flags & DECODE_DEC_SCALE)
3245 0           out[i] = newSVnv((double)v / pow10_int(ct->param));
3246             else
3247 0           out[i] = newSViv(v);
3248 0           break;
3249             }
3250 0           case CT_DECIMAL64: {
3251 0           int64_t v; memcpy(&v, p, 8);
3252 0 0         if (decode_flags & DECODE_DEC_SCALE)
3253 0           out[i] = newSVnv((double)v / pow10_int(ct->param));
3254             else
3255 0           out[i] = newSViv((IV)v);
3256 0           break;
3257             }
3258 0           case CT_DECIMAL128: {
3259             #ifdef __SIZEOF_INT128__
3260 0 0         if (decode_flags & DECODE_DEC_SCALE) {
3261             __int128 sv128;
3262 0           memcpy(&sv128, p, 16);
3263             /* Use long double for Decimal128 to preserve more precision */
3264 0           out[i] = newSVnv((NV)((long double)sv128 / (long double)pow10_int(ct->param)));
3265             } else {
3266 0           out[i] = int128_to_sv(p, 1);
3267             }
3268             #else
3269             out[i] = newSVpvn(p, 16);
3270             #endif
3271 0           break;
3272             }
3273 0           case CT_UUID: {
3274             /* UUID: two LE UInt64 halves, each reversed for display */
3275             char ustr[37];
3276 0           const unsigned char *u = (const unsigned char *)p;
3277 0           snprintf(ustr, sizeof(ustr),
3278             "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
3279 0           u[7],u[6],u[5],u[4],u[3],u[2],u[1],u[0],
3280 0           u[15],u[14],u[13],u[12],u[11],u[10],u[9],u[8]);
3281 0           out[i] = newSVpvn(ustr, 36);
3282 0           break;
3283             }
3284 0           case CT_IPV4: {
3285             /* UInt32 LE, MSB is first octet */
3286             uint32_t v;
3287             struct in_addr addr;
3288             char abuf[INET_ADDRSTRLEN];
3289 0           memcpy(&v, p, 4);
3290 0           addr.s_addr = htonl(v);
3291 0           inet_ntop(AF_INET, &addr, abuf, sizeof(abuf));
3292 0           out[i] = newSVpv(abuf, 0);
3293 0           break;
3294             }
3295 0           case CT_IPV6: {
3296             /* 16 bytes in network byte order */
3297             char abuf[INET6_ADDRSTRLEN];
3298 0           inet_ntop(AF_INET6, p, abuf, sizeof(abuf));
3299 0           out[i] = newSVpv(abuf, 0);
3300 0           break;
3301             }
3302 0           case CT_INT128: {
3303             #ifdef __SIZEOF_INT128__
3304 0           out[i] = int128_to_sv(p, 1);
3305             #else
3306             out[i] = newSVpvn(p, 16);
3307             #endif
3308 0           break;
3309             }
3310 0           case CT_UINT128: {
3311             #ifdef __SIZEOF_INT128__
3312 0           out[i] = int128_to_sv(p, 0);
3313             #else
3314             out[i] = newSVpvn(p, 16);
3315             #endif
3316 0           break;
3317             }
3318 0           case CT_INT256:
3319 0           out[i] = int256_to_sv(p, 1); break;
3320 0           case CT_UINT256:
3321 0           out[i] = int256_to_sv(p, 0); break;
3322 0           case CT_FIXEDSTRING: default:
3323 0           out[i] = newSVpvn(p, fsz); break;
3324             }
3325             }
3326 0 0         if (tz_set) restore_tz(saved_tz);
3327 0           *pos += nrows * fsz;
3328 0           return out;
3329             }
3330              
3331             /* CT_UNKNOWN: try reading as String */
3332 0 0         for (i = 0; i < nrows; i++) {
3333             const char *s;
3334             size_t slen;
3335 0 0         if (read_native_string_ref(buf, len, pos, &s, &slen) <= 0) {
3336             uint64_t j;
3337 0 0         for (j = 0; j < i; j++) SvREFCNT_dec(out[j]);
3338 0           goto fail;
3339             }
3340 0           out[i] = newSVpvn(s, slen);
3341             }
3342 0           return out;
3343              
3344 0           fail:
3345 0           Safefree(out);
3346 0           return NULL;
3347             }
3348              
3349             /* --- Native protocol column encoder (for INSERT) --- */
3350              
3351             /* TSV unescape: \\ → \, \n → newline, \t → tab, \0 → null byte */
3352 0           static size_t tsv_unescape(const char *src, size_t src_len, char *dst) {
3353 0           size_t i, j = 0;
3354 0 0         for (i = 0; i < src_len; i++) {
3355 0 0         if (src[i] == '\\' && i + 1 < src_len) {
    0          
3356 0           switch (src[i+1]) {
3357 0           case '\\': dst[j++] = '\\'; i++; break;
3358 0           case 'n': dst[j++] = '\n'; i++; break;
3359 0           case 't': dst[j++] = '\t'; i++; break;
3360 0           case '0': dst[j++] = '\0'; i++; break;
3361 0           case '\'': dst[j++] = '\''; i++; break;
3362 0           case 'b': dst[j++] = '\b'; i++; break;
3363 0           case 'r': dst[j++] = '\r'; i++; break;
3364 0           case 'a': dst[j++] = '\a'; i++; break;
3365 0           case 'f': dst[j++] = '\f'; i++; break;
3366 0           default: dst[j++] = src[i]; break;
3367             }
3368             } else {
3369 0           dst[j++] = src[i];
3370             }
3371             }
3372 0           return j;
3373             }
3374              
3375 0           static int is_tsv_null(const char *s, size_t len) {
3376 0 0         return len == 2 && s[0] == '\\' && s[1] == 'N';
    0          
    0          
3377             }
3378              
3379             /* TSV escape: inverse of tsv_unescape — appends escaped bytes to buffer */
3380 0           static void tsv_escape(native_buf_t *b, const char *s, size_t len) {
3381 0           size_t i, start = 0;
3382 0 0         for (i = 0; i < len; i++) {
3383 0           char esc = 0;
3384 0           switch (s[i]) {
3385 0           case '\\': esc = '\\'; break;
3386 0           case '\t': esc = 't'; break;
3387 0           case '\n': esc = 'n'; break;
3388 0           case '\0': esc = '0'; break;
3389 0           case '\b': esc = 'b'; break;
3390 0           case '\r': esc = 'r'; break;
3391 0           case '\a': esc = 'a'; break;
3392 0           case '\f': esc = 'f'; break;
3393             }
3394 0 0         if (esc) {
3395 0 0         if (i > start)
3396 0           nbuf_append(b, s + start, i - start);
3397 0           nbuf_grow(b, 2);
3398 0           b->data[b->len++] = '\\';
3399 0           b->data[b->len++] = esc;
3400 0           start = i + 1;
3401             }
3402             }
3403 0 0         if (start < len)
3404 0           nbuf_append(b, s + start, len - start);
3405 0           }
3406              
3407             /* Serialize an AV of AVs to TabSeparated format for HTTP INSERT.
3408             * Returns malloc'd buffer; caller must Safefree(). */
3409 0           static char* serialize_av_to_tsv(pTHX_ AV *rows, size_t *out_len) {
3410             native_buf_t b;
3411 0           SSize_t nrows = av_len(rows) + 1;
3412             SSize_t r;
3413              
3414 0           nbuf_init(&b);
3415              
3416 0 0         for (r = 0; r < nrows; r++) {
3417 0           SV **row_svp = av_fetch(rows, r, 0);
3418             AV *row;
3419             SSize_t ncols, c;
3420              
3421 0 0         if (!row_svp || !SvROK(*row_svp) ||
    0          
3422 0 0         SvTYPE(SvRV(*row_svp)) != SVt_PVAV) {
3423 0           Safefree(b.data);
3424 0           croak("insert data: row %" IVdf " is not an ARRAY ref", (IV)r);
3425             }
3426 0           row = (AV *)SvRV(*row_svp);
3427 0           ncols = av_len(row) + 1;
3428              
3429 0 0         for (c = 0; c < ncols; c++) {
3430 0           SV **val_svp = av_fetch(row, c, 0);
3431 0 0         if (c > 0)
3432 0           nbuf_u8(&b, '\t');
3433              
3434 0 0         if (!val_svp || !SvOK(*val_svp)) {
    0          
3435 0           nbuf_append(&b, "\\N", 2);
3436 0 0         } else if (SvROK(*val_svp)) {
3437             /* Nested AV/HV refs (Array/Tuple/Map columns) cannot be
3438             * round-tripped through TSV without column-type info from
3439             * the server, which the HTTP path doesn't have. Fail loudly
3440             * rather than silently sending ARRAY(0x...) garbage. */
3441 0           Safefree(b.data);
3442 0           croak("insert data: row %" IVdf " column %" IVdf " is a "
3443             "reference; nested Array/Tuple/Map columns require "
3444             "the native protocol", (IV)r, (IV)c);
3445             } else {
3446             STRLEN vlen;
3447 0           const char *v = SvPV(*val_svp, vlen);
3448 0           tsv_escape(&b, v, vlen);
3449             }
3450             }
3451 0           nbuf_u8(&b, '\n');
3452             }
3453              
3454 0           *out_len = b.len;
3455 0           return b.data;
3456             }
3457              
3458             /*
3459             * Encode a column of text values into native binary format.
3460             * Returns 1 on success, 0 if type is unsupported (caller falls back to inline SQL).
3461             */
3462 0           static int encode_column_text(native_buf_t *b,
3463             const char **values, size_t *value_lens,
3464             uint64_t nrows, col_type_t *ct) {
3465             uint64_t i;
3466              
3467 0           switch (ct->code) {
3468 0           case CT_INT8: case CT_ENUM8: {
3469 0 0         for (i = 0; i < nrows; i++) {
3470 0           int8_t v = (int8_t)strtol(values[i], NULL, 10);
3471 0           nbuf_append(b, (const char *)&v, 1);
3472             }
3473 0           return 1;
3474             }
3475 0           case CT_INT16: case CT_ENUM16: {
3476 0 0         for (i = 0; i < nrows; i++) {
3477 0           int16_t v = (int16_t)strtol(values[i], NULL, 10);
3478 0           nbuf_append(b, (const char *)&v, 2);
3479             }
3480 0           return 1;
3481             }
3482 0           case CT_INT32: case CT_DATE32: {
3483 0 0         for (i = 0; i < nrows; i++) {
3484             int32_t v;
3485 0 0         if (ct->code == CT_DATE32 && value_lens[i] >= 10
    0          
3486 0 0         && values[i][4] == '-')
3487 0           v = date_string_to_days(values[i], value_lens[i]);
3488             else
3489 0           v = (int32_t)strtol(values[i], NULL, 10);
3490 0           nbuf_append(b, (const char *)&v, 4);
3491             }
3492 0           return 1;
3493             }
3494 0           case CT_INT64: {
3495 0 0         for (i = 0; i < nrows; i++) {
3496 0           int64_t v = (int64_t)strtoll(values[i], NULL, 10);
3497 0           nbuf_append(b, (const char *)&v, 8);
3498             }
3499 0           return 1;
3500             }
3501 0           case CT_UINT8: case CT_BOOL: {
3502 0 0         for (i = 0; i < nrows; i++) {
3503 0           uint8_t v = (uint8_t)strtoul(values[i], NULL, 10);
3504 0           nbuf_append(b, (const char *)&v, 1);
3505             }
3506 0           return 1;
3507             }
3508 0           case CT_UINT16: case CT_DATE: {
3509 0 0         for (i = 0; i < nrows; i++) {
3510             uint16_t v;
3511 0 0         if (ct->code == CT_DATE && value_lens[i] >= 10
    0          
3512 0 0         && values[i][4] == '-')
3513 0           v = (uint16_t)date_string_to_days(values[i], value_lens[i]);
3514             else
3515 0           v = (uint16_t)strtoul(values[i], NULL, 10);
3516 0           nbuf_append(b, (const char *)&v, 2);
3517             }
3518 0           return 1;
3519             }
3520 0           case CT_UINT32: case CT_DATETIME: {
3521 0 0         for (i = 0; i < nrows; i++) {
3522             uint32_t v;
3523 0 0         if (ct->code == CT_DATETIME && value_lens[i] >= 10
    0          
3524 0 0         && values[i][4] == '-')
3525 0           v = datetime_string_to_epoch(values[i], value_lens[i]);
3526             else
3527 0           v = (uint32_t)strtoul(values[i], NULL, 10);
3528 0           nbuf_append(b, (const char *)&v, 4);
3529             }
3530 0           return 1;
3531             }
3532 0           case CT_UINT64: {
3533 0 0         for (i = 0; i < nrows; i++) {
3534 0           uint64_t v = (uint64_t)strtoull(values[i], NULL, 10);
3535 0           nbuf_append(b, (const char *)&v, 8);
3536             }
3537 0           return 1;
3538             }
3539 0           case CT_FLOAT32: {
3540 0 0         for (i = 0; i < nrows; i++) {
3541 0           float v = strtof(values[i], NULL);
3542 0           nbuf_append(b, (const char *)&v, 4);
3543             }
3544 0           return 1;
3545             }
3546 0           case CT_FLOAT64: {
3547 0 0         for (i = 0; i < nrows; i++) {
3548 0           double v = strtod(values[i], NULL);
3549 0           nbuf_append(b, (const char *)&v, 8);
3550             }
3551 0           return 1;
3552             }
3553 0           case CT_DATETIME64: {
3554 0 0         for (i = 0; i < nrows; i++) {
3555             int64_t v;
3556 0 0         if (value_lens[i] >= 10 && values[i][4] == '-') {
    0          
3557 0           uint32_t epoch = datetime_string_to_epoch(values[i], value_lens[i]);
3558             int s;
3559 0           v = (int64_t)epoch;
3560 0 0         for (s = 0; s < ct->param; s++) v *= 10;
3561             /* parse fractional seconds if present (e.g. ".123") */
3562 0 0         if (value_lens[i] >= 20 && values[i][19] == '.') {
    0          
3563 0           const char *fp = values[i] + 20;
3564 0           const char *fe = values[i] + value_lens[i];
3565 0           int64_t frac = 0;
3566 0           int digits = 0, prec = ct->param;
3567 0 0         while (fp < fe && digits < prec) {
    0          
3568 0           frac = frac * 10 + (*fp - '0');
3569 0           fp++;
3570 0           digits++;
3571             }
3572 0 0         while (digits < prec) { frac *= 10; digits++; }
3573 0           v += frac;
3574             }
3575             } else {
3576 0           v = (int64_t)strtoll(values[i], NULL, 10);
3577             }
3578 0           nbuf_append(b, (const char *)&v, 8);
3579             }
3580 0           return 1;
3581             }
3582 0           case CT_DECIMAL32: {
3583 0 0         for (i = 0; i < nrows; i++) {
3584 0           const char *p = values[i];
3585 0           int neg = 0;
3586 0           int64_t integer_part = 0, frac_part = 0;
3587 0           int frac_digits = 0, scale = ct->param, s;
3588 0 0         if (*p == '-') { neg = 1; p++; }
3589 0 0         else if (*p == '+') p++;
3590 0 0         while (*p >= '0' && *p <= '9') { integer_part = integer_part * 10 + (*p - '0'); p++; }
    0          
3591 0 0         if (*p == '.') {
3592 0           p++;
3593 0 0         while (*p >= '0' && *p <= '9' && frac_digits < scale) {
    0          
    0          
3594 0           frac_part = frac_part * 10 + (*p - '0');
3595 0           p++;
3596 0           frac_digits++;
3597             }
3598             }
3599 0 0         for (s = frac_digits; s < scale; s++) frac_part *= 10;
3600 0 0         for (s = 0; s < scale; s++) integer_part *= 10;
3601             {
3602 0           int64_t raw = integer_part + frac_part;
3603 0 0         if (neg) raw = -raw;
3604 0           int32_t v = (int32_t)raw;
3605 0           nbuf_append(b, (const char *)&v, 4);
3606             }
3607             }
3608 0           return 1;
3609             }
3610 0           case CT_DECIMAL64: {
3611 0 0         for (i = 0; i < nrows; i++) {
3612 0           const char *p = values[i];
3613 0           int neg = 0;
3614 0           int64_t integer_part = 0, frac_part = 0;
3615 0           int frac_digits = 0, scale = ct->param, s;
3616 0 0         if (*p == '-') { neg = 1; p++; }
3617 0 0         else if (*p == '+') p++;
3618 0 0         while (*p >= '0' && *p <= '9') { integer_part = integer_part * 10 + (*p - '0'); p++; }
    0          
3619 0 0         if (*p == '.') {
3620 0           p++;
3621 0 0         while (*p >= '0' && *p <= '9' && frac_digits < scale) {
    0          
    0          
3622 0           frac_part = frac_part * 10 + (*p - '0');
3623 0           p++;
3624 0           frac_digits++;
3625             }
3626             }
3627 0 0         for (s = frac_digits; s < scale; s++) frac_part *= 10;
3628 0 0         for (s = 0; s < scale; s++) integer_part *= 10;
3629             {
3630 0           int64_t v = integer_part + frac_part;
3631 0 0         if (neg) v = -v;
3632 0           nbuf_append(b, (const char *)&v, 8);
3633             }
3634             }
3635 0           return 1;
3636             }
3637 0           case CT_STRING: {
3638 0 0         for (i = 0; i < nrows; i++) {
3639 0 0         if (memchr(values[i], '\\', value_lens[i])) {
3640             char *tmp;
3641             size_t ulen;
3642 0           Newx(tmp, value_lens[i], char);
3643 0           ulen = tsv_unescape(values[i], value_lens[i], tmp);
3644 0           nbuf_string(b, tmp, ulen);
3645 0           Safefree(tmp);
3646             } else {
3647 0           nbuf_string(b, values[i], value_lens[i]);
3648             }
3649             }
3650 0           return 1;
3651             }
3652 0           case CT_FIXEDSTRING: {
3653 0           size_t fsz = (size_t)ct->param;
3654 0 0         for (i = 0; i < nrows; i++) {
3655 0 0         if (memchr(values[i], '\\', value_lens[i])) {
3656             char *tmp;
3657             size_t ulen;
3658 0           size_t tmp_sz = value_lens[i] > fsz ? value_lens[i] : fsz;
3659 0           Newxz(tmp, tmp_sz, char);
3660 0           ulen = tsv_unescape(values[i], value_lens[i], tmp);
3661             (void)ulen;
3662 0           nbuf_append(b, tmp, fsz);
3663 0           Safefree(tmp);
3664             } else {
3665 0           nbuf_grow(b, fsz);
3666             {
3667 0           size_t cplen = value_lens[i] < fsz ? value_lens[i] : fsz;
3668 0           memcpy(b->data + b->len, values[i], cplen);
3669 0 0         if (cplen < fsz)
3670 0           memset(b->data + b->len + cplen, 0, fsz - cplen);
3671             }
3672 0           b->len += fsz;
3673             }
3674             }
3675 0           return 1;
3676             }
3677 0           case CT_UUID: {
3678             /* Parse "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" → 16 bytes LE halves */
3679 0 0         for (i = 0; i < nrows; i++) {
3680             unsigned char ubytes[16];
3681 0           const char *s = values[i];
3682 0           size_t slen = value_lens[i];
3683 0 0         if (slen >= 36) {
3684             /* parse hex digits, skip dashes */
3685             unsigned char raw[16];
3686 0           int k = 0, j;
3687 0 0         for (j = 0; j < (int)slen && k < 32; j++) {
    0          
3688 0           char c = s[j];
3689 0 0         if (c == '-') continue;
3690             {
3691             unsigned char nibble;
3692 0 0         if (c >= '0' && c <= '9') nibble = c - '0';
    0          
3693 0 0         else if (c >= 'a' && c <= 'f') nibble = 10 + c - 'a';
    0          
3694 0 0         else if (c >= 'A' && c <= 'F') nibble = 10 + c - 'A';
    0          
3695 0           else nibble = 0;
3696 0 0         if (k % 2 == 0) raw[k/2] = nibble << 4;
3697 0           else raw[k/2] |= nibble;
3698             }
3699 0           k++;
3700             }
3701             /* Reverse each 8-byte half for LE storage */
3702 0 0         for (k = 0; k < 8; k++) ubytes[k] = raw[7 - k];
3703 0 0         for (k = 0; k < 8; k++) ubytes[8 + k] = raw[15 - k];
3704             } else {
3705 0           memset(ubytes, 0, 16);
3706             }
3707 0           nbuf_append(b, (const char *)ubytes, 16);
3708             }
3709 0           return 1;
3710             }
3711 0           case CT_IPV4: {
3712 0 0         for (i = 0; i < nrows; i++) {
3713             struct in_addr addr;
3714 0           uint32_t v = 0;
3715             char tmp[64];
3716 0           size_t cplen = value_lens[i] < 63 ? value_lens[i] : 63;
3717 0           memcpy(tmp, values[i], cplen);
3718 0           tmp[cplen] = '\0';
3719 0 0         if (inet_pton(AF_INET, tmp, &addr) == 1)
3720 0           v = ntohl(addr.s_addr);
3721 0           nbuf_append(b, (const char *)&v, 4);
3722             }
3723 0           return 1;
3724             }
3725 0           case CT_IPV6: {
3726 0 0         for (i = 0; i < nrows; i++) {
3727             unsigned char addr[16];
3728             char tmp[64];
3729 0           size_t cplen = value_lens[i] < 63 ? value_lens[i] : 63;
3730 0           memcpy(tmp, values[i], cplen);
3731 0           tmp[cplen] = '\0';
3732 0           memset(addr, 0, 16);
3733 0           inet_pton(AF_INET6, tmp, addr);
3734 0           nbuf_append(b, (const char *)addr, 16);
3735             }
3736 0           return 1;
3737             }
3738 0           case CT_NULLABLE: {
3739             /* null bitmap + inner column */
3740             uint8_t *nulls;
3741             const char **inner_vals;
3742             size_t *inner_lens;
3743             static const char zero_str[] = "0";
3744             static const char empty_str[] = "";
3745              
3746 0           Newx(nulls, nrows, uint8_t);
3747 0 0         Newxz(inner_vals, nrows, const char *);
3748 0 0         Newx(inner_lens, nrows, size_t);
3749              
3750 0 0         for (i = 0; i < nrows; i++) {
3751 0 0         if (is_tsv_null(values[i], value_lens[i])) {
3752 0           nulls[i] = 1;
3753             /* placeholder for null — use zero/empty depending on inner type */
3754 0 0         if (ct->inner->code == CT_STRING || ct->inner->code == CT_FIXEDSTRING) {
    0          
3755 0           inner_vals[i] = empty_str;
3756 0           inner_lens[i] = 0;
3757             } else {
3758 0           inner_vals[i] = zero_str;
3759 0           inner_lens[i] = 1;
3760             }
3761             } else {
3762 0           nulls[i] = 0;
3763 0           inner_vals[i] = values[i];
3764 0           inner_lens[i] = value_lens[i];
3765             }
3766             }
3767              
3768 0           nbuf_append(b, (const char *)nulls, nrows);
3769             {
3770 0           int rc = encode_column_text(b, inner_vals, inner_lens, nrows, ct->inner);
3771 0           Safefree(nulls);
3772 0           Safefree(inner_vals);
3773 0           Safefree(inner_lens);
3774 0           return rc;
3775             }
3776             }
3777 0           case CT_LOWCARDINALITY: {
3778             /* Trivial 1:1 dictionary: each value is its own dict entry.
3779             * This is correct wire format, just not deduplicated. */
3780             int key_type;
3781             size_t idx_size;
3782 0           uint64_t ser_type, version = 1;
3783             native_buf_t dict_buf;
3784             int rc;
3785              
3786 0 0         if (nrows <= 0xFF) { key_type = 0; idx_size = 1; }
3787 0 0         else if (nrows <= 0xFFFF) { key_type = 1; idx_size = 2; }
3788 0           else { key_type = 2; idx_size = 4; }
3789              
3790 0           ser_type = (uint64_t)key_type | (1ULL << 9) | (1ULL << 10);
3791             /* HasAdditionalKeys | NeedUpdateDictionary */
3792              
3793             /* version (prefix) */
3794 0           nbuf_append(b, (const char *)&version, 8);
3795             /* serialization_type */
3796 0           nbuf_append(b, (const char *)&ser_type, 8);
3797             /* num_keys */
3798             {
3799 0           uint64_t nk = nrows;
3800 0           nbuf_append(b, (const char *)&nk, 8);
3801             }
3802             /* dictionary data: encode as inner type */
3803 0           nbuf_init(&dict_buf);
3804 0           rc = encode_column_text(&dict_buf, values, value_lens, nrows, ct->inner);
3805 0 0         if (!rc) { Safefree(dict_buf.data); return 0; }
3806 0           nbuf_append(b, dict_buf.data, dict_buf.len);
3807 0           Safefree(dict_buf.data);
3808             /* num_indices */
3809             {
3810 0           uint64_t ni = nrows;
3811 0           nbuf_append(b, (const char *)&ni, 8);
3812             }
3813             /* indices: [0, 1, 2, ..., nrows-1] */
3814 0 0         for (i = 0; i < nrows; i++) {
3815 0 0         if (idx_size == 1) {
3816 0           uint8_t idx = (uint8_t)i;
3817 0           nbuf_append(b, (const char *)&idx, 1);
3818 0 0         } else if (idx_size == 2) {
3819 0           uint16_t idx = (uint16_t)i;
3820 0           nbuf_append(b, (const char *)&idx, 2);
3821             } else {
3822 0           uint32_t idx = (uint32_t)i;
3823 0           nbuf_append(b, (const char *)&idx, 4);
3824             }
3825             }
3826 0           return 1;
3827             }
3828 0           default:
3829 0           return 0; /* unsupported type — fall back to inline SQL */
3830             }
3831             }
3832              
3833             /*
3834             * Encode a column of Perl SV values into native binary format.
3835             * Like encode_column_text() but takes SVs directly — no TSV parsing/unescaping.
3836             * Returns 1 on success, 0 if type is unsupported.
3837             */
3838 0           static int encode_column_sv(pTHX_ native_buf_t *b,
3839             SV **values, uint64_t nrows,
3840             col_type_t *ct) {
3841             uint64_t i;
3842              
3843 0           switch (ct->code) {
3844 0           case CT_INT8: case CT_ENUM8: {
3845 0 0         for (i = 0; i < nrows; i++) {
3846 0 0         int8_t v = SvIOK(values[i]) ? (int8_t)SvIV(values[i])
3847 0           : (int8_t)strtol(SvPV_nolen(values[i]), NULL, 10);
3848 0           nbuf_append(b, (const char *)&v, 1);
3849             }
3850 0           return 1;
3851             }
3852 0           case CT_INT16: case CT_ENUM16: {
3853 0 0         for (i = 0; i < nrows; i++) {
3854 0 0         int16_t v = SvIOK(values[i]) ? (int16_t)SvIV(values[i])
3855 0           : (int16_t)strtol(SvPV_nolen(values[i]), NULL, 10);
3856 0           nbuf_append(b, (const char *)&v, 2);
3857             }
3858 0           return 1;
3859             }
3860 0           case CT_INT32: case CT_DATE32: {
3861 0 0         for (i = 0; i < nrows; i++) {
3862             int32_t v;
3863 0 0         if (SvIOK(values[i])) {
3864 0           v = (int32_t)SvIV(values[i]);
3865             } else {
3866             STRLEN vlen;
3867 0           const char *s = SvPV(values[i], vlen);
3868 0 0         if (ct->code == CT_DATE32 && vlen >= 10 && s[4] == '-')
    0          
    0          
3869 0           v = date_string_to_days(s, vlen);
3870             else
3871 0           v = (int32_t)strtol(s, NULL, 10);
3872             }
3873 0           nbuf_append(b, (const char *)&v, 4);
3874             }
3875 0           return 1;
3876             }
3877 0           case CT_INT64: {
3878 0 0         for (i = 0; i < nrows; i++) {
3879 0           int64_t v = SvIOK(values[i]) ? (int64_t)SvIV(values[i])
3880 0 0         : (int64_t)strtoll(SvPV_nolen(values[i]), NULL, 10);
3881 0           nbuf_append(b, (const char *)&v, 8);
3882             }
3883 0           return 1;
3884             }
3885 0           case CT_UINT8: case CT_BOOL: {
3886 0 0         for (i = 0; i < nrows; i++) {
3887 0 0         uint8_t v = SvIOK(values[i]) ? (uint8_t)SvUV(values[i])
3888 0           : (uint8_t)strtoul(SvPV_nolen(values[i]), NULL, 10);
3889 0           nbuf_append(b, (const char *)&v, 1);
3890             }
3891 0           return 1;
3892             }
3893 0           case CT_UINT16: case CT_DATE: {
3894 0 0         for (i = 0; i < nrows; i++) {
3895             uint16_t v;
3896 0 0         if (SvIOK(values[i])) {
3897 0           v = (uint16_t)SvUV(values[i]);
3898             } else {
3899             STRLEN vlen;
3900 0           const char *s = SvPV(values[i], vlen);
3901 0 0         if (ct->code == CT_DATE && vlen >= 10 && s[4] == '-')
    0          
    0          
3902 0           v = (uint16_t)date_string_to_days(s, vlen);
3903             else
3904 0           v = (uint16_t)strtoul(s, NULL, 10);
3905             }
3906 0           nbuf_append(b, (const char *)&v, 2);
3907             }
3908 0           return 1;
3909             }
3910 0           case CT_UINT32: case CT_DATETIME: {
3911 0 0         for (i = 0; i < nrows; i++) {
3912             uint32_t v;
3913 0 0         if (SvIOK(values[i])) {
3914 0           v = (uint32_t)SvUV(values[i]);
3915             } else {
3916             STRLEN vlen;
3917 0           const char *s = SvPV(values[i], vlen);
3918 0 0         if (ct->code == CT_DATETIME && vlen >= 10 && s[4] == '-')
    0          
    0          
3919 0           v = datetime_string_to_epoch(s, vlen);
3920             else
3921 0           v = (uint32_t)strtoul(s, NULL, 10);
3922             }
3923 0           nbuf_append(b, (const char *)&v, 4);
3924             }
3925 0           return 1;
3926             }
3927 0           case CT_UINT64: {
3928 0 0         for (i = 0; i < nrows; i++) {
3929 0           uint64_t v = SvIOK(values[i]) ? (uint64_t)SvUV(values[i])
3930 0 0         : (uint64_t)strtoull(SvPV_nolen(values[i]), NULL, 10);
3931 0           nbuf_append(b, (const char *)&v, 8);
3932             }
3933 0           return 1;
3934             }
3935 0           case CT_FLOAT32: {
3936 0 0         for (i = 0; i < nrows; i++) {
3937 0           float v = SvNOK(values[i]) ? (float)SvNV(values[i])
3938 0 0         : strtof(SvPV_nolen(values[i]), NULL);
3939 0           nbuf_append(b, (const char *)&v, 4);
3940             }
3941 0           return 1;
3942             }
3943 0           case CT_FLOAT64: {
3944 0 0         for (i = 0; i < nrows; i++) {
3945 0           double v = SvNOK(values[i]) ? SvNV(values[i])
3946 0 0         : strtod(SvPV_nolen(values[i]), NULL);
3947 0           nbuf_append(b, (const char *)&v, 8);
3948             }
3949 0           return 1;
3950             }
3951 0           case CT_DATETIME64: {
3952 0 0         for (i = 0; i < nrows; i++) {
3953             int64_t v;
3954 0 0         if (SvIOK(values[i])) {
3955 0           v = (int64_t)SvIV(values[i]);
3956             } else {
3957             STRLEN vlen;
3958 0           const char *s = SvPV(values[i], vlen);
3959 0 0         if (vlen >= 10 && s[4] == '-') {
    0          
3960 0           uint32_t epoch = datetime_string_to_epoch(s, vlen);
3961             int sc;
3962 0           v = (int64_t)epoch;
3963 0 0         for (sc = 0; sc < ct->param; sc++) v *= 10;
3964 0 0         if (vlen >= 20 && s[19] == '.') {
    0          
3965 0           const char *fp = s + 20;
3966 0           const char *fe = s + vlen;
3967 0           int64_t frac = 0;
3968 0           int digits = 0, prec = ct->param;
3969 0 0         while (fp < fe && digits < prec) {
    0          
3970 0           frac = frac * 10 + (*fp - '0');
3971 0           fp++;
3972 0           digits++;
3973             }
3974 0 0         while (digits < prec) { frac *= 10; digits++; }
3975 0           v += frac;
3976             }
3977             } else {
3978 0           v = (int64_t)strtoll(s, NULL, 10);
3979             }
3980             }
3981 0           nbuf_append(b, (const char *)&v, 8);
3982             }
3983 0           return 1;
3984             }
3985 0           case CT_DECIMAL32: {
3986 0 0         for (i = 0; i < nrows; i++) {
3987             STRLEN vlen;
3988 0           const char *p = SvPV(values[i], vlen);
3989 0           int neg = 0;
3990 0           int64_t integer_part = 0, frac_part = 0;
3991 0           int frac_digits = 0, scale = ct->param, s;
3992 0 0         if (*p == '-') { neg = 1; p++; }
3993 0 0         else if (*p == '+') p++;
3994 0 0         while (*p >= '0' && *p <= '9') { integer_part = integer_part * 10 + (*p - '0'); p++; }
    0          
3995 0 0         if (*p == '.') {
3996 0           p++;
3997 0 0         while (*p >= '0' && *p <= '9' && frac_digits < scale) {
    0          
    0          
3998 0           frac_part = frac_part * 10 + (*p - '0'); p++; frac_digits++;
3999             }
4000             }
4001 0 0         for (s = frac_digits; s < scale; s++) frac_part *= 10;
4002 0 0         for (s = 0; s < scale; s++) integer_part *= 10;
4003             {
4004 0           int64_t raw = integer_part + frac_part;
4005 0 0         if (neg) raw = -raw;
4006 0           int32_t v = (int32_t)raw;
4007 0           nbuf_append(b, (const char *)&v, 4);
4008             }
4009             }
4010 0           return 1;
4011             }
4012 0           case CT_DECIMAL64: {
4013 0 0         for (i = 0; i < nrows; i++) {
4014             STRLEN vlen;
4015 0           const char *p = SvPV(values[i], vlen);
4016 0           int neg = 0;
4017 0           int64_t integer_part = 0, frac_part = 0;
4018 0           int frac_digits = 0, scale = ct->param, s;
4019 0 0         if (*p == '-') { neg = 1; p++; }
4020 0 0         else if (*p == '+') p++;
4021 0 0         while (*p >= '0' && *p <= '9') { integer_part = integer_part * 10 + (*p - '0'); p++; }
    0          
4022 0 0         if (*p == '.') {
4023 0           p++;
4024 0 0         while (*p >= '0' && *p <= '9' && frac_digits < scale) {
    0          
    0          
4025 0           frac_part = frac_part * 10 + (*p - '0'); p++; frac_digits++;
4026             }
4027             }
4028 0 0         for (s = frac_digits; s < scale; s++) frac_part *= 10;
4029 0 0         for (s = 0; s < scale; s++) integer_part *= 10;
4030             {
4031 0           int64_t v = integer_part + frac_part;
4032 0 0         if (neg) v = -v;
4033 0           nbuf_append(b, (const char *)&v, 8);
4034             }
4035             }
4036 0           return 1;
4037             }
4038 0           case CT_STRING: {
4039 0 0         for (i = 0; i < nrows; i++) {
4040             STRLEN vlen;
4041 0           const char *v = SvPV(values[i], vlen);
4042 0           nbuf_string(b, v, vlen);
4043             }
4044 0           return 1;
4045             }
4046 0           case CT_FIXEDSTRING: {
4047 0           size_t fsz = (size_t)ct->param;
4048 0 0         for (i = 0; i < nrows; i++) {
4049             STRLEN vlen;
4050 0           const char *v = SvPV(values[i], vlen);
4051 0           size_t cplen = (size_t)vlen < fsz ? (size_t)vlen : fsz;
4052 0           nbuf_grow(b, fsz);
4053 0           memcpy(b->data + b->len, v, cplen);
4054 0 0         if (cplen < fsz)
4055 0           memset(b->data + b->len + cplen, 0, fsz - cplen);
4056 0           b->len += fsz;
4057             }
4058 0           return 1;
4059             }
4060 0           case CT_UUID: {
4061 0 0         for (i = 0; i < nrows; i++) {
4062             unsigned char ubytes[16];
4063             STRLEN slen;
4064 0           const char *s = SvPV(values[i], slen);
4065 0 0         if (slen >= 36) {
4066             unsigned char raw[16];
4067 0           int k = 0, j;
4068 0 0         for (j = 0; j < (int)slen && k < 32; j++) {
    0          
4069 0           char c = s[j];
4070 0 0         if (c == '-') continue;
4071             {
4072             unsigned char nibble;
4073 0 0         if (c >= '0' && c <= '9') nibble = c - '0';
    0          
4074 0 0         else if (c >= 'a' && c <= 'f') nibble = 10 + c - 'a';
    0          
4075 0 0         else if (c >= 'A' && c <= 'F') nibble = 10 + c - 'A';
    0          
4076 0           else nibble = 0;
4077 0 0         if (k % 2 == 0) raw[k/2] = nibble << 4;
4078 0           else raw[k/2] |= nibble;
4079             }
4080 0           k++;
4081             }
4082 0 0         for (k = 0; k < 8; k++) ubytes[k] = raw[7 - k];
4083 0 0         for (k = 0; k < 8; k++) ubytes[8 + k] = raw[15 - k];
4084             } else {
4085 0           memset(ubytes, 0, 16);
4086             }
4087 0           nbuf_append(b, (const char *)ubytes, 16);
4088             }
4089 0           return 1;
4090             }
4091 0           case CT_IPV4: {
4092 0 0         for (i = 0; i < nrows; i++) {
4093             struct in_addr addr;
4094 0           uint32_t v = 0;
4095             char tmp[64];
4096             STRLEN vlen;
4097 0           const char *s = SvPV(values[i], vlen);
4098 0           size_t cplen = (size_t)vlen < 63 ? (size_t)vlen : 63;
4099 0           memcpy(tmp, s, cplen);
4100 0           tmp[cplen] = '\0';
4101 0 0         if (inet_pton(AF_INET, tmp, &addr) == 1)
4102 0           v = ntohl(addr.s_addr);
4103 0           nbuf_append(b, (const char *)&v, 4);
4104             }
4105 0           return 1;
4106             }
4107 0           case CT_IPV6: {
4108 0 0         for (i = 0; i < nrows; i++) {
4109             unsigned char addr[16];
4110             char tmp[64];
4111             STRLEN vlen;
4112 0           const char *s = SvPV(values[i], vlen);
4113 0           size_t cplen = (size_t)vlen < 63 ? (size_t)vlen : 63;
4114 0           memcpy(tmp, s, cplen);
4115 0           tmp[cplen] = '\0';
4116 0           memset(addr, 0, 16);
4117 0           inet_pton(AF_INET6, tmp, addr);
4118 0           nbuf_append(b, (const char *)addr, 16);
4119             }
4120 0           return 1;
4121             }
4122 0           case CT_NULLABLE: {
4123             uint8_t *nulls;
4124             SV **inner_vals;
4125             SV *zero_sv;
4126              
4127 0           Newx(nulls, nrows, uint8_t);
4128 0 0         Newx(inner_vals, nrows ? nrows : 1, SV *);
    0          
    0          
4129 0           zero_sv = newSViv(0);
4130              
4131 0 0         for (i = 0; i < nrows; i++) {
4132 0 0         if (!SvOK(values[i])) {
4133 0           nulls[i] = 1;
4134 0           inner_vals[i] = zero_sv;
4135             } else {
4136 0           nulls[i] = 0;
4137 0           inner_vals[i] = values[i];
4138             }
4139             }
4140              
4141 0           nbuf_append(b, (const char *)nulls, nrows);
4142             {
4143 0           int rc = encode_column_sv(aTHX_ b, inner_vals, nrows, ct->inner);
4144 0           Safefree(nulls);
4145 0           Safefree(inner_vals);
4146 0           SvREFCNT_dec(zero_sv);
4147 0           return rc;
4148             }
4149             }
4150 0           case CT_LOWCARDINALITY: {
4151             int key_type;
4152             size_t idx_size;
4153 0           uint64_t ser_type, version = 1;
4154             native_buf_t dict_buf;
4155             int rc;
4156              
4157 0 0         if (nrows <= 0xFF) { key_type = 0; idx_size = 1; }
4158 0 0         else if (nrows <= 0xFFFF) { key_type = 1; idx_size = 2; }
4159 0           else { key_type = 2; idx_size = 4; }
4160              
4161 0           ser_type = (uint64_t)key_type | (1ULL << 9) | (1ULL << 10);
4162              
4163 0           nbuf_append(b, (const char *)&version, 8);
4164 0           nbuf_append(b, (const char *)&ser_type, 8);
4165             {
4166 0           uint64_t nk = nrows;
4167 0           nbuf_append(b, (const char *)&nk, 8);
4168             }
4169 0           nbuf_init(&dict_buf);
4170 0           rc = encode_column_sv(aTHX_ &dict_buf, values, nrows, ct->inner);
4171 0 0         if (!rc) { Safefree(dict_buf.data); return 0; }
4172 0           nbuf_append(b, dict_buf.data, dict_buf.len);
4173 0           Safefree(dict_buf.data);
4174             {
4175 0           uint64_t ni = nrows;
4176 0           nbuf_append(b, (const char *)&ni, 8);
4177             }
4178 0 0         for (i = 0; i < nrows; i++) {
4179 0 0         if (idx_size == 1) {
4180 0           uint8_t idx = (uint8_t)i;
4181 0           nbuf_append(b, (const char *)&idx, 1);
4182 0 0         } else if (idx_size == 2) {
4183 0           uint16_t idx = (uint16_t)i;
4184 0           nbuf_append(b, (const char *)&idx, 2);
4185             } else {
4186 0           uint32_t idx = (uint32_t)i;
4187 0           nbuf_append(b, (const char *)&idx, 4);
4188             }
4189             }
4190 0           return 1;
4191             }
4192 0           case CT_ARRAY: {
4193             /* Each value must be an AV ref. Wire format: offsets + flat inner data */
4194 0           uint64_t total = 0;
4195             uint64_t *offsets;
4196             SV **all_elems;
4197 0           uint64_t pos = 0;
4198             int rc;
4199              
4200 0 0         for (i = 0; i < nrows; i++) {
4201             AV *av;
4202 0 0         if (!SvROK(values[i]) || SvTYPE(SvRV(values[i])) != SVt_PVAV)
    0          
4203 0           return 0;
4204 0           av = (AV *)SvRV(values[i]);
4205 0 0         { SSize_t cnt = av_len(av) + 1; if (cnt > 0) total += (uint64_t)cnt; }
4206             }
4207              
4208 0 0         Newx(offsets, nrows, uint64_t);
4209 0 0         Newx(all_elems, total ? total : 1, SV *);
    0          
    0          
4210              
4211 0 0         for (i = 0; i < nrows; i++) {
4212 0           AV *av = (AV *)SvRV(values[i]);
4213 0           SSize_t n = av_len(av) + 1, j;
4214 0 0         for (j = 0; j < n; j++) {
4215 0           SV **ep = av_fetch(av, j, 0);
4216 0 0         all_elems[pos++] = ep ? *ep : &PL_sv_undef;
4217             }
4218 0           offsets[i] = pos;
4219             }
4220              
4221             /* write offsets as uint64 LE */
4222 0           nbuf_append(b, (const char *)offsets, nrows * 8);
4223 0           rc = encode_column_sv(aTHX_ b, all_elems, total, ct->inner);
4224 0           Safefree(offsets);
4225 0           Safefree(all_elems);
4226 0           return rc;
4227             }
4228 0           case CT_TUPLE: {
4229             /* Each value must be an AV ref with num_inners elements */
4230             int j;
4231 0 0         for (j = 0; j < ct->num_inners; j++) {
4232             SV **col_vals;
4233             int rc;
4234 0 0         Newx(col_vals, nrows ? nrows : 1, SV *);
    0          
    0          
4235 0 0         for (i = 0; i < nrows; i++) {
4236             AV *av;
4237             SV **ep;
4238 0 0         if (!SvROK(values[i]) || SvTYPE(SvRV(values[i])) != SVt_PVAV) {
    0          
4239 0           Safefree(col_vals);
4240 0           return 0;
4241             }
4242 0           av = (AV *)SvRV(values[i]);
4243 0           ep = av_fetch(av, j, 0);
4244 0 0         col_vals[i] = ep ? *ep : &PL_sv_undef;
4245             }
4246 0           rc = encode_column_sv(aTHX_ b, col_vals, nrows, ct->inners[j]);
4247 0           Safefree(col_vals);
4248 0 0         if (!rc) return 0;
4249             }
4250 0           return 1;
4251             }
4252 0           case CT_MAP: {
4253             /* Each value must be a hashref. Wire format: offsets + key column + value column */
4254 0           uint64_t total = 0;
4255             uint64_t *offsets;
4256             SV **all_keys, **all_vals;
4257 0           uint64_t pos = 0;
4258             int rc;
4259              
4260 0 0         if (ct->num_inners != 2) return 0;
4261              
4262 0 0         for (i = 0; i < nrows; i++) {
4263             HV *hv;
4264 0 0         if (!SvROK(values[i]) || SvTYPE(SvRV(values[i])) != SVt_PVHV)
    0          
4265 0           return 0;
4266 0           hv = (HV *)SvRV(values[i]);
4267 0 0         total += HvUSEDKEYS(hv);
4268             }
4269              
4270 0 0         Newx(offsets, nrows, uint64_t);
4271 0 0         Newx(all_keys, total ? total : 1, SV *);
    0          
    0          
4272 0 0         Newx(all_vals, total ? total : 1, SV *);
    0          
    0          
4273              
4274 0 0         for (i = 0; i < nrows; i++) {
4275 0           HV *hv = (HV *)SvRV(values[i]);
4276             HE *he;
4277 0           hv_iterinit(hv);
4278 0 0         while ((he = hv_iternext(hv))) {
4279 0           all_keys[pos] = hv_iterkeysv(he);
4280 0           all_vals[pos] = hv_iterval(hv, he);
4281 0           pos++;
4282             }
4283 0           offsets[i] = pos;
4284             }
4285              
4286 0           nbuf_append(b, (const char *)offsets, nrows * 8);
4287 0           rc = encode_column_sv(aTHX_ b, all_keys, total, ct->inners[0]);
4288 0 0         if (rc) rc = encode_column_sv(aTHX_ b, all_vals, total, ct->inners[1]);
4289 0           Safefree(offsets);
4290 0           Safefree(all_keys);
4291 0           Safefree(all_vals);
4292 0           return rc;
4293             }
4294 0           default:
4295 0           return 0;
4296             }
4297             }
4298              
4299             /*
4300             * Wrap a filled Data block body into a CLIENT_DATA packet with optional LZ4 + empty trailing block.
4301             * Consumes body->data (frees it). Returns malloc'd packet, or NULL on failure.
4302             */
4303 0           static char* wrap_data_block(ev_clickhouse_t *self, native_buf_t *body, size_t *out_len) {
4304             native_buf_t pkt;
4305              
4306 0           nbuf_init(&pkt);
4307 0           nbuf_varuint(&pkt, CLIENT_DATA);
4308 0           nbuf_cstring(&pkt, ""); /* table name — outside compression */
4309              
4310             #ifdef HAVE_LZ4
4311             if (self->compress) {
4312             char *compressed;
4313             size_t comp_len;
4314             compressed = ch_lz4_compress(body->data, body->len, &comp_len);
4315             Safefree(body->data);
4316             body->data = NULL;
4317             if (compressed) {
4318             nbuf_append(&pkt, compressed, comp_len);
4319             Safefree(compressed);
4320             } else {
4321             Safefree(pkt.data);
4322             *out_len = 0;
4323             return NULL;
4324             }
4325             } else
4326             #endif
4327             {
4328 0           nbuf_append(&pkt, body->data, body->len);
4329 0           Safefree(body->data);
4330 0           body->data = NULL;
4331             }
4332              
4333 0           nbuf_empty_data_block(&pkt, self->compress);
4334              
4335 0           *out_len = pkt.len;
4336 0           return pkt.data;
4337             }
4338              
4339             /*
4340             * Build a native protocol Data block from TabSeparated text data.
4341             * col_names/col_types_str are string references into the sample block buffer.
4342             * Returns malloc'd packet data (CLIENT_DATA + block), or NULL on failure.
4343             */
4344 0           static char* build_native_insert_data(ev_clickhouse_t *self,
4345             const char *tsv_data, size_t tsv_len,
4346             const char **col_names, size_t *col_name_lens,
4347             const char **col_types_str, size_t *col_type_lens,
4348             col_type_t **col_types,
4349             int num_cols,
4350             size_t *out_len) {
4351             /* Parse TSV into rows and fields */
4352 0           int nrows = 0, max_rows = 64;
4353 0           const char **fields = NULL; /* flat array: fields[row * num_cols + col] */
4354 0           size_t *field_lens = NULL;
4355 0           const char *p = tsv_data;
4356 0           const char *end = tsv_data + tsv_len;
4357              
4358 0           Newxz(fields, max_rows * num_cols, const char *);
4359 0           Newx(field_lens, max_rows * num_cols, size_t);
4360              
4361 0 0         while (p < end) {
4362 0           const char *line_end = memchr(p, '\n', end - p);
4363 0 0         const char *line_limit = line_end ? line_end : end;
4364             int col;
4365              
4366             /* skip empty trailing line */
4367 0 0         if (p == line_limit) { p = line_limit + 1; continue; }
4368              
4369 0 0         if (nrows >= max_rows) {
4370 0 0         if (max_rows > INT_MAX / 2 ||
    0          
4371 0 0         (num_cols > 0 && max_rows * 2 > INT_MAX / num_cols)) {
4372 0           Safefree(fields);
4373 0           Safefree(field_lens);
4374 0           *out_len = 0;
4375 0           return NULL;
4376             }
4377 0           max_rows *= 2;
4378 0           Renew(fields, max_rows * num_cols, const char *);
4379 0           Renew(field_lens, max_rows * num_cols, size_t);
4380             }
4381              
4382             /* split line by tabs */
4383             {
4384 0           const char *fp = p;
4385 0 0         for (col = 0; col < num_cols; col++) {
4386             const char *tab;
4387 0 0         if (fp > line_limit) fp = line_limit;
4388 0 0         if (col < num_cols - 1) {
4389 0           tab = memchr(fp, '\t', line_limit - fp);
4390 0 0         if (!tab) tab = line_limit;
4391             } else {
4392 0           tab = line_limit;
4393             }
4394 0           fields[nrows * num_cols + col] = fp;
4395 0           field_lens[nrows * num_cols + col] = tab - fp;
4396 0           fp = tab + 1;
4397             }
4398             }
4399 0           nrows++;
4400 0           p = line_limit + 1;
4401             }
4402              
4403 0 0         if (nrows == 0) {
4404 0           Safefree(fields);
4405 0           Safefree(field_lens);
4406 0           *out_len = 0;
4407 0           return NULL;
4408             }
4409              
4410             /* Build the Data block body: block info + num_cols + num_rows + columns */
4411             {
4412             native_buf_t body;
4413             int col;
4414              
4415 0           nbuf_init(&body);
4416              
4417             /* block info */
4418 0           nbuf_varuint(&body, 1); /* field_num = 1 */
4419 0           nbuf_u8(&body, 0); /* is_overflows = false */
4420 0           nbuf_varuint(&body, 2); /* field_num = 2 */
4421             {
4422 0           int32_t bucket = -1;
4423 0           nbuf_append(&body, (const char *)&bucket, 4);
4424             }
4425 0           nbuf_varuint(&body, 0); /* end of block info */
4426 0           nbuf_varuint(&body, (uint64_t)num_cols);
4427 0           nbuf_varuint(&body, (uint64_t)nrows);
4428              
4429             /* encode each column */
4430 0 0         for (col = 0; col < num_cols; col++) {
4431             const char **col_vals;
4432             size_t *col_vlens;
4433             int row;
4434              
4435             /* column name and type */
4436 0           nbuf_string(&body, col_names[col], col_name_lens[col]);
4437 0           nbuf_string(&body, col_types_str[col], col_type_lens[col]);
4438 0           nbuf_u8(&body, 0); /* has_custom_serialization = false */
4439              
4440             /* gather column values from row-major fields */
4441 0           Newxz(col_vals, nrows, const char *);
4442 0           Newx(col_vlens, nrows, size_t);
4443 0 0         for (row = 0; row < nrows; row++) {
4444 0           col_vals[row] = fields[row * num_cols + col];
4445 0           col_vlens[row] = field_lens[row * num_cols + col];
4446             }
4447              
4448 0 0         if (!encode_column_text(&body, col_vals, col_vlens,
4449 0           (uint64_t)nrows, col_types[col])) {
4450 0           Safefree(col_vals);
4451 0           Safefree(col_vlens);
4452 0           Safefree(body.data);
4453 0           Safefree(fields);
4454 0           Safefree(field_lens);
4455 0           *out_len = (size_t)-1; /* sentinel: encode failure */
4456 0           return NULL;
4457             }
4458 0           Safefree(col_vals);
4459 0           Safefree(col_vlens);
4460             }
4461              
4462 0           Safefree(fields);
4463 0           Safefree(field_lens);
4464              
4465             {
4466 0           char *result = wrap_data_block(self, &body, out_len);
4467 0 0         if (!result) { *out_len = 0; return NULL; }
4468 0           return result;
4469             }
4470             }
4471             }
4472              
4473             /*
4474             * Build a native protocol Data block from an AV of AV refs.
4475             * Like build_native_insert_data() but encodes SVs directly via encode_column_sv().
4476             */
4477 0           static char* build_native_insert_data_from_av(pTHX_ ev_clickhouse_t *self,
4478             AV *rows,
4479             const char **col_names, size_t *col_name_lens,
4480             const char **col_types_str, size_t *col_type_lens,
4481             col_type_t **col_types,
4482             int num_cols,
4483             size_t *out_len) {
4484 0           SSize_t nrows = av_len(rows) + 1;
4485             native_buf_t body;
4486             int col;
4487              
4488 0 0         if (nrows <= 0) {
4489 0           *out_len = 0;
4490 0           return NULL;
4491             }
4492              
4493 0           nbuf_init(&body);
4494              
4495             /* block info */
4496 0           nbuf_varuint(&body, 1); /* field_num = 1 */
4497 0           nbuf_u8(&body, 0); /* is_overflows = false */
4498 0           nbuf_varuint(&body, 2); /* field_num = 2 */
4499             {
4500 0           int32_t bucket = -1;
4501 0           nbuf_append(&body, (const char *)&bucket, 4);
4502             }
4503 0           nbuf_varuint(&body, 0); /* end of block info */
4504 0           nbuf_varuint(&body, (uint64_t)num_cols);
4505 0           nbuf_varuint(&body, (uint64_t)nrows);
4506              
4507             /* encode each column */
4508 0 0         for (col = 0; col < num_cols; col++) {
4509             SV **col_vals;
4510             SSize_t row;
4511              
4512 0           nbuf_string(&body, col_names[col], col_name_lens[col]);
4513 0           nbuf_string(&body, col_types_str[col], col_type_lens[col]);
4514 0           nbuf_u8(&body, 0); /* has_custom_serialization = false */
4515              
4516             /* gather column values from row-major AV */
4517 0 0         Newx(col_vals, nrows, SV *);
4518 0 0         for (row = 0; row < nrows; row++) {
4519 0           SV **row_svp = av_fetch(rows, row, 0);
4520             AV *row_av;
4521             SV **val_svp;
4522              
4523 0 0         if (!row_svp || !SvROK(*row_svp) || SvTYPE(SvRV(*row_svp)) != SVt_PVAV) {
    0          
    0          
4524 0           Safefree(col_vals);
4525 0           Safefree(body.data);
4526 0           *out_len = (size_t)-1; /* sentinel: encode failure */
4527 0           return NULL;
4528             }
4529 0           row_av = (AV *)SvRV(*row_svp);
4530 0           val_svp = av_fetch(row_av, col, 0);
4531 0 0         col_vals[row] = (val_svp && *val_svp) ? *val_svp : &PL_sv_undef;
    0          
4532             }
4533              
4534 0 0         if (!encode_column_sv(aTHX_ &body, col_vals, (uint64_t)nrows, col_types[col])) {
4535 0           Safefree(col_vals);
4536 0           Safefree(body.data);
4537 0           *out_len = (size_t)-1; /* sentinel: encode failure */
4538 0           return NULL;
4539             }
4540 0           Safefree(col_vals);
4541             }
4542              
4543             {
4544 0           char *result = wrap_data_block(self, &body, out_len);
4545 0 0         if (!result) { *out_len = 0; return NULL; }
4546 0           return result;
4547             }
4548             }
4549              
4550             /* --- Native protocol response parser --- */
4551              
4552             /*
4553             * Skip block info fields (revision >= DBMS_MIN_REVISION_WITH_BLOCK_INFO).
4554             * Returns 1 on success, 0 if need more data.
4555             */
4556 0           static int skip_block_info(const char *buf, size_t len, size_t *pos) {
4557 0           for (;;) {
4558             uint64_t field_num;
4559 0           int rc = read_varuint(buf, len, pos, &field_num);
4560 0 0         if (rc == 0) return 0;
4561 0 0         if (rc < 0) return -1;
4562 0 0         if (field_num == 0) return 1; /* end marker */
4563 0 0         if (field_num == 1) {
4564             /* is_overflows: UInt8 */
4565             uint8_t dummy;
4566 0           rc = read_u8(buf, len, pos, &dummy);
4567 0 0         if (rc <= 0) return rc;
4568 0 0         } else if (field_num == 2) {
4569             /* bucket_num: Int32 */
4570             int32_t dummy;
4571 0           rc = read_i32(buf, len, pos, &dummy);
4572 0 0         if (rc <= 0) return rc;
4573             } else {
4574 0           return -1; /* protocol error */
4575             }
4576             }
4577             }
4578              
4579             /*
4580             * Try to parse one server packet from recv_buf.
4581             * Returns:
4582             * 1 = packet consumed, continue reading
4583             * 0 = need more data
4584             * -1 = error (message in *errmsg, caller must Safefree)
4585             * 2 = EndOfStream
4586             * 3 = Pong
4587             * 4 = Hello parsed (self->server_* fields populated)
4588             */
4589              
4590             /* Parse a Data block (table_name + optional-LZ4-chain + block_info + columns)
4591             * and decode-and-discard each column. Used by SERVER_LOG and SERVER_PROFILE_EVENTS,
4592             * which carry a block in the same wire format as SERVER_DATA but whose contents
4593             * the client does not surface to Perl.
4594             *
4595             * `lz4_optional` (used by PROFILE_EVENTS only): if set, fall back to parsing
4596             * the body uncompressed when LZ4 hard-fails — older servers occasionally send
4597             * profile_events uncompressed even on a compressed connection.
4598             *
4599             * Returns 1 on success (advances *outer_pos past the consumed bytes),
4600             * 0 on need-more-data,
4601             * -1 on hard error (sets *errmsg). */
4602 0           static int parse_and_discard_block(ev_clickhouse_t *self,
4603             const char *buf, size_t len, size_t *outer_pos,
4604             const char *kind, int lz4_optional,
4605             char **errmsg) {
4606 0           size_t pos = *outer_pos;
4607             int rc;
4608 0           char *decompressed = NULL;
4609             const char *bbuf;
4610             size_t blen, bpos;
4611             char errbuf[64];
4612              
4613 0           rc = skip_native_string(buf, len, &pos);
4614 0 0         if (rc == 0) return 0;
4615 0 0         if (rc < 0) {
4616 0           snprintf(errbuf, sizeof(errbuf), "malformed %s block", kind);
4617 0           *errmsg = safe_strdup(errbuf);
4618 0           return -1;
4619             }
4620              
4621             #ifdef HAVE_LZ4
4622             if (self->compress) {
4623             int need_more = 0;
4624             const char *lz4_err = NULL;
4625             decompressed = ch_lz4_decompress_chain(buf, len, &pos, &blen,
4626             &need_more, &lz4_err);
4627             if (!decompressed) {
4628             if (need_more) return 0;
4629             if (!lz4_optional) {
4630             snprintf(errbuf, sizeof(errbuf), "%s: LZ4 decompression failed", kind);
4631             *errmsg = safe_strdup(lz4_err ? lz4_err : errbuf);
4632             return -1;
4633             }
4634             /* fall through to uncompressed parsing */
4635             }
4636             }
4637             if (decompressed) {
4638             bbuf = decompressed;
4639             bpos = 0;
4640             } else
4641             #endif
4642             {
4643 0           bbuf = buf;
4644 0           blen = len;
4645 0           bpos = pos;
4646             }
4647              
4648             #define _BAIL(rc_val) do { \
4649             if (decompressed) Safefree(decompressed); \
4650             if (rc_val < 0) { snprintf(errbuf, sizeof(errbuf), "malformed %s block", kind); *errmsg = safe_strdup(errbuf); } \
4651             return rc_val; \
4652             } while (0)
4653              
4654             if (CH_CLIENT_REVISION >= DBMS_MIN_REVISION_WITH_BLOCK_INFO) {
4655 0           rc = skip_block_info(bbuf, blen, &bpos);
4656 0 0         if (rc <= 0) _BAIL(rc);
    0          
    0          
4657             }
4658             {
4659             uint64_t nc, nr, c;
4660 0           rc = read_varuint(bbuf, blen, &bpos, &nc);
4661 0 0         if (rc <= 0) _BAIL(rc);
    0          
    0          
4662 0           rc = read_varuint(bbuf, blen, &bpos, &nr);
4663 0 0         if (rc <= 0) _BAIL(rc);
    0          
    0          
4664              
4665 0 0         for (c = 0; c < nc; c++) {
4666             const char *ctype;
4667             size_t ctype_len;
4668 0           rc = skip_native_string(bbuf, blen, &bpos);
4669 0 0         if (rc <= 0) _BAIL(rc);
    0          
    0          
4670 0           rc = read_native_string_ref(bbuf, blen, &bpos, &ctype, &ctype_len);
4671 0 0         if (rc <= 0) _BAIL(rc);
    0          
    0          
4672             /* custom serialization flag (revision >= 54446) */
4673 0 0         if (bpos >= blen) _BAIL(0);
    0          
4674 0 0         if ((uint8_t)bbuf[bpos]) {
4675 0 0         if (decompressed) Safefree(decompressed);
4676 0           *errmsg = safe_strdup("custom serialization not supported");
4677 0           return -1;
4678             }
4679 0           bpos++;
4680 0 0         if (nr > 0) {
4681 0           col_type_t *ct = parse_col_type(ctype, ctype_len);
4682 0           int col_err = 0;
4683 0           SV **vals = decode_column(bbuf, blen, &bpos, nr, ct, &col_err, 0);
4684 0 0         if (!vals) {
4685 0           free_col_type(ct);
4686 0 0         if (col_err || decompressed) {
    0          
4687 0 0         if (decompressed) Safefree(decompressed);
4688 0           snprintf(errbuf, sizeof(errbuf), "malformed %s block", kind);
4689 0           *errmsg = safe_strdup(errbuf);
4690 0           return -1;
4691             }
4692 0           return 0;
4693             }
4694             {
4695             uint64_t j;
4696 0 0         for (j = 0; j < nr; j++) SvREFCNT_dec(vals[j]);
4697             }
4698 0           Safefree(vals);
4699 0           free_col_type(ct);
4700             }
4701             }
4702             }
4703              
4704             #undef _BAIL
4705              
4706 0 0         if (!decompressed) pos = bpos;
4707 0 0         if (decompressed) Safefree(decompressed);
4708 0           *outer_pos = pos;
4709 0           return 1;
4710             }
4711              
4712 0           static int parse_native_packet(ev_clickhouse_t *self, char **errmsg) {
4713 0           const char *buf = self->recv_buf;
4714 0           size_t len = self->recv_len;
4715 0           size_t pos = 0;
4716             uint64_t ptype;
4717             int rc;
4718              
4719 0           rc = read_varuint(buf, len, &pos, &ptype);
4720 0 0         if (rc == 0) return 0;
4721 0 0         if (rc < 0) {
4722 0           *errmsg = safe_strdup("malformed packet type");
4723 0           return -1;
4724             }
4725              
4726 0           switch ((int)ptype) {
4727              
4728 0           case SERVER_HELLO: {
4729 0           char *sname = NULL;
4730             size_t sname_len;
4731             uint64_t major, minor, revision;
4732              
4733 0           rc = read_native_string_alloc(buf, len, &pos, &sname, &sname_len);
4734 0 0         if (rc == 0) return 0;
4735 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed server name"); return -1; }
4736              
4737 0           rc = read_varuint(buf, len, &pos, &major);
4738 0 0         if (rc == 0) { Safefree(sname); return 0; }
4739 0 0         if (rc < 0) { Safefree(sname); *errmsg = safe_strdup("malformed server version major"); return -1; }
4740              
4741 0           rc = read_varuint(buf, len, &pos, &minor);
4742 0 0         if (rc == 0) { Safefree(sname); return 0; }
4743 0 0         if (rc < 0) { Safefree(sname); *errmsg = safe_strdup("malformed server version minor"); return -1; }
4744              
4745 0           rc = read_varuint(buf, len, &pos, &revision);
4746 0 0         if (rc == 0) { Safefree(sname); return 0; }
4747 0 0         if (rc < 0) { Safefree(sname); *errmsg = safe_strdup("malformed server revision"); return -1; }
4748              
4749 0 0         if (self->server_name) Safefree(self->server_name);
4750 0           self->server_name = sname;
4751 0           self->server_version_major = (unsigned int)major;
4752 0           self->server_version_minor = (unsigned int)minor;
4753 0           self->server_revision = (unsigned int)revision;
4754              
4755             /* timezone (our revision >= 54423) */
4756             if (CH_CLIENT_REVISION >= DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE) {
4757 0           char *tz = NULL;
4758 0           rc = read_native_string_alloc(buf, len, &pos, &tz, NULL);
4759 0 0         if (rc == 0) return 0;
4760 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed timezone"); return -1; }
4761 0 0         if (self->server_timezone) Safefree(self->server_timezone);
4762 0           self->server_timezone = tz;
4763             }
4764              
4765             /* display_name (our revision >= 54372) */
4766             if (CH_CLIENT_REVISION >= DBMS_MIN_REVISION_WITH_SERVER_DISPLAY_NAME) {
4767 0           char *dn = NULL;
4768 0           rc = read_native_string_alloc(buf, len, &pos, &dn, NULL);
4769 0 0         if (rc == 0) return 0;
4770 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed display name"); return -1; }
4771 0 0         if (self->server_display_name) Safefree(self->server_display_name);
4772 0           self->server_display_name = dn;
4773             }
4774              
4775             /* version_patch (our revision >= 54401) */
4776             if (CH_CLIENT_REVISION >= DBMS_MIN_REVISION_WITH_VERSION_PATCH) {
4777             uint64_t patch;
4778 0           rc = read_varuint(buf, len, &pos, &patch);
4779 0 0         if (rc == 0) return 0;
4780 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed version patch"); return -1; }
4781 0           self->server_version_patch = (unsigned int)patch;
4782             }
4783              
4784             /* consume from recv_buf */
4785 0           recv_consume(self, pos);
4786 0           return 4;
4787             }
4788              
4789 0           case SERVER_DATA:
4790             case SERVER_TOTALS:
4791             case SERVER_EXTREMES: {
4792             uint64_t num_cols, num_rows;
4793             const char *dbuf; /* data buffer (may point to decompressed data) */
4794             size_t dlen, dpos;
4795 0           char *decompressed = NULL;
4796              
4797             /* table name — outside compression */
4798 0           rc = skip_native_string(buf, len, &pos);
4799 0 0         if (rc == 0) return 0;
4800 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed table name"); return -1; }
4801              
4802             #ifdef HAVE_LZ4
4803             if (self->compress) {
4804             /* Decompress the block body — may span multiple LZ4 sub-blocks. */
4805             int need_more = 0;
4806             const char *lz4_err = NULL;
4807             decompressed = ch_lz4_decompress_chain(buf, len, &pos, &dlen,
4808             &need_more, &lz4_err);
4809             if (!decompressed) {
4810             if (need_more) return 0;
4811             *errmsg = safe_strdup(lz4_err ? lz4_err : "LZ4 decompression failed");
4812             return -1;
4813             }
4814             dbuf = decompressed;
4815             dpos = 0;
4816             } else
4817             #endif
4818             {
4819 0           dbuf = buf;
4820 0           dlen = len;
4821 0           dpos = pos;
4822             }
4823              
4824             /* block info */
4825             if (CH_CLIENT_REVISION >= DBMS_MIN_REVISION_WITH_BLOCK_INFO) {
4826 0           rc = skip_block_info(dbuf, dlen, &dpos);
4827 0 0         if (rc == 0) {
4828 0 0         if (decompressed) { Safefree(decompressed); *errmsg = safe_strdup("truncated compressed block"); return -1; }
4829 0           return 0;
4830             }
4831 0 0         if (rc < 0) { if (decompressed) Safefree(decompressed); *errmsg = safe_strdup("malformed block info"); return -1; }
    0          
4832             }
4833              
4834 0           rc = read_varuint(dbuf, dlen, &dpos, &num_cols);
4835 0 0         if (rc == 0) {
4836 0 0         if (decompressed) { Safefree(decompressed); *errmsg = safe_strdup("truncated compressed block"); return -1; }
4837 0           return 0;
4838             }
4839 0 0         if (rc < 0) { if (decompressed) Safefree(decompressed); *errmsg = safe_strdup("malformed num_cols"); return -1; }
    0          
4840              
4841 0           rc = read_varuint(dbuf, dlen, &dpos, &num_rows);
4842 0 0         if (rc == 0) {
4843 0 0         if (decompressed) { Safefree(decompressed); *errmsg = safe_strdup("truncated compressed block"); return -1; }
4844 0           return 0;
4845             }
4846 0 0         if (rc < 0) { if (decompressed) Safefree(decompressed); *errmsg = safe_strdup("malformed num_rows"); return -1; }
    0          
4847              
4848             /* Empty data block — skip or handle column names/types */
4849 0 0         if (num_rows == 0) {
4850             /* INSERT two-phase: server sent sample block with column structure */
4851 0 0         if (self->native_state == NATIVE_WAIT_INSERT_META
4852 0 0         && (self->insert_data || self->insert_av) && num_cols > 0) {
    0          
    0          
4853             const char **cnames;
4854             size_t *cname_lens;
4855             const char **ctypes_str;
4856             size_t *ctype_lens;
4857             col_type_t **ctypes;
4858             char *data_pkt;
4859             size_t data_pkt_len;
4860             uint64_t c;
4861              
4862 0 0         Newxz(cnames, num_cols, const char *);
4863 0 0         Newxz(cname_lens, num_cols, size_t);
4864 0 0         Newxz(ctypes_str, num_cols, const char *);
4865 0 0         Newxz(ctype_lens, num_cols, size_t);
4866 0 0         Newxz(ctypes, num_cols, col_type_t *);
4867              
4868 0 0         for (c = 0; c < num_cols; c++) {
4869 0           ctypes[c] = NULL;
4870 0           rc = read_native_string_ref(dbuf, dlen, &dpos,
4871 0           &cnames[c], &cname_lens[c]);
4872 0 0         if (rc <= 0) {
4873 0           int was_compressed = decompressed != NULL;
4874 0 0         for (c = 0; c < num_cols; c++) if (ctypes[c]) free_col_type(ctypes[c]);
    0          
4875 0           Safefree(cnames); Safefree(cname_lens);
4876 0           Safefree(ctypes_str); Safefree(ctype_lens);
4877 0           Safefree(ctypes);
4878 0 0         if (decompressed) Safefree(decompressed);
4879 0 0         if (rc < 0 || was_compressed) { *errmsg = safe_strdup("malformed cname"); return -1; }
    0          
4880 0           return 0;
4881             }
4882 0           rc = read_native_string_ref(dbuf, dlen, &dpos,
4883 0           &ctypes_str[c], &ctype_lens[c]);
4884 0 0         if (rc <= 0) {
4885 0           int was_compressed = decompressed != NULL;
4886 0 0         for (c = 0; c < num_cols; c++) if (ctypes[c]) free_col_type(ctypes[c]);
    0          
4887 0           Safefree(cnames); Safefree(cname_lens);
4888 0           Safefree(ctypes_str); Safefree(ctype_lens);
4889 0           Safefree(ctypes);
4890 0 0         if (decompressed) Safefree(decompressed);
4891 0 0         if (rc < 0 || was_compressed) { *errmsg = safe_strdup("malformed ctype"); return -1; }
    0          
4892 0           return 0;
4893             }
4894 0           ctypes[c] = parse_col_type(ctypes_str[c], ctype_lens[c]);
4895              
4896             /* custom serialization flag (revision >= 54446) */
4897 0 0         if (dpos >= dlen) {
4898 0           int was_compressed = decompressed != NULL;
4899 0 0         for (c = 0; c < num_cols; c++) if (ctypes[c]) free_col_type(ctypes[c]);
    0          
4900 0           Safefree(cnames); Safefree(cname_lens);
4901 0           Safefree(ctypes_str); Safefree(ctype_lens);
4902 0           Safefree(ctypes);
4903 0 0         if (decompressed) Safefree(decompressed);
4904 0 0         if (was_compressed) { *errmsg = safe_strdup("truncated custom_ser"); return -1; }
4905 0           return 0;
4906             }
4907 0 0         if ((uint8_t)dbuf[dpos]) {
4908 0 0         for (c = 0; c < num_cols; c++) if (ctypes[c]) free_col_type(ctypes[c]);
    0          
4909 0           Safefree(cnames); Safefree(cname_lens);
4910 0           Safefree(ctypes_str); Safefree(ctype_lens);
4911 0           Safefree(ctypes);
4912 0 0         if (decompressed) Safefree(decompressed);
4913 0           *errmsg = safe_strdup("custom serialization not supported");
4914 0           return -1;
4915             }
4916 0           dpos++;
4917             }
4918              
4919             /* Build binary data block from stored data */
4920 0 0         if (self->insert_av) {
4921 0           data_pkt = build_native_insert_data_from_av(aTHX_ self,
4922 0           (AV *)SvRV(self->insert_av),
4923             cnames, cname_lens, ctypes_str, ctype_lens,
4924             ctypes, (int)num_cols, &data_pkt_len);
4925             } else {
4926 0           data_pkt = build_native_insert_data(self,
4927 0           self->insert_data, self->insert_data_len,
4928             cnames, cname_lens, ctypes_str, ctype_lens,
4929             ctypes, (int)num_cols, &data_pkt_len);
4930             }
4931              
4932 0 0         for (c = 0; c < num_cols; c++)
4933 0           free_col_type(ctypes[c]);
4934 0           Safefree(cnames); Safefree(cname_lens);
4935 0           Safefree(ctypes_str); Safefree(ctype_lens);
4936 0           Safefree(ctypes);
4937              
4938             {
4939             /* Check encode-failure sentinel before freeing insert data */
4940 0 0         int encode_failed = (!data_pkt && data_pkt_len == (size_t)-1);
    0          
4941              
4942             /* Free stored INSERT data */
4943 0 0         if (self->insert_data) {
4944 0           Safefree(self->insert_data);
4945 0           self->insert_data = NULL;
4946 0           self->insert_data_len = 0;
4947             }
4948 0 0         if (self->insert_av) {
4949 0           SvREFCNT_dec(self->insert_av);
4950 0           self->insert_av = NULL;
4951             }
4952              
4953 0 0         if (decompressed) Safefree(decompressed);
4954 0           else pos = dpos;
4955 0           recv_consume(self, pos);
4956              
4957 0 0         if (!data_pkt) {
4958             /* Send empty Data block to complete the INSERT protocol */
4959             native_buf_t fallback;
4960 0           nbuf_init(&fallback);
4961 0           nbuf_empty_data_block(&fallback, self->compress);
4962 0           data_pkt = fallback.data;
4963 0           data_pkt_len = fallback.len;
4964 0 0         if (encode_failed)
4965 0           self->insert_err = safe_strdup(
4966             "native INSERT encoding failed (unsupported type)");
4967             }
4968             }
4969              
4970             /* Send the data block — write to send_buf and start writing */
4971 0           self->native_state = NATIVE_WAIT_RESULT;
4972 0           send_replace(self, data_pkt, data_pkt_len);
4973 0 0         if (try_write(self)) return -2;
4974 0           return 1;
4975             }
4976              
4977             /* INSERT two-phase with 0-column sample block: free data,
4978             * send empty Data block, transition to WAIT_RESULT */
4979 0 0         if (self->native_state == NATIVE_WAIT_INSERT_META
4980 0 0         && (self->insert_data || self->insert_av) && num_cols == 0) {
    0          
    0          
4981             native_buf_t fallback;
4982              
4983 0 0         if (self->insert_data) {
4984 0           Safefree(self->insert_data);
4985 0           self->insert_data = NULL;
4986 0           self->insert_data_len = 0;
4987             }
4988 0 0         if (self->insert_av) {
4989 0           SvREFCNT_dec(self->insert_av);
4990 0           self->insert_av = NULL;
4991             }
4992              
4993 0 0         if (decompressed) Safefree(decompressed);
4994 0           else pos = dpos;
4995 0           recv_consume(self, pos);
4996              
4997 0           nbuf_init(&fallback);
4998 0           nbuf_empty_data_block(&fallback, self->compress);
4999 0           self->native_state = NATIVE_WAIT_RESULT;
5000 0           send_replace(self, fallback.data, fallback.len);
5001 0           self->insert_err = safe_strdup(
5002             "INSERT failed: server sent 0-column sample block");
5003 0 0         if (try_write(self)) return -2;
5004 0           return 1;
5005             }
5006              
5007             /* Normal empty block — skip column names/types/custom_serialization */
5008             {
5009             uint64_t c;
5010 0 0         for (c = 0; c < num_cols; c++) {
5011 0 0         if (skip_native_string(dbuf, dlen, &dpos) <= 0) {
5012 0 0         if (decompressed) Safefree(decompressed);
5013 0           return 0;
5014             }
5015 0 0         if (skip_native_string(dbuf, dlen, &dpos) <= 0) {
5016 0 0         if (decompressed) Safefree(decompressed);
5017 0           return 0;
5018             }
5019             /* custom serialization flag (revision >= 54446) */
5020 0 0         if (dpos >= dlen) {
5021 0 0         if (decompressed) Safefree(decompressed);
5022 0           return 0;
5023             }
5024 0 0         if ((uint8_t)dbuf[dpos]) {
5025 0 0         if (decompressed) Safefree(decompressed);
5026 0           *errmsg = safe_strdup("custom serialization not supported");
5027 0           return -1;
5028             }
5029 0           dpos++;
5030             }
5031             }
5032 0 0         if (decompressed) Safefree(decompressed);
5033 0           else pos = dpos; /* uncompressed: advance pos to match dpos */
5034 0           recv_consume(self, pos);
5035 0           return 1;
5036             }
5037              
5038             /* Decode columns and convert to rows */
5039             {
5040 0           SV ***columns = NULL;
5041 0           col_type_t **col_types = NULL;
5042 0           const char **cnames = NULL;
5043 0           size_t *cname_lens = NULL;
5044             uint64_t c, r;
5045 0           int named = (self->decode_flags & DECODE_NAMED_ROWS) ? 1 : 0;
5046              
5047 0 0         Newxz(columns, num_cols, SV**);
5048 0 0         Newxz(col_types, num_cols, col_type_t*);
5049 0 0         if (named) {
5050 0 0         Newxz(cnames, num_cols, const char *);
5051 0 0         Newx(cname_lens, num_cols, size_t);
5052             }
5053              
5054 0 0         for (c = 0; c < num_cols; c++) {
5055             const char *cname, *ctype;
5056             size_t cname_len, ctype_len;
5057              
5058 0           columns[c] = NULL;
5059 0           col_types[c] = NULL;
5060              
5061 0           rc = read_native_string_ref(dbuf, dlen, &dpos, &cname, &cname_len);
5062 0 0         if (rc == 0) {
5063 0 0         if (decompressed) { *errmsg = safe_strdup("truncated cname"); goto data_error; }
5064 0           goto data_need_more;
5065             }
5066 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed cname"); goto data_error; }
5067              
5068 0 0         if (named) {
5069 0           cnames[c] = cname;
5070 0           cname_lens[c] = cname_len;
5071             }
5072              
5073             /* Save column names/types on first data block of each query */
5074 0 0         if (c == 0 && !self->native_rows && self->native_col_names) {
    0          
    0          
5075 0           SvREFCNT_dec((SV*)self->native_col_names);
5076 0           self->native_col_names = NULL;
5077 0 0         if (self->native_col_types) {
5078 0           SvREFCNT_dec((SV*)self->native_col_types);
5079 0           self->native_col_types = NULL;
5080             }
5081             }
5082 0 0         if (!self->native_col_names) {
5083 0 0         if (c == 0) {
5084 0           self->native_col_names = newAV();
5085 0           self->native_col_types = newAV();
5086             }
5087             }
5088 0 0         if (self->native_col_names && av_len(self->native_col_names) + 1 < (SSize_t)num_cols)
    0          
5089 0           av_push(self->native_col_names, newSVpvn(cname, cname_len));
5090              
5091 0           rc = read_native_string_ref(dbuf, dlen, &dpos, &ctype, &ctype_len);
5092 0 0         if (rc == 0) {
5093 0 0         if (decompressed) { *errmsg = safe_strdup("truncated ctype"); goto data_error; }
5094 0           goto data_need_more;
5095             }
5096 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed ctype"); goto data_error; }
5097              
5098 0           col_types[c] = parse_col_type(ctype, ctype_len);
5099 0 0         if (self->native_col_types && av_len(self->native_col_types) + 1 < (SSize_t)num_cols)
    0          
5100 0           av_push(self->native_col_types, newSVpvn(ctype, ctype_len));
5101              
5102             /* custom serialization flag (revision >= 54446) */
5103 0 0         if (dpos >= dlen) {
5104 0 0         if (decompressed) { *errmsg = safe_strdup("truncated custom_ser"); goto data_error; }
5105 0           goto data_need_more;
5106             }
5107 0 0         if ((uint8_t)dbuf[dpos]) {
5108 0           *errmsg = safe_strdup("custom serialization not supported");
5109 0           goto data_error;
5110             }
5111 0           dpos++;
5112              
5113             /* Allocate LC dict state on first column of first block */
5114 0 0         if (c == 0 && !self->lc_dicts && num_cols > 0) {
    0          
    0          
5115 0 0         Newxz(self->lc_dicts, num_cols, SV**);
5116 0 0         Newxz(self->lc_dict_sizes, num_cols, uint64_t);
5117 0           self->lc_num_cols = (int)num_cols;
5118             }
5119              
5120             {
5121 0           int col_err = 0;
5122 0           columns[c] = decode_column_ex(dbuf, dlen, &dpos, num_rows, col_types[c], &col_err, self->decode_flags, self, (int)c);
5123 0 0         if (!columns[c]) {
5124 0 0         if (col_err || decompressed) {
    0          
5125 0           *errmsg = safe_strdup("decode_column failed");
5126 0           goto data_error;
5127             }
5128 0           goto data_need_more;
5129             }
5130             }
5131             }
5132              
5133             /* Convert column-oriented to row-oriented */
5134             {
5135             AV **target;
5136 0 0         if (ptype == SERVER_TOTALS) {
5137 0 0         if (!self->native_totals) self->native_totals = newAV();
5138 0           target = &self->native_totals;
5139 0 0         } else if (ptype == SERVER_EXTREMES) {
5140 0 0         if (!self->native_extremes) self->native_extremes = newAV();
5141 0           target = &self->native_extremes;
5142             } else {
5143 0 0         if (!self->native_rows) self->native_rows = newAV();
5144 0           target = &self->native_rows;
5145             }
5146              
5147 0 0         if (named) {
5148 0 0         for (r = 0; r < num_rows; r++) {
5149 0           HV *hv = newHV();
5150 0 0         for (c = 0; c < num_cols; c++) {
5151 0 0         if (!hv_store(hv, cnames[c], cname_lens[c], columns[c][r], 0))
5152 0           SvREFCNT_dec(columns[c][r]);
5153             }
5154 0           av_push(*target, newRV_noinc((SV*)hv));
5155             }
5156             } else {
5157 0 0         for (r = 0; r < num_rows; r++) {
5158 0           AV *row = newAV();
5159 0 0         if (num_cols > 0)
5160 0           av_extend(row, num_cols - 1);
5161 0 0         for (c = 0; c < num_cols; c++) {
5162 0           av_push(row, columns[c][r]);
5163             }
5164 0           av_push(*target, newRV_noinc((SV*)row));
5165             }
5166             }
5167             }
5168              
5169             /* Fire on_data streaming callback if set (only for DATA, not TOTALS/EXTREMES) */
5170             {
5171 0 0         SV *on_data = (ptype == SERVER_DATA) ? peek_cb_on_data(self) : NULL;
5172 0 0         if (on_data && self->native_rows) {
    0          
5173 0           self->callback_depth++;
5174             {
5175 0           dSP;
5176 0           ENTER; SAVETMPS;
5177 0 0         PUSHMARK(SP);
5178 0           PUSHs(sv_2mortal(newRV_inc((SV*)self->native_rows)));
5179 0           PUTBACK;
5180 0           call_sv(on_data, G_DISCARD | G_EVAL);
5181 0 0         if (SvTRUE(ERRSV))
    0          
5182 0 0         warn("EV::ClickHouse: exception in on_data handler: %s",
5183             SvPV_nolen(ERRSV));
5184 0 0         FREETMPS; LEAVE;
5185             }
5186 0           self->callback_depth--;
5187             /* Clear accumulated rows for next block */
5188 0           SvREFCNT_dec((SV*)self->native_rows);
5189 0           self->native_rows = NULL;
5190 0 0         if (check_destroyed(self)) {
5191 0 0         if (cnames) Safefree(cnames);
5192 0 0         if (cname_lens) Safefree(cname_lens);
5193 0 0         for (c = 0; c < num_cols; c++) {
5194 0           Safefree(columns[c]);
5195 0           free_col_type(col_types[c]);
5196             }
5197 0           Safefree(columns); Safefree(col_types);
5198 0 0         if (decompressed) Safefree(decompressed);
5199 0           return -2;
5200             }
5201             }
5202             }
5203              
5204             /* Cleanup column arrays (SVs moved to rows, don't dec refcnt) */
5205 0 0         for (c = 0; c < num_cols; c++) {
5206 0           Safefree(columns[c]);
5207 0           free_col_type(col_types[c]);
5208             }
5209 0           Safefree(columns);
5210 0           Safefree(col_types);
5211 0 0         if (cnames) Safefree(cnames);
5212 0 0         if (cname_lens) Safefree(cname_lens);
5213 0 0         if (decompressed) Safefree(decompressed);
5214 0           else pos = dpos; /* uncompressed: advance pos to match dpos */
5215              
5216             /* Consume from recv_buf */
5217 0           recv_consume(self, pos);
5218 0           return 1;
5219              
5220 0           data_error:
5221 0           data_need_more:
5222             /* Cleanup partial decode */
5223 0 0         for (c = 0; c < num_cols; c++) {
5224 0 0         if (columns[c]) {
5225             uint64_t j;
5226 0 0         for (j = 0; j < num_rows; j++) {
5227 0 0         if (columns[c][j]) SvREFCNT_dec(columns[c][j]);
5228             }
5229 0           Safefree(columns[c]);
5230             }
5231 0 0         if (col_types[c]) free_col_type(col_types[c]);
5232             }
5233 0           Safefree(columns);
5234 0           Safefree(col_types);
5235 0 0         if (cnames) Safefree(cnames);
5236 0 0         if (cname_lens) Safefree(cname_lens);
5237 0 0         if (decompressed) Safefree(decompressed);
5238 0 0         if (*errmsg) {
5239             /* data_error: flush recv_buf — data is malformed, cannot resume */
5240 0           self->recv_len = 0;
5241 0           return -1;
5242             }
5243 0           return 0;
5244             }
5245             }
5246              
5247 0           case SERVER_EXCEPTION: {
5248             /* code: Int32, name: String, message: String,
5249             * stack_trace: String, has_nested: UInt8 */
5250             int32_t code;
5251             const char *name, *msg, *stack;
5252             size_t name_len, msg_len, stack_len;
5253             uint8_t has_nested;
5254             char *err;
5255              
5256             /* We just read the top-level exception */
5257 0           rc = read_i32(buf, len, &pos, &code);
5258 0 0         if (rc == 0) return 0;
5259 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed exception code"); return -1; }
5260              
5261 0           rc = read_native_string_ref(buf, len, &pos, &name, &name_len);
5262 0 0         if (rc == 0) return 0;
5263 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed exception name"); return -1; }
5264              
5265 0           rc = read_native_string_ref(buf, len, &pos, &msg, &msg_len);
5266 0 0         if (rc == 0) return 0;
5267 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed exception message"); return -1; }
5268              
5269 0           rc = read_native_string_ref(buf, len, &pos, &stack, &stack_len);
5270 0 0         if (rc == 0) return 0;
5271 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed exception stack"); return -1; }
5272              
5273 0           rc = read_u8(buf, len, &pos, &has_nested);
5274 0 0         if (rc == 0) return 0;
5275 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed exception has_nested"); return -1; }
5276              
5277             /* Skip nested exceptions — keep the top-level code, not the innermost. */
5278 0 0         while (has_nested) {
5279             int32_t nested_code;
5280 0           rc = read_i32(buf, len, &pos, &nested_code);
5281 0 0         if (rc == 0) return 0;
5282 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed nested exception"); return -1; }
5283              
5284 0           rc = skip_native_string(buf, len, &pos);
5285 0 0         if (rc == 0) return 0;
5286 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed nested exception"); return -1; }
5287              
5288 0           rc = skip_native_string(buf, len, &pos);
5289 0 0         if (rc == 0) return 0;
5290 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed nested exception"); return -1; }
5291              
5292 0           rc = skip_native_string(buf, len, &pos);
5293 0 0         if (rc == 0) return 0;
5294 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed nested exception"); return -1; }
5295              
5296 0           rc = read_u8(buf, len, &pos, &has_nested);
5297 0 0         if (rc == 0) return 0;
5298 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed nested exception"); return -1; }
5299             }
5300              
5301 0           self->last_error_code = code;
5302              
5303 0           Newx(err, msg_len + name_len + 64, char);
5304 0           snprintf(err, msg_len + name_len + 64, "Code: %d. %.*s: %.*s",
5305             (int)code, (int)name_len, name, (int)msg_len, msg);
5306              
5307 0           recv_consume(self, pos);
5308              
5309 0           *errmsg = err;
5310 0           return -1;
5311             }
5312              
5313 0           case SERVER_PROGRESS: {
5314             /* rows: VarUInt, bytes: VarUInt, total_rows: VarUInt,
5315             * written_rows: VarUInt (>= 54420), written_bytes: VarUInt (>= 54420)
5316             */
5317 0           uint64_t p_rows, p_bytes, p_total, p_wrows = 0, p_wbytes = 0;
5318 0           rc = read_varuint(buf, len, &pos, &p_rows);
5319 0 0         if (rc == 0) return 0;
5320 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed progress packet"); return -1; }
5321              
5322 0           rc = read_varuint(buf, len, &pos, &p_bytes);
5323 0 0         if (rc == 0) return 0;
5324 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed progress packet"); return -1; }
5325              
5326 0           rc = read_varuint(buf, len, &pos, &p_total);
5327 0 0         if (rc == 0) return 0;
5328 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed progress packet"); return -1; }
5329              
5330             if (CH_CLIENT_REVISION >= DBMS_MIN_REVISION_WITH_PROGRESS_WRITES) {
5331 0           rc = read_varuint(buf, len, &pos, &p_wrows);
5332 0 0         if (rc == 0) return 0;
5333 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed progress packet"); return -1; }
5334              
5335 0           rc = read_varuint(buf, len, &pos, &p_wbytes);
5336 0 0         if (rc == 0) return 0;
5337 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed progress packet"); return -1; }
5338             }
5339              
5340 0           recv_consume(self, pos);
5341              
5342 0 0         if (NULL != self->on_progress) {
5343 0           dSP;
5344 0           self->callback_depth++;
5345 0           ENTER; SAVETMPS;
5346 0 0         PUSHMARK(SP);
5347 0 0         EXTEND(SP, 5);
5348 0           PUSHs(sv_2mortal(newSVuv(p_rows)));
5349 0           PUSHs(sv_2mortal(newSVuv(p_bytes)));
5350 0           PUSHs(sv_2mortal(newSVuv(p_total)));
5351 0           PUSHs(sv_2mortal(newSVuv(p_wrows)));
5352 0           PUSHs(sv_2mortal(newSVuv(p_wbytes)));
5353 0           PUTBACK;
5354 0           call_sv(self->on_progress, G_DISCARD | G_EVAL);
5355 0 0         if (SvTRUE(ERRSV))
    0          
5356 0 0         warn("EV::ClickHouse: exception in progress handler: %s",
5357             SvPV_nolen(ERRSV));
5358 0 0         FREETMPS; LEAVE;
5359 0           self->callback_depth--;
5360 0 0         if (check_destroyed(self)) return -2; /* destroyed */
5361             }
5362              
5363 0           return 1;
5364             }
5365              
5366 0           case SERVER_PROFILE_INFO: {
5367             uint64_t pi_rows, pi_blocks, pi_bytes, pi_applied_limit;
5368             uint64_t pi_rows_before_limit, pi_calc_rows_before_limit;
5369              
5370 0           rc = read_varuint(buf, len, &pos, &pi_rows);
5371 0 0         if (rc == 0) return 0;
5372 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed profile_info packet"); return -1; }
5373 0           rc = read_varuint(buf, len, &pos, &pi_blocks);
5374 0 0         if (rc == 0) return 0;
5375 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed profile_info packet"); return -1; }
5376 0           rc = read_varuint(buf, len, &pos, &pi_bytes);
5377 0 0         if (rc == 0) return 0;
5378 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed profile_info packet"); return -1; }
5379 0           rc = read_varuint(buf, len, &pos, &pi_applied_limit);
5380 0 0         if (rc == 0) return 0;
5381 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed profile_info packet"); return -1; }
5382 0           rc = read_varuint(buf, len, &pos, &pi_rows_before_limit);
5383 0 0         if (rc == 0) return 0;
5384 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed profile_info packet"); return -1; }
5385 0           rc = read_varuint(buf, len, &pos, &pi_calc_rows_before_limit);
5386 0 0         if (rc == 0) return 0;
5387 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed profile_info packet"); return -1; }
5388              
5389 0           self->profile_rows = pi_rows;
5390 0           self->profile_bytes = pi_bytes;
5391 0           self->profile_rows_before_limit = pi_rows_before_limit;
5392 0           recv_consume(self, pos);
5393 0           return 1;
5394             }
5395              
5396 0           case SERVER_TABLE_COLUMNS: {
5397             /* Format: string(table_name) + string(column_description) */
5398 0           rc = skip_native_string(buf, len, &pos);
5399 0 0         if (rc == 0) return 0;
5400 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed table_columns packet"); return -1; }
5401 0           rc = skip_native_string(buf, len, &pos);
5402 0 0         if (rc == 0) return 0;
5403 0 0         if (rc < 0) { *errmsg = safe_strdup("malformed table_columns packet"); return -1; }
5404 0           recv_consume(self, pos);
5405 0           return 1;
5406             }
5407              
5408 0           case SERVER_LOG: {
5409 0           rc = parse_and_discard_block(self, buf, len, &pos, "server log", 0, errmsg);
5410 0 0         if (rc <= 0) return rc;
5411 0           recv_consume(self, pos);
5412 0           return 1;
5413             }
5414              
5415 0           case SERVER_PROFILE_EVENTS: {
5416 0           rc = parse_and_discard_block(self, buf, len, &pos, "profile_events", 1, errmsg);
5417 0 0         if (rc <= 0) return rc;
5418              
5419 0           recv_consume(self, pos);
5420 0           return 1;
5421             }
5422              
5423 0           case SERVER_PONG:
5424 0           recv_consume(self, pos);
5425 0           return 3;
5426              
5427 0           case SERVER_END_OF_STREAM:
5428 0           recv_consume(self, pos);
5429 0           return 2;
5430              
5431 0           default: {
5432             /* Unknown packet type */
5433             char err[64];
5434 0           snprintf(err, sizeof(err), "unknown server packet type: %llu",
5435             (unsigned long long)ptype);
5436 0           *errmsg = safe_strdup(err);
5437 0           self->recv_len = 0;
5438 0           return -1;
5439             }
5440             }
5441             }
5442              
5443             /*
5444             * Process native protocol responses from recv_buf.
5445             * Called from on_readable when protocol == PROTO_NATIVE.
5446             */
5447 0           static void process_native_response(ev_clickhouse_t *self) {
5448 0 0         while (self->recv_len > 0 && self->magic == EV_CH_MAGIC) {
    0          
5449 0           char *errmsg = NULL;
5450             int rc;
5451 0           rc = parse_native_packet(self, &errmsg);
5452              
5453 0 0         if (rc == 0) {
5454             /* need more data */
5455 0           return;
5456             }
5457              
5458 0 0         if (rc == -2) {
5459             /* object destroyed inside callback */
5460 0           return;
5461             }
5462              
5463 0 0         if (rc == 4) {
5464             /* ServerHello received — send addendum (revision >= 54458) */
5465 0 0         if (self->native_state == NATIVE_WAIT_HELLO) {
5466             /* Addendum: quota_key (only if server supports it) */
5467 0 0         if (self->server_revision >= DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM) {
5468             native_buf_t ab;
5469 0           nbuf_init(&ab);
5470 0           nbuf_cstring(&ab, ""); /* quota_key */
5471 0           send_replace(self, ab.data, ab.len);
5472 0 0         if (try_write(self)) return;
5473             }
5474 0           self->native_state = NATIVE_IDLE;
5475 0           self->connected = 1;
5476              
5477             /* fire on_connect */
5478 0           if (NULL != self->on_connect &&
5479 0           fire_zero_arg_cb(self, self->on_connect, "connect")) return;
5480             /* start pipeline if queries were queued during connect */
5481 0 0         if (!ngx_queue_empty(&self->send_queue))
5482 0           pipeline_advance(self);
5483             }
5484             /* pipeline_advance -> try_write may free self; no data
5485             * in recv_buf for the just-dispatched request yet */
5486 0           return;
5487             }
5488              
5489 0 0         if (rc == -1) {
5490             /* error */
5491 0 0         if (self->native_state == NATIVE_WAIT_HELLO) {
5492             /* Hello failed — connection-level error */
5493 0           emit_error(self, errmsg);
5494 0           Safefree(errmsg);
5495 0 0         if (check_destroyed(self)) return;
5496 0 0         if (cancel_pending(self, "connection failed")) return;
5497 0           cleanup_connection(self);
5498 0           return;
5499             }
5500              
5501             /* Stop query timeout timer */
5502 0 0         if (self->timing) {
5503 0           ev_timer_stop(self->loop, &self->timer);
5504 0           self->timing = 0;
5505             }
5506              
5507             /* Query error — deliver to callback */
5508 0 0         if (self->native_rows) {
5509 0           SvREFCNT_dec((SV*)self->native_rows);
5510 0           self->native_rows = NULL;
5511             }
5512 0 0         if (self->insert_data) {
5513 0           Safefree(self->insert_data);
5514 0           self->insert_data = NULL;
5515 0           self->insert_data_len = 0;
5516             }
5517 0 0         if (self->insert_av) {
5518 0           SvREFCNT_dec(self->insert_av);
5519 0           self->insert_av = NULL;
5520             }
5521 0 0         if (self->insert_err) {
5522 0           Safefree(self->insert_err);
5523 0           self->insert_err = NULL;
5524             }
5525 0           self->native_state = NATIVE_IDLE;
5526 0           self->recv_len = 0; /* flush malformed data */
5527 0 0         if (self->send_count > 0) self->send_count--;
5528 0           lc_free_dicts(self);
5529 0           int destroyed = deliver_error(self, errmsg);
5530 0           Safefree(errmsg);
5531 0 0         if (destroyed) return;
5532              
5533             /* advance pipeline — may free self via try_write error */
5534 0           pipeline_advance(self);
5535 0           return;
5536             }
5537              
5538 0 0         if (rc == 2) {
5539             /* EndOfStream — deliver accumulated rows or deferred error */
5540 0 0         if (self->timing) {
5541 0           ev_timer_stop(self->loop, &self->timer);
5542 0           self->timing = 0;
5543             }
5544 0           self->native_state = NATIVE_IDLE;
5545 0 0         if (self->send_count > 0) self->send_count--;
5546 0           lc_free_dicts(self);
5547              
5548 0 0         if (self->insert_err) {
5549 0           char *err = self->insert_err;
5550 0           self->insert_err = NULL;
5551 0 0         if (self->native_rows) {
5552 0           SvREFCNT_dec((SV*)self->native_rows);
5553 0           self->native_rows = NULL;
5554             }
5555 0           int destroyed = deliver_error(self, err);
5556 0           Safefree(err);
5557 0 0         if (destroyed) return;
5558             } else {
5559 0           AV *rows = self->native_rows;
5560 0           self->native_rows = NULL;
5561 0 0         if (deliver_rows(self, rows)) return;
5562             }
5563              
5564             /* advance pipeline — may free self via try_write error */
5565 0           pipeline_advance(self);
5566 0           return;
5567             }
5568              
5569 0 0         if (rc == 3) {
5570             /* Pong — ack a keepalive ping, or deliver to user's ping() cb */
5571 0           self->native_state = NATIVE_IDLE;
5572 0 0         if (self->ka_in_flight > 0) {
5573             /* Keepalive ack: not tied to send_count or any user cb */
5574 0           self->ka_in_flight--;
5575 0           continue;
5576             }
5577 0 0         if (self->timing) {
5578 0           ev_timer_stop(self->loop, &self->timer);
5579 0           self->timing = 0;
5580             }
5581 0 0         if (self->send_count > 0) self->send_count--;
5582 0           AV *rows = newAV();
5583 0 0         if (deliver_rows(self, rows)) return;
5584 0           pipeline_advance(self);
5585 0           return;
5586             }
5587              
5588             /* rc == 1: Data/Progress/ProfileInfo — continue reading */
5589             }
5590             }
5591              
5592             /* --- Async TCP connect --- */
5593              
5594 0           static void start_connect(ev_clickhouse_t *self) {
5595 0           struct addrinfo hints, *res = NULL;
5596             int fd, ret;
5597             char port_str[16];
5598              
5599 0           emit_trace(self, "connect %s:%u (%s)",
5600             self->host, self->port,
5601 0 0         self->protocol == PROTO_NATIVE ? "native" : "http");
5602 0           snprintf(port_str, sizeof(port_str), "%u", self->port);
5603              
5604 0           Zero(&hints, 1, struct addrinfo);
5605 0           hints.ai_family = AF_UNSPEC;
5606 0           hints.ai_socktype = SOCK_STREAM;
5607              
5608 0           ret = getaddrinfo(self->host, port_str, &hints, &res);
5609 0 0         if (ret != 0) {
5610             char errbuf[256];
5611 0           snprintf(errbuf, sizeof(errbuf), "getaddrinfo: %s", gai_strerror(ret));
5612 0           fail_connection(self, errbuf);
5613 0           return;
5614             }
5615              
5616 0           fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
5617 0 0         if (fd < 0) {
5618 0           freeaddrinfo(res);
5619 0           fail_connection(self, "socket() failed");
5620 0           return;
5621             }
5622              
5623             /* non-blocking */
5624             {
5625 0           int fl = fcntl(fd, F_GETFL);
5626 0 0         if (fl < 0 || fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0) {
    0          
5627 0           freeaddrinfo(res);
5628 0           close(fd);
5629 0           fail_connection(self, "fcntl O_NONBLOCK failed");
5630 0           return;
5631             }
5632             }
5633              
5634             /* TCP_NODELAY */
5635             {
5636 0           int one = 1;
5637 0           setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
5638             }
5639              
5640 0           self->fd = fd;
5641 0           self->connecting = 1;
5642              
5643 0           ret = connect(fd, res->ai_addr, res->ai_addrlen);
5644 0           freeaddrinfo(res);
5645              
5646 0 0         if (ret == 0) {
5647             /* connected immediately — connected=1 is deferred for native
5648             * (until ServerHello) and TLS (until handshake completes) */
5649 0           self->connecting = 0;
5650 0 0         if (self->protocol != PROTO_NATIVE && !self->tls_enabled)
    0          
5651 0           self->connected = 1;
5652 0           ev_io_init(&self->rio, io_cb, self->fd, EV_READ);
5653 0           self->rio.data = (void *)self;
5654 0           ev_io_init(&self->wio, io_cb, self->fd, EV_WRITE);
5655 0           self->wio.data = (void *)self;
5656 0           on_connect_done(self);
5657 0           return;
5658             }
5659              
5660 0 0         if (errno != EINPROGRESS) {
5661             char errbuf[256];
5662 0           snprintf(errbuf, sizeof(errbuf), "connect: %s", strerror(errno));
5663 0           close(fd);
5664 0           self->fd = -1;
5665 0           self->connecting = 0;
5666 0           fail_connection(self, errbuf);
5667 0           return;
5668             }
5669              
5670             /* in progress — wait for writability */
5671 0           ev_io_init(&self->rio, io_cb, self->fd, EV_READ);
5672 0           self->rio.data = (void *)self;
5673 0           ev_io_init(&self->wio, io_cb, self->fd, EV_WRITE);
5674 0           self->wio.data = (void *)self;
5675              
5676 0           start_writing(self);
5677              
5678 0 0         if (self->connect_timeout > 0) {
5679 0           ev_timer_set(&self->timer, (ev_tstamp)self->connect_timeout, 0.0);
5680 0           ev_timer_start(self->loop, &self->timer);
5681 0           self->timing = 1;
5682             }
5683             }
5684              
5685 0           static void on_connect_done(ev_clickhouse_t *self) {
5686 0           self->connecting = 0;
5687 0           self->reconnect_attempts = 0;
5688              
5689 0           stop_writing(self);
5690 0 0         if (self->timing) {
5691 0           ev_timer_stop(self->loop, &self->timer);
5692 0           self->timing = 0;
5693             }
5694              
5695             #ifdef HAVE_OPENSSL
5696 0 0         if (self->tls_enabled) {
5697             int ret;
5698 0           self->ssl_ctx = SSL_CTX_new(TLS_client_method());
5699 0 0         if (!self->ssl_ctx) {
5700 0           fail_connection(self, "SSL_CTX_new failed");
5701 0           return;
5702             }
5703 0           SSL_CTX_set_default_verify_paths(self->ssl_ctx);
5704 0 0         if (self->tls_skip_verify)
5705 0           SSL_CTX_set_verify(self->ssl_ctx, SSL_VERIFY_NONE, NULL);
5706             else
5707 0           SSL_CTX_set_verify(self->ssl_ctx, SSL_VERIFY_PEER, NULL);
5708 0 0         if (self->tls_ca_file) {
5709 0 0         if (SSL_CTX_load_verify_locations(self->ssl_ctx, self->tls_ca_file, NULL) != 1) {
5710 0           fail_connection(self, "SSL_CTX_load_verify_locations failed");
5711 0           return;
5712             }
5713             }
5714 0           self->ssl = SSL_new(self->ssl_ctx);
5715 0 0         if (!self->ssl) {
5716 0           fail_connection(self, "SSL_new failed");
5717 0           return;
5718             }
5719 0           SSL_set_fd(self->ssl, self->fd);
5720              
5721 0           int host_is_ip = is_ip_literal(self->host);
5722              
5723             /* SNI must not be sent for IP address literals (RFC 6066 s3) */
5724 0 0         if (!host_is_ip)
5725 0           SSL_set_tlsext_host_name(self->ssl, self->host);
5726              
5727             /* Verify server certificate matches hostname or IP */
5728 0 0         if (!self->tls_skip_verify) {
5729 0           X509_VERIFY_PARAM *param = SSL_get0_param(self->ssl);
5730 0           X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
5731 0 0         if (host_is_ip)
5732 0           X509_VERIFY_PARAM_set1_ip_asc(param, self->host);
5733             else
5734 0           X509_VERIFY_PARAM_set1_host(param, self->host, 0);
5735             }
5736              
5737 0           ret = SSL_connect(self->ssl);
5738 0 0         if (ret == 1) {
5739             /* handshake done immediately */
5740 0           goto handshake_done;
5741             } else {
5742 0           int err = SSL_get_error(self->ssl, ret);
5743 0 0         if (err == SSL_ERROR_WANT_READ) {
5744 0           start_reading(self);
5745 0 0         } else if (err == SSL_ERROR_WANT_WRITE) {
5746 0           start_writing(self);
5747             } else {
5748 0           fail_connection(self, "SSL_connect failed");
5749 0           return;
5750             }
5751             /* continue TLS handshake in io_cb */
5752 0           return;
5753             }
5754             }
5755 0           handshake_done:
5756             #endif
5757              
5758 0 0         if (self->protocol == PROTO_NATIVE) {
5759             /* Send ClientHello and wait for ServerHello */
5760             size_t hello_len;
5761 0           char *hello = build_native_hello(self, &hello_len);
5762 0           send_replace(self, hello, hello_len);
5763              
5764 0           self->native_state = NATIVE_WAIT_HELLO;
5765 0           start_writing(self);
5766 0           return;
5767             }
5768              
5769             /* HTTP protocol: connection is ready */
5770 0           self->connected = 1;
5771              
5772 0           if (NULL != self->on_connect &&
5773 0           fire_zero_arg_cb(self, self->on_connect, "connect")) return;
5774              
5775             /* start pipeline if queries were queued during connect */
5776 0 0         if (!ngx_queue_empty(&self->send_queue))
5777 0           pipeline_advance(self);
5778             }
5779              
5780             /* --- I/O callbacks --- */
5781              
5782             /* Returns 1 if self was freed (caller must not access self). */
5783 0           static int try_write(ev_clickhouse_t *self) {
5784 0 0         while (self->send_pos < self->send_len) {
5785 0           ssize_t n = ch_write(self, self->send_buf + self->send_pos,
5786 0           self->send_len - self->send_pos);
5787 0 0         if (n < 0) {
5788 0 0         if (errno == EAGAIN || errno == EWOULDBLOCK) {
    0          
5789 0           start_writing(self);
5790 0           return 0;
5791             }
5792             /* write error */
5793 0           emit_error(self, strerror(errno));
5794 0 0         if (check_destroyed(self)) return 1;
5795 0 0         if (cancel_pending(self, "write error")) return 1;
5796 0           cleanup_connection(self);
5797 0           return 0;
5798             }
5799 0 0         if (n == 0) {
5800 0           emit_error(self, "connection closed during write");
5801 0 0         if (check_destroyed(self)) return 1;
5802 0 0         if (cancel_pending(self, "connection closed")) return 1;
5803 0           cleanup_connection(self);
5804 0           return 0;
5805             }
5806 0           self->send_pos += n;
5807             }
5808              
5809             /* all sent */
5810 0           stop_writing(self);
5811 0           self->send_len = 0;
5812 0           self->send_pos = 0;
5813              
5814             /* start reading responses */
5815 0           start_reading(self);
5816              
5817             /* check if more to send */
5818 0 0         if (!ngx_queue_empty(&self->send_queue))
5819 0           return pipeline_advance(self);
5820 0           return 0;
5821             }
5822              
5823 0           static void on_readable(ev_clickhouse_t *self) {
5824             ssize_t n;
5825              
5826 0           ensure_recv_cap(self, self->recv_len + 4096);
5827 0           n = ch_read(self, self->recv_buf + self->recv_len,
5828 0           self->recv_cap - self->recv_len);
5829              
5830 0 0         if (n < 0) {
5831 0 0         if (errno == EAGAIN || errno == EWOULDBLOCK) return;
    0          
5832 0           emit_error(self, strerror(errno));
5833 0 0         if (check_destroyed(self)) return;
5834 0 0         if (cancel_pending(self, "read error")) return;
5835 0           cleanup_connection(self);
5836 0           return;
5837             }
5838              
5839 0 0         if (n == 0) {
5840             /* connection closed — fire on_error and drain pending if we
5841             * have an in-flight request or haven't finished handshake */
5842 0 0         int had_inflight = (self->send_count > 0 || !self->connected);
    0          
5843 0           int has_queued = !ngx_queue_empty(&self->send_queue);
5844              
5845 0 0         if (had_inflight) {
5846 0           emit_error(self, "connection closed by server");
5847 0 0         if (check_destroyed(self)) return;
5848             /* Only cancel in-flight cb_queue (irrecoverable).
5849             * Keep send_queue if auto_reconnect — those haven't been sent yet. */
5850 0 0         if (!self->auto_reconnect || !has_queued) {
    0          
5851 0 0         if (cancel_pending(self, "connection closed")) return;
5852             } else {
5853             /* Cancel only the in-flight cb_queue entries */
5854 0 0         while (!ngx_queue_empty(&self->cb_queue)) {
5855 0           SV *cb = pop_cb(self);
5856 0 0         if (cb == NULL) break;
5857 0           self->callback_depth++;
5858             {
5859 0           dSP;
5860 0           ENTER; SAVETMPS;
5861 0 0         PUSHMARK(SP);
5862 0           PUSHs(&PL_sv_undef);
5863 0           PUSHs(sv_2mortal(newSVpv("connection closed", 0)));
5864 0           PUTBACK;
5865 0           invoke_cb(cb);
5866 0 0         FREETMPS; LEAVE;
5867             }
5868 0           self->callback_depth--;
5869 0 0         if (check_destroyed(self)) return;
5870             }
5871 0           self->send_count = 0;
5872             }
5873             }
5874 0           cleanup_connection(self);
5875              
5876             /* Auto-reconnect if we have queued requests or flag is set */
5877 0 0         if (self->auto_reconnect && self->host && self->magic == EV_CH_MAGIC) {
    0          
    0          
5878 0           schedule_reconnect(self);
5879             }
5880 0           return;
5881             }
5882              
5883 0           self->recv_len += n;
5884              
5885 0 0         if (self->protocol == PROTO_HTTP) {
5886 0           process_http_response(self);
5887             } else {
5888 0           process_native_response(self);
5889             }
5890             }
5891              
5892 0           static void io_cb(EV_P_ ev_io *w, int revents) {
5893 0           ev_clickhouse_t *self = (ev_clickhouse_t *)w->data;
5894             (void)loop;
5895              
5896 0 0         if (self == NULL || self->magic != EV_CH_MAGIC) return;
    0          
5897              
5898 0 0         if (self->connecting) {
5899             /* check connect result */
5900 0           int err = 0;
5901 0           socklen_t errlen = sizeof(err);
5902              
5903 0 0         if (self->timing) {
5904 0           ev_timer_stop(self->loop, &self->timer);
5905 0           self->timing = 0;
5906             }
5907 0           stop_writing(self);
5908              
5909 0 0         if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) < 0)
5910 0           err = errno;
5911 0 0         if (err != 0) {
5912             char errbuf[256];
5913 0           snprintf(errbuf, sizeof(errbuf), "connect: %s", strerror(err));
5914 0           fail_connection(self, errbuf);
5915 0           return;
5916             }
5917              
5918 0           on_connect_done(self);
5919 0           return;
5920             }
5921              
5922             #ifdef HAVE_OPENSSL
5923 0 0         if (self->ssl && !self->connected && self->native_state != NATIVE_WAIT_HELLO
    0          
    0          
5924 0 0         && self->native_state != NATIVE_WAIT_RESULT
5925 0 0         && self->native_state != NATIVE_WAIT_INSERT_META) {
5926             /* TLS handshake in progress */
5927 0           int ret = SSL_connect(self->ssl);
5928 0 0         if (ret == 1) {
5929 0           stop_reading(self);
5930 0           stop_writing(self);
5931              
5932 0 0         if (self->protocol == PROTO_NATIVE) {
5933             /* Send ClientHello over TLS, then wait for ServerHello */
5934             size_t hello_len;
5935 0           char *hello = build_native_hello(self, &hello_len);
5936 0           send_replace(self, hello, hello_len);
5937 0           self->native_state = NATIVE_WAIT_HELLO;
5938 0           start_writing(self);
5939 0           return;
5940             }
5941              
5942             /* HTTP protocol: fire on_connect */
5943 0           self->connected = 1;
5944 0           if (NULL != self->on_connect &&
5945 0           fire_zero_arg_cb(self, self->on_connect, "connect")) return;
5946 0 0         if (!ngx_queue_empty(&self->send_queue))
5947 0           pipeline_advance(self);
5948 0           return;
5949             } else {
5950 0           int err = SSL_get_error(self->ssl, ret);
5951 0           stop_reading(self);
5952 0           stop_writing(self);
5953 0 0         if (err == SSL_ERROR_WANT_READ) {
5954 0           start_reading(self);
5955 0 0         } else if (err == SSL_ERROR_WANT_WRITE) {
5956 0           start_writing(self);
5957             } else {
5958 0           fail_connection(self, "SSL handshake failed");
5959             }
5960 0           return;
5961             }
5962             }
5963             #endif
5964              
5965 0 0         if (revents & EV_WRITE) {
5966 0 0         if (try_write(self)) return;
5967 0 0         if (self->fd < 0) return;
5968             }
5969              
5970 0 0         if (revents & EV_READ) {
5971 0           on_readable(self);
5972             }
5973             }
5974              
5975 0           static void timer_cb(EV_P_ ev_timer *w, int revents) {
5976 0           ev_clickhouse_t *self = (ev_clickhouse_t *)w->data;
5977             (void)loop;
5978             (void)revents;
5979              
5980 0 0         if (self == NULL || self->magic != EV_CH_MAGIC) return;
    0          
5981              
5982 0           self->timing = 0;
5983              
5984 0 0         if (self->connecting) {
5985 0           stop_writing(self);
5986 0           fail_connection(self, "connect timeout");
5987             } else {
5988             /* query timeout */
5989 0 0         if (self->native_rows) {
5990 0           SvREFCNT_dec((SV*)self->native_rows);
5991 0           self->native_rows = NULL;
5992             }
5993 0 0         if (self->native_col_names) {
5994 0           SvREFCNT_dec((SV*)self->native_col_names);
5995 0           self->native_col_names = NULL;
5996             }
5997 0 0         if (self->native_col_types) {
5998 0           SvREFCNT_dec((SV*)self->native_col_types);
5999 0           self->native_col_types = NULL;
6000             }
6001 0           lc_free_dicts(self);
6002 0 0         if (self->insert_data) {
6003 0           Safefree(self->insert_data);
6004 0           self->insert_data = NULL;
6005 0           self->insert_data_len = 0;
6006             }
6007 0 0         if (self->insert_av) {
6008 0           SvREFCNT_dec(self->insert_av);
6009 0           self->insert_av = NULL;
6010             }
6011 0 0         if (self->insert_err) {
6012 0           Safefree(self->insert_err);
6013 0           self->insert_err = NULL;
6014             }
6015 0           self->native_state = NATIVE_IDLE;
6016 0 0         if (self->send_count > 0) self->send_count--;
6017              
6018 0 0         if (deliver_error(self, "query timeout")) return;
6019              
6020             /* Must reconnect — server may still be processing */
6021 0 0         if (cancel_pending(self, "query timeout")) return;
6022 0           cleanup_connection(self);
6023 0 0         if (self->auto_reconnect && self->host)
    0          
6024 0           schedule_reconnect(self);
6025             }
6026             }
6027              
6028             /* --- Keepalive timer callback --- */
6029              
6030 0           static void ka_timer_cb(EV_P_ ev_timer *w, int revents) {
6031 0           ev_clickhouse_t *self = (ev_clickhouse_t *)((char *)w -
6032             offsetof(ev_clickhouse_t, ka_timer));
6033             (void)revents;
6034              
6035 0 0         if (self->magic != EV_CH_MAGIC) return;
6036 0 0         if (!self->connected || self->send_count > 0) return;
    0          
6037              
6038             /* Send a ping to keep the connection alive */
6039 0 0         if (self->protocol == PROTO_NATIVE) {
6040             native_buf_t pkt;
6041 0           nbuf_init(&pkt);
6042 0           nbuf_varuint(&pkt, CLIENT_PING);
6043 0           ensure_send_cap(self, self->send_len + pkt.len);
6044 0           Copy(pkt.data, self->send_buf + self->send_len, pkt.len, char);
6045 0           self->send_len += pkt.len;
6046 0           self->ka_in_flight++;
6047 0           Safefree(pkt.data);
6048 0 0         if (!self->writing) start_writing(self);
6049             }
6050             /* HTTP: no-op ping — just rely on TCP keepalive or let the
6051             * connection drop and auto-reconnect handles it. */
6052             }
6053              
6054 0           static void start_keepalive(ev_clickhouse_t *self) {
6055 0 0         if (self->keepalive > 0 && !self->ka_timing && self->connected) {
    0          
    0          
6056 0           ev_timer_init(&self->ka_timer, ka_timer_cb, self->keepalive, self->keepalive);
6057 0           ev_timer_start(self->loop, &self->ka_timer);
6058 0           self->ka_timing = 1;
6059             }
6060 0           }
6061              
6062 0           static void stop_keepalive(ev_clickhouse_t *self) {
6063 0 0         if (self->ka_timing) {
6064 0           ev_timer_stop(self->loop, &self->ka_timer);
6065 0           self->ka_timing = 0;
6066             }
6067 0           }
6068              
6069             /* --- Reconnect with backoff --- */
6070              
6071 0           static void reconnect_timer_cb(EV_P_ ev_timer *w, int revents) {
6072 0           ev_clickhouse_t *self = (ev_clickhouse_t *)((char *)w -
6073             offsetof(ev_clickhouse_t, reconnect_timer));
6074             (void)revents; (void)loop;
6075 0           self->reconnect_timing = 0;
6076 0 0         if (self->magic != EV_CH_MAGIC || self->connected || self->connecting) return;
    0          
    0          
6077 0           start_connect(self);
6078             }
6079              
6080 0           static void schedule_reconnect(ev_clickhouse_t *self) {
6081 0 0         if (!self->auto_reconnect || !self->host || self->magic != EV_CH_MAGIC) return;
    0          
    0          
6082 0 0         if (self->reconnect_delay <= 0) {
6083 0           self->reconnect_attempts = 0;
6084 0           start_connect(self);
6085 0           return;
6086             }
6087 0           double delay = self->reconnect_delay;
6088             int i;
6089 0 0         for (i = 0; i < self->reconnect_attempts && i < 20; i++)
    0          
6090 0           delay *= 2;
6091 0 0         if (self->reconnect_max_delay > 0 && delay > self->reconnect_max_delay)
    0          
6092 0           delay = self->reconnect_max_delay;
6093 0           self->reconnect_attempts++;
6094 0 0         if (self->reconnect_timing) {
6095 0           ev_timer_stop(self->loop, &self->reconnect_timer);
6096 0           self->reconnect_timing = 0;
6097             }
6098 0           ev_timer_init(&self->reconnect_timer, reconnect_timer_cb, delay, 0);
6099 0           ev_timer_start(self->loop, &self->reconnect_timer);
6100 0           self->reconnect_timing = 1;
6101             }
6102              
6103             /* Free LowCardinality cross-block dictionary state */
6104 0           static void lc_free_dicts(ev_clickhouse_t *self) {
6105 0 0         if (self->lc_dicts) {
6106             int c;
6107 0 0         for (c = 0; c < self->lc_num_cols; c++) {
6108 0 0         if (self->lc_dicts[c]) {
6109             uint64_t j;
6110 0 0         for (j = 0; j < self->lc_dict_sizes[c]; j++)
6111 0           SvREFCNT_dec(self->lc_dicts[c][j]);
6112 0           Safefree(self->lc_dicts[c]);
6113             }
6114             }
6115 0           Safefree(self->lc_dicts);
6116 0           Safefree(self->lc_dict_sizes);
6117 0           self->lc_dicts = NULL;
6118 0           self->lc_dict_sizes = NULL;
6119 0           self->lc_num_cols = 0;
6120             }
6121 0           }
6122              
6123             /* --- Pipeline orchestrator --- */
6124              
6125             /*
6126             * ClickHouse HTTP does not support true HTTP pipelining.
6127             * We send one request at a time, wait for the response, then send the next.
6128             */
6129             /* Returns 1 if self was freed (caller must not access self). */
6130 0           static int pipeline_advance(ev_clickhouse_t *self) {
6131 0 0         if (!self->connected) return 0;
6132              
6133             /* if we're still waiting for a response, just ensure reading */
6134 0 0         if (self->send_count > 0) {
6135 0           start_reading(self);
6136 0           return 0;
6137             }
6138              
6139             /* Check drain callback when all pending work is done */
6140 0 0         if (ngx_queue_empty(&self->send_queue) && self->pending_count == 0
    0          
6141 0 0         && self->on_drain) {
6142 0           SV *drain_cb = self->on_drain;
6143 0           self->on_drain = NULL;
6144 0 0         if (fire_zero_arg_cb(self, drain_cb, "drain")) {
6145 0           SvREFCNT_dec(drain_cb);
6146 0           return 1;
6147             }
6148 0           SvREFCNT_dec(drain_cb);
6149             }
6150              
6151             /* Restart keepalive timer when idle */
6152 0 0         if (ngx_queue_empty(&self->send_queue) && self->pending_count == 0
    0          
6153 0 0         && self->keepalive > 0 && !self->ka_timing) {
    0          
6154 0           start_keepalive(self);
6155             }
6156              
6157             /* send next request from queue */
6158 0 0         if (!ngx_queue_empty(&self->send_queue)) {
6159 0           ngx_queue_t *q = ngx_queue_head(&self->send_queue);
6160 0           ev_ch_send_t *send = ngx_queue_data(q, ev_ch_send_t, queue);
6161              
6162             /* Stop keepalive during active query */
6163 0           stop_keepalive(self);
6164 0           emit_trace(self, "dispatch query (pending=%d)", self->pending_count);
6165              
6166             /* set up send buffer */
6167 0           ensure_send_cap(self, send->data_len);
6168 0           Copy(send->data, self->send_buf, send->data_len, char);
6169 0           self->send_len = send->data_len;
6170 0           self->send_pos = 0;
6171              
6172             /* move cb to recv queue */
6173 0           ngx_queue_remove(q);
6174 0           push_cb_owned_ex(self, send->cb, send->raw,
6175             send->on_data, send->query_timeout);
6176 0 0         if (send->on_data) { SvREFCNT_dec(send->on_data); send->on_data = NULL; }
6177             /* Track query_id */
6178 0 0         if (self->last_query_id) { Safefree(self->last_query_id); self->last_query_id = NULL; }
6179 0 0         if (send->query_id) { self->last_query_id = send->query_id; send->query_id = NULL; }
6180              
6181             /* transfer deferred insert data from send entry to self */
6182 0 0         if (send->insert_data) {
6183 0           self->insert_data = send->insert_data;
6184 0           self->insert_data_len = send->insert_data_len;
6185 0           send->insert_data = NULL;
6186             }
6187 0 0         if (send->insert_av) {
6188 0           self->insert_av = send->insert_av;
6189 0           send->insert_av = NULL;
6190             }
6191              
6192 0           Safefree(send->data);
6193             {
6194 0           double qt = send->query_timeout;
6195 0           release_send(send);
6196 0           self->send_count++;
6197              
6198             /* Start query timeout timer */
6199             {
6200 0 0         double timeout = qt > 0 ? qt : self->query_timeout;
6201 0 0         if (timeout > 0 && !self->timing) {
    0          
6202 0           ev_timer_set(&self->timer, (ev_tstamp)timeout, 0.0);
6203 0           ev_timer_start(self->loop, &self->timer);
6204 0           self->timing = 1;
6205             }
6206             }
6207             }
6208              
6209 0 0         if (self->protocol == PROTO_NATIVE) {
6210 0 0         if (self->insert_data || self->insert_av)
    0          
6211 0           self->native_state = NATIVE_WAIT_INSERT_META;
6212             else
6213 0           self->native_state = NATIVE_WAIT_RESULT;
6214             }
6215              
6216 0           return try_write(self);
6217             }
6218 0           return 0;
6219             }
6220              
6221             /* --- OpenSSL init (must be in plain C, not inside XS BOOT) --- */
6222              
6223 24           static void ch_openssl_init(void) {
6224             #ifdef HAVE_OPENSSL
6225             #if OPENSSL_VERSION_NUMBER >= 0x10100000L
6226 24           OPENSSL_init_ssl(0, NULL);
6227             #else
6228             SSL_library_init();
6229             SSL_load_error_strings();
6230             OpenSSL_add_all_algorithms();
6231             #endif
6232             #endif
6233 24           }
6234              
6235             /* --- XS interface --- */
6236              
6237             MODULE = EV::ClickHouse PACKAGE = EV::ClickHouse
6238              
6239             BOOT:
6240             {
6241 24 50         I_EV_API("EV::ClickHouse");
    50          
    50          
6242 24           ch_openssl_init();
6243             }
6244              
6245             EV::ClickHouse
6246             _new(char *class, EV::Loop loop)
6247             CODE:
6248             {
6249             PERL_UNUSED_VAR(class);
6250 0           Newxz(RETVAL, 1, ev_clickhouse_t);
6251 0           RETVAL->magic = EV_CH_MAGIC;
6252 0           RETVAL->loop = loop;
6253 0           RETVAL->fd = -1;
6254 0           RETVAL->protocol = PROTO_HTTP;
6255 0           ngx_queue_init(&RETVAL->cb_queue);
6256 0           ngx_queue_init(&RETVAL->send_queue);
6257              
6258 0           Newx(RETVAL->recv_buf, RECV_BUF_INIT, char);
6259 0           RETVAL->recv_cap = RECV_BUF_INIT;
6260 0           Newx(RETVAL->send_buf, SEND_BUF_INIT, char);
6261 0           RETVAL->send_cap = SEND_BUF_INIT;
6262              
6263 0           ev_init(&RETVAL->timer, timer_cb);
6264 0           RETVAL->timer.data = (void *)RETVAL;
6265             }
6266             OUTPUT:
6267             RETVAL
6268              
6269             void
6270             DESTROY(EV::ClickHouse self)
6271             CODE:
6272             {
6273 0 0         if (self->magic != EV_CH_MAGIC) return;
6274              
6275 0           stop_reading(self);
6276 0           stop_writing(self);
6277 0 0         if (self->timing) {
6278 0           ev_timer_stop(self->loop, &self->timer);
6279 0           self->timing = 0;
6280             }
6281 0 0         if (self->ka_timing) {
6282 0           ev_timer_stop(self->loop, &self->ka_timer);
6283 0           self->ka_timing = 0;
6284             }
6285 0 0         if (self->reconnect_timing) {
6286 0           ev_timer_stop(self->loop, &self->reconnect_timer);
6287 0           self->reconnect_timing = 0;
6288             }
6289              
6290 0 0         if (PL_dirty) {
6291 0           self->magic = EV_CH_FREED;
6292 0 0         while (!ngx_queue_empty(&self->send_queue)) {
6293 0           ngx_queue_t *q = ngx_queue_head(&self->send_queue);
6294 0           ev_ch_send_t *send = ngx_queue_data(q, ev_ch_send_t, queue);
6295 0           ngx_queue_remove(q);
6296 0           Safefree(send->data);
6297 0 0         if (send->insert_data) Safefree(send->insert_data);
6298 0 0         if (send->insert_av) SvREFCNT_dec(send->insert_av);
6299 0 0         if (send->on_data) SvREFCNT_dec(send->on_data);
6300 0           SvREFCNT_dec(send->cb);
6301 0           release_send(send);
6302             }
6303 0 0         while (!ngx_queue_empty(&self->cb_queue)) {
6304 0           ngx_queue_t *q = ngx_queue_head(&self->cb_queue);
6305 0           ev_ch_cb_t *cbt = ngx_queue_data(q, ev_ch_cb_t, queue);
6306 0           ngx_queue_remove(q);
6307 0 0         if (cbt->on_data) SvREFCNT_dec(cbt->on_data);
6308 0           SvREFCNT_dec(cbt->cb);
6309 0           release_cbt(cbt);
6310             }
6311              
6312 0 0         #ifdef HAVE_OPENSSL
6313 0 0         if (self->ssl) { SSL_free(self->ssl); self->ssl = NULL; }
6314 0 0         if (self->ssl_ctx) { SSL_CTX_free(self->ssl_ctx); self->ssl_ctx = NULL; }
6315 0 0         #endif
6316 0 0         if (self->fd >= 0) close(self->fd);
6317 0 0         CLEAR_STR(self->host);
6318 0 0         CLEAR_STR(self->user);
6319 0 0         CLEAR_STR(self->password);
6320 0 0         CLEAR_STR(self->database);
6321 0 0         CLEAR_STR(self->session_id);
6322 0 0         CLEAR_STR(self->tls_ca_file);
6323 0 0         CLEAR_STR(self->server_name);
6324 0 0         CLEAR_STR(self->server_display_name);
6325 0 0         CLEAR_STR(self->server_timezone);
6326 0 0         CLEAR_STR(self->last_query_id);
6327 0 0         CLEAR_STR(self->insert_data);
6328 0 0         CLEAR_STR(self->insert_err);
6329 0 0         CLEAR_STR(self->recv_buf);
6330 0 0         CLEAR_STR(self->send_buf);
6331 0 0         CLEAR_SV(self->native_rows);
6332 0 0         CLEAR_SV(self->native_col_names);
6333 0 0         CLEAR_SV(self->native_col_types);
6334 0 0         CLEAR_SV(self->native_totals);
6335 0 0         CLEAR_SV(self->native_extremes);
6336 0 0         CLEAR_SV(self->default_settings);
6337 0 0         CLEAR_SV(self->on_disconnect);
6338 0 0         CLEAR_SV(self->on_drain);
6339 0           CLEAR_SV(self->on_trace);
6340 0           CLEAR_SV(self->insert_av);
6341             Safefree(self);
6342             return;
6343 0 0         }
6344 0            
6345             if (cancel_pending(self, "object destroyed"))
6346 0 0         return; /* inner DESTROY already freed self */
6347 0            
6348 0           #ifdef HAVE_OPENSSL
6349 0           if (self->ssl) {
6350             SSL_shutdown(self->ssl);
6351 0 0         SSL_free(self->ssl);
6352 0           self->ssl = NULL;
6353 0           }
6354             if (self->ssl_ctx) {
6355             SSL_CTX_free(self->ssl_ctx);
6356 0 0         self->ssl_ctx = NULL;
6357 0           }
6358 0           #endif
6359              
6360             if (self->fd >= 0) {
6361 0           close(self->fd);
6362 0           self->fd = -1;
6363             }
6364 0 0          
6365 0 0         self->loop = NULL;
6366 0 0         self->connected = 0;
6367 0 0          
6368 0 0         CLEAR_SV(self->on_connect);
6369 0 0         CLEAR_SV(self->on_error);
6370 0 0         CLEAR_SV(self->on_progress);
6371 0 0         CLEAR_SV(self->on_disconnect);
6372 0 0         CLEAR_SV(self->on_drain);
6373 0 0         CLEAR_SV(self->on_trace);
6374 0 0         CLEAR_STR(self->last_query_id);
6375 0 0         CLEAR_STR(self->host);
6376 0 0         CLEAR_STR(self->user);
6377 0 0         CLEAR_STR(self->password);
6378 0 0         CLEAR_STR(self->database);
6379 0 0         CLEAR_STR(self->session_id);
6380 0 0         CLEAR_STR(self->tls_ca_file);
6381 0 0         CLEAR_STR(self->server_name);
6382 0 0         CLEAR_STR(self->server_display_name);
6383 0 0         CLEAR_STR(self->server_timezone);
6384 0 0         CLEAR_SV(self->native_rows);
6385 0           CLEAR_SV(self->native_col_names);
6386 0 0         CLEAR_SV(self->native_col_types);
6387 0 0         CLEAR_SV(self->native_totals);
6388 0 0         CLEAR_SV(self->native_extremes);
6389 0 0         lc_free_dicts(self);
6390 0 0         CLEAR_SV(self->default_settings);
6391 0 0         CLEAR_STR(self->insert_data);
6392             CLEAR_SV(self->insert_av);
6393 0           CLEAR_STR(self->insert_err);
6394 0 0         CLEAR_STR(self->recv_buf);
6395 0           CLEAR_STR(self->send_buf);
6396              
6397             self->magic = EV_CH_FREED;
6398             if (self->callback_depth == 0) {
6399             Safefree(self);
6400             }
6401             /* else: check_destroyed() will Safefree when callback_depth reaches 0 */
6402             }
6403              
6404             void
6405             _set_tls_ca_file(EV::ClickHouse self, const char *path)
6406             CODE:
6407             {
6408 0 0         CLEAR_STR(self->tls_ca_file);
6409 0           self->tls_ca_file = safe_strdup(path);
6410             }
6411              
6412             void
6413             connect(EV::ClickHouse self, const char *host, unsigned int port, const char *user, const char *password, const char *database)
6414             CODE:
6415             {
6416 0 0         if (self->connected || self->connecting) {
    0          
6417 0           croak("already connected");
6418             }
6419 0 0         if (has_http_unsafe_chars(host) || has_http_unsafe_chars(user) ||
6420 0 0         has_http_unsafe_chars(password) || has_http_unsafe_chars(database)) {
6421 0           croak("connection parameters must not contain CR or LF");
6422             }
6423              
6424 0 0         CLEAR_STR(self->host);
6425 0 0         CLEAR_STR(self->user);
6426 0 0         CLEAR_STR(self->password);
6427 0 0         CLEAR_STR(self->database);
6428              
6429 0           self->host = safe_strdup(host);
6430 0           self->port = port;
6431 0           self->user = safe_strdup(user);
6432 0           self->password = safe_strdup(password);
6433 0           self->database = safe_strdup(database);
6434              
6435 0           start_connect(self);
6436             }
6437              
6438             void
6439             reset(EV::ClickHouse self)
6440             CODE:
6441             {
6442 0 0         if (NULL == self->host) {
6443 0           croak("no previous connection to reset");
6444             }
6445              
6446 0 0         if (cancel_pending(self, "connection reset")) return;
6447 0           cleanup_connection(self);
6448 0           start_connect(self);
6449             }
6450              
6451             void
6452             finish(EV::ClickHouse self)
6453             CODE:
6454             {
6455 0 0         if (cancel_pending(self, "connection finished")) return;
6456 0           cleanup_connection(self);
6457             }
6458              
6459             void
6460             query(EV::ClickHouse self, SV *sql_sv, ...)
6461             CODE:
6462             {
6463             STRLEN sql_len;
6464             const char *sql;
6465             ev_ch_send_t *s;
6466             char *req;
6467             size_t req_len;
6468             SV *cb;
6469 0           HV *settings = NULL;
6470 0           int raw = 0;
6471              
6472 0 0         if (items == 3) {
6473 0           cb = ST(2);
6474 0 0         } else if (items == 4) {
6475 0 0         if (!(SvROK(ST(2)) && SvTYPE(SvRV(ST(2))) == SVt_PVHV))
    0          
6476 0           croak("settings must be a HASH reference");
6477 0           settings = (HV *)SvRV(ST(2));
6478 0           cb = ST(3);
6479             } else {
6480 0           croak("Usage: $ch->query($sql, [\\%%settings], $cb)");
6481             }
6482              
6483 0 0         if (!self->connected && !self->connecting) {
    0          
6484 0           croak("not connected");
6485             }
6486 0 0         if (!(SvROK(cb) && SvTYPE(SvRV(cb)) == SVt_PVCV)) {
    0          
6487 0           croak("callback must be a CODE reference");
6488             }
6489              
6490 0 0         if (self->protocol == PROTO_NATIVE && (self->insert_data || self->insert_av)) {
    0          
    0          
6491 0           croak("cannot queue native query while INSERT is pending");
6492             }
6493              
6494             /* Extract client-side options from settings */
6495 0           SV *on_data_sv = NULL;
6496 0           double query_timeout = 0;
6497 0           HV *settings_copy = NULL; /* owned copy if we need to expand params */
6498 0 0         if (settings) {
6499             SV **svp;
6500             /* Expand params => { x => 1 } to param_x => '1' in a copy */
6501 0           svp = hv_fetch(settings, "params", 6, 0);
6502 0 0         if (svp && SvOK(*svp) && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVHV) {
    0          
    0          
    0          
6503 0           HV *phv = (HV *)SvRV(*svp);
6504             HE *pe;
6505             /* Copy settings to avoid mutating caller's hashref */
6506 0           settings_copy = newHVhv(settings);
6507 0           settings = settings_copy;
6508 0           hv_iterinit(phv);
6509 0 0         while ((pe = hv_iternext(phv))) {
6510             I32 pklen;
6511 0           char *pkey = hv_iterkey(pe, &pklen);
6512 0           SV *pval = hv_iterval(phv, pe);
6513             char *prefixed;
6514 0           Newx(prefixed, pklen + 7, char);
6515 0           Copy("param_", prefixed, 6, char);
6516 0           Copy(pkey, prefixed + 6, pklen, char);
6517 0           (void)hv_store(settings, prefixed, pklen + 6,
6518             newSVsv(pval), 0);
6519 0           Safefree(prefixed);
6520             }
6521             }
6522 0           svp = hv_fetch(settings, "raw", 3, 0);
6523 0 0         if (svp)
6524 0           raw = SvTRUE(*svp) ? 1 : 0;
6525 0           svp = hv_fetch(settings, "on_data", 7, 0);
6526 0 0         if (svp && SvOK(*svp) && SvROK(*svp) && SvTYPE(SvRV(*svp)) == SVt_PVCV)
    0          
    0          
    0          
6527 0           on_data_sv = *svp;
6528 0           svp = hv_fetch(settings, "query_timeout", 13, 0);
6529 0 0         if (svp && SvOK(*svp))
    0          
6530 0           query_timeout = SvNV(*svp);
6531             }
6532              
6533 0           sql = SvPV(sql_sv, sql_len);
6534              
6535 0 0         if (raw && self->protocol == PROTO_NATIVE) {
    0          
6536 0 0         if (settings_copy) SvREFCNT_dec((SV*)settings_copy);
6537 0           croak("raw mode is only supported with the HTTP protocol");
6538             }
6539              
6540 0 0         if (on_data_sv && self->protocol == PROTO_HTTP) {
    0          
6541 0 0         if (settings_copy) SvREFCNT_dec((SV*)settings_copy);
6542 0           croak("on_data is only supported with the native protocol");
6543             }
6544              
6545 0 0         if (self->protocol == PROTO_HTTP) {
6546 0           req = build_http_query_request(self, sql, sql_len, self->compress,
6547             self->default_settings, settings,
6548             &req_len);
6549             } else {
6550 0           req = build_native_query(self, sql, sql_len,
6551             self->default_settings, settings, &req_len);
6552             }
6553              
6554 0           s = alloc_send();
6555 0           s->data = req;
6556 0           s->data_len = req_len;
6557 0           s->raw = raw;
6558 0 0         if (on_data_sv) s->on_data = SvREFCNT_inc(on_data_sv);
6559 0           s->query_timeout = query_timeout;
6560 0 0         if (settings) {
6561 0           SV **qid = hv_fetch(settings, "query_id", 8, 0);
6562 0 0         if (qid && SvOK(*qid)) {
    0          
6563             STRLEN qlen;
6564 0           const char *qstr = SvPV(*qid, qlen);
6565 0           Newx(s->query_id, qlen + 1, char);
6566 0           Copy(qstr, s->query_id, qlen, char);
6567 0           s->query_id[qlen] = '\0';
6568             }
6569             }
6570 0           s->cb = SvREFCNT_inc(cb);
6571 0 0         if (settings_copy) SvREFCNT_dec((SV*)settings_copy);
6572 0           ngx_queue_insert_tail(&self->send_queue, &s->queue);
6573 0           self->pending_count++;
6574              
6575 0 0         if (self->connected && self->callback_depth == 0)
    0          
6576 0           pipeline_advance(self);
6577             }
6578              
6579             void
6580             insert(EV::ClickHouse self, SV *table_sv, SV *data_sv, ...)
6581             CODE:
6582             {
6583             STRLEN table_len;
6584             const char *table;
6585             ev_ch_send_t *s;
6586             char *req;
6587             size_t req_len;
6588             SV *cb;
6589 0           HV *settings = NULL;
6590 0           int data_is_av = 0;
6591 0           AV *data_av = NULL;
6592              
6593 0 0         if (items == 4) {
6594 0           cb = ST(3);
6595 0 0         } else if (items == 5) {
6596 0 0         if (!(SvROK(ST(3)) && SvTYPE(SvRV(ST(3))) == SVt_PVHV))
    0          
6597 0           croak("settings must be a HASH reference");
6598 0           settings = (HV *)SvRV(ST(3));
6599 0           cb = ST(4);
6600             } else {
6601 0           croak("Usage: $ch->insert($table, $data, [\\%%settings], $cb)");
6602             }
6603              
6604 0 0         if (!self->connected && !self->connecting) {
    0          
6605 0           croak("not connected");
6606             }
6607 0 0         if (!(SvROK(cb) && SvTYPE(SvRV(cb)) == SVt_PVCV)) {
    0          
6608 0           croak("callback must be a CODE reference");
6609             }
6610              
6611 0           table = SvPV(table_sv, table_len);
6612              
6613             /* Detect arrayref-of-arrayrefs vs TSV string */
6614 0 0         if (SvROK(data_sv) && SvTYPE(SvRV(data_sv)) == SVt_PVAV) {
    0          
6615 0           data_is_av = 1;
6616 0           data_av = (AV *)SvRV(data_sv);
6617             }
6618              
6619 0 0         if (self->protocol == PROTO_HTTP) {
6620             STRLEN data_len;
6621             const char *data;
6622 0           char *tsv_buf = NULL;
6623              
6624 0 0         if (data_is_av) {
6625 0           tsv_buf = serialize_av_to_tsv(aTHX_ data_av, &data_len);
6626 0           data = tsv_buf;
6627             } else {
6628 0           data = SvPV(data_sv, data_len);
6629             }
6630              
6631 0           req = build_http_insert_request(self, table, table_len,
6632             data, data_len, self->compress,
6633             self->default_settings, settings,
6634             &req_len);
6635 0 0         if (tsv_buf) Safefree(tsv_buf);
6636             } else {
6637             /* Native insert: two-phase approach.
6638             * Phase 1: send INSERT query without inline data, receive sample block.
6639             * Phase 2: encode data as binary columnar Data block, send compressed. */
6640             char *insert_sql;
6641             size_t insert_sql_len;
6642              
6643             /* Only one native INSERT at a time */
6644 0 0         if (self->insert_data || self->insert_av) {
    0          
6645 0           croak("cannot pipeline native INSERT: previous INSERT still pending");
6646             }
6647              
6648             /* Build: "INSERT INTO table FORMAT TabSeparated" (no inline data) */
6649 0           Newx(insert_sql, table_len + 64, char);
6650 0           insert_sql_len = snprintf(insert_sql, table_len + 64,
6651             "INSERT INTO %.*s FORMAT TabSeparated",
6652             (int)table_len, table);
6653 0           req = build_native_query(self, insert_sql, insert_sql_len,
6654             self->default_settings, settings, &req_len);
6655 0           Safefree(insert_sql);
6656             }
6657              
6658 0           s = alloc_send();
6659 0           s->data = req;
6660 0           s->data_len = req_len;
6661 0 0         if (settings) {
6662 0           SV **qid = hv_fetch(settings, "query_id", 8, 0);
6663 0 0         if (qid && SvOK(*qid)) {
    0          
6664             STRLEN qlen;
6665 0           const char *qstr = SvPV(*qid, qlen);
6666 0           Newx(s->query_id, qlen + 1, char);
6667 0           Copy(qstr, s->query_id, qlen, char);
6668 0           s->query_id[qlen] = '\0';
6669             }
6670             }
6671              
6672             /* For native INSERT, store data in the send entry (deferred to dispatch).
6673             * Even empty data needs the two-phase INSERT protocol to send an empty
6674             * DATA block. */
6675 0 0         if (self->protocol == PROTO_NATIVE) {
6676 0 0         if (data_is_av) {
6677 0           s->insert_av = SvREFCNT_inc(data_sv);
6678             } else {
6679             STRLEN data_len;
6680 0           const char *data = SvPV(data_sv, data_len);
6681 0 0         Newx(s->insert_data, data_len > 0 ? data_len : 1, char);
6682 0 0         if (data_len > 0)
6683 0           Copy(data, s->insert_data, data_len, char);
6684 0           s->insert_data_len = data_len;
6685             }
6686             }
6687              
6688 0           s->cb = SvREFCNT_inc(cb);
6689 0           ngx_queue_insert_tail(&self->send_queue, &s->queue);
6690 0           self->pending_count++;
6691              
6692 0 0         if (self->connected && self->callback_depth == 0)
    0          
6693 0           pipeline_advance(self);
6694             }
6695              
6696             void
6697             ping(EV::ClickHouse self, SV *cb)
6698             CODE:
6699             {
6700             ev_ch_send_t *s;
6701             char *req;
6702             size_t req_len;
6703              
6704 0 0         if (!self->connected && !self->connecting) {
    0          
6705 0           croak("not connected");
6706             }
6707 0 0         if (!(SvROK(cb) && SvTYPE(SvRV(cb)) == SVt_PVCV)) {
    0          
6708 0           croak("callback must be a CODE reference");
6709             }
6710              
6711 0 0         if (self->protocol == PROTO_HTTP) {
6712 0           req = build_http_ping_request(self, &req_len);
6713             } else {
6714 0           req = build_native_ping(&req_len);
6715             }
6716              
6717 0           s = alloc_send();
6718 0           s->data = req;
6719 0           s->data_len = req_len;
6720 0           s->cb = SvREFCNT_inc(cb);
6721 0           ngx_queue_insert_tail(&self->send_queue, &s->queue);
6722 0           self->pending_count++;
6723              
6724 0 0         if (self->connected && self->callback_depth == 0)
    0          
6725 0           pipeline_advance(self);
6726             }
6727              
6728             SV*
6729             on_connect(EV::ClickHouse self, SV *handler = NULL)
6730             CODE:
6731             {
6732 0           RETVAL = handler_accessor(&self->on_connect, handler, items > 1);
6733             }
6734             OUTPUT:
6735             RETVAL
6736              
6737             SV*
6738             on_error(EV::ClickHouse self, SV *handler = NULL)
6739             CODE:
6740             {
6741 0           RETVAL = handler_accessor(&self->on_error, handler, items > 1);
6742             }
6743             OUTPUT:
6744             RETVAL
6745              
6746             SV*
6747             on_progress(EV::ClickHouse self, SV *handler = NULL)
6748             CODE:
6749             {
6750 0           RETVAL = handler_accessor(&self->on_progress, handler, items > 1);
6751             }
6752             OUTPUT:
6753             RETVAL
6754              
6755             int
6756             is_connected(EV::ClickHouse self)
6757             CODE:
6758             {
6759 0 0         RETVAL = self->connected ? 1 : 0;
6760             }
6761             OUTPUT:
6762             RETVAL
6763              
6764             int
6765             pending_count(EV::ClickHouse self)
6766             CODE:
6767             {
6768 0 0         RETVAL = self->pending_count;
6769             }
6770             OUTPUT:
6771             RETVAL
6772              
6773             SV *
6774             server_info(EV::ClickHouse self)
6775             CODE:
6776             {
6777 0 0         if (self->server_name) {
6778             char buf[256];
6779 0           int n = snprintf(buf, sizeof(buf), "%s %u.%u.%u (revision %u)",
6780             self->server_name,
6781             self->server_version_major,
6782             self->server_version_minor,
6783             self->server_version_patch,
6784             self->server_revision);
6785 0 0         if (n >= (int)sizeof(buf)) n = (int)sizeof(buf) - 1;
6786 0           RETVAL = newSVpvn(buf, n);
6787             } else {
6788 0           RETVAL = &PL_sv_undef;
6789             }
6790             }
6791             OUTPUT:
6792             RETVAL
6793              
6794             SV *
6795             server_version(EV::ClickHouse self)
6796             CODE:
6797             {
6798 0 0         if (self->server_revision) {
6799             char buf[64];
6800 0           int n = snprintf(buf, sizeof(buf), "%u.%u.%u",
6801             self->server_version_major,
6802             self->server_version_minor,
6803             self->server_version_patch);
6804 0 0         if (n >= (int)sizeof(buf)) n = (int)sizeof(buf) - 1;
6805 0           RETVAL = newSVpvn(buf, n);
6806             } else {
6807 0           RETVAL = &PL_sv_undef;
6808             }
6809             }
6810             OUTPUT:
6811             RETVAL
6812              
6813             void
6814             skip_pending(EV::ClickHouse self)
6815             CODE:
6816             {
6817             /* Cancel queued + in-flight callbacks first (delivers errors), then
6818             * tear down the socket if a request was on the wire — capture the
6819             * had_inflight state up front because cancel_pending zeroes send_count. */
6820 0           int had_inflight = self->send_count > 0;
6821 0 0         if (cancel_pending(self, "skipped")) return;
6822 0 0         CLEAR_STR(self->insert_data);
6823 0           self->insert_data_len = 0;
6824 0 0         CLEAR_SV(self->insert_av);
6825 0 0         CLEAR_STR(self->insert_err);
6826 0 0         if (had_inflight)
6827 0           cleanup_connection(self);
6828             }
6829              
6830             void
6831             _set_protocol(EV::ClickHouse self, int proto)
6832             CODE:
6833             {
6834 0           self->protocol = proto;
6835             }
6836              
6837             void
6838             _set_compress(EV::ClickHouse self, int val)
6839             CODE:
6840             {
6841 0           self->compress = val;
6842             }
6843              
6844             void
6845             _set_session_id(EV::ClickHouse self, const char *sid)
6846             CODE:
6847             {
6848 0 0         CLEAR_STR(self->session_id);
6849 0           self->session_id = safe_strdup(sid);
6850             }
6851              
6852             void
6853             _set_connect_timeout(EV::ClickHouse self, NV val)
6854             CODE:
6855             {
6856 0           self->connect_timeout = val;
6857             }
6858              
6859             void
6860             _set_tls(EV::ClickHouse self, int val)
6861             CODE:
6862             {
6863 0           #ifdef HAVE_OPENSSL
6864 0 0         self->tls_enabled = val;
6865             #else
6866             if (val) croak("TLS support not compiled in (OpenSSL not found)");
6867             #endif
6868             }
6869              
6870             void
6871             _set_settings(EV::ClickHouse self, SV *href)
6872             CODE:
6873             {
6874 0 0         if (!(SvROK(href) && SvTYPE(SvRV(href)) == SVt_PVHV))
    0          
6875 0           croak("settings must be a HASH reference");
6876 0 0         if (self->default_settings)
6877 0           SvREFCNT_dec((SV *)self->default_settings);
6878 0           self->default_settings = (HV *)SvRV(href);
6879 0           SvREFCNT_inc((SV *)self->default_settings);
6880             }
6881              
6882             SV*
6883             on_disconnect(EV::ClickHouse self, SV *handler = NULL)
6884             CODE:
6885             {
6886 0           RETVAL = handler_accessor(&self->on_disconnect, handler, items > 1);
6887             }
6888             OUTPUT:
6889             RETVAL
6890              
6891             SV *
6892             server_timezone(EV::ClickHouse self)
6893             CODE:
6894             {
6895 0 0         if (self->server_timezone)
6896 0           RETVAL = newSVpv(self->server_timezone, 0);
6897             else
6898 0           RETVAL = &PL_sv_undef;
6899             }
6900             OUTPUT:
6901             RETVAL
6902              
6903             void
6904             _set_tls_skip_verify(EV::ClickHouse self, int val)
6905             CODE:
6906             {
6907 0           self->tls_skip_verify = val;
6908             }
6909              
6910             void
6911             _set_query_timeout(EV::ClickHouse self, NV val)
6912             CODE:
6913             {
6914 0           self->query_timeout = val;
6915             }
6916              
6917             void
6918             _set_auto_reconnect(EV::ClickHouse self, int val)
6919             CODE:
6920             {
6921 0           self->auto_reconnect = val;
6922             }
6923              
6924             void
6925             _set_decode_flags(EV::ClickHouse self, unsigned int flags)
6926             CODE:
6927             {
6928 0           self->decode_flags = (uint32_t)flags;
6929             }
6930              
6931             SV *
6932             column_names(EV::ClickHouse self)
6933             CODE:
6934             {
6935 0 0         if (self->native_col_names)
6936 0           RETVAL = newRV_inc((SV*)self->native_col_names);
6937             else
6938 0           RETVAL = &PL_sv_undef;
6939             }
6940             OUTPUT:
6941             RETVAL
6942              
6943             SV *
6944             last_query_id(EV::ClickHouse self)
6945             CODE:
6946             {
6947 0 0         if (self->last_query_id)
6948 0           RETVAL = newSVpv(self->last_query_id, 0);
6949             else
6950 0           RETVAL = &PL_sv_undef;
6951             }
6952             OUTPUT:
6953             RETVAL
6954              
6955             SV *
6956             last_error_code(EV::ClickHouse self)
6957             CODE:
6958             {
6959 0           RETVAL = newSViv(self->last_error_code);
6960             }
6961             OUTPUT:
6962             RETVAL
6963              
6964             SV *
6965             column_types(EV::ClickHouse self)
6966             CODE:
6967             {
6968 0 0         if (self->native_col_types)
6969 0           RETVAL = newRV_inc((SV*)self->native_col_types);
6970             else
6971 0           RETVAL = &PL_sv_undef;
6972             }
6973             OUTPUT:
6974             RETVAL
6975              
6976             SV *
6977             last_totals(EV::ClickHouse self)
6978             CODE:
6979             {
6980 0 0         if (self->native_totals)
6981 0           RETVAL = newRV_inc((SV*)self->native_totals);
6982             else
6983 0           RETVAL = &PL_sv_undef;
6984             }
6985             OUTPUT:
6986             RETVAL
6987              
6988             SV *
6989             last_extremes(EV::ClickHouse self)
6990             CODE:
6991             {
6992 0 0         if (self->native_extremes)
6993 0           RETVAL = newRV_inc((SV*)self->native_extremes);
6994             else
6995 0           RETVAL = &PL_sv_undef;
6996             }
6997             OUTPUT:
6998             RETVAL
6999              
7000             SV *
7001             profile_rows_before_limit(EV::ClickHouse self)
7002             CODE:
7003             {
7004 0           RETVAL = newSVuv(self->profile_rows_before_limit);
7005             }
7006             OUTPUT:
7007             RETVAL
7008              
7009             SV *
7010             profile_rows(EV::ClickHouse self)
7011             CODE:
7012             {
7013 0           RETVAL = newSVuv(self->profile_rows);
7014             }
7015             OUTPUT:
7016             RETVAL
7017              
7018             SV *
7019             profile_bytes(EV::ClickHouse self)
7020             CODE:
7021             {
7022 0           RETVAL = newSVuv(self->profile_bytes);
7023             }
7024             OUTPUT:
7025             RETVAL
7026              
7027             SV*
7028             on_trace(EV::ClickHouse self, SV *handler = NULL)
7029             CODE:
7030             {
7031 0           RETVAL = handler_accessor(&self->on_trace, handler, items > 1);
7032             }
7033             OUTPUT:
7034             RETVAL
7035              
7036             void
7037             _set_keepalive(EV::ClickHouse self, double val)
7038             CODE:
7039             {
7040 0           self->keepalive = val;
7041             }
7042              
7043             void
7044             _set_reconnect_delay(EV::ClickHouse self, double val)
7045             CODE:
7046             {
7047 0           self->reconnect_delay = val;
7048             }
7049              
7050             void
7051             _set_reconnect_max_delay(EV::ClickHouse self, double val)
7052             CODE:
7053             {
7054 0           self->reconnect_max_delay = val;
7055             }
7056              
7057             void
7058             drain(EV::ClickHouse self, SV *cb)
7059             CODE:
7060             {
7061 0 0         if (!(SvROK(cb) && SvTYPE(SvRV(cb)) == SVt_PVCV))
    0          
7062 0           croak("drain callback must be a CODE reference");
7063 0 0         CLEAR_SV(self->on_drain);
7064 0 0         if (self->pending_count == 0 && ngx_queue_empty(&self->send_queue)) {
    0          
7065             /* Nothing pending — fire immediately */
7066 0           (void)fire_zero_arg_cb(self, cb, "drain");
7067             } else {
7068 0           self->on_drain = SvREFCNT_inc(cb);
7069             }
7070             }
7071              
7072             void
7073             cancel(EV::ClickHouse self)
7074             CODE:
7075             {
7076 0 0         if (self->protocol == PROTO_NATIVE && self->send_count > 0) {
    0          
7077             /* Send CLIENT_CANCEL packet */
7078             native_buf_t pkt;
7079 0           nbuf_init(&pkt);
7080 0           nbuf_varuint(&pkt, CLIENT_CANCEL);
7081 0           ensure_send_cap(self, self->send_len + pkt.len);
7082 0           Copy(pkt.data, self->send_buf + self->send_len, pkt.len, char);
7083 0           self->send_len += pkt.len;
7084 0           Safefree(pkt.data);
7085 0 0         if (!self->writing) start_writing(self);
7086             /* We still need to wait for EndOfStream or Exception from server */
7087 0 0         } else if (self->protocol == PROTO_HTTP && self->send_count > 0) {
    0          
7088             /* HTTP: close connection to cancel */
7089 0 0         CLEAR_SV(self->native_rows);
7090 0 0         if (cancel_pending(self, "query cancelled")) return;
7091 0           cleanup_connection(self);
7092 0 0         if (self->auto_reconnect && self->host)
    0          
7093 0           schedule_reconnect(self);
7094             }
7095             }