chdb-ruby 0.1.0.rc.2 → 0.2.0.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +75 -19
- data/dependencies.yml +1 -1
- data/ext/chdb/chdb.c +2 -11
- data/ext/chdb/chdb_handle.c +49 -7
- data/ext/chdb/chdb_handle.h +15 -1
- data/ext/chdb/connection.c +87 -4
- data/ext/chdb/connection.h +6 -0
- data/ext/chdb/constants.c +14 -0
- data/ext/chdb/constants.h +2 -0
- data/ext/chdb/exception.c +1 -0
- data/ext/chdb/local_result.c +4 -3
- data/ext/chdb/local_result.h +2 -0
- data/ext/chdb/streaming_result.c +39 -0
- data/ext/chdb/streaming_result.h +20 -0
- data/lib/chdb/data_path.rb +10 -13
- data/lib/chdb/database.rb +25 -5
- data/lib/chdb/parameter_binding.rb +2 -8
- data/lib/chdb/result_set.rb +40 -0
- data/lib/chdb/sql_processor.rb +7 -0
- data/lib/chdb/statement.rb +20 -4
- data/lib/chdb/streaming_result.rb +8 -0
- data/lib/chdb/version.rb +1 -1
- metadata +13 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3dffafce42be0cf41b18bdc22e47ae17a4dfea85169a641b2068a9d0e54b306
|
4
|
+
data.tar.gz: e0cde6afb8872dd2a72f5dc8b90392ec0163707daa430907aa126e63a57bbf77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d198f71a5348388d2b49eb5819014490d0300a20fb1d48c8342da35162d41b650d3c872f1d65b15777d2a73a47b9ca5438f551a066c05b59faab2f2b6148bd02
|
7
|
+
data.tar.gz: f73e40324a300d87849350637257a593f484ba2b8a8f7e40c671969ed9dbc2c1737437641dbe96a7a119d22b33bff753a514bd66f612453c0f9305ffea620a4a
|
data/README.md
CHANGED
@@ -20,69 +20,122 @@ Note that this module is only compatible with ChDB 3.0.0 or newer.
|
|
20
20
|
|
21
21
|
## Quick start
|
22
22
|
|
23
|
-
|
23
|
+
Before using chdb-ruby, install the gem first. This will download the libchdb C++ library dependencies, so please be patient:
|
24
|
+
```bash
|
25
|
+
gem install chdb-ruby
|
26
|
+
```
|
27
|
+
|
28
|
+
Below are examples of common interfaces usage:
|
29
|
+
|
30
|
+
```ruby
|
24
31
|
require 'chdb'
|
25
32
|
|
26
33
|
# Open a database
|
27
|
-
|
34
|
+
# Parameter explanation:
|
35
|
+
# 1. path supports two formats:
|
36
|
+
# - ":memory:" in-memory temporary database (data destroyed on close)
|
37
|
+
# - "file:/path/to/db" file-based persistent database
|
38
|
+
# Configuration parameters can be appended via URL-style query (e.g. 'file:test.db?results_as_hash=true')
|
39
|
+
# 2. options hash supports:
|
40
|
+
# - results_as_hash: controls whether result sets return as hashes (default: arrays)
|
41
|
+
db = ChDB::Database.new('file:test.db', results_as_hash: true)
|
42
|
+
|
43
|
+
# Create a database
|
44
|
+
db.execute('CREATE DATABASE IF NOT EXISTS test')
|
28
45
|
|
29
46
|
# Create a table
|
47
|
+
db.execute('DROP TABLE IF EXISTS test.test_table')
|
30
48
|
rows = db.execute <<-SQL
|
31
|
-
CREATE TABLE test_table(
|
49
|
+
CREATE TABLE test.test_table(
|
32
50
|
id Int32,
|
33
51
|
name String)
|
34
52
|
ENGINE = MergeTree()
|
35
|
-
ORDER BY id
|
53
|
+
ORDER BY id
|
36
54
|
SQL
|
37
55
|
|
38
56
|
# Execute a few inserts
|
39
57
|
{
|
40
|
-
|
41
|
-
|
58
|
+
1 => 'Alice',
|
59
|
+
2 => 'Bob'
|
42
60
|
}.each do |pair|
|
43
|
-
db.execute 'INSERT INTO test_table VALUES ( ?, ? )', pair
|
61
|
+
db.execute 'INSERT INTO test.test_table VALUES ( ?, ? )', pair
|
44
62
|
end
|
45
63
|
|
46
64
|
# Find a few rows
|
47
|
-
db.execute('SELECT * FROM test_table ORDER BY id') do |row|
|
65
|
+
db.execute('SELECT * FROM test.test_table ORDER BY id') do |row|
|
48
66
|
p row
|
49
67
|
end
|
50
68
|
# [{ 'id' => '1', 'name' => 'Alice' },
|
51
69
|
# { 'id' => '2', 'name' => 'Bob' }]
|
52
70
|
|
71
|
+
# When you need to open another database, you must first close the previous database
|
72
|
+
db.close()
|
73
|
+
|
53
74
|
# Open another database
|
54
|
-
db = ChDB::Database.new '
|
75
|
+
db = ChDB::Database.new 'file:test.db'
|
55
76
|
|
56
77
|
# Create another table
|
78
|
+
db.execute('DROP TABLE IF EXISTS test.test2_table')
|
57
79
|
rows = db.execute <<-SQL
|
58
|
-
CREATE TABLE test2_table(
|
80
|
+
CREATE TABLE test.test2_table(
|
59
81
|
id Int32,
|
60
82
|
name String)
|
61
83
|
ENGINE = MergeTree()
|
62
|
-
ORDER BY id
|
84
|
+
ORDER BY id
|
63
85
|
SQL
|
64
86
|
|
65
87
|
# Execute inserts with parameter markers
|
66
|
-
db.execute('INSERT INTO test2_table (id, name)
|
88
|
+
db.execute('INSERT INTO test.test2_table (id, name)
|
67
89
|
VALUES (?, ?)', [3, 'Charlie'])
|
68
90
|
|
69
|
-
|
91
|
+
# Find rows with the first row displaying column names
|
92
|
+
db.execute2('SELECT * FROM test.test2_table') do |row|
|
70
93
|
p row
|
71
94
|
end
|
72
|
-
# [
|
95
|
+
# ["id", "name"]
|
96
|
+
# ["3", "Charlie"]
|
97
|
+
|
98
|
+
# Close the database
|
99
|
+
db.close()
|
100
|
+
|
101
|
+
# Use ChDB::Database.open to automatically close the database connection:
|
102
|
+
ChDB::Database.open('file:test.db') do |db|
|
103
|
+
result = db.execute('SELECT 1')
|
104
|
+
p result.to_a # => [["1"]]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Query with specific output formats (CSV, JSON, etc.):
|
108
|
+
# See more details at https://clickhouse.com/docs/interfaces/formats.
|
109
|
+
ChDB::Database.open(':memory:') do |db|
|
110
|
+
csv_data = db.query_with_format('SELECT 1 as a, 2 as b', 'CSV')
|
111
|
+
p csv_data
|
112
|
+
# "1,2\n"
|
113
|
+
|
114
|
+
json_data = db.query_with_format('SELECT 1 as a, 2 as b', 'JSON')
|
115
|
+
p json_data
|
116
|
+
end
|
117
|
+
|
118
|
+
# Execute streaming query
|
119
|
+
ChDB::Database.open(':memory:') do |db|
|
120
|
+
total_rows = 0
|
121
|
+
collected = []
|
122
|
+
db.send_query('SELECT * FROM numbers(200000)') do |chunk|
|
123
|
+
collected << chunk.buf
|
124
|
+
total_rows += chunk.rows_read
|
125
|
+
end
|
126
|
+
p total_rows # => 200000
|
127
|
+
end
|
73
128
|
```
|
74
129
|
|
75
130
|
## Thread Safety
|
76
131
|
|
77
|
-
When using `ChDB::Database.new` to open a
|
78
|
-
|
79
|
-
For example, the following code is fine because only the database
|
80
|
-
instance is shared among threads:
|
132
|
+
When using `ChDB::Database.new` or `ChDB::Database.open` to open a database connection, all read/write operations within that session are thread-safe. However, currently only one active database connection is allowed per process. Therefore, when you need to open another database connection, you must first close the previous connection.
|
133
|
+
**Please note that `ChDB::Database.new`, `ChDB::Database.open`, and `ChDB::Database.close` methods themselves are not thread-safe.** If used in multi-threaded environments, external synchronization must be implemented to prevent concurrent calls to these methods, which could lead to undefined behavior.
|
81
134
|
|
82
135
|
```ruby
|
83
136
|
require 'chdb'
|
84
137
|
|
85
|
-
db = ChDB::Database.new
|
138
|
+
db = ChDB::Database.new ':memory:'
|
86
139
|
|
87
140
|
latch = Queue.new
|
88
141
|
|
@@ -95,6 +148,9 @@ ts = 10.times.map {
|
|
95
148
|
10.times { latch << nil }
|
96
149
|
|
97
150
|
p ts.map(&:value)
|
151
|
+
# [[["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]]]
|
152
|
+
|
153
|
+
db.close()
|
98
154
|
```
|
99
155
|
|
100
156
|
Other instances can be shared among threads, but they require that you provide
|
data/dependencies.yml
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
chdb:
|
2
|
-
version: "3.
|
2
|
+
version: "3.2.0"
|
data/ext/chdb/chdb.c
CHANGED
@@ -6,17 +6,7 @@
|
|
6
6
|
#include "connection.h"
|
7
7
|
#include "exception.h"
|
8
8
|
#include "local_result.h"
|
9
|
-
|
10
|
-
void init_chdb_constants()
|
11
|
-
{
|
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
|
-
}
|
9
|
+
#include "streaming_result.h"
|
20
10
|
|
21
11
|
void Init_chdb_native()
|
22
12
|
{
|
@@ -26,6 +16,7 @@ void Init_chdb_native()
|
|
26
16
|
init_chdb_handle();
|
27
17
|
init_chdb_constants();
|
28
18
|
init_local_result();
|
19
|
+
init_streaming_result();
|
29
20
|
init_connection();
|
30
21
|
|
31
22
|
DEBUG_PRINT("chdb extension initialized successfully");
|
data/ext/chdb/chdb_handle.c
CHANGED
@@ -2,30 +2,53 @@
|
|
2
2
|
|
3
3
|
#include <dlfcn.h>
|
4
4
|
#include <ruby.h>
|
5
|
-
|
5
|
+
#include "constants.h"
|
6
6
|
#include "exception.h"
|
7
7
|
|
8
8
|
void *chdb_handle = NULL;
|
9
9
|
connect_chdb_func connect_chdb_ptr = NULL;
|
10
10
|
close_conn_func close_conn_ptr = NULL;
|
11
11
|
query_conn_func query_conn_ptr = NULL;
|
12
|
+
free_result_v2_func free_result_v2_ptr = NULL;
|
13
|
+
query_conn_streaming_func query_conn_streaming_ptr = NULL;
|
14
|
+
chdb_streaming_result_error_func chdb_streaming_result_error_ptr = NULL;
|
15
|
+
chdb_streaming_fetch_result_func chdb_streaming_fetch_result_ptr = NULL;
|
16
|
+
chdb_streaming_cancel_query_func chdb_streaming_cancel_query_ptr = NULL;
|
17
|
+
chdb_destroy_result_func chdb_destroy_result_ptr = NULL;
|
12
18
|
|
13
|
-
VALUE get_chdb_rb_path(
|
19
|
+
VALUE get_chdb_rb_path()
|
14
20
|
{
|
15
21
|
VALUE chdb_module = rb_const_get(rb_cObject, rb_intern("ChDB"));
|
16
22
|
return rb_funcall(chdb_module, rb_intern("lib_file_path"), 0);
|
17
23
|
}
|
18
24
|
|
25
|
+
void close_chdb_handle()
|
26
|
+
{
|
27
|
+
if (chdb_handle)
|
28
|
+
{
|
29
|
+
dlclose(chdb_handle);
|
30
|
+
chdb_handle = NULL;
|
31
|
+
DEBUG_PRINT("Close chdb handle");
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
19
35
|
void init_chdb_handle()
|
20
36
|
{
|
21
37
|
VALUE rb_path = get_chdb_rb_path();
|
22
38
|
VALUE lib_dir = rb_file_dirname(rb_file_dirname(rb_path));
|
23
39
|
VALUE lib_path = rb_str_cat2(lib_dir, "/lib/chdb/lib/libchdb.so");
|
24
|
-
|
40
|
+
|
41
|
+
DEBUG_PRINT("chdb.rb path from Ruby: %s\n", StringValueCStr(lib_path));
|
25
42
|
|
26
43
|
connect_chdb_ptr = NULL;
|
27
44
|
close_conn_ptr = NULL;
|
28
45
|
query_conn_ptr = NULL;
|
46
|
+
free_result_v2_ptr = NULL;
|
47
|
+
query_conn_streaming_ptr = NULL;
|
48
|
+
chdb_streaming_result_error_ptr = NULL;
|
49
|
+
chdb_streaming_fetch_result_ptr = NULL;
|
50
|
+
chdb_streaming_cancel_query_ptr = NULL;
|
51
|
+
chdb_destroy_result_ptr = NULL;
|
29
52
|
|
30
53
|
chdb_handle = dlopen(RSTRING_PTR(lib_path), RTLD_LAZY | RTLD_GLOBAL);
|
31
54
|
if (!chdb_handle)
|
@@ -37,13 +60,32 @@ void init_chdb_handle()
|
|
37
60
|
connect_chdb_ptr = (connect_chdb_func)dlsym(chdb_handle, "connect_chdb");
|
38
61
|
close_conn_ptr = (close_conn_func)dlsym(chdb_handle, "close_conn");
|
39
62
|
query_conn_ptr = (query_conn_func)dlsym(chdb_handle, "query_conn");
|
63
|
+
free_result_v2_ptr = (free_result_v2_func)dlsym(chdb_handle, "free_result_v2");
|
64
|
+
query_conn_streaming_ptr = (query_conn_streaming_func)dlsym(chdb_handle, "query_conn_streaming");
|
65
|
+
chdb_streaming_result_error_ptr = (chdb_streaming_result_error_func)dlsym(chdb_handle, "chdb_streaming_result_error");
|
66
|
+
chdb_streaming_fetch_result_ptr = (chdb_streaming_fetch_result_func)dlsym(chdb_handle, "chdb_streaming_fetch_result");
|
67
|
+
chdb_streaming_cancel_query_ptr = (chdb_streaming_cancel_query_func)dlsym(chdb_handle, "chdb_streaming_cancel_query");
|
68
|
+
chdb_destroy_result_ptr = (chdb_destroy_result_func)dlsym(chdb_handle, "chdb_destroy_result");
|
40
69
|
|
41
|
-
if (!connect_chdb_ptr || !close_conn_ptr || !query_conn_ptr
|
70
|
+
if (!connect_chdb_ptr || !close_conn_ptr || !query_conn_ptr || !free_result_v2_ptr ||
|
71
|
+
!query_conn_streaming_ptr || !chdb_streaming_result_error_ptr || !chdb_streaming_fetch_result_ptr ||
|
72
|
+
!chdb_streaming_cancel_query_ptr || !chdb_destroy_result_ptr)
|
42
73
|
{
|
43
|
-
|
74
|
+
close_chdb_handle();
|
75
|
+
|
76
|
+
rb_raise(cChDBError,
|
77
|
+
"Symbol loading failed: %s\nMissing functions: connect_chdb(%p), close_conn(%p), query_conn(%p), free_result_v2(%p), query_conn_streaming(%p), chdb_streaming_result_error(%p), chdb_streaming_fetch_result(%p), chdb_streaming_cancel_query(%p), chdb_destroy_result(%p)",
|
44
78
|
dlerror(),
|
45
79
|
(void*)connect_chdb_ptr,
|
46
80
|
(void*)close_conn_ptr,
|
47
|
-
(void*)query_conn_ptr
|
81
|
+
(void*)query_conn_ptr,
|
82
|
+
(void*)free_result_v2_ptr,
|
83
|
+
(void*)query_conn_streaming_ptr,
|
84
|
+
(void*)chdb_streaming_result_error_ptr,
|
85
|
+
(void*)chdb_streaming_fetch_result_ptr,
|
86
|
+
(void*)chdb_streaming_cancel_query_ptr,
|
87
|
+
(void*)chdb_destroy_result_ptr);
|
48
88
|
}
|
49
|
-
|
89
|
+
|
90
|
+
rb_set_end_proc(close_chdb_handle, 0);
|
91
|
+
}
|
data/ext/chdb/chdb_handle.h
CHANGED
@@ -1,16 +1,30 @@
|
|
1
1
|
#ifndef CHDB_HANDLE_H
|
2
2
|
#define CHDB_HANDLE_H
|
3
3
|
|
4
|
+
#include "include/chdb.h"
|
5
|
+
|
4
6
|
typedef struct chdb_conn **(*connect_chdb_func)(int, char**);
|
5
7
|
typedef void (*close_conn_func)(struct chdb_conn**);
|
6
8
|
typedef struct local_result_v2 *(*query_conn_func)(struct chdb_conn*, const char*, const char*);
|
9
|
+
typedef void (*free_result_v2_func)(struct local_result_v2*);
|
10
|
+
typedef chdb_streaming_result *(*query_conn_streaming_func)(struct chdb_conn*, const char*, const char*);
|
11
|
+
typedef const char *(*chdb_streaming_result_error_func)(chdb_streaming_result*);
|
12
|
+
typedef struct local_result_v2 *(*chdb_streaming_fetch_result_func)(struct chdb_conn*, chdb_streaming_result*);
|
13
|
+
typedef void (*chdb_streaming_cancel_query_func)(struct chdb_conn*, chdb_streaming_result*);
|
14
|
+
typedef void (*chdb_destroy_result_func)(chdb_streaming_result*);
|
7
15
|
|
8
16
|
extern connect_chdb_func connect_chdb_ptr;
|
9
17
|
extern close_conn_func close_conn_ptr;
|
10
18
|
extern query_conn_func query_conn_ptr;
|
19
|
+
extern free_result_v2_func free_result_v2_ptr;
|
20
|
+
extern query_conn_streaming_func query_conn_streaming_ptr;
|
21
|
+
extern chdb_streaming_result_error_func chdb_streaming_result_error_ptr;
|
22
|
+
extern chdb_streaming_fetch_result_func chdb_streaming_fetch_result_ptr;
|
23
|
+
extern chdb_streaming_cancel_query_func chdb_streaming_cancel_query_ptr;
|
24
|
+
extern chdb_destroy_result_func chdb_destroy_result_ptr;
|
11
25
|
|
12
26
|
extern void *chdb_handle;
|
13
27
|
|
14
28
|
void init_chdb_handle();
|
15
29
|
|
16
|
-
#endif
|
30
|
+
#endif
|
data/ext/chdb/connection.c
CHANGED
@@ -5,11 +5,13 @@
|
|
5
5
|
#include "exception.h"
|
6
6
|
#include "include/chdb.h"
|
7
7
|
#include "local_result.h"
|
8
|
+
#include "streaming_result.h"
|
8
9
|
|
9
|
-
|
10
|
+
void connection_free(void *ptr)
|
10
11
|
{
|
11
12
|
Connection *conn = (Connection *)ptr;
|
12
|
-
DEBUG_PRINT("Closing connection: %p", (void*)conn->c_conn);
|
13
|
+
DEBUG_PRINT("Closing connection in connection_free: %p", (void*)conn->c_conn);
|
14
|
+
|
13
15
|
if (conn->c_conn)
|
14
16
|
{
|
15
17
|
close_conn_ptr(conn->c_conn);
|
@@ -31,6 +33,9 @@ void init_connection()
|
|
31
33
|
rb_define_alloc_func(cConnection, connection_alloc);
|
32
34
|
rb_define_method(cConnection, "initialize", connection_initialize, 2);
|
33
35
|
rb_define_method(cConnection, "query", connection_query, 2);
|
36
|
+
rb_define_method(cConnection, "send_query", connection_streaming_query, 2);
|
37
|
+
rb_define_method(cConnection, "fetch_streaming_result", connection_streaming_fecth_result, 1);
|
38
|
+
rb_define_method(cConnection, "cancel_streaming_query", connection_streaming_cancel_query, 1);
|
34
39
|
rb_define_method(cConnection, "close", connection_close, 0);
|
35
40
|
}
|
36
41
|
|
@@ -67,7 +72,6 @@ VALUE connection_initialize(VALUE self, VALUE argc, VALUE argv)
|
|
67
72
|
}
|
68
73
|
|
69
74
|
xfree(c_argv);
|
70
|
-
rb_gc_unregister_address(&argv);
|
71
75
|
return self;
|
72
76
|
}
|
73
77
|
|
@@ -93,6 +97,7 @@ VALUE connection_query(VALUE self, VALUE query, VALUE format)
|
|
93
97
|
if (c_result->error_message)
|
94
98
|
{
|
95
99
|
VALUE error_message = rb_str_new_cstr(c_result->error_message);
|
100
|
+
free_result_v2_ptr(c_result);
|
96
101
|
rb_raise(cChDBError, "CHDB error: %s", StringValueCStr(error_message));
|
97
102
|
}
|
98
103
|
|
@@ -104,12 +109,90 @@ VALUE connection_query(VALUE self, VALUE query, VALUE format)
|
|
104
109
|
return result_obj;
|
105
110
|
}
|
106
111
|
|
112
|
+
VALUE connection_streaming_query(VALUE self, VALUE query, VALUE format)
|
113
|
+
{
|
114
|
+
Connection *conn;
|
115
|
+
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
|
116
|
+
|
117
|
+
Check_Type(query, T_STRING);
|
118
|
+
Check_Type(format, T_STRING);
|
119
|
+
|
120
|
+
chdb_streaming_result *c_result = query_conn_streaming_ptr(
|
121
|
+
*conn->c_conn,
|
122
|
+
StringValueCStr(query),
|
123
|
+
StringValueCStr(format)
|
124
|
+
);
|
125
|
+
|
126
|
+
if (!c_result)
|
127
|
+
{
|
128
|
+
rb_raise(cChDBError, "Query failed with nil streaming result");
|
129
|
+
}
|
130
|
+
|
131
|
+
const char *error = chdb_streaming_result_error_ptr(c_result);
|
132
|
+
if (error)
|
133
|
+
{
|
134
|
+
VALUE error_message = rb_str_new_cstr(error);
|
135
|
+
chdb_destroy_result_ptr(c_result);
|
136
|
+
rb_raise(cChDBError, "CHDB error: %s", StringValueCStr(error_message));
|
137
|
+
}
|
138
|
+
|
139
|
+
VALUE result_obj = rb_class_new_instance(0, NULL, cStreamingResult);
|
140
|
+
StreamingResult *result;
|
141
|
+
TypedData_Get_Struct(result_obj, StreamingResult, &StreamingResultType, result);
|
142
|
+
result->c_result = c_result;
|
143
|
+
|
144
|
+
return result_obj;
|
145
|
+
}
|
146
|
+
|
147
|
+
VALUE connection_streaming_fecth_result(VALUE self, VALUE streaming_result)
|
148
|
+
{
|
149
|
+
Connection *conn;
|
150
|
+
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
|
151
|
+
|
152
|
+
StreamingResult *result;
|
153
|
+
TypedData_Get_Struct(streaming_result, StreamingResult, &StreamingResultType, result);
|
154
|
+
|
155
|
+
struct local_result_v2 *c_result = chdb_streaming_fetch_result_ptr(*conn->c_conn, result->c_result);
|
156
|
+
|
157
|
+
if (!c_result)
|
158
|
+
{
|
159
|
+
rb_raise(cChDBError, "Failed to fetch streaming result");
|
160
|
+
}
|
161
|
+
|
162
|
+
if (c_result->error_message)
|
163
|
+
{
|
164
|
+
VALUE error_message = rb_str_new_cstr(c_result->error_message);
|
165
|
+
free_result_v2_ptr(c_result);
|
166
|
+
rb_raise(cChDBError, "CHDB error: %s", StringValueCStr(error_message));
|
167
|
+
}
|
168
|
+
|
169
|
+
VALUE result_obj = rb_class_new_instance(0, NULL, cLocalResult);
|
170
|
+
LocalResult *local_result;
|
171
|
+
TypedData_Get_Struct(result_obj, LocalResult, &LocalResultType, local_result);
|
172
|
+
local_result->c_result = c_result;
|
173
|
+
|
174
|
+
return result_obj;
|
175
|
+
}
|
176
|
+
|
177
|
+
VALUE connection_streaming_cancel_query(VALUE self, VALUE streaming_result)
|
178
|
+
{
|
179
|
+
Connection *conn;
|
180
|
+
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
|
181
|
+
|
182
|
+
StreamingResult *result;
|
183
|
+
TypedData_Get_Struct(streaming_result, StreamingResult, &StreamingResultType, result);
|
184
|
+
|
185
|
+
chdb_streaming_cancel_query_ptr(*conn->c_conn, result->c_result);
|
186
|
+
return Qnil;
|
187
|
+
}
|
188
|
+
|
107
189
|
VALUE connection_close(VALUE self)
|
108
190
|
{
|
109
191
|
Connection *conn;
|
110
192
|
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
|
193
|
+
DEBUG_PRINT("Closing connection in connection_close: %p", (void*)conn->c_conn);
|
111
194
|
|
112
|
-
if (conn->c_conn)
|
195
|
+
if (conn && conn->c_conn)
|
113
196
|
{
|
114
197
|
close_conn_ptr(conn->c_conn);
|
115
198
|
conn->c_conn = NULL;
|
data/ext/chdb/connection.h
CHANGED
@@ -18,6 +18,12 @@ VALUE connection_initialize(VALUE self, VALUE argc, VALUE argv);
|
|
18
18
|
|
19
19
|
VALUE connection_query(VALUE self, VALUE query, VALUE format);
|
20
20
|
|
21
|
+
VALUE connection_streaming_query(VALUE self, VALUE query, VALUE format);
|
22
|
+
|
23
|
+
VALUE connection_streaming_fecth_result(VALUE self, VALUE streaming_result);
|
24
|
+
|
25
|
+
VALUE connection_streaming_cancel_query(VALUE self, VALUE streaming_result);
|
26
|
+
|
21
27
|
VALUE connection_close(VALUE self);
|
22
28
|
|
23
29
|
#endif
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#include "constants.h"
|
2
|
+
|
3
|
+
#include <ruby.h>
|
4
|
+
|
5
|
+
void init_chdb_constants()
|
6
|
+
{
|
7
|
+
VALUE mChDB = rb_define_module("ChDB");
|
8
|
+
VALUE mChDBConstants = rb_define_module_under(mChDB, "Constants");
|
9
|
+
VALUE mmChDBOpen = rb_define_module_under(mChDBConstants, "Open");
|
10
|
+
|
11
|
+
rb_define_const(mmChDBOpen, "READONLY", INT2FIX(CHDB_OPEN_READONLY));
|
12
|
+
rb_define_const(mmChDBOpen, "READWRITE", INT2FIX(CHDB_OPEN_READWRITE));
|
13
|
+
rb_define_const(mmChDBOpen, "CREATE", INT2FIX(CHDB_OPEN_CREATE));
|
14
|
+
}
|
data/ext/chdb/constants.h
CHANGED
data/ext/chdb/exception.c
CHANGED
data/ext/chdb/local_result.c
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
#include "local_result.h"
|
2
2
|
|
3
3
|
#include "constants.h"
|
4
|
-
#include "
|
4
|
+
#include "chdb_handle.h"
|
5
5
|
|
6
6
|
VALUE cLocalResult;
|
7
7
|
|
8
|
-
|
8
|
+
void local_result_free(void *ptr)
|
9
9
|
{
|
10
10
|
LocalResult *result = (LocalResult *)ptr;
|
11
11
|
DEBUG_PRINT("Freeing LocalResult: %p", (void*)result);
|
12
12
|
if (result->c_result)
|
13
13
|
{
|
14
|
-
|
14
|
+
DEBUG_PRINT("Freeing local_result_v2: %p", (void*)result->c_result);
|
15
|
+
free_result_v2_ptr(result->c_result);
|
15
16
|
}
|
16
17
|
free(result);
|
17
18
|
}
|
data/ext/chdb/local_result.h
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
#include "streaming_result.h"
|
2
|
+
|
3
|
+
#include "constants.h"
|
4
|
+
#include "chdb_handle.h"
|
5
|
+
|
6
|
+
VALUE cStreamingResult;
|
7
|
+
|
8
|
+
void streaming_result_free(void *ptr)
|
9
|
+
{
|
10
|
+
StreamingResult *result = (StreamingResult *)ptr;
|
11
|
+
DEBUG_PRINT("Freeing StreamingResult: %p", (void*)result);
|
12
|
+
if (result->c_result)
|
13
|
+
{
|
14
|
+
DEBUG_PRINT("Freeing chdb_streaming_result: %p", (void*)result->c_result);
|
15
|
+
chdb_destroy_result_ptr(result->c_result);
|
16
|
+
}
|
17
|
+
free(result);
|
18
|
+
}
|
19
|
+
|
20
|
+
const rb_data_type_t StreamingResultType =
|
21
|
+
{
|
22
|
+
"StreamingResult",
|
23
|
+
{NULL, streaming_result_free, NULL},
|
24
|
+
};
|
25
|
+
|
26
|
+
void init_streaming_result()
|
27
|
+
{
|
28
|
+
VALUE mChDB = rb_define_module("ChDB");
|
29
|
+
cStreamingResult = rb_define_class_under(mChDB, "StreamingResult", rb_cObject);
|
30
|
+
rb_define_alloc_func(cStreamingResult, streaming_result_alloc);
|
31
|
+
}
|
32
|
+
|
33
|
+
VALUE streaming_result_alloc(VALUE klass)
|
34
|
+
{
|
35
|
+
StreamingResult *result = ALLOC(StreamingResult);
|
36
|
+
DEBUG_PRINT("Allocating StreamingResult: %p", (void*)result);
|
37
|
+
result->c_result = NULL;
|
38
|
+
return rb_data_typed_object_wrap(klass, result, &StreamingResultType);
|
39
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef CHDB_STREAMING_RESULT_H
|
2
|
+
#define CHDB_STREAMING_RESULT_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
|
6
|
+
#include "include/chdb.h"
|
7
|
+
|
8
|
+
typedef struct
|
9
|
+
{
|
10
|
+
chdb_streaming_result *c_result;
|
11
|
+
} StreamingResult;
|
12
|
+
|
13
|
+
extern VALUE cStreamingResult;
|
14
|
+
extern const rb_data_type_t StreamingResultType;
|
15
|
+
|
16
|
+
void init_streaming_result();
|
17
|
+
|
18
|
+
VALUE streaming_result_alloc(VALUE klass);
|
19
|
+
|
20
|
+
#endif
|
data/lib/chdb/data_path.rb
CHANGED
@@ -23,7 +23,7 @@ module ChDB
|
|
23
23
|
|
24
24
|
def generate_arguments # rubocop:disable Metrics/MethodLength
|
25
25
|
args = ['clickhouse', "--path=#{@dir_path}"]
|
26
|
-
excluded_keys = %
|
26
|
+
excluded_keys = %w[results_as_hash readonly readwrite flags]
|
27
27
|
|
28
28
|
@query_params.each do |key, value|
|
29
29
|
next if excluded_keys.include?(key)
|
@@ -72,8 +72,7 @@ module ChDB
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def merge_options(options)
|
75
|
-
@query_params = @query_params.merge(options)
|
76
|
-
# @query_params = @query_params.merge(options.transform_keys(&:to_s))
|
75
|
+
@query_params = @query_params.merge(options.transform_keys(&:to_s))
|
77
76
|
end
|
78
77
|
|
79
78
|
def directory_path(path)
|
@@ -87,31 +86,29 @@ module ChDB
|
|
87
86
|
end
|
88
87
|
|
89
88
|
def ensure_directory_exists
|
90
|
-
if
|
91
|
-
raise DirectoryNotFoundException, "Directory #{@dir_path} required" unless Dir.exist?(@dir_path)
|
89
|
+
return if Dir.exist?(@dir_path)
|
92
90
|
|
93
|
-
|
94
|
-
end
|
91
|
+
raise DirectoryNotFoundException, "Directory #{@dir_path} required" if @mode.nobits?(Constants::Open::CREATE)
|
95
92
|
|
96
93
|
FileUtils.mkdir_p(@dir_path, mode: 0o755)
|
97
94
|
end
|
98
95
|
|
99
96
|
def check_params # rubocop:disable Metrics/MethodLength
|
100
97
|
@mode = Constants::Open::READWRITE | Constants::Open::CREATE
|
101
|
-
@mode = Constants::Open::READONLY if @query_params[
|
98
|
+
@mode = Constants::Open::READONLY if @query_params['readonly']
|
102
99
|
|
103
|
-
if @query_params[
|
104
|
-
raise InvalidArgumentException, 'conflicting options: readonly and readwrite' if @query_params[
|
100
|
+
if @query_params['readwrite']
|
101
|
+
raise InvalidArgumentException, 'conflicting options: readonly and readwrite' if @query_params['readonly']
|
105
102
|
|
106
103
|
@mode = Constants::Open::READWRITE
|
107
104
|
end
|
108
105
|
|
109
|
-
return unless @query_params[
|
110
|
-
if @query_params[
|
106
|
+
return unless @query_params['flags']
|
107
|
+
if @query_params['readonly'] || @query_params['readwrite']
|
111
108
|
raise InvalidArgumentException, 'conflicting options: flags with readonly and/or readwrite'
|
112
109
|
end
|
113
110
|
|
114
|
-
@mode = @query_params[
|
111
|
+
@mode = @query_params['flags']
|
115
112
|
end
|
116
113
|
|
117
114
|
def remove_file_prefix(str)
|
data/lib/chdb/database.rb
CHANGED
@@ -13,6 +13,8 @@ module ChDB
|
|
13
13
|
# Represents a database connection and provides methods to interact with the database.
|
14
14
|
class Database # rubocop:disable Metrics/ClassLength
|
15
15
|
class << self
|
16
|
+
@@instance = nil # rubocop:disable Style/ClassVars
|
17
|
+
|
16
18
|
# Without block works exactly as new.
|
17
19
|
# With block, like new closes the database at the end, but unlike new
|
18
20
|
# returns the result of the block instead of the database instance.
|
@@ -36,16 +38,20 @@ module ChDB
|
|
36
38
|
attr_accessor :results_as_hash, :conn
|
37
39
|
|
38
40
|
def initialize(file, options = {}) # rubocop:disable Metrics/MethodLength
|
41
|
+
raise InternalException, 'Existing database instance is not closed' if @@instance
|
42
|
+
|
39
43
|
file = file.to_path if file.respond_to? :to_path
|
40
44
|
|
41
45
|
@data_path = DataPath.new(file, options)
|
42
|
-
@results_as_hash = @data_path.query_params[
|
46
|
+
@results_as_hash = @data_path.query_params['results_as_hash'] || false
|
43
47
|
@readonly = @data_path.mode & Constants::Open::READONLY != 0
|
44
48
|
|
45
49
|
argv = @data_path.generate_arguments
|
46
50
|
@conn = ChDB::Connection.new(argv.size, argv)
|
47
51
|
@closed = false
|
48
52
|
|
53
|
+
@@instance = self # rubocop:disable Style/ClassVars
|
54
|
+
|
49
55
|
return unless block_given?
|
50
56
|
|
51
57
|
begin
|
@@ -61,6 +67,8 @@ module ChDB
|
|
61
67
|
@data_path.close if @data_path.respond_to?(:close)
|
62
68
|
@conn.close if @conn.respond_to?(:close)
|
63
69
|
@closed = true
|
70
|
+
|
71
|
+
@@instance = nil # rubocop:disable Style/ClassVars
|
64
72
|
end
|
65
73
|
|
66
74
|
def closed?
|
@@ -86,14 +94,14 @@ module ChDB
|
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
89
|
-
def execute2(sql, *bind_vars, &) # rubocop:disable Metrics/MethodLength
|
97
|
+
def execute2(sql, *bind_vars, &block) # rubocop:disable Metrics/MethodLength
|
90
98
|
prepare(sql) do |stmt|
|
91
99
|
result = stmt.execute(*bind_vars)
|
92
100
|
stmt.parse
|
93
101
|
|
94
|
-
if
|
102
|
+
if block
|
95
103
|
yield stmt.columns
|
96
|
-
result.each(&)
|
104
|
+
result.each(&block)
|
97
105
|
else
|
98
106
|
return result.each_with_object([stmt.columns]) do |row, arr|
|
99
107
|
arr << row
|
@@ -111,7 +119,7 @@ module ChDB
|
|
111
119
|
end
|
112
120
|
end
|
113
121
|
|
114
|
-
def query_with_format(sql,
|
122
|
+
def query_with_format(sql, format = 'CSV', bind_vars = [])
|
115
123
|
result = prepare(sql).execute_with_format(bind_vars, format)
|
116
124
|
if block_given?
|
117
125
|
yield result
|
@@ -120,6 +128,18 @@ module ChDB
|
|
120
128
|
end
|
121
129
|
end
|
122
130
|
|
131
|
+
def send_query(sql, format = 'CSV', bind_vars = [], &block)
|
132
|
+
prepare(sql) do |stmt|
|
133
|
+
result = stmt.send_query(bind_vars, format)
|
134
|
+
|
135
|
+
if block
|
136
|
+
result.each(&block)
|
137
|
+
else
|
138
|
+
result
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
123
143
|
def get_first_row(sql, *bind_vars)
|
124
144
|
execute(sql, *bind_vars).first
|
125
145
|
end
|
@@ -11,14 +11,8 @@ module ChDB
|
|
11
11
|
def bind_params(*bind_vars)
|
12
12
|
index = 1
|
13
13
|
bind_vars.flatten.each do |var|
|
14
|
-
|
15
|
-
|
16
|
-
# Currently using positional parameters instead of named parameters
|
17
|
-
var.each { |key, val| bind_param key, val }
|
18
|
-
else
|
19
|
-
bind_param index, var
|
20
|
-
index += 1
|
21
|
-
end
|
14
|
+
bind_param index, var
|
15
|
+
index += 1
|
22
16
|
end
|
23
17
|
end
|
24
18
|
end
|
data/lib/chdb/result_set.rb
CHANGED
@@ -54,4 +54,44 @@ module ChDB
|
|
54
54
|
class HashResultSet < ResultSet # :nodoc:
|
55
55
|
alias next next_hash
|
56
56
|
end
|
57
|
+
|
58
|
+
class StreamingResultSet
|
59
|
+
include Enumerable
|
60
|
+
|
61
|
+
def initialize(db, streaming_result)
|
62
|
+
@db = db
|
63
|
+
@streaming_result = streaming_result
|
64
|
+
@done = false
|
65
|
+
end
|
66
|
+
|
67
|
+
def eof?
|
68
|
+
@done
|
69
|
+
end
|
70
|
+
|
71
|
+
def next
|
72
|
+
return nil if @done
|
73
|
+
|
74
|
+
result = @db.conn.fetch_streaming_result(@streaming_result)
|
75
|
+
|
76
|
+
if result.nil? || result.rows_read.zero?
|
77
|
+
@done = true
|
78
|
+
nil
|
79
|
+
else
|
80
|
+
result
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def each
|
85
|
+
while (node = self.next)
|
86
|
+
yield node
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def cancel
|
91
|
+
return nil if @done
|
92
|
+
|
93
|
+
@db.conn.cancel_streaming_query(@streaming_result)
|
94
|
+
@done = true
|
95
|
+
end
|
96
|
+
end
|
57
97
|
end
|
data/lib/chdb/sql_processor.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'chdb/errors'
|
4
|
+
|
3
5
|
module ChDB
|
4
6
|
# This module provides functionality for processing SQL queries,
|
5
7
|
# including binding variables and escaping values.
|
6
8
|
module SQLProcessor
|
7
9
|
def process_sql
|
10
|
+
placeholders = @sql.scan(/(?<!\\)\?/).size
|
11
|
+
if placeholders != @bind_vars.size
|
12
|
+
raise ChDB::SQLException, "Wrong number of bind variables (#{@bind_vars.size} for #{placeholders})"
|
13
|
+
end
|
14
|
+
|
8
15
|
escaped_values = @bind_vars.map { |v| escape(v) }
|
9
16
|
sql = @sql.dup
|
10
17
|
sql.gsub(/(?<!\\)\?/) { escaped_values.shift or '?' }
|
data/lib/chdb/statement.rb
CHANGED
@@ -8,6 +8,7 @@ rescue LoadError
|
|
8
8
|
require 'chdb/chdb_native'
|
9
9
|
end
|
10
10
|
require 'chdb/local_result'
|
11
|
+
require 'chdb/streaming_result'
|
11
12
|
require 'chdb/result_set'
|
12
13
|
require 'chdb/result_handler'
|
13
14
|
require 'chdb/parameter_binding'
|
@@ -71,6 +72,21 @@ module ChDB
|
|
71
72
|
@result.buf
|
72
73
|
end
|
73
74
|
|
75
|
+
def send_query(*bind_vars, format)
|
76
|
+
reset! if @executed
|
77
|
+
|
78
|
+
bind_params(*bind_vars) unless bind_vars.empty?
|
79
|
+
|
80
|
+
my_processed_sql = process_sql
|
81
|
+
streaming_result = @connection.conn.send_query(my_processed_sql, format)
|
82
|
+
streaming_result.output_format = format
|
83
|
+
|
84
|
+
results = StreamingResultSet.new(@connection, streaming_result)
|
85
|
+
|
86
|
+
yield results if block_given?
|
87
|
+
results
|
88
|
+
end
|
89
|
+
|
74
90
|
def reset!
|
75
91
|
@executed = false
|
76
92
|
@parsed = false
|
@@ -78,7 +94,7 @@ module ChDB
|
|
78
94
|
@bind_vars.clear
|
79
95
|
@parsed_data.clear
|
80
96
|
@columns.clear
|
81
|
-
@
|
97
|
+
@result = nil
|
82
98
|
end
|
83
99
|
|
84
100
|
def step
|
@@ -101,14 +117,14 @@ module ChDB
|
|
101
117
|
end
|
102
118
|
|
103
119
|
@parsed = true
|
104
|
-
@
|
120
|
+
@result = nil
|
105
121
|
end
|
106
122
|
|
107
123
|
private
|
108
124
|
|
109
125
|
def validate_inputs(db, sql_str)
|
110
|
-
raise
|
111
|
-
raise
|
126
|
+
raise InvalidArgumentException, 'SQL statement cannot be nil' if sql_str.nil?
|
127
|
+
raise InvalidArgumentException, 'prepare called on a closed database' if db.nil? || db.closed?
|
112
128
|
end
|
113
129
|
|
114
130
|
def encode_sql(sql_str)
|
data/lib/chdb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chdb-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.rc.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xiaozhe Yu
|
8
8
|
- Auxten Wang
|
9
|
+
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2025-
|
12
|
+
date: 2025-05-07 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: csv
|
@@ -27,6 +28,7 @@ dependencies:
|
|
27
28
|
description: |
|
28
29
|
Ruby library to interface with the chDB database engine (https://clickhouse.com/docs/chdb). Precompiled
|
29
30
|
binaries are available for common platforms for recent versions of Ruby.
|
31
|
+
email:
|
30
32
|
executables: []
|
31
33
|
extensions:
|
32
34
|
- ext/chdb/extconf.rb
|
@@ -41,12 +43,15 @@ files:
|
|
41
43
|
- ext/chdb/chdb_handle.h
|
42
44
|
- ext/chdb/connection.c
|
43
45
|
- ext/chdb/connection.h
|
46
|
+
- ext/chdb/constants.c
|
44
47
|
- ext/chdb/constants.h
|
45
48
|
- ext/chdb/exception.c
|
46
49
|
- ext/chdb/exception.h
|
47
50
|
- ext/chdb/extconf.rb
|
48
51
|
- ext/chdb/local_result.c
|
49
52
|
- ext/chdb/local_result.h
|
53
|
+
- ext/chdb/streaming_result.c
|
54
|
+
- ext/chdb/streaming_result.h
|
50
55
|
- lib/chdb.rb
|
51
56
|
- lib/chdb/constants.rb
|
52
57
|
- lib/chdb/data_path.rb
|
@@ -58,6 +63,7 @@ files:
|
|
58
63
|
- lib/chdb/result_set.rb
|
59
64
|
- lib/chdb/sql_processor.rb
|
60
65
|
- lib/chdb/statement.rb
|
66
|
+
- lib/chdb/streaming_result.rb
|
61
67
|
- lib/chdb/version.rb
|
62
68
|
- lib/chdb/version_info.rb
|
63
69
|
homepage: https://github.com/chdb-io/chdb-ruby
|
@@ -69,6 +75,7 @@ metadata:
|
|
69
75
|
changelog_uri: https://github.com/chdb-io/chdb-ruby/blob/main/CHANGELOG.md
|
70
76
|
source_code_uri: https://github.com/chdb-io/chdb-ruby
|
71
77
|
rubygems_mfa_required: 'true'
|
78
|
+
post_install_message:
|
72
79
|
rdoc_options:
|
73
80
|
- "--main"
|
74
81
|
- README.md
|
@@ -81,11 +88,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
88
|
version: '3.1'
|
82
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
90
|
requirements:
|
84
|
-
- - "
|
91
|
+
- - ">"
|
85
92
|
- !ruby/object:Gem::Version
|
86
|
-
version:
|
93
|
+
version: 1.3.1
|
87
94
|
requirements: []
|
88
|
-
rubygems_version: 3.
|
95
|
+
rubygems_version: 3.3.27
|
96
|
+
signing_key:
|
89
97
|
specification_version: 4
|
90
98
|
summary: Ruby library to interface with the chDB database engine (https://clickhouse.com/docs/chdb).
|
91
99
|
test_files: []
|