File Coverage

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