Skip to content

Commit b8db6e6

Browse files
committed
feat: Add ext directory for C++ chDB library integration
Implement native extensions in the `ext` directory to enable interaction with the C++ library.
1 parent 6182e88 commit b8db6e6

File tree

8 files changed

+409
-0
lines changed

8 files changed

+409
-0
lines changed

ext/chdb/chdb.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <ruby.h>
2+
3+
#include "chdb.h"
4+
#include "constants.h"
5+
#include "connection.h"
6+
#include "local_result.h"
7+
8+
VALUE cChDBError;
9+
VALUE cLocalResult;
10+
11+
void init_chdb_constants() {
12+
VALUE mChDB = rb_define_module("ChDB");
13+
VALUE mChDBConstants = rb_define_module_under(mChDB, "Constants");
14+
VALUE mmChDBOpen = rb_define_module_under(mChDBConstants, "Open");
15+
16+
rb_define_const(mmChDBOpen, "READONLY", INT2FIX(CHDB_OPEN_READONLY));
17+
rb_define_const(mmChDBOpen, "READWRITE", INT2FIX(CHDB_OPEN_READWRITE));
18+
rb_define_const(mmChDBOpen, "CREATE", INT2FIX(CHDB_OPEN_CREATE));
19+
}
20+
21+
void init_exception() {
22+
VALUE mChDB = rb_define_module("ChDB");
23+
if (rb_const_defined(mChDB, rb_intern("Exception")))
24+
cChDBError = rb_const_get(mChDB, rb_intern("Exception"));
25+
else
26+
cChDBError = rb_define_class_under(mChDB, "Exception", rb_eStandardError);
27+
}
28+
29+
void init_local_result() {
30+
VALUE mChDB = rb_define_module("ChDB");
31+
cLocalResult = rb_define_class_under(mChDB, "LocalResult", rb_cObject);
32+
rb_define_alloc_func(cLocalResult, local_result_alloc);
33+
rb_define_method(cLocalResult, "buf", local_result_buf, 0);
34+
rb_define_method(cLocalResult, "elapsed", local_result_elapsed, 0);
35+
rb_define_method(cLocalResult, "rows_read", local_result_rows_read, 0);
36+
rb_define_method(cLocalResult, "bytes_read", local_result_bytes_read, 0);
37+
}
38+
39+
void init_connection() {
40+
VALUE mChDB = rb_define_module("ChDB");
41+
VALUE cConnection = rb_define_class_under(mChDB, "Connection", rb_cObject);
42+
rb_define_alloc_func(cConnection, connection_alloc);
43+
rb_define_method(cConnection, "initialize", connection_initialize, 2);
44+
rb_define_method(cConnection, "query", connection_query, 2);
45+
rb_define_method(cConnection, "close", connection_close, 0);
46+
}
47+
48+
49+
void Init_chdb(void)
50+
{
51+
DEBUG_PRINT("Initializing chdb extension");
52+
53+
init_chdb_constants();
54+
init_exception();
55+
init_local_result();
56+
init_connection();
57+
58+
DEBUG_PRINT("chdb extension initialized successfully");
59+
}

