EV-ClickHouse
view release on metacpan or search on metacpan
ClickHouse.xs view on Meta::CPAN
char *http_decoded; /* chunked body accumulated so far (NULL = none) */
size_t http_decoded_len;
size_t http_decoded_cap;
size_t http_chunk_off; /* offset into recv_buf of next unparsed chunk hdr */
int http_chunk_active; /* 1 = a partially decoded chunked body is held */
/* native protocol state */
char *server_name;
char *server_display_name;
char *server_timezone;
unsigned int server_version_major, server_version_minor, server_revision;
unsigned int server_version_patch;
int native_state; /* NATIVE_IDLE, NATIVE_WAIT_HELLO, NATIVE_WAIT_RESULT, ... */
AV *native_rows; /* accumulate rows across Data blocks */
char *insert_data; /* pending TabSeparated data for two-phase INSERT */
size_t insert_data_len;
SV *insert_av; /* pending AV* of AV*s for arrayref INSERT */
char *insert_err; /* deferred error from unsupported INSERT encoding */
/* queues */
ngx_queue_t cb_queue;
ngx_queue_t send_queue;
int pending_count;
int send_count;
/* options */
char *session_id;
char *query_log_comment; /* prepended as a SQL block comment per query */
int compress;
double connect_timeout;
HV *default_settings; /* connection-level ClickHouse settings */
SV *on_connect;
SV *on_error;
SV *on_progress;
SV *on_disconnect;
SV *on_query_complete; /* fires after each query (success or error) */
SV *on_query_start; /* fires when a query is dispatched */
SV *on_log; /* native SERVER_LOG packets */
SV *on_failover; /* multi-host: ($oh, $op, $nh, $np, $msg) */
char *last_tls_error; /* OpenSSL error from last failed handshake */
char **failover_hosts; /* parallel arrays: hosts + ports. */
unsigned int *failover_ports; /* NULL if multi-host failover disabled. */
int failover_n;
int failover_idx;
unsigned int failover_default_port;
double query_start_time; /* ev_now() captured in pipeline_advance */
int tls_skip_verify;
double query_timeout;
size_t max_query_size; /* 0 = unlimited; client-side croak guard */
size_t max_recv_buffer; /* 0 = unlimited; defensive recv ceiling */
int http_basic_auth; /* 0=X-ClickHouse-{User,Key} (default);
* 1=Authorization: Basic ... (for proxies) */
int auto_reconnect;
uint32_t decode_flags;
AV *native_col_names; /* column names from last native result */
AV *native_col_types; /* column type strings from last native result */
SV *on_drain; /* callback fired when pending_count drops to 0 */
char *last_query_id; /* query_id of the last dispatched query */
SV *on_trace; /* debug trace callback */
ev_timer ka_timer; /* keepalive timer */
double keepalive; /* keepalive interval (0 = disabled) */
int ka_timing;
int callback_depth;
/* error info from last SERVER_EXCEPTION or HTTP error */
int32_t last_error_code;
/* profile info from last SERVER_PROFILE_INFO */
uint64_t profile_rows;
uint64_t profile_bytes;
uint64_t profile_rows_before_limit;
/* totals / extremes from last native query */
AV *native_totals;
AV *native_extremes;
/* reconnect backoff */
double reconnect_delay;
double reconnect_max_delay;
double reconnect_jitter; /* fractional [0, 1+]: actual delay
picks uniformly in [d, d*(1+jitter)] */
int reconnect_attempts;
int reconnect_max_attempts; /* 0 = unlimited */
int pending_addendum_finish; /* set when addendum partially written;
* io_cb completes finish_connect after drain */
ev_timer reconnect_timer;
int reconnect_timing;
/* on_progress throttling (0 = fire every packet) */
double progress_period;
double progress_last; /* ev_now() of last on_progress dispatch */
uint64_t progress_acc[5]; /* coalesced totals since last dispatch */
/* LowCardinality cross-block dictionary state */
SV ***lc_dicts; /* array of dictionaries, one per column */
uint64_t *lc_dict_sizes; /* size of each dictionary */
int lc_num_cols; /* number of columns with LC state */
};
struct ev_ch_cb_s {
SV *cb;
int raw; /* return raw response body instead of parsed rows */
SV *on_data; /* per-query streaming callback (fires per block) */
SV *on_complete;/* per-query on_query_complete override (or NULL) */
double query_timeout; /* per-query timeout (0=use default) */
ngx_queue_t queue;
};
struct ev_ch_send_s {
char *data; /* full HTTP request or native packet */
size_t data_len;
SV *cb;
char *insert_data; /* deferred TSV data for native INSERT */
size_t insert_data_len;
SV *insert_av; /* deferred AV* data for native INSERT */
int raw; /* return raw response body */
SV *on_data; /* per-query streaming callback */
SV *on_complete; /* per-query on_query_complete override */
double query_timeout; /* per-query timeout */
char *query_id; /* query_id for tracking */
ngx_queue_t queue;
};
/* Forward declarations for helpers defined further down (or in xs/io.c)
* but called from earlier code in this file or from xs/*.c included
* before the definition site. */
static void timer_cb(EV_P_ ev_timer *w, int revents);
static void stop_keepalive(ev_clickhouse_t *self);
static void schedule_reconnect(ev_clickhouse_t *self);
static void lc_free_dicts(ev_clickhouse_t *self);
static void start_reading(ev_clickhouse_t *self);
static void stop_reading(ev_clickhouse_t *self);
static void start_writing(ev_clickhouse_t *self);
static void stop_writing(ev_clickhouse_t *self);
static void emit_error(ev_clickhouse_t *self, const char *msg);
static void emit_trace(ev_clickhouse_t *self, const char *fmt, ...);
static int cleanup_connection(ev_clickhouse_t *self);
static int cancel_pending(ev_clickhouse_t *self, const char *errmsg);
static int check_destroyed(ev_clickhouse_t *self);
static char *safe_strdup(const char *s);
static void failover_free(ev_clickhouse_t *self);
static int finish_connect(ev_clickhouse_t *self);
static int try_write(ev_clickhouse_t *self);
static int pipeline_advance(ev_clickhouse_t *self);
/* Two helpers shared between the PL_dirty and normal arms of DESTROY:
* adding a new on_* slot or persistent string requires touching only
* one place. on_failover is omitted from CONNECTION_HANDLERS because
* failover_free() handles it together with the host ring. */
#define CLEAR_CONNECTION_HANDLERS(self) do { \
CLEAR_SV((self)->on_connect); \
CLEAR_SV((self)->on_error); \
CLEAR_SV((self)->on_progress); \
CLEAR_SV((self)->on_disconnect); \
CLEAR_SV((self)->on_query_complete); \
CLEAR_SV((self)->on_query_start); \
CLEAR_SV((self)->on_log); \
CLEAR_SV((self)->on_drain); \
CLEAR_SV((self)->on_trace); \
} while (0)
#define CLEAR_PERSISTENT_STATE(self) do { \
CLEAR_STR((self)->last_tls_error); \
CLEAR_STR((self)->last_query_id); \
CLEAR_STR((self)->host); \
CLEAR_STR((self)->user); \
CLEAR_STR((self)->password); \
CLEAR_STR((self)->database); \
CLEAR_STR((self)->session_id); \
CLEAR_STR((self)->query_log_comment); \
CLEAR_STR((self)->tls_ca_file); \
CLEAR_STR((self)->tls_cert_file); \
CLEAR_STR((self)->tls_key_file); \
CLEAR_STR((self)->server_name); \
CLEAR_STR((self)->server_display_name); \
CLEAR_STR((self)->server_timezone); \
CLEAR_STR((self)->insert_err); \
CLEAR_STR((self)->recv_buf); \
CLEAR_STR((self)->http_decoded); \
CLEAR_STR((self)->send_buf); \
CLEAR_SV((self)->native_rows); \
CLEAR_SV((self)->native_col_names); \
CLEAR_SV((self)->native_col_types); \
CLEAR_SV((self)->native_totals); \
CLEAR_SV((self)->native_extremes); \
CLEAR_SV((self)->default_settings); \
} while (0)
#include "xs/macros.h"
#include "xs/queues.c"
/* --- watcher helpers --- */
static void start_reading(ev_clickhouse_t *self) {
if (!self->reading && self->fd >= 0) {
ev_io_start(self->loop, &self->rio);
self->reading = 1;
}
}
static void stop_reading(ev_clickhouse_t *self) {
if (self->reading) {
ev_io_stop(self->loop, &self->rio);
self->reading = 0;
}
}
static void start_writing(ev_clickhouse_t *self) {
if (!self->writing && self->fd >= 0) {
ev_io_start(self->loop, &self->wio);
self->writing = 1;
}
}
static void stop_writing(ev_clickhouse_t *self) {
if (self->writing) {
ev_io_stop(self->loop, &self->wio);
self->writing = 0;
}
}
static void stop_timing(ev_clickhouse_t *self) {
if (self->timing) {
ev_timer_stop(self->loop, &self->timer);
self->timing = 0;
}
}
static int check_destroyed(ev_clickhouse_t *self) {
if (self->magic == EV_CH_FREED && self->callback_depth == 0) {
Safefree(self);
return 1;
}
return 0;
}
/* Free the per-connection failover host list (allocated by setter). */
/* Free just the host-list arrays. Called from _set_failover before
* re-populating + from failover_free below. Keeps on_failover alive. */
static void failover_free_hosts(ev_clickhouse_t *self) {
if (self->failover_hosts) {
for (int i = 0; i < self->failover_n; i++)
if (self->failover_hosts[i]) Safefree(self->failover_hosts[i]);
Safefree(self->failover_hosts);
self->failover_hosts = NULL;
}
if (self->failover_ports) {
Safefree(self->failover_ports);
self->failover_ports = NULL;
}
self->failover_n = 0;
self->failover_idx = 0;
}
/* Full failover-state teardown including the on_failover SV. DESTROY only. */
static void failover_free(ev_clickhouse_t *self) {
failover_free_hosts(self);
if (self->on_failover) {
SvREFCNT_dec(self->on_failover);
self->on_failover = NULL;
}
}
/* Word-boundary case-insensitive match against the failover-trigger
* keyword set. Used by emit_error to decide whether to rotate the
* multi-host ring. Returns 1 on match, 0 otherwise. */
static int failover_msg_match(const char *msg) {
static const char *KEYWORDS[] = {
"connect", "refused", "timeout", "unreachable", "route",
"reset", "closed", "broken", "down", "dns", "resolution",
"getaddrinfo", NULL,
};
if (!msg) return 0;
for (const char **k = KEYWORDS; *k; k++) {
size_t kl = strlen(*k);
const char *p = msg;
for (;;) {
const char *m = NULL;
for (const char *q = p; *q; q++) {
if (strncasecmp(q, *k, kl) == 0) { m = q; break; }
}
if (!m) break;
int boundary_l = (m == msg) || !isalnum((unsigned char)m[-1]);
int boundary_r = !isalnum((unsigned char)m[kl]);
if (boundary_l && boundary_r) return 1;
p = m + 1;
}
}
return 0;
}
/* Advance failover ring + fire on_failover callback (if set). Caller is
* responsible for callback_depth bookkeeping. */
static void failover_advance(ev_clickhouse_t *self, const char *msg) {
if (!self->failover_hosts || self->failover_n <= 0) return;
if (!failover_msg_match(msg)) return;
char *old_host = self->host ? safe_strdup(self->host) : NULL;
unsigned int old_port = self->port;
self->failover_idx = (self->failover_idx + 1) % self->failover_n;
ClickHouse.xs view on Meta::CPAN
}
static void invoke_cb(SV *cb) {
call_sv(cb, G_DISCARD | G_EVAL);
WARN_AND_CLEAR_ERRSV("callback");
SvREFCNT_dec(cb);
}
/* Invoke `cb` with (undef, errmsg). Caller manages callback_depth. */
static void invoke_err_cb(SV *cb, const char *errmsg) {
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
PUSHs(&PL_sv_undef);
PUSHs(sv_2mortal(newSVpv(errmsg, 0)));
PUTBACK;
invoke_cb(cb);
FREETMPS;
LEAVE;
}
/* Fire on_query_complete with (query_id, rows, bytes, error_code, duration_s, errmsg).
* Caller must guard callback_depth around any invoke that follows. Safe to
* call when on_query_complete is unset. `override` (when non-NULL) is the
* per-query override hook from the settings hashref; it REPLACES the
* connection-level handler for this query so per-query instrumentation
* doesn't double-count against global metrics. */
static void fire_on_query_complete_ex(ev_clickhouse_t *self, const char *errmsg,
SV *override) {
SV *target = override ? override : self->on_query_complete;
if (!target) return;
double dur = self->query_start_time > 0
? ev_now(self->loop) - self->query_start_time : 0.0;
self->callback_depth++;
{
dSP;
ENTER; SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 6);
PUSHs(self->last_query_id
? sv_2mortal(newSVpv(self->last_query_id, 0)) : &PL_sv_undef);
PUSHs(sv_2mortal(newSVuv(self->profile_rows)));
PUSHs(sv_2mortal(newSVuv(self->profile_bytes)));
PUSHs(sv_2mortal(newSViv(self->last_error_code)));
PUSHs(sv_2mortal(newSVnv(dur)));
PUSHs(errmsg ? sv_2mortal(newSVpv(errmsg, 0)) : &PL_sv_undef);
PUTBACK;
PINNED_CALL_SV(target, G_DISCARD | G_EVAL);
WARN_AND_CLEAR_ERRSV("on_query_complete");
FREETMPS; LEAVE;
}
self->callback_depth--;
/* Reset so a subsequent fire for a never-dispatched cancelled
* query (e.g. from cancel_pending draining send_queue) sees
* query_start_time == 0 and reports dur = 0.0, not a stale
* duration carried over from the previous in-flight query. */
self->query_start_time = 0;
}
/* IS_KEEPALIVE_CB is defined in xs/macros.h; keepalive_noop_cb is the
* sentinel that backs it (declared below this comment). */
static int deliver_error(ev_clickhouse_t *self, const char *errmsg) {
SV *oqc = NULL;
SV *cb = pop_cb_ex(self, &oqc);
if (cb == NULL) {
fire_on_query_complete_ex(self, errmsg, oqc);
if (oqc) SvREFCNT_dec(oqc);
return check_destroyed(self);
}
self->callback_depth++;
if (!IS_KEEPALIVE_CB(cb)) fire_on_query_complete_ex(self, errmsg, oqc);
invoke_err_cb(cb, errmsg);
if (oqc) SvREFCNT_dec(oqc);
self->callback_depth--;
return check_destroyed(self);
}
/* Returns 1 if self was freed. */
static int deliver_rows(ev_clickhouse_t *self, AV *rows) {
SV *oqc = NULL;
SV *cb = pop_cb_ex(self, &oqc);
if (cb == NULL) {
if (rows) SvREFCNT_dec((SV*)rows);
fire_on_query_complete_ex(self, NULL, oqc);
if (oqc) SvREFCNT_dec(oqc);
return check_destroyed(self);
}
self->callback_depth++;
if (!IS_KEEPALIVE_CB(cb)) fire_on_query_complete_ex(self, NULL, oqc);
{
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
PUSHs(rows ? sv_2mortal(newRV_noinc((SV*)rows)) : &PL_sv_undef);
PUTBACK;
invoke_cb(cb);
FREETMPS;
LEAVE;
}
if (oqc) SvREFCNT_dec(oqc);
self->callback_depth--;
return check_destroyed(self);
}
/* Deliver raw response body as scalar string. Returns 1 if self was freed. */
static int deliver_raw_body(ev_clickhouse_t *self, const char *data, size_t len) {
SV *oqc = NULL;
SV *cb = pop_cb_ex(self, &oqc);
if (cb == NULL) {
fire_on_query_complete_ex(self, NULL, oqc);
if (oqc) SvREFCNT_dec(oqc);
return check_destroyed(self);
}
self->callback_depth++;
if (!IS_KEEPALIVE_CB(cb)) fire_on_query_complete_ex(self, NULL, oqc);
ClickHouse.xs view on Meta::CPAN
}
/* Write a SQL block comment "<slashstar> <cmt> <starslash> " to buf.
* Returns bytes written, or 0 if cmt is NULL. Both call sites reserve
* strlen(cmt) + 7 bytes for the comment plus the 7 framing bytes. */
static size_t qlc_emit_prefix(char *buf, const char *cmt) {
size_t cl, off = 0;
if (!cmt) return 0;
cl = strlen(cmt);
memcpy(buf + off, "/* ", 3); off += 3;
memcpy(buf + off, cmt, cl); off += cl;
memcpy(buf + off, " */ ", 4); off += 4;
return off;
}
static int has_http_unsafe_chars(const char *s) {
/* XS gives us NUL-terminated C strings, so only reject CR/LF. */
if (!s) return 0;
for (; *s; s++)
if (*s == '\r' || *s == '\n') return 1;
return 0;
}
/* Drop the first `n` bytes from recv_buf, shifting any remaining bytes left. */
static inline void recv_consume(struct ev_clickhouse_s *self, size_t n) {
/* Saturating: a user callback fired from inside the parser may
* call reset() which zeroes recv_len; an unguarded subtract would
* underflow size_t to a huge value and the next ch_read would write
* past the buffer. */
if (n >= self->recv_len) { self->recv_len = 0; return; }
memmove(self->recv_buf, self->recv_buf + n, self->recv_len - n);
self->recv_len -= n;
}
static void ensure_send_cap(struct ev_clickhouse_s *self, size_t need);
/* Replace send_buf content with `src` (heap-allocated, freed here). */
static inline void send_replace(struct ev_clickhouse_s *self, char *src, size_t len) {
ensure_send_cap(self, len);
Copy(src, self->send_buf, len, char);
self->send_len = len;
self->send_pos = 0;
Safefree(src);
}
static int is_ip_literal(const char *s) {
struct in_addr a4;
struct in6_addr a6;
return (inet_pton(AF_INET, s, &a4) == 1 ||
inet_pton(AF_INET6, s, &a6) == 1);
}
/* Tears down the socket + per-connection state, then fires on_disconnect.
* Returns 1 if on_disconnect freed self (caller must not touch self), else 0. */
static int cleanup_connection(ev_clickhouse_t *self) {
int was_connected = self->connected;
if (was_connected) emit_trace(self, "disconnect");
stop_reading(self);
stop_writing(self);
stop_keepalive(self);
stop_timing(self);
#ifdef HAVE_OPENSSL
if (self->ssl) {
SSL_shutdown(self->ssl);
SSL_free(self->ssl);
self->ssl = NULL;
}
if (self->ssl_ctx) {
SSL_CTX_free(self->ssl_ctx);
self->ssl_ctx = NULL;
}
#endif
if (self->fd >= 0) {
close(self->fd);
self->fd = -1;
}
self->connected = 0;
self->connecting = 0;
self->dns_pending = 0; /* finish/reset interrupts async DNS */
self->send_len = 0;
self->send_pos = 0;
self->recv_len = 0;
if (self->http_decoded) Safefree(self->http_decoded);
self->http_decoded = NULL;
self->http_decoded_len = 0;
self->http_decoded_cap = 0;
self->http_chunk_off = 0;
self->http_chunk_active = 0;
self->send_count = 0;
self->pending_addendum_finish = 0;
self->native_state = NATIVE_IDLE;
CLEAR_SV(self->native_rows);
CLEAR_SV(self->native_col_names);
CLEAR_SV(self->native_col_types);
CLEAR_SV(self->native_totals);
CLEAR_SV(self->native_extremes);
lc_free_dicts(self);
CLEAR_INSERT(self);
CLEAR_STR(self->insert_err);
/* Fire on_disconnect AFTER state is reset, so a handler that queues
* new queries or calls reconnect sees clean state. The handler can
* drop the last $ch ref (-> DESTROY); propagate that so callers
* don't touch a freed self. */
if (was_connected && self->on_disconnect)
return fire_zero_arg_cb(self, self->on_disconnect, "disconnect");
return 0;
}
/* Fire user error callback + on_query_complete (per-query override or
* connection-level â fire_on_query_complete_ex falls back). Honors the
* documented "fires after every query (success or error)" contract for
* cancelled queries. oqc is consumed (refcount-dec'd) here.
* HTTP keepalive PINGs are suppressed to match the success-path behavior:
* users instrumenting via on_query_complete shouldn't see spurious zero-
* row completions for pings they didn't initiate. */
static void fire_err_and_complete(ev_clickhouse_t *self, SV *cb,
const char *errmsg, SV *oqc) {
/* Fire on_query_complete BEFORE the user cb to match the order
* used by deliver_error / deliver_rows on the normal path â
* instrumentation observers expect the global hook to run first.
* Keepalive PINGs are suppressed to match the success-path
* behavior so observers don't see spurious zero-row completions. */
if (!IS_KEEPALIVE_CB(cb))
fire_on_query_complete_ex(self, errmsg, oqc);
if (oqc) SvREFCNT_dec(oqc);
/* A user error callback may drop the last ref to $ch (DESTROY runs
* deferred while callback_depth > 0). invoke_err_cb itself is safe
* either way â caller's outer magic check handles the next loop. */
invoke_err_cb(cb, errmsg);
}
/* Drain in-flight cb_queue, delivering errmsg to each callback and resetting
* send_count. Caller manages callback_depth. */
static void drain_cb_queue(ev_clickhouse_t *self, const char *errmsg) {
while (!ngx_queue_empty(&self->cb_queue)) {
SV *oqc = NULL;
SV *cb = pop_cb_ex(self, &oqc);
if (cb == NULL) break;
fire_err_and_complete(self, cb, errmsg, oqc);
if (self->magic != EV_CH_MAGIC) break;
}
self->send_count = 0;
}
/* Returns 1 if self was freed. */
static int cancel_pending(ev_clickhouse_t *self, const char *errmsg) {
self->callback_depth++;
while (!ngx_queue_empty(&self->send_queue)) {
ngx_queue_t *q = ngx_queue_head(&self->send_queue);
ev_ch_send_t *send = ngx_queue_data(q, ev_ch_send_t, queue);
SV *cb = send->cb;
SV *oqc = send->on_complete; /* transfer ownership for fire below */
send->on_complete = NULL;
ngx_queue_remove(q);
Safefree(send->data);
CLEAR_INSERT(send);
CLEAR_SV(send->on_data);
release_send(send);
self->pending_count--;
fire_err_and_complete(self, cb, errmsg, oqc);
if (self->magic != EV_CH_MAGIC) break;
}
drain_cb_queue(self, errmsg);
self->callback_depth--;
return check_destroyed(self);
}
/* --- I/O helpers (with optional TLS) --- */
static ssize_t ch_read(ev_clickhouse_t *self, void *buf, size_t len) {
#ifdef HAVE_OPENSSL
if (self->ssl) {
int ssl_len = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
int ret = SSL_read(self->ssl, buf, ssl_len);
if (ret <= 0) {
int err = SSL_get_error(self->ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
errno = EAGAIN;
ClickHouse.xs view on Meta::CPAN
static void write_native_settings(native_buf_t *b, HV *defaults, HV *overrides) {
HE *entry;
if (overrides) {
hv_iterinit(overrides);
while ((entry = hv_iternext(overrides))) {
I32 klen;
STRLEN vlen;
char *key = hv_iterkey(entry, &klen);
char *val = SvPV(hv_iterval(overrides, entry), vlen);
if (is_client_only_key(key, klen)) continue;
/* param_* keys go in the parameters block, not settings */
if (klen > 6 && memcmp(key, "param_", 6) == 0) continue;
nbuf_varuint(b, (uint64_t)klen);
nbuf_append(b, key, (size_t)klen);
nbuf_u8(b, 0); /* is_important = 0 */
nbuf_varuint(b, (uint64_t)vlen);
nbuf_append(b, val, vlen);
}
}
if (defaults) {
hv_iterinit(defaults);
while ((entry = hv_iternext(defaults))) {
I32 klen;
STRLEN vlen;
char *key = hv_iterkey(entry, &klen);
char *val = SvPV(hv_iterval(defaults, entry), vlen);
if (overrides && hv_exists(overrides, key, klen))
continue;
if (is_client_only_key(key, klen)) continue;
if (klen > 6 && memcmp(key, "param_", 6) == 0) continue;
nbuf_varuint(b, (uint64_t)klen);
nbuf_append(b, key, (size_t)klen);
nbuf_u8(b, 0); /* is_important = 0 */
nbuf_varuint(b, (uint64_t)vlen);
nbuf_append(b, val, vlen);
}
}
}
/* Textually included so all helpers stay file-local statics and the
* compiler sees one translation unit. Order matters: each file may
* call into helpers defined above it, but not below. xs/io.c must
* come last because it's the only file that reaches into every other
* subsystem (TCP/TLS, HTTP, native, types). */
#include "xs/codecs.c"
#include "xs/proto_http.c"
#include "xs/proto_native_build.c"
#include "xs/types.c"
#include "xs/proto_native_parse.c"
#include "xs/io.c"
/* --- XS interface --- */
MODULE = EV::ClickHouse PACKAGE = EV::ClickHouse
BOOT:
{
I_EV_API("EV::ClickHouse");
ch_openssl_init();
/* Permanent no-op CV used for internal callbacks (HTTP keepalive ping). */
keepalive_noop_cb = newRV_inc((SV*)get_cv("EV::ClickHouse::__keepalive_noop", GV_ADD));
/* Per-process rand() seed so reconnect_jitter desynchronises forks
* (otherwise every worker generates the same sequence and the
* jitter is uniform across the herd, defeating its purpose). */
srand((unsigned)time(NULL) ^ (unsigned)getpid());
}
EV::ClickHouse
_new(char *class, EV::Loop loop)
CODE:
{
PERL_UNUSED_VAR(class);
Newxz(RETVAL, 1, ev_clickhouse_t);
RETVAL->magic = EV_CH_MAGIC;
RETVAL->loop = loop;
RETVAL->fd = -1;
RETVAL->protocol = PROTO_HTTP;
ngx_queue_init(&RETVAL->cb_queue);
ngx_queue_init(&RETVAL->send_queue);
Newx(RETVAL->recv_buf, RECV_BUF_INIT, char);
RETVAL->recv_cap = RECV_BUF_INIT;
Newx(RETVAL->send_buf, SEND_BUF_INIT, char);
RETVAL->send_cap = SEND_BUF_INIT;
ev_init(&RETVAL->timer, timer_cb);
RETVAL->timer.data = (void *)RETVAL;
}
OUTPUT:
RETVAL
void
DESTROY(EV::ClickHouse self)
CODE:
{
if (self->magic != EV_CH_MAGIC) return;
stop_reading(self);
stop_writing(self);
stop_timing(self);
stop_keepalive(self);
if (self->reconnect_timing) {
ev_timer_stop(self->loop, &self->reconnect_timer);
self->reconnect_timing = 0;
}
if (PL_dirty) {
self->magic = EV_CH_FREED;
while (!ngx_queue_empty(&self->send_queue)) {
ngx_queue_t *q = ngx_queue_head(&self->send_queue);
ev_ch_send_t *send = ngx_queue_data(q, ev_ch_send_t, queue);
ngx_queue_remove(q);
Safefree(send->data);
CLEAR_INSERT(send);
CLEAR_SV(send->on_data);
CLEAR_SV(send->on_complete);
SvREFCNT_dec(send->cb);
release_send(send);
}
while (!ngx_queue_empty(&self->cb_queue)) {
ngx_queue_t *q = ngx_queue_head(&self->cb_queue);
ev_ch_cb_t *cbt = ngx_queue_data(q, ev_ch_cb_t, queue);
ngx_queue_remove(q);
CLEAR_SV(cbt->on_data);
CLEAR_SV(cbt->on_complete);
SvREFCNT_dec(cbt->cb);
release_cbt(cbt);
}
#ifdef HAVE_OPENSSL
if (self->ssl) { SSL_free(self->ssl); self->ssl = NULL; }
if (self->ssl_ctx) { SSL_CTX_free(self->ssl_ctx); self->ssl_ctx = NULL; }
#endif
if (self->fd >= 0) close(self->fd);
CLEAR_CONNECTION_HANDLERS(self);
CLEAR_PERSISTENT_STATE(self);
failover_free(self);
CLEAR_INSERT(self);
lc_free_dicts(self);
Safefree(self);
return;
}
if (cancel_pending(self, "object destroyed"))
return; /* inner DESTROY already freed self */
/* A user callback fired from cancel_pending may have called
* $ch->reset() which re-arms watchers / opens a fresh fd.
* Stop everything again before tearing the struct down so the
* EV loop can't dispatch into freed memory. */
stop_reading(self);
stop_writing(self);
stop_timing(self);
stop_keepalive(self);
if (self->reconnect_timing) {
ev_timer_stop(self->loop, &self->reconnect_timer);
self->reconnect_timing = 0;
}
#ifdef HAVE_OPENSSL
if (self->ssl) {
SSL_shutdown(self->ssl);
SSL_free(self->ssl);
self->ssl = NULL;
}
if (self->ssl_ctx) {
SSL_CTX_free(self->ssl_ctx);
self->ssl_ctx = NULL;
}
#endif
if (self->fd >= 0) {
close(self->fd);
self->fd = -1;
}
self->loop = NULL;
self->connected = 0;
CLEAR_CONNECTION_HANDLERS(self);
CLEAR_PERSISTENT_STATE(self);
failover_free(self);
lc_free_dicts(self);
CLEAR_INSERT(self);
self->magic = EV_CH_FREED;
if (self->callback_depth == 0) {
Safefree(self);
}
/* else: check_destroyed() will Safefree when callback_depth reaches 0 */
}
void
_set_tls_ca_file(EV::ClickHouse self, const char *path)
CODE:
{
CLEAR_STR(self->tls_ca_file);
self->tls_ca_file = safe_strdup(path);
}
void
_set_tls_cert_file(EV::ClickHouse self, const char *path)
CODE:
{
CLEAR_STR(self->tls_cert_file);
self->tls_cert_file = safe_strdup(path);
}
void
_set_tls_key_file(EV::ClickHouse self, const char *path)
CODE:
{
CLEAR_STR(self->tls_key_file);
self->tls_key_file = safe_strdup(path);
ClickHouse.xs view on Meta::CPAN
OUTPUT:
RETVAL
SV *
last_totals(EV::ClickHouse self)
CODE:
{
RETVAL = self->native_totals ? newRV_inc((SV*)self->native_totals)
: &PL_sv_undef;
}
OUTPUT:
RETVAL
SV *
last_extremes(EV::ClickHouse self)
CODE:
{
RETVAL = self->native_extremes ? newRV_inc((SV*)self->native_extremes)
: &PL_sv_undef;
}
OUTPUT:
RETVAL
SV *
profile_rows_before_limit(EV::ClickHouse self)
CODE:
{
RETVAL = newSVuv(self->profile_rows_before_limit);
}
OUTPUT:
RETVAL
SV *
profile_rows(EV::ClickHouse self)
CODE:
{
RETVAL = newSVuv(self->profile_rows);
}
OUTPUT:
RETVAL
SV *
profile_bytes(EV::ClickHouse self)
CODE:
{
RETVAL = newSVuv(self->profile_bytes);
}
OUTPUT:
RETVAL
SV*
on_trace(EV::ClickHouse self, SV *handler = NULL)
CODE:
{
RETVAL = handler_accessor(&self->on_trace, handler, items > 1);
}
OUTPUT:
RETVAL
void
_set_keepalive(EV::ClickHouse self, double val)
CODE:
{
self->keepalive = val;
}
void
_set_reconnect_delay(EV::ClickHouse self, double val)
CODE:
{
self->reconnect_delay = val;
}
void
_set_reconnect_max_delay(EV::ClickHouse self, double val)
CODE:
{
self->reconnect_max_delay = val;
}
void
_set_reconnect_jitter(EV::ClickHouse self, double val)
CODE:
{
self->reconnect_jitter = val < 0 ? 0 : val;
}
void
_set_reconnect_max_attempts(EV::ClickHouse self, int val)
CODE:
{
self->reconnect_max_attempts = val;
}
void
_set_progress_period(EV::ClickHouse self, double val)
CODE:
{
self->progress_period = val;
}
void
drain(EV::ClickHouse self, SV *cb)
CODE:
{
if (!(SvROK(cb) && SvTYPE(SvRV(cb)) == SVt_PVCV))
croak("drain callback must be a CODE reference");
CLEAR_SV(self->on_drain);
if (self->pending_count == 0 && ngx_queue_empty(&self->send_queue)) {
/* Nothing pending â fire immediately */
(void)fire_zero_arg_cb(self, cb, "drain");
} else {
self->on_drain = SvREFCNT_inc(cb);
}
}
void
cancel(EV::ClickHouse self)
CODE:
{
if (self->protocol == PROTO_NATIVE && self->send_count > 0) {
/* Send CLIENT_CANCEL packet */
native_buf_t pkt;
nbuf_init(&pkt);
( run in 1.583 second using v1.01-cache-2.11-cpan-14f38c9f855 )