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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84935b6317fa4bef4b62197645f061b179e3358e93cba3b976672eb1988715fc
4
- data.tar.gz: 7ff6b2f4b46ea16bcfa2e7de486e9a5bb30aa7d4dccb14e32af7b9886d6fcac4
3
+ metadata.gz: 97d0a0fdf67bcfdd00102540f9dd675e1fe87176e349ef06db6f1c72f78b5219
4
+ data.tar.gz: da34340b078ae95c6b9fa7ddb872de4be16f3bc298169004761d532a9f378f20
5
5
  SHA512:
6
- metadata.gz: d90dbdbf57d5cc282049f6297d00674ff57d9fafbfd7bfab8db8c87fb1153d3c9ea93ceda3b440d066c24ceef5dd1e349dedea6f4b17557bda590bb92f93fc5a
7
- data.tar.gz: cbcbd5c50c6ddc92c01001b36cbe5aad8a87729ea5518a881dcf8c21621679d1c3740c0ed97672d597aca351ea09548d6f7b5e722fde32077cb811a57c3dfd40
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.5', '3.3.5', '3.4.0-preview1', 'head']
19
- duckdb: ['1.1.1', '1.1.0', '1.0.0']
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.5', '3.3.5', '3.4.0-preview1', 'head']
19
- duckdb: ['1.1.1', '1.1.0', '1.0.0']
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
@@ -16,7 +16,7 @@ jobs:
16
16
  strategy:
17
17
  matrix:
18
18
  ruby: ['3.1.6', '3.2.5', '3.3.5', 'ucrt', 'mingw', 'mswin', 'head']
19
- duckdb: ['1.1.1', '1.1.0', '1.0.0']
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
@@ -15,3 +15,4 @@
15
15
  *.obj
16
16
  *.so
17
17
  Makefile
18
+ ng_test
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-compose build ubuntu --build-arg RUBY_VERSION=3.1.3 --build-arg DUCKDB_VERSION=0.6.0
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.5
1
+ ARG RUBY_VERSION=3.3.6
2
2
  FROM ruby:${RUBY_VERSION}
3
3
 
4
- ARG DUCKDB_VERSION=1.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
- RUN mv libduckdb/duckdb.* /usr/local/include
14
- RUN mv libduckdb/libduckdb.so /usr/local/lib
15
- RUN ldconfig /usr/local/lib
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
@@ -1,4 +1,4 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in duckdb.gemspec
4
4
  gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- duckdb (1.1.1.0)
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
- mini_portile2 (2.8.7)
13
- minitest (5.25.1)
14
- nokogiri (1.16.7)
15
- mini_portile2 (~> 2.8.2)
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
- ruby
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.9
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 extranct it.
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.
@@ -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, "ExtractedStatements", rb_cObject);
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
- duckdb_destroy_prepare(&(p->prepared_statement));
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
- rb_raise(eDuckDBError, "Fail to get DuckDB::PreparedStatement object from ExtractedStatements object");
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
- rb_raise(eDuckDBError, "%s", duckdb_result_error(&(ctxr->result)));
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
  }
@@ -74,16 +74,7 @@ module DuckDB
74
74
  # appender.flush
75
75
  #
76
76
  def append_date(value)
77
- date = case value
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 = case value
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
@@ -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 query_sql(sql) if args.empty? && kwargs.empty?
27
+ return query_multi_sql(sql) if args.empty? && kwargs.empty?
30
28
 
31
- stmt = PreparedStatement.new(self, sql)
32
- stmt.bind_args(*args, **kwargs)
33
- stmt.execute
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
- stmt = PreparedStatement.new(self, sql)
54
- stmt.bind_args(*args, **kwargs)
55
- stmt.pending_prepared
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
- stmt = PreparedStatement.new(self, sql)
77
- stmt.bind_args(*args, **kwargs)
78
- stmt.pending_prepared_stream
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
- def prepared_statement(str)
109
- PreparedStatement.new(self, str)
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
 
@@ -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
@@ -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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DuckDB
4
+ # represents the version of the DuckDB library.
5
+ # If DuckDB.library_version is v0.2.0, then DuckDB::LIBRARY_VERSION is 0.2.0.
4
6
  LIBRARY_VERSION = library_version[1..]
5
7
  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
- bind_varchar(index, value.to_s('F'))
286
+ bind_decimal(index, value)
243
287
  else
244
288
  raise(DuckDB::Error, "not supported type `#{value}` (#{value.class})")
245
289
  end
@@ -3,5 +3,5 @@
3
3
  module DuckDB
4
4
  # The version string of ruby-duckdb.
5
5
  # Currently, ruby-duckdb is NOT semantic versioning.
6
- VERSION = '1.1.1.0'
6
+ VERSION = '1.1.3.1'
7
7
  end
data/lib/duckdb.rb CHANGED
@@ -6,6 +6,7 @@ require 'duckdb/version'
6
6
  require 'duckdb/converter'
7
7
  require 'duckdb/database'
8
8
  require 'duckdb/connection'
9
+ require 'duckdb/extracted_statements'
9
10
  require 'duckdb/result'
10
11
  require 'duckdb/prepared_statement'
11
12
  require 'duckdb/pending_result'
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.0
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-10-06 00:00:00.000000000 Z
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.16
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.