ext/chdb/chdb.h

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#pragma once
2+
3+
#ifdef __cplusplus
4+
# include <condition_variable>
5+
# include <cstddef>
6+
# include <cstdint>
7+
# include <mutex>
8+
# include <queue>
9+
# include <string>
10+
extern "C" {
11+
#else
12+
# include <stdbool.h>
13+
# include <stddef.h>
14+
# include <stdint.h>
15+
#endif
16+
17+
#define CHDB_EXPORT __attribute__((visibility("default")))
18+
struct local_result
19+
{
20+
char * buf;
21+
size_t len;
22+
void * _vec; // std::vector<char> *, for freeing
23+
double elapsed;
24+
uint64_t rows_read;
25+
uint64_t bytes_read;
26+
};
27+
28+
#ifdef __cplusplus
29+
struct local_result_v2
30+
{
31+
char * buf = nullptr;
32+
size_t len = 0;
33+
void * _vec = nullptr; // std::vector<char> *, for freeing
34+
double elapsed = 0.0;
35+
uint64_t rows_read = 0;
36+
uint64_t bytes_read = 0;
37+
char * error_message = nullptr;
38+
};
39+
#else
40+
struct local_result_v2
41+
{
42+
char * buf;
43+
size_t len;
44+
void * _vec; // std::vector<char> *, for freeing
45+
double elapsed;
46+
uint64_t rows_read;
47+
uint64_t bytes_read;
48+
char * error_message;
49+
};
50+
#endif
51+
52+
CHDB_EXPORT struct local_result * query_stable(int argc, char ** argv);
53+
CHDB_EXPORT void free_result(struct local_result * result);
54+
55+
CHDB_EXPORT struct local_result_v2 * query_stable_v2(int argc, char ** argv);
56+
CHDB_EXPORT void free_result_v2(struct local_result_v2 * result);
57+
58+
#ifdef __cplusplus
59+
struct query_request
60+
{
61+
std::string query;
62+
std::string format;
63+
};
64+
65+
struct query_queue
66+
{
67+
std::mutex mutex;
68+
std::condition_variable query_cv; // For query submission
69+
std::condition_variable result_cv; // For query result retrieval
70+
query_request current_query;
71+
local_result_v2 * current_result = nullptr;
72+
bool has_query = false;
73+
bool shutdown = false;
74+
bool cleanup_done = false;
75+
};
76+
#endif
77+
78+
/**
79+
* Connection structure for chDB
80+
* Contains server instance, connection state, and query processing queue
81+
*/
82+
struct chdb_conn
83+
{
84+
void * server; /* ClickHouse LocalServer instance */
85+
bool connected; /* Connection state flag */
86+
void * queue; /* Query processing queue */
87+
};
88+
89+
/**
90+
* Creates a new chDB connection.
91+
* Only one active connection is allowed per process.
92+
* Creating a new connection with different path requires closing existing connection.
93+
*
94+
* @param argc Number of command-line arguments
95+
* @param argv Command-line arguments array (--path=<db_path> to specify database location)
96+
* @return Pointer to connection pointer, or NULL on failure
97+
* @note Default path is ":memory:" if not specified
98+
*/
99+
CHDB_EXPORT struct chdb_conn ** connect_chdb(int argc, char ** argv);
100+
101+
/**
102+
* Closes an existing chDB connection and cleans up resources.
103+
* Thread-safe function that handles connection shutdown and cleanup.
104+
*
105+
* @param conn Pointer to connection pointer to close
106+
*/
107+
CHDB_EXPORT void close_conn(struct chdb_conn ** conn);
108+
109+
/**
110+
* Executes a query on the given connection.
111+
* Thread-safe function that handles query execution in a separate thread.
112+
*
113+
* @param conn Connection to execute query on
114+
* @param query SQL query string to execute
115+
* @param format Output format string (e.g., "CSV", default format)
116+
* @return Query result structure containing output or error message
117+
* @note Returns error result if connection is invalid or closed
118+
*/
119+
CHDB_EXPORT struct local_result_v2 * query_conn(struct chdb_conn * conn, const char * query, const char * format);
120+
121+
#ifdef __cplusplus
122+
}
123+
#endif

ext/chdb/connection.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include "connection.h"
2+
3+
VALUE connection_alloc(VALUE klass) {
4+
Connection *conn = ALLOC(Connection);
5+
DEBUG_PRINT("Allocating Connection: %p", (void*)conn);
6+
conn->c_conn = NULL;
7+
return rb_data_typed_object_wrap(klass, conn, &ConnectionType);
8+
}
9+
10+
VALUE connection_initialize(VALUE self, VALUE argc, VALUE argv) {
11+
Check_Type(argc, T_FIXNUM);
12+
Check_Type(argv, T_ARRAY);
13+
14+
int c_argc = NUM2INT(argc);
15+
char **c_argv = ALLOC_N(char *, c_argc);
16+
17+
for (int i = 0; i < c_argc; i++)
18+
{
19+
VALUE arg = rb_ary_entry(argv, i);
20+
c_argv[i] = StringValueCStr(arg);
21+
}
22+
23+
Connection *conn;
24+
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
25+
conn->c_conn = connect_chdb(c_argc, c_argv);
26+
27+
if (!conn->c_conn)
28+
{
29+
xfree(c_argv);
30+
rb_raise(cChDBError, "Failed to connect to chDB");
31+
}
32+
33+
xfree(c_argv);
34+
rb_gc_unregister_address(&argv);
35+
return self;
36+
}
37+
38+
VALUE connection_query(VALUE self, VALUE query, VALUE format) {
39+
Connection *conn;
40+
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
41+
42+
Check_Type(query, T_STRING);
43+
Check_Type(format, T_STRING);
44+
45+
struct local_result_v2 *c_result = query_conn(
46+
*conn->c_conn,
47+
StringValueCStr(query),
48+
StringValueCStr(format)
49+
);
50+
51+
if (!c_result)
52+
rb_raise(cChDBError, "Query failed with nil result");
53+
54+
if (c_result->error_message)
55+
{
56+
VALUE error_message = rb_str_new_cstr(c_result->error_message);
57+
rb_raise(cChDBError, "CHDB error: %s", StringValueCStr(error_message));
58+
}
59+
60+
VALUE result_obj = rb_class_new_instance(0, NULL, cLocalResult);
61+
LocalResult *result;
62+
TypedData_Get_Struct(result_obj, LocalResult, &LocalResultType, result);
63+
result->c_result = c_result;
64+
65+
return result_obj;
66+
}
67+
68+
VALUE connection_close(VALUE self) {
69+
Connection *conn;
70+
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
71+
72+
if (conn->c_conn) {
73+
close_conn(conn->c_conn);
74+
conn->c_conn = NULL;
75+
}
76+
return Qnil;
77+
}
78+
79+
static void connection_free(void *ptr) {
80+
Connection *conn = (Connection *)ptr;
81+
DEBUG_PRINT("Closing connection: %p", (void*)conn->c_conn);
82+
if (conn->c_conn) {
83+
close_conn(conn->c_conn);
84+
}
85+
free(conn);
86+
}
87+
88+
const rb_data_type_t ConnectionType = {
89+
"Connection",
90+
{NULL, connection_free, NULL},
91+
};
92+
93+
// 其他 Connection 方法保持不变...

