duckdb 1.1.1.0 → 1.1.3.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/.github/workflows/test_on_macos.yml +3 -2
- data/.github/workflows/test_on_ubuntu.yml +2 -2
- data/.github/workflows/test_on_windows.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +30 -1
- data/CONTRIBUTION.md +1 -1
- data/Dockerfile +20 -9
- data/Gemfile +1 -1
- data/Gemfile.lock +17 -7
- data/README.md +15 -2
- data/ext/duckdb/appender.c +22 -0
- data/ext/duckdb/extracted_statements.c +14 -3
- data/ext/duckdb/prepared_statement.c +55 -5
- data/lib/duckdb/appender.rb +30 -22
- data/lib/duckdb/connection.rb +37 -23
- data/lib/duckdb/converter.rb +20 -0
- data/lib/duckdb/database.rb +1 -6
- data/lib/duckdb/extracted_statements.rb +20 -0
- data/lib/duckdb/library_version.rb +2 -0
- data/lib/duckdb/prepared_statement.rb +45 -1
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97d0a0fdf67bcfdd00102540f9dd675e1fe87176e349ef06db6f1c72f78b5219
|
4
|
+
data.tar.gz: da34340b078ae95c6b9fa7ddb872de4be16f3bc298169004761d532a9f378f20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 924d05547e0f9bf6f4772db3a93085930cd48961a0eee92d114815ea8286cd4ad6d1454764ffd97caa0cc6be103a2b084d1f402e98a973dc529b674754b4cfd7
|
7
|
+
data.tar.gz: 022f30d9b659b487f109d91b7d9818a26bb6d0a7fffd62fc8ae0375453688b3981b652fcb00826a826f15503ee119ca73830fe251329d51235d46ffa6c17ff50
|
@@ -15,8 +15,9 @@ jobs:
|
|
15
15
|
runs-on: macos-latest
|
16
16
|
strategy:
|
17
17
|
matrix:
|
18
|
-
ruby: ['3.1.6', '3.2.
|
19
|
-
|
18
|
+
# ruby: ['3.1.6', '3.2.6', '3.3.6', '3.4.0-preview2', 'head']
|
19
|
+
ruby: ['3.1.6', '3.2.6', '3.3.6', '3.4.0-preview2']
|
20
|
+
duckdb: ['1.1.3', '1.1.1', '1.0.0']
|
20
21
|
|
21
22
|
steps:
|
22
23
|
- uses: actions/checkout@v4
|
@@ -15,8 +15,8 @@ jobs:
|
|
15
15
|
runs-on: ubuntu-latest
|
16
16
|
strategy:
|
17
17
|
matrix:
|
18
|
-
ruby: ['3.1.6', '3.2.
|
19
|
-
duckdb: ['1.1.
|
18
|
+
ruby: ['3.1.6', '3.2.6', '3.3.6', '3.4.0-preview2', 'head']
|
19
|
+
duckdb: ['1.1.3', '1.1.1', '1.0.0']
|
20
20
|
|
21
21
|
steps:
|
22
22
|
- uses: actions/checkout@v4
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,9 +2,38 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
# Unreleased
|
5
|
+
# 1.1.3.1 - 2024-11-27
|
6
|
+
- fix to `DuckDB::Connection#query` with multiple SQL statements. Calling PreparedStatement#destroy after each statement executed.
|
7
|
+
- install valgrind in docker development environment.
|
8
|
+
- add `DuckDB::Appender#append_default`.
|
9
|
+
|
10
|
+
# 1.1.3.0 - 2024-11-10
|
11
|
+
- add `DuckDB::PreparedStatement#bind_decimal`. (Thanks to @otegami)
|
12
|
+
- bump duckdb to 1.1.3.
|
13
|
+
|
14
|
+
# 1.1.2.1 - 2024-11-04
|
15
|
+
- `DuckDB::Connection#query` accepts multiple SQL statement.
|
16
|
+
- When multiple SQL statements are given, `DuckDB::Connection#query` returns the last statement result.
|
17
|
+
- `DuckDB::Connection#query` does not support multiple SQL statements with bind parameters. If you pass 2 or more argument,
|
18
|
+
`DuckDB::Connection#query` will regard first argument as only one SQL statement and the rest as bind parameters.
|
19
|
+
- add `DuckDB::ExtracteStatements#each` method.
|
20
|
+
- add `DuckDB::ExtracteStatementsImpl#destroy` method.
|
21
|
+
- add `DuckDB::PreparedStatement#prepare`.
|
22
|
+
- `DuckDB::Connection#prepared_statement` accepts block and calls `PreparedStatement#destroy` after block executed.
|
23
|
+
```ruby
|
24
|
+
con.prepared_statement('SELECT * FROM table WHERE id = ?') do |stmt|
|
25
|
+
stmt.bind(1)
|
26
|
+
stmt.execute
|
27
|
+
end
|
28
|
+
```
|
29
|
+
# 1.1.2.0 - 2024-10-20
|
30
|
+
- bump duckdb to 1.1.2.
|
31
|
+
- add `DuckDB::PreparedStatement#destroy`.
|
32
|
+
- `DuckDB::Connection#query`, `DuckDB::Connection#async_query`, `DuckDB::Connection#async_query_stream` call
|
33
|
+
`DuckDB::PreparedStatement#destroy` to free the prepared statement immediately (#775, #781).
|
5
34
|
|
6
35
|
# 1.1.1.0 - 2024-10-06
|
7
|
-
- bump duckdb 1.1.1.
|
36
|
+
- bump duckdb to 1.1.1.
|
8
37
|
## Breaking changes
|
9
38
|
- drop duckdb v0.10.x.
|
10
39
|
|
data/CONTRIBUTION.md
CHANGED
@@ -14,7 +14,7 @@ docker compose run --rm ubuntu bash
|
|
14
14
|
|
15
15
|
In case you want custom ruby or duckdb versions, use `--build-arg` options
|
16
16
|
```
|
17
|
-
docker
|
17
|
+
docker compose build ubuntu --build-arg RUBY_VERSION=3.1.3 --build-arg DUCKDB_VERSION=1.0.0
|
18
18
|
```
|
19
19
|
|
20
20
|
### Without docker
|
data/Dockerfile
CHANGED
@@ -1,21 +1,32 @@
|
|
1
|
-
ARG RUBY_VERSION=3.3.
|
1
|
+
ARG RUBY_VERSION=3.3.6
|
2
2
|
FROM ruby:${RUBY_VERSION}
|
3
3
|
|
4
|
-
ARG DUCKDB_VERSION=1.1.
|
4
|
+
ARG DUCKDB_VERSION=1.1.3
|
5
|
+
ARG VALGRIND_VERSION=3.21.0
|
5
6
|
|
6
7
|
RUN apt update -qq && \
|
7
|
-
apt install -y build-essential curl git wget
|
8
|
+
apt install -y build-essential curl git wget libc6-dbg
|
8
9
|
|
9
10
|
COPY getduckdb.sh .
|
10
11
|
RUN ./getduckdb.sh
|
11
12
|
|
12
|
-
RUN unzip duckdb.zip -d libduckdb
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
RUN unzip duckdb.zip -d libduckdb && \
|
14
|
+
mv libduckdb/duckdb.* /usr/local/include && \
|
15
|
+
mv libduckdb/libduckdb.so /usr/local/lib && \
|
16
|
+
ldconfig /usr/local/lib
|
17
|
+
|
18
|
+
RUN mkdir valgrind-tmp && \
|
19
|
+
cd valgrind-tmp && \
|
20
|
+
wget https://sourceware.org/pub/valgrind/valgrind-${VALGRIND_VERSION}.tar.bz2 && \
|
21
|
+
tar xf valgrind-${VALGRIND_VERSION}.tar.bz2 && \
|
22
|
+
cd valgrind-${VALGRIND_VERSION} && \
|
23
|
+
./configure && \
|
24
|
+
make -s && \
|
25
|
+
make -s install && \
|
26
|
+
cd .. && \
|
27
|
+
rm -rf /valgrind-tmp
|
16
28
|
|
17
29
|
COPY . /root/ruby-duckdb
|
18
30
|
WORKDIR /root/ruby-duckdb
|
19
31
|
RUN git config --global --add safe.directory /root/ruby-duckdb
|
20
|
-
RUN bundle install
|
21
|
-
RUN rake build
|
32
|
+
RUN bundle install && rake build
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
duckdb (1.1.1
|
4
|
+
duckdb (1.1.3.1)
|
5
5
|
bigdecimal (>= 3.1.4)
|
6
6
|
|
7
7
|
GEM
|
@@ -9,10 +9,16 @@ GEM
|
|
9
9
|
specs:
|
10
10
|
benchmark-ips (2.14.0)
|
11
11
|
bigdecimal (3.1.8)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
minitest (5.25.2)
|
13
|
+
nokogiri (1.16.7-aarch64-linux)
|
14
|
+
racc (~> 1.4)
|
15
|
+
nokogiri (1.16.7-arm-linux)
|
16
|
+
racc (~> 1.4)
|
17
|
+
nokogiri (1.16.7-arm64-darwin)
|
18
|
+
racc (~> 1.4)
|
19
|
+
nokogiri (1.16.7-x86-linux)
|
20
|
+
racc (~> 1.4)
|
21
|
+
nokogiri (1.16.7-x86_64-darwin)
|
16
22
|
racc (~> 1.4)
|
17
23
|
nokogiri (1.16.7-x86_64-linux)
|
18
24
|
racc (~> 1.4)
|
@@ -25,7 +31,11 @@ GEM
|
|
25
31
|
stackprof (0.2.26)
|
26
32
|
|
27
33
|
PLATFORMS
|
28
|
-
|
34
|
+
aarch64-linux
|
35
|
+
arm-linux
|
36
|
+
arm64-darwin
|
37
|
+
x86-linux
|
38
|
+
x86_64-darwin
|
29
39
|
x86_64-linux
|
30
40
|
|
31
41
|
DEPENDENCIES
|
@@ -39,4 +49,4 @@ DEPENDENCIES
|
|
39
49
|
stackprof
|
40
50
|
|
41
51
|
BUNDLED WITH
|
42
|
-
2.5.
|
52
|
+
2.5.22
|
data/README.md
CHANGED
@@ -46,7 +46,7 @@ brew install duckdb
|
|
46
46
|
|
47
47
|
Using [Ruby + Devkit](https://rubyinstaller.org/downloads/) is recommended.
|
48
48
|
|
49
|
-
1. Download libduckdb-windows-amd64.zip from [DuckDB](https://github.com/duckdb/duckdb/releases) and
|
49
|
+
1. Download libduckdb-windows-amd64.zip from [DuckDB](https://github.com/duckdb/duckdb/releases) and extract it.
|
50
50
|
2. Copy `duckdb.dll` into `C:\Windows\System32`
|
51
51
|
|
52
52
|
## How to install
|
@@ -115,7 +115,7 @@ con.query('SELECT * FROM users WHERE name = $name AND email = $email', name: 'Al
|
|
115
115
|
```
|
116
116
|
### Using prepared statement
|
117
117
|
|
118
|
-
You can use prepared statement.
|
118
|
+
You can use prepared statement. Prepared statement object is created by `Connection#prepare` method or `DuckDB::PreparedStatement.new`.
|
119
119
|
|
120
120
|
```ruby
|
121
121
|
stmt = con.prepare('SELECT * FROM users WHERE name = $name AND email = $email')
|
@@ -125,7 +125,20 @@ stmt = con.prepare('SELECT * FROM users WHERE name = $name AND email = $email')
|
|
125
125
|
# stmt = DuckDB::PreparedStatement.new(con, 'SELECT * FROM users WHERE name = $name AND email = $email')
|
126
126
|
stmt.bind(name: 'Alice', email: 'alice@example.com')
|
127
127
|
result = stmt.execute
|
128
|
+
stmt.destroy
|
128
129
|
```
|
130
|
+
You must call `PreparedStatement#destroy` method after using prepared statement. Otherwise, automatically destroyed
|
131
|
+
when the PreparedStatement object is garbage collected.
|
132
|
+
|
133
|
+
Instead of calling `PreparedStatement#destroy`, you can use block.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
result = con.prepare('SELECT * FROM users WHERE name = $name AND email = $email') do |stmt|
|
137
|
+
stmt.bind(name: 'Alice', email: 'alice@example.com')
|
138
|
+
stmt.execute
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
129
142
|
### Using async query
|
130
143
|
|
131
144
|
You can use async query.
|
data/ext/duckdb/appender.c
CHANGED
@@ -23,6 +23,11 @@ static VALUE appender_append_varchar(VALUE self, VALUE val);
|
|
23
23
|
static VALUE appender_append_varchar_length(VALUE self, VALUE val, VALUE len);
|
24
24
|
static VALUE appender_append_blob(VALUE self, VALUE val);
|
25
25
|
static VALUE appender_append_null(VALUE self);
|
26
|
+
|
27
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_1_0
|
28
|
+
static VALUE appender_append_default(VALUE self);
|
29
|
+
#endif
|
30
|
+
|
26
31
|
static VALUE appender__append_date(VALUE self, VALUE yearval, VALUE monthval, VALUE dayval);
|
27
32
|
static VALUE appender__append_interval(VALUE self, VALUE months, VALUE days, VALUE micros);
|
28
33
|
static VALUE appender__append_time(VALUE self, VALUE hour, VALUE min, VALUE sec, VALUE micros);
|
@@ -281,6 +286,18 @@ static VALUE appender_append_null(VALUE self) {
|
|
281
286
|
return self;
|
282
287
|
}
|
283
288
|
|
289
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_1_0
|
290
|
+
static VALUE appender_append_default(VALUE self) {
|
291
|
+
rubyDuckDBAppender *ctx;
|
292
|
+
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
|
293
|
+
|
294
|
+
if (duckdb_append_default(ctx->appender) == DuckDBError) {
|
295
|
+
rb_raise(eDuckDBError, "failed to append");
|
296
|
+
}
|
297
|
+
return self;
|
298
|
+
}
|
299
|
+
#endif
|
300
|
+
|
284
301
|
static VALUE appender__append_date(VALUE self, VALUE year, VALUE month, VALUE day) {
|
285
302
|
duckdb_date dt;
|
286
303
|
rubyDuckDBAppender *ctx;
|
@@ -406,6 +423,11 @@ void rbduckdb_init_duckdb_appender(void) {
|
|
406
423
|
rb_define_method(cDuckDBAppender, "append_varchar_length", appender_append_varchar_length, 2);
|
407
424
|
rb_define_method(cDuckDBAppender, "append_blob", appender_append_blob, 1);
|
408
425
|
rb_define_method(cDuckDBAppender, "append_null", appender_append_null, 0);
|
426
|
+
|
427
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_1_0
|
428
|
+
rb_define_method(cDuckDBAppender, "append_default", appender_append_default, 0);
|
429
|
+
#endif
|
430
|
+
|
409
431
|
rb_define_private_method(cDuckDBAppender, "_append_date", appender__append_date, 3);
|
410
432
|
rb_define_private_method(cDuckDBAppender, "_append_interval", appender__append_interval, 3);
|
411
433
|
rb_define_private_method(cDuckDBAppender, "_append_time", appender__append_time, 4);
|
@@ -17,6 +17,7 @@ static VALUE allocate(VALUE klass);
|
|
17
17
|
static size_t memsize(const void *p);
|
18
18
|
|
19
19
|
static VALUE duckdb_extracted_statements_initialize(VALUE self, VALUE con, VALUE query);
|
20
|
+
static VALUE duckdb_extracted_statements_destroy(VALUE self);
|
20
21
|
static VALUE duckdb_extracted_statements_size(VALUE self);
|
21
22
|
static VALUE duckdb_extracted_statements_prepared_statement(VALUE self, VALUE con, VALUE index);
|
22
23
|
|
@@ -56,12 +57,22 @@ static VALUE duckdb_extracted_statements_initialize(VALUE self, VALUE con, VALUE
|
|
56
57
|
|
57
58
|
if (ctx->num_statements == 0) {
|
58
59
|
error = duckdb_extract_statements_error(ctx->extracted_statements);
|
59
|
-
rb_raise(eDuckDBError, "%s", error);
|
60
|
+
rb_raise(eDuckDBError, "%s", error ? error : "Failed to extract statements(Database connection closed?).");
|
60
61
|
}
|
61
62
|
|
62
63
|
return self;
|
63
64
|
}
|
64
65
|
|
66
|
+
static VALUE duckdb_extracted_statements_destroy(VALUE self) {
|
67
|
+
rubyDuckDBExtractedStatements *ctx;
|
68
|
+
|
69
|
+
TypedData_Get_Struct(self, rubyDuckDBExtractedStatements, &extract_statements_data_type, ctx);
|
70
|
+
|
71
|
+
duckdb_destroy_extracted(&(ctx->extracted_statements));
|
72
|
+
|
73
|
+
return Qnil;
|
74
|
+
}
|
75
|
+
|
65
76
|
static VALUE duckdb_extracted_statements_size(VALUE self) {
|
66
77
|
rubyDuckDBExtractedStatements *ctx;
|
67
78
|
|
@@ -70,7 +81,6 @@ static VALUE duckdb_extracted_statements_size(VALUE self) {
|
|
70
81
|
return ULL2NUM(ctx->num_statements);
|
71
82
|
}
|
72
83
|
|
73
|
-
|
74
84
|
static VALUE duckdb_extracted_statements_prepared_statement(VALUE self, VALUE con, VALUE index) {
|
75
85
|
rubyDuckDBConnection *pcon;
|
76
86
|
rubyDuckDBExtractedStatements *ctx;
|
@@ -85,10 +95,11 @@ static VALUE duckdb_extracted_statements_prepared_statement(VALUE self, VALUE co
|
|
85
95
|
}
|
86
96
|
|
87
97
|
void rbduckdb_init_duckdb_extracted_statements(void) {
|
88
|
-
cDuckDBExtractedStatements = rb_define_class_under(mDuckDB, "
|
98
|
+
cDuckDBExtractedStatements = rb_define_class_under(mDuckDB, "ExtractedStatementsImpl", rb_cObject);
|
89
99
|
|
90
100
|
rb_define_alloc_func(cDuckDBExtractedStatements, allocate);
|
91
101
|
rb_define_method(cDuckDBExtractedStatements, "initialize", duckdb_extracted_statements_initialize, 2);
|
102
|
+
rb_define_method(cDuckDBExtractedStatements, "destroy", duckdb_extracted_statements_destroy, 0);
|
92
103
|
rb_define_method(cDuckDBExtractedStatements, "size", duckdb_extracted_statements_size, 0);
|
93
104
|
rb_define_method(cDuckDBExtractedStatements, "prepared_statement", duckdb_extracted_statements_prepared_statement, 2);
|
94
105
|
}
|
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
VALUE cDuckDBPreparedStatement;
|
4
4
|
|
5
|
+
static void destroy_prepared_statement(rubyDuckDBPreparedStatement *p);
|
5
6
|
static void deallocate(void *ctx);
|
6
7
|
static VALUE allocate(VALUE klass);
|
7
8
|
static size_t memsize(const void *p);
|
8
9
|
static VALUE duckdb_prepared_statement_initialize(VALUE self, VALUE con, VALUE query);
|
9
10
|
static VALUE duckdb_prepared_statement_nparams(VALUE self);
|
10
11
|
static VALUE duckdb_prepared_statement_execute(VALUE self);
|
12
|
+
static VALUE duckdb_prepared_statement_destroy(VALUE self);
|
11
13
|
static idx_t check_index(VALUE vidx);
|
12
14
|
|
13
15
|
static VALUE duckdb_prepared_statement_bind_parameter_index(VALUE self, VALUE name);
|
@@ -30,6 +32,7 @@ static VALUE duckdb_prepared_statement__bind_time(VALUE self, VALUE vidx, VALUE
|
|
30
32
|
static VALUE duckdb_prepared_statement__bind_timestamp(VALUE self, VALUE vidx, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
|
31
33
|
static VALUE duckdb_prepared_statement__bind_interval(VALUE self, VALUE vidx, VALUE months, VALUE days, VALUE micros);
|
32
34
|
static VALUE duckdb_prepared_statement__bind_hugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
35
|
+
static VALUE duckdb_prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lower, VALUE upper, VALUE width, VALUE scale);
|
33
36
|
|
34
37
|
static const rb_data_type_t prepared_statement_data_type = {
|
35
38
|
"DuckDB/PreparedStatement",
|
@@ -37,10 +40,17 @@ static const rb_data_type_t prepared_statement_data_type = {
|
|
37
40
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
38
41
|
};
|
39
42
|
|
43
|
+
static void destroy_prepared_statement(rubyDuckDBPreparedStatement *p) {
|
44
|
+
if (p->prepared_statement) {
|
45
|
+
duckdb_destroy_prepare(&(p->prepared_statement));
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
40
49
|
static void deallocate(void *ctx) {
|
41
50
|
rubyDuckDBPreparedStatement *p = (rubyDuckDBPreparedStatement *)ctx;
|
42
51
|
|
43
|
-
|
52
|
+
destroy_prepared_statement(p);
|
53
|
+
// duckdb_destroy_prepare(&(p->prepared_statement));
|
44
54
|
xfree(p);
|
45
55
|
}
|
46
56
|
|
@@ -62,7 +72,8 @@ VALUE rbduckdb_prepared_statement_new(duckdb_connection con, duckdb_extracted_st
|
|
62
72
|
TypedData_Get_Struct(obj, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
63
73
|
|
64
74
|
if (duckdb_prepare_extracted_statement(con, extracted_statements, index, &(ctx->prepared_statement)) == DuckDBError) {
|
65
|
-
|
75
|
+
const char *error = duckdb_prepare_error(ctx->prepared_statement);
|
76
|
+
rb_raise(eDuckDBError, "%s", error ? error : "Failed to create DuckDB::PreparedStatement object.");
|
66
77
|
}
|
67
78
|
return obj;
|
68
79
|
}
|
@@ -80,7 +91,7 @@ static VALUE duckdb_prepared_statement_initialize(VALUE self, VALUE con, VALUE q
|
|
80
91
|
|
81
92
|
if (duckdb_prepare(ctxcon->con, StringValuePtr(query), &(ctx->prepared_statement)) == DuckDBError) {
|
82
93
|
const char *error = duckdb_prepare_error(ctx->prepared_statement);
|
83
|
-
rb_raise(eDuckDBError, "%s", error);
|
94
|
+
rb_raise(eDuckDBError, "%s", error ? error : "Failed to prepare statement(Database connection closed?).");
|
84
95
|
}
|
85
96
|
return self;
|
86
97
|
}
|
@@ -91,20 +102,37 @@ static VALUE duckdb_prepared_statement_nparams(VALUE self) {
|
|
91
102
|
return ULL2NUM(duckdb_nparams(ctx->prepared_statement));
|
92
103
|
}
|
93
104
|
|
94
|
-
|
95
105
|
static VALUE duckdb_prepared_statement_execute(VALUE self) {
|
96
106
|
rubyDuckDBPreparedStatement *ctx;
|
97
107
|
rubyDuckDBResult *ctxr;
|
98
108
|
VALUE result = rbduckdb_create_result();
|
109
|
+
const char *p = NULL;
|
99
110
|
|
100
111
|
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
101
112
|
ctxr = get_struct_result(result);
|
102
113
|
if (duckdb_execute_prepared(ctx->prepared_statement, &(ctxr->result)) == DuckDBError) {
|
103
|
-
|
114
|
+
p = duckdb_result_error(&(ctxr->result));
|
115
|
+
if (p == NULL) {
|
116
|
+
p = duckdb_prepare_error(ctx->prepared_statement);
|
117
|
+
}
|
118
|
+
rb_raise(eDuckDBError, "%s", p ? p : "Failed to execute prepared statement.");
|
104
119
|
}
|
105
120
|
return result;
|
106
121
|
}
|
107
122
|
|
123
|
+
/*
|
124
|
+
* :nodoc:
|
125
|
+
*/
|
126
|
+
static VALUE duckdb_prepared_statement_destroy(VALUE self) {
|
127
|
+
rubyDuckDBPreparedStatement *ctx;
|
128
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
129
|
+
destroy_prepared_statement(ctx);
|
130
|
+
/*
|
131
|
+
ctx->prepared_statement = NULL;
|
132
|
+
*/
|
133
|
+
return Qnil;
|
134
|
+
}
|
135
|
+
|
108
136
|
static idx_t check_index(VALUE vidx) {
|
109
137
|
idx_t idx = NUM2ULL(vidx);
|
110
138
|
if (idx <= 0) {
|
@@ -378,6 +406,26 @@ static VALUE duckdb_prepared_statement__bind_hugeint(VALUE self, VALUE vidx, VAL
|
|
378
406
|
return self;
|
379
407
|
}
|
380
408
|
|
409
|
+
static VALUE duckdb_prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lower, VALUE upper, VALUE width, VALUE scale) {
|
410
|
+
duckdb_hugeint hugeint;
|
411
|
+
duckdb_decimal decimal;
|
412
|
+
rubyDuckDBPreparedStatement *ctx;
|
413
|
+
idx_t idx = check_index(vidx);
|
414
|
+
|
415
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
416
|
+
hugeint.lower = NUM2ULL(lower);
|
417
|
+
hugeint.upper = NUM2LL(upper);
|
418
|
+
decimal.value = hugeint;
|
419
|
+
decimal.width = (uint8_t)NUM2UINT(width);
|
420
|
+
decimal.scale = (uint8_t)NUM2UINT(scale);
|
421
|
+
|
422
|
+
if (duckdb_bind_decimal(ctx->prepared_statement, idx, decimal) == DuckDBError) {
|
423
|
+
rb_raise(eDuckDBError, "fail to bind %llu parameter", (unsigned long long)idx);
|
424
|
+
}
|
425
|
+
|
426
|
+
return self;
|
427
|
+
}
|
428
|
+
|
381
429
|
rubyDuckDBPreparedStatement *get_struct_prepared_statement(VALUE self) {
|
382
430
|
rubyDuckDBPreparedStatement *ctx;
|
383
431
|
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
@@ -391,6 +439,7 @@ void rbduckdb_init_duckdb_prepared_statement(void) {
|
|
391
439
|
|
392
440
|
rb_define_method(cDuckDBPreparedStatement, "initialize", duckdb_prepared_statement_initialize, 2);
|
393
441
|
rb_define_method(cDuckDBPreparedStatement, "execute", duckdb_prepared_statement_execute, 0);
|
442
|
+
rb_define_method(cDuckDBPreparedStatement, "destroy", duckdb_prepared_statement_destroy, 0);
|
394
443
|
rb_define_method(cDuckDBPreparedStatement, "nparams", duckdb_prepared_statement_nparams, 0);
|
395
444
|
rb_define_method(cDuckDBPreparedStatement, "bind_parameter_index", duckdb_prepared_statement_bind_parameter_index, 1);
|
396
445
|
rb_define_method(cDuckDBPreparedStatement, "parameter_name", duckdb_prepared_statement_parameter_name, 1);
|
@@ -412,4 +461,5 @@ void rbduckdb_init_duckdb_prepared_statement(void) {
|
|
412
461
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_timestamp", duckdb_prepared_statement__bind_timestamp, 8);
|
413
462
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_interval", duckdb_prepared_statement__bind_interval, 4);
|
414
463
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_hugeint", duckdb_prepared_statement__bind_hugeint, 3);
|
464
|
+
rb_define_private_method(cDuckDBPreparedStatement, "_bind_decimal", duckdb_prepared_statement__bind_decimal, 5);
|
415
465
|
}
|
data/lib/duckdb/appender.rb
CHANGED
@@ -74,16 +74,7 @@ module DuckDB
|
|
74
74
|
# appender.flush
|
75
75
|
#
|
76
76
|
def append_date(value)
|
77
|
-
date =
|
78
|
-
when Date, Time
|
79
|
-
value
|
80
|
-
else
|
81
|
-
begin
|
82
|
-
Date.parse(value)
|
83
|
-
rescue
|
84
|
-
raise(ArgumentError, "Cannot parse argument `#{value}` to Date.")
|
85
|
-
end
|
86
|
-
end
|
77
|
+
date = to_date(value)
|
87
78
|
|
88
79
|
_append_date(date.year, date.month, date.day)
|
89
80
|
end
|
@@ -126,18 +117,7 @@ module DuckDB
|
|
126
117
|
# appender.flush
|
127
118
|
#
|
128
119
|
def append_timestamp(value)
|
129
|
-
time =
|
130
|
-
when Time
|
131
|
-
value
|
132
|
-
when Date
|
133
|
-
value.to_time
|
134
|
-
else
|
135
|
-
begin
|
136
|
-
Time.parse(value)
|
137
|
-
rescue
|
138
|
-
raise(ArgumentError, "Cannot parse argument `#{value}` to Time or Date.")
|
139
|
-
end
|
140
|
-
end
|
120
|
+
time = to_time(value)
|
141
121
|
|
142
122
|
_append_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec / 1000)
|
143
123
|
end
|
@@ -233,5 +213,33 @@ module DuckDB
|
|
233
213
|
def blob?(value)
|
234
214
|
value.instance_of?(DuckDB::Blob) || value.encoding == Encoding::BINARY
|
235
215
|
end
|
216
|
+
|
217
|
+
def to_date(value)
|
218
|
+
case value
|
219
|
+
when Date, Time
|
220
|
+
value
|
221
|
+
else
|
222
|
+
begin
|
223
|
+
Date.parse(value)
|
224
|
+
rescue StandardError
|
225
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Date.")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_time(value)
|
231
|
+
case value
|
232
|
+
when Time
|
233
|
+
value
|
234
|
+
when Date
|
235
|
+
value.to_time
|
236
|
+
else
|
237
|
+
begin
|
238
|
+
Time.parse(value)
|
239
|
+
rescue StandardError
|
240
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Time or Date.")
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
236
244
|
end
|
237
245
|
end
|
data/lib/duckdb/connection.rb
CHANGED
@@ -8,7 +8,6 @@ module DuckDB
|
|
8
8
|
# con = db.connect
|
9
9
|
# con.query(sql)
|
10
10
|
class Connection
|
11
|
-
#
|
12
11
|
# executes sql with args.
|
13
12
|
# The first argument sql must be SQL string.
|
14
13
|
# The rest arguments are parameters of SQL string.
|
@@ -24,16 +23,27 @@ module DuckDB
|
|
24
23
|
#
|
25
24
|
# sql = 'SELECT * FROM users WHERE name = $name AND email = $email'
|
26
25
|
# dave = con.query(sql, name: 'Dave', email: 'dave@example.com')
|
27
|
-
#
|
28
26
|
def query(sql, *args, **kwargs)
|
29
|
-
return
|
27
|
+
return query_multi_sql(sql) if args.empty? && kwargs.empty?
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
prepare(sql) do |stmt|
|
30
|
+
stmt.bind_args(*args, **kwargs)
|
31
|
+
stmt.execute
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_multi_sql(sql)
|
36
|
+
stmts = ExtractedStatements.new(self, sql)
|
37
|
+
result = nil
|
38
|
+
stmts.each do |stmt|
|
39
|
+
result = stmt.execute
|
40
|
+
stmt.destroy
|
41
|
+
end
|
42
|
+
result
|
43
|
+
ensure
|
44
|
+
stmts&.destroy
|
34
45
|
end
|
35
46
|
|
36
|
-
#
|
37
47
|
# executes sql with args asynchronously.
|
38
48
|
# The first argument sql must be SQL string.
|
39
49
|
# The rest arguments are parameters of SQL string.
|
@@ -48,14 +58,13 @@ module DuckDB
|
|
48
58
|
# pending_result.execute_task while pending_result.state == :not_ready
|
49
59
|
# result = pending_result.execute_pending
|
50
60
|
# result.each.first
|
51
|
-
#
|
52
61
|
def async_query(sql, *args, **kwargs)
|
53
|
-
|
54
|
-
|
55
|
-
|
62
|
+
prepare(sql) do |stmt|
|
63
|
+
stmt.bind_args(*args, **kwargs)
|
64
|
+
stmt.pending_prepared
|
65
|
+
end
|
56
66
|
end
|
57
67
|
|
58
|
-
#
|
59
68
|
# executes sql with args asynchronously and provides streaming result.
|
60
69
|
# The first argument sql must be SQL string.
|
61
70
|
# The rest arguments are parameters of SQL string.
|
@@ -71,17 +80,15 @@ module DuckDB
|
|
71
80
|
# pending_result.execute_task while pending_result.state == :not_ready
|
72
81
|
# result = pending_result.execute_pending
|
73
82
|
# result.each.first
|
74
|
-
#
|
75
83
|
def async_query_stream(sql, *args, **kwargs)
|
76
|
-
|
77
|
-
|
78
|
-
|
84
|
+
prepare(sql) do |stmt|
|
85
|
+
stmt.bind_args(*args, **kwargs)
|
86
|
+
stmt.pending_prepared_stream
|
87
|
+
end
|
79
88
|
end
|
80
89
|
|
81
|
-
#
|
82
90
|
# connects DuckDB database
|
83
91
|
# The first argument is DuckDB::Database object
|
84
|
-
#
|
85
92
|
def connect(db)
|
86
93
|
conn = _connect(db)
|
87
94
|
return conn unless block_given?
|
@@ -93,9 +100,10 @@ module DuckDB
|
|
93
100
|
end
|
94
101
|
end
|
95
102
|
|
96
|
-
#
|
97
103
|
# returns PreparedStatement object.
|
98
104
|
# The first argument is SQL string.
|
105
|
+
# If block is given, the block is executed with PreparedStatement object
|
106
|
+
# and the object is cleaned up immediately.
|
99
107
|
#
|
100
108
|
# require 'duckdb'
|
101
109
|
# db = DuckDB::Database.open('duckdb_file')
|
@@ -105,14 +113,20 @@ module DuckDB
|
|
105
113
|
# stmt = con.prepared_statement(sql)
|
106
114
|
# stmt.bind_args(name: 'Dave', email: 'dave@example.com')
|
107
115
|
# result = stmt.execute
|
108
|
-
|
109
|
-
|
116
|
+
#
|
117
|
+
# # or
|
118
|
+
# result = con.prepared_statement(sql) do |stmt|
|
119
|
+
# stmt.bind_args(name: 'Dave', email: 'dave@example.com')
|
120
|
+
# stmt.execute
|
121
|
+
# end
|
122
|
+
def prepared_statement(str, &)
|
123
|
+
return PreparedStatement.new(self, str) unless block_given?
|
124
|
+
|
125
|
+
PreparedStatement.prepare(self, str, &)
|
110
126
|
end
|
111
127
|
|
112
|
-
#
|
113
128
|
# returns Appender object.
|
114
129
|
# The first argument is table name
|
115
|
-
#
|
116
130
|
def appender(table)
|
117
131
|
appender = create_appender(table)
|
118
132
|
|
data/lib/duckdb/converter.rb
CHANGED
@@ -155,6 +155,19 @@ module DuckDB
|
|
155
155
|
end
|
156
156
|
end
|
157
157
|
|
158
|
+
def _parse_deciaml(value)
|
159
|
+
case value
|
160
|
+
when BigDecimal
|
161
|
+
value
|
162
|
+
else
|
163
|
+
begin
|
164
|
+
BigDecimal(value.to_s)
|
165
|
+
rescue StandardError => e
|
166
|
+
raise(ArgumentError, "Cannot parse `#{value.inspect}` to BigDecimal object. #{e.message}")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
158
171
|
def _to_query_progress(percentage, rows_processed, total_rows_to_process)
|
159
172
|
DuckDB::QueryProgress.new(percentage, rows_processed, total_rows_to_process).freeze
|
160
173
|
end
|
@@ -171,5 +184,12 @@ module DuckDB
|
|
171
184
|
raise(ArgumentError, "The argument `#{value.inspect}` must be Integer.")
|
172
185
|
end
|
173
186
|
end
|
187
|
+
|
188
|
+
def decimal_to_hugeint(value)
|
189
|
+
integer_value = (value * (10 ** value.scale)).to_i
|
190
|
+
integer_to_hugeint(integer_value)
|
191
|
+
rescue FloatDomainError => e
|
192
|
+
raise(ArgumentError, "The argument `#{value.inspect}` must be converted to Integer. #{e.message}")
|
193
|
+
end
|
174
194
|
end
|
175
195
|
end
|
data/lib/duckdb/database.rb
CHANGED
@@ -20,13 +20,11 @@ module DuckDB
|
|
20
20
|
# result.each do |row|
|
21
21
|
# p row
|
22
22
|
# end
|
23
|
-
#
|
24
23
|
class Database
|
25
24
|
private_class_method :_open
|
26
25
|
private_class_method :_open_ext
|
27
26
|
|
28
27
|
class << self
|
29
|
-
##
|
30
28
|
# Opens database.
|
31
29
|
# The first argument is DuckDB database file path to open.
|
32
30
|
# If there is no argument, the method opens DuckDB database in memory.
|
@@ -40,7 +38,6 @@ module DuckDB
|
|
40
38
|
# con = db.connect
|
41
39
|
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR(30))')
|
42
40
|
# end
|
43
|
-
#
|
44
41
|
def open(dbpath = nil, config = nil)
|
45
42
|
db = _db_open(dbpath, config)
|
46
43
|
return db unless block_given?
|
@@ -54,7 +51,7 @@ module DuckDB
|
|
54
51
|
|
55
52
|
private
|
56
53
|
|
57
|
-
def _db_open(dbpath, config)
|
54
|
+
def _db_open(dbpath, config) # :nodoc:
|
58
55
|
if config
|
59
56
|
_open_ext(dbpath, config)
|
60
57
|
else
|
@@ -63,7 +60,6 @@ module DuckDB
|
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
66
|
-
##
|
67
63
|
# connects database.
|
68
64
|
#
|
69
65
|
# The method yields block and disconnects the database if block given
|
@@ -75,7 +71,6 @@ module DuckDB
|
|
75
71
|
# db.connect do |con|
|
76
72
|
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR(30))')
|
77
73
|
# end
|
78
|
-
#
|
79
74
|
def connect
|
80
75
|
conn = _connect
|
81
76
|
return conn unless block_given?
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DuckDB
|
4
|
+
class ExtractedStatements < ExtractedStatementsImpl
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(con, sql)
|
8
|
+
@con = con
|
9
|
+
super(con, sql)
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
return to_enum(__method__) { size } unless block_given?
|
14
|
+
|
15
|
+
size.times do |i|
|
16
|
+
yield prepared_statement(@con, i)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -22,6 +22,31 @@ module DuckDB
|
|
22
22
|
RANGE_INT32 = -2_147_483_648..2_147_483_647
|
23
23
|
RANGE_INT64 = -9_223_372_036_854_775_808..9_223_372_036_854_775_807
|
24
24
|
|
25
|
+
class << self
|
26
|
+
# return DuckDB::PreparedStatement object.
|
27
|
+
# The first argument is DuckDB::Connection object.
|
28
|
+
# The second argument is SQL string.
|
29
|
+
# If block is given, the block is executed and the statement is destroyed.
|
30
|
+
#
|
31
|
+
# require 'duckdb'
|
32
|
+
# db = DuckDB::Database.open('duckdb_database')
|
33
|
+
# con = db.connection
|
34
|
+
# DuckDB::PreparedStatement.prepare(con, 'SELECT * FROM users WHERE id = ?') do |stmt|
|
35
|
+
# stmt.bind(1, 1)
|
36
|
+
# stmt.execute
|
37
|
+
# end
|
38
|
+
def prepare(con, sql)
|
39
|
+
stmt = new(con, sql)
|
40
|
+
return stmt unless block_given?
|
41
|
+
|
42
|
+
begin
|
43
|
+
yield stmt
|
44
|
+
ensure
|
45
|
+
stmt.destroy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
25
50
|
def pending_prepared
|
26
51
|
PendingResult.new(self)
|
27
52
|
end
|
@@ -191,6 +216,25 @@ module DuckDB
|
|
191
216
|
_bind_interval(index, value.interval_months, value.interval_days, value.interval_micros)
|
192
217
|
end
|
193
218
|
|
219
|
+
# binds i-th parameter with SQL prepared statement.
|
220
|
+
# The first argument is index of parameter.
|
221
|
+
# The index of first parameter is 1 not 0.
|
222
|
+
# The second argument value is to expected BigDecimal value or any value
|
223
|
+
# that can be parsed into a BigDecimal.
|
224
|
+
#
|
225
|
+
# require 'duckdb'
|
226
|
+
# db = DuckDB::Database.open('duckdb_database')
|
227
|
+
# con = db.connect
|
228
|
+
# sql ='SELECT value FROM decimals WHERE decimal = ?'
|
229
|
+
# stmt = PreparedStatement.new(con, sql)
|
230
|
+
# stmt.bind_decimal(1, BigDecimal('987654.321'))
|
231
|
+
def bind_decimal(index, value)
|
232
|
+
decimal = _parse_deciaml(value)
|
233
|
+
lower, upper = decimal_to_hugeint(decimal)
|
234
|
+
width = decimal.to_s('F').gsub(/[^0-9]/, '').length
|
235
|
+
_bind_decimal(index, lower, upper, width, decimal.scale)
|
236
|
+
end
|
237
|
+
|
194
238
|
# binds i-th parameter with SQL prepared statement.
|
195
239
|
# The first argument is index of parameter.
|
196
240
|
# The index of first parameter is 1 not 0.
|
@@ -239,7 +283,7 @@ module DuckDB
|
|
239
283
|
when Date
|
240
284
|
bind_varchar(index, value.strftime('%Y-%m-%d'))
|
241
285
|
when BigDecimal
|
242
|
-
|
286
|
+
bind_decimal(index, value)
|
243
287
|
else
|
244
288
|
raise(DuckDB::Error, "not supported type `#{value}` (#{value.class})")
|
245
289
|
end
|
data/lib/duckdb/version.rb
CHANGED
data/lib/duckdb.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duckdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.1
|
4
|
+
version: 1.1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masaki Suketa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bigdecimal
|
@@ -149,6 +149,7 @@ files:
|
|
149
149
|
- lib/duckdb/converter.rb
|
150
150
|
- lib/duckdb/converter/int_to_sym.rb
|
151
151
|
- lib/duckdb/database.rb
|
152
|
+
- lib/duckdb/extracted_statements.rb
|
152
153
|
- lib/duckdb/infinity.rb
|
153
154
|
- lib/duckdb/interval.rb
|
154
155
|
- lib/duckdb/library_version.rb
|
@@ -180,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
181
|
- !ruby/object:Gem::Version
|
181
182
|
version: '0'
|
182
183
|
requirements: []
|
183
|
-
rubygems_version: 3.5.
|
184
|
+
rubygems_version: 3.5.22
|
184
185
|
signing_key:
|
185
186
|
specification_version: 4
|
186
187
|
summary: This module is Ruby binding for DuckDB database engine.
|