EV-ClickHouse
view release on metacpan or search on metacpan
ClickHouse.xs view on Meta::CPAN
#define SERVER_EXTREMES 8
#define SERVER_LOG 10
#define SERVER_TABLE_COLUMNS 11
#define SERVER_PROFILE_EVENTS 14
#define SERVER_TIMEZONE_UPDATE 17
/* Query kind */
#define QUERY_INITIAL 1
/* Query stage */
#define STAGE_COMPLETE 2
/* Native protocol states */
#define NATIVE_IDLE 0
#define NATIVE_WAIT_HELLO 1
#define NATIVE_WAIT_RESULT 2
#define NATIVE_WAIT_INSERT_META 3
/* Decode flags for column value formatting (opt-in) */
#define DECODE_DT_STR (1 << 0) /* Date/DateTime/DateTime64 â string */
#define DECODE_DEC_SCALE (1 << 1) /* Decimal â scaled NV */
#define DECODE_ENUM_STR (1 << 2) /* Enum â string label */
#define DECODE_NAMED_ROWS (1 << 3) /* results as arrayref of hashrefs */
struct ev_clickhouse_s {
unsigned int magic;
struct ev_loop *loop;
int fd;
ev_io rio, wio;
ev_timer timer;
int reading, writing, timing;
int connected, connecting;
int dns_pending; /* set while EV::cares is resolving the host;
query()/insert()/ping() queue against this
state so calls between new() and connect
don't croak with "not connected" */
unsigned int connect_gen; /* bumped by every start_connect; lets
fail_connection notice when the user
started a new connect from on_error */
int protocol; /* PROTO_HTTP or PROTO_NATIVE */
#ifdef HAVE_OPENSSL
SSL_CTX *ssl_ctx;
SSL *ssl;
#endif
int tls_enabled;
char *tls_ca_file;
char *tls_cert_file; /* client certificate (mutual TLS) */
char *tls_key_file; /* client private key (mutual TLS) */
/* connection params */
char *host, *user, *password, *database;
unsigned int port;
/* send/recv buffers */
char *send_buf;
size_t send_len, send_pos, send_cap;
char *recv_buf;
size_t recv_len, recv_cap;
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 */
ClickHouse.xs view on Meta::CPAN
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. */
ClickHouse.xs view on Meta::CPAN
* 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. */
( run in 0.699 second using v1.01-cache-2.11-cpan-b16cb0d3907 )