ext/chdb/connection.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef CHDB_CONNECTION_H
2+
#define CHDB_CONNECTION_H
3+
4+
#include <ruby.h>
5+
6+
typedef struct {
7+
struct chdb_conn **c_conn;
8+
} Connection;
9+
10+
extern const rb_data_type_t ConnectionType;
11+
12+
VALUE connection_alloc(VALUE klass);
13+
14+
VALUE connection_initialize(VALUE self, VALUE argc, VALUE argv);
15+
16+
VALUE connection_query(VALUE self, VALUE query, VALUE format);
17+
18+
VALUE connection_close(VALUE self);
19+
20+
#endif

ext/chdb/constants.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef CHDB_CONSTANTS_H
2+
#define CHDB_CONSTANTS_H
3+
4+
#define CHDB_OPEN_READONLY 0x00000001
5+
#define CHDB_OPEN_READWRITE 0x00000002
6+
#define CHDB_OPEN_CREATE 0x00000004
7+
8+
#define CHDB_DEBUG 0
9+
#if CHDB_DEBUG
10+
#define DEBUG_PRINT(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
11+
#else
12+
#define DEBUG_PRINT(fmt, ...) ((void)0)
13+
#endif
14+
15+
#endif

ext/chdb/extconf.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
require "mkmf"
4+
5+
system_found = have_library("chdb") && have_header("chdb.h")
6+
7+
unless system_found
8+
abort "chdb.h or chdb library not found! Please install chdb development files.\n" \
9+
"You can try installing with: gem install chdb -- --with-opt-dir=/usr/local/lib \n" \
10+
"Or any other path that contains chdb.h and libchdb.so"
11+
end
12+
13+
$srcs = Dir['*.c'] + ['chdb.c']
14+
15+
if RbConfig::CONFIG["GCC"] == 'yes'
16+
$CFLAGS << " -Wno-declaration-after-statement" # rubocop:disable Style/GlobalVars
17+
$CFLAGS = $CFLAGS.gsub(/-Wno-self-assign|-Wno-parentheses-equality|-Wno-constant-logical-operand/, "") # rubocop:disable Style/GlobalVars
18+
end
19+
20+
create_makefile("chdb/chdb")

ext/chdb/local_result.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include "local_result.h"
2+
3+
static void local_result_free(void *ptr) {
4+
LocalResult *result = (LocalResult *)ptr;
5+
DEBUG_PRINT("Freeing LocalResult: %p", (void*)result);
6+
if (result->c_result) {
7+
free_result_v2(result->c_result);
8+
}
9+
free(result);
10+
}
11+
12+
const rb_data_type_t LocalResultType = {
13+
"LocalResult",
14+
{NULL, local_result_free, NULL},
15+
};
16+
17+
VALUE local_result_alloc(VALUE klass) {
18+
LocalResult *result = ALLOC(LocalResult);
19+
DEBUG_PRINT("Allocating LocalResult: %p", (void*)result);
20+
result->c_result = NULL;
21+
return rb_data_typed_object_wrap(klass, result, &LocalResultType);
22+
}
23+
24+
VALUE local_result_buf(VALUE self) {
25+
LocalResult *result;
26+
TypedData_Get_Struct(self, LocalResult, &LocalResultType, result);
27+
28+
if (!result->c_result || !result->c_result->buf)
29+
{
30+
DEBUG_PRINT("Buffer access attempted on empty result");
31+
return Qnil;
32+
}
33+
34+
DEBUG_PRINT("Returning buffer of length %zu", result->c_result->len);
35+
return rb_str_new(result->c_result->buf, result->c_result->len);
36+
}
37+
38+
VALUE local_result_elapsed(VALUE self) {
39+
LocalResult *result;
40+
TypedData_Get_Struct(self, LocalResult, &LocalResultType, result);
41+
DEBUG_PRINT("Query elapsed time: %f", result->c_result->elapsed);
42+
return DBL2NUM(result->c_result->elapsed);
43+
}
44+
45+
VALUE local_result_rows_read(VALUE self) {
46+
LocalResult *result;
47+
TypedData_Get_Struct(self, LocalResult, &LocalResultType, result);
48+
DEBUG_PRINT("Rows read: %" PRIu64, result->c_result->rows_read);
49+
return ULONG2NUM(result->c_result->rows_read);
50+
}
51+
52+
VALUE local_result_bytes_read(VALUE self) {
53+
LocalResult *result;
54+
TypedData_Get_Struct(self, LocalResult, &LocalResultType, result);
55+
DEBUG_PRINT("Bytes read: %" PRIu64, result->c_result->bytes_read);
56+
return ULONG2NUM(result->c_result->bytes_read);
57+
}

0 commit comments

Comments
 (0)