chdb-ruby 0.1.0.rc.2 → 0.1.0
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 +64 -19
- data/ext/chdb/chdb.c +0 -11
- data/ext/chdb/chdb_handle.c +27 -7
- data/ext/chdb/chdb_handle.h +3 -1
- data/ext/chdb/connection.c +6 -4
- 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 -2
- data/lib/chdb/data_path.rb +10 -13
- data/lib/chdb/database.rb +10 -2
- data/lib/chdb/parameter_binding.rb +2 -8
- data/lib/chdb/sql_processor.rb +7 -0
- data/lib/chdb/statement.rb +2 -2
- data/lib/chdb/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b53978c961a9a237f689559adad514d7b95ad435ddfa83284bd78dbf985a339
|
4
|
+
data.tar.gz: 4d5d284e83f4f52800739f39ed2185b629182a39e10f359f3c884e4fc27c9de5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05d288d130263d52120d1741351e081d05c072c992b9f7f05cc91be3454b2f423971295dfb670acee179cd1905017b24ead943fc9cc67b4747f155edc6541c41
|
7
|
+
data.tar.gz: 4607787c66d0f16b3a74377d1e6e43a6cf3ab7a5aac60b27b30fccfaa2fc6a5eff09f780d611e690a9805278a5a104a99eab5f458b97718949f45446b4b5ccb3
|
data/README.md
CHANGED
@@ -20,69 +20,111 @@ 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
|
73
117
|
```
|
74
118
|
|
75
119
|
## Thread Safety
|
76
120
|
|
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:
|
121
|
+
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.
|
122
|
+
**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
123
|
|
82
124
|
```ruby
|
83
125
|
require 'chdb'
|
84
126
|
|
85
|
-
db = ChDB::Database.new
|
127
|
+
db = ChDB::Database.new ':memory:'
|
86
128
|
|
87
129
|
latch = Queue.new
|
88
130
|
|
@@ -95,6 +137,9 @@ ts = 10.times.map {
|
|
95
137
|
10.times { latch << nil }
|
96
138
|
|
97
139
|
p ts.map(&:value)
|
140
|
+
# [[["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]], [["1"]]]
|
141
|
+
|
142
|
+
db.close()
|
98
143
|
```
|
99
144
|
|
100
145
|
Other instances can be shared among threads, but they require that you provide
|
data/ext/chdb/chdb.c
CHANGED
@@ -7,17 +7,6 @@
|
|
7
7
|
#include "exception.h"
|
8
8
|
#include "local_result.h"
|
9
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
|
-
}
|
20
|
-
|
21
10
|
void Init_chdb_native()
|
22
11
|
{
|
23
12
|
DEBUG_PRINT("Initializing chdb extension");
|
data/ext/chdb/chdb_handle.c
CHANGED
@@ -2,30 +2,43 @@
|
|
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;
|
12
13
|
|
13
|
-
VALUE get_chdb_rb_path(
|
14
|
+
VALUE get_chdb_rb_path()
|
14
15
|
{
|
15
16
|
VALUE chdb_module = rb_const_get(rb_cObject, rb_intern("ChDB"));
|
16
17
|
return rb_funcall(chdb_module, rb_intern("lib_file_path"), 0);
|
17
18
|
}
|
18
19
|
|
20
|
+
void close_chdb_handle()
|
21
|
+
{
|
22
|
+
if (chdb_handle)
|
23
|
+
{
|
24
|
+
dlclose(chdb_handle);
|
25
|
+
chdb_handle = NULL;
|
26
|
+
DEBUG_PRINT("Close chdb handle");
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
19
30
|
void init_chdb_handle()
|
20
31
|
{
|
21
32
|
VALUE rb_path = get_chdb_rb_path();
|
22
33
|
VALUE lib_dir = rb_file_dirname(rb_file_dirname(rb_path));
|
23
34
|
VALUE lib_path = rb_str_cat2(lib_dir, "/lib/chdb/lib/libchdb.so");
|
24
|
-
|
35
|
+
|
36
|
+
DEBUG_PRINT("chdb.rb path from Ruby: %s\n", StringValueCStr(lib_path));
|
25
37
|
|
26
38
|
connect_chdb_ptr = NULL;
|
27
39
|
close_conn_ptr = NULL;
|
28
40
|
query_conn_ptr = NULL;
|
41
|
+
free_result_v2_ptr = NULL;
|
29
42
|
|
30
43
|
chdb_handle = dlopen(RSTRING_PTR(lib_path), RTLD_LAZY | RTLD_GLOBAL);
|
31
44
|
if (!chdb_handle)
|
@@ -37,13 +50,20 @@ void init_chdb_handle()
|
|
37
50
|
connect_chdb_ptr = (connect_chdb_func)dlsym(chdb_handle, "connect_chdb");
|
38
51
|
close_conn_ptr = (close_conn_func)dlsym(chdb_handle, "close_conn");
|
39
52
|
query_conn_ptr = (query_conn_func)dlsym(chdb_handle, "query_conn");
|
53
|
+
free_result_v2_ptr = (free_result_v2_func)dlsym(chdb_handle, "free_result_v2");
|
40
54
|
|
41
|
-
if (!connect_chdb_ptr || !close_conn_ptr || !query_conn_ptr)
|
55
|
+
if (!connect_chdb_ptr || !close_conn_ptr || !query_conn_ptr || !free_result_v2_ptr)
|
42
56
|
{
|
43
|
-
|
57
|
+
close_chdb_handle();
|
58
|
+
|
59
|
+
rb_raise(cChDBError,
|
60
|
+
"Symbol loading failed: %s\nMissing functions: connect_chdb(%p) close_conn(%p) query_conn(%p), free_result_v2(%p)",
|
44
61
|
dlerror(),
|
45
62
|
(void*)connect_chdb_ptr,
|
46
63
|
(void*)close_conn_ptr,
|
47
|
-
(void*)query_conn_ptr
|
64
|
+
(void*)query_conn_ptr,
|
65
|
+
(void*)free_result_v2_ptr);
|
48
66
|
}
|
49
|
-
|
67
|
+
|
68
|
+
rb_set_end_proc(close_chdb_handle, 0);
|
69
|
+
}
|
data/ext/chdb/chdb_handle.h
CHANGED
@@ -4,13 +4,15 @@
|
|
4
4
|
typedef struct chdb_conn **(*connect_chdb_func)(int, char**);
|
5
5
|
typedef void (*close_conn_func)(struct chdb_conn**);
|
6
6
|
typedef struct local_result_v2 *(*query_conn_func)(struct chdb_conn*, const char*, const char*);
|
7
|
+
typedef void (*free_result_v2_func)(struct local_result_v2*);
|
7
8
|
|
8
9
|
extern connect_chdb_func connect_chdb_ptr;
|
9
10
|
extern close_conn_func close_conn_ptr;
|
10
11
|
extern query_conn_func query_conn_ptr;
|
12
|
+
extern free_result_v2_func free_result_v2_ptr;
|
11
13
|
|
12
14
|
extern void *chdb_handle;
|
13
15
|
|
14
16
|
void init_chdb_handle();
|
15
17
|
|
16
|
-
#endif
|
18
|
+
#endif
|
data/ext/chdb/connection.c
CHANGED
@@ -6,10 +6,11 @@
|
|
6
6
|
#include "include/chdb.h"
|
7
7
|
#include "local_result.h"
|
8
8
|
|
9
|
-
|
9
|
+
void connection_free(void *ptr)
|
10
10
|
{
|
11
11
|
Connection *conn = (Connection *)ptr;
|
12
|
-
DEBUG_PRINT("Closing connection: %p", (void*)conn->c_conn);
|
12
|
+
DEBUG_PRINT("Closing connection in connection_free: %p", (void*)conn->c_conn);
|
13
|
+
|
13
14
|
if (conn->c_conn)
|
14
15
|
{
|
15
16
|
close_conn_ptr(conn->c_conn);
|
@@ -67,7 +68,6 @@ VALUE connection_initialize(VALUE self, VALUE argc, VALUE argv)
|
|
67
68
|
}
|
68
69
|
|
69
70
|
xfree(c_argv);
|
70
|
-
rb_gc_unregister_address(&argv);
|
71
71
|
return self;
|
72
72
|
}
|
73
73
|
|
@@ -93,6 +93,7 @@ VALUE connection_query(VALUE self, VALUE query, VALUE format)
|
|
93
93
|
if (c_result->error_message)
|
94
94
|
{
|
95
95
|
VALUE error_message = rb_str_new_cstr(c_result->error_message);
|
96
|
+
free_result_v2_ptr(c_result);
|
96
97
|
rb_raise(cChDBError, "CHDB error: %s", StringValueCStr(error_message));
|
97
98
|
}
|
98
99
|
|
@@ -108,8 +109,9 @@ VALUE connection_close(VALUE self)
|
|
108
109
|
{
|
109
110
|
Connection *conn;
|
110
111
|
TypedData_Get_Struct(self, Connection, &ConnectionType, conn);
|
112
|
+
DEBUG_PRINT("Closing connection in connection_close: %p", (void*)conn->c_conn);
|
111
113
|
|
112
|
-
if (conn->c_conn)
|
114
|
+
if (conn && conn->c_conn)
|
113
115
|
{
|
114
116
|
close_conn_ptr(conn->c_conn);
|
115
117
|
conn->c_conn = NULL;
|
@@ -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
@@ -2,16 +2,18 @@
|
|
2
2
|
|
3
3
|
#include "constants.h"
|
4
4
|
#include "include/chdb.h"
|
5
|
+
#include "chdb_handle.h"
|
5
6
|
|
6
7
|
VALUE cLocalResult;
|
7
8
|
|
8
|
-
|
9
|
+
void local_result_free(void *ptr)
|
9
10
|
{
|
10
11
|
LocalResult *result = (LocalResult *)ptr;
|
11
12
|
DEBUG_PRINT("Freeing LocalResult: %p", (void*)result);
|
12
13
|
if (result->c_result)
|
13
14
|
{
|
14
|
-
|
15
|
+
DEBUG_PRINT("Freeing local_result_v2: %p", (void*)result->c_result);
|
16
|
+
free_result_v2_ptr(result->c_result);
|
15
17
|
}
|
16
18
|
free(result);
|
17
19
|
}
|
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?
|
@@ -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
|
@@ -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/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
@@ -107,8 +107,8 @@ module ChDB
|
|
107
107
|
private
|
108
108
|
|
109
109
|
def validate_inputs(db, sql_str)
|
110
|
-
raise
|
111
|
-
raise
|
110
|
+
raise InvalidArgumentException, 'SQL statement cannot be nil' if sql_str.nil?
|
111
|
+
raise InvalidArgumentException, 'prepare called on a closed database' if db.nil? || db.closed?
|
112
112
|
end
|
113
113
|
|
114
114
|
def encode_sql(sql_str)
|
data/lib/chdb/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chdb-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xiaozhe Yu
|
8
8
|
- Auxten Wang
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
11
|
+
date: 2025-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: csv
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- ext/chdb/chdb_handle.h
|
42
42
|
- ext/chdb/connection.c
|
43
43
|
- ext/chdb/connection.h
|
44
|
+
- ext/chdb/constants.c
|
44
45
|
- ext/chdb/constants.h
|
45
46
|
- ext/chdb/exception.c
|
46
47
|
- ext/chdb/exception.h
|