duckdb 0.9.1.2 → 0.9.2.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: b492788be53be66a956ad49e4cd8a82ffa6273c12f037b2c0abd51253321802f
4
- data.tar.gz: 50571029b1d1160a7abb6ac223bd4cda4f3791b29d26cdebac6b795f160a2a14
3
+ metadata.gz: b83a52918000c5a6b188eede2053e8f6d3118baad3e451b6a0db48aa0ab8b977
4
+ data.tar.gz: 12dec4bf0fbf2819067e58f57047a8fd2a0b5f0a6e329fa89d8dc0edc59ed338
5
5
  SHA512:
6
- metadata.gz: d80355e181599217574191f3acba24d6dbcb37e4a73b774689590f42fc9b92032d4a4285962ee65100d3a3186825b29f09066d9fd0f8f24ce46aaec85cba4701
7
- data.tar.gz: b126f7e0057c77cae338f7c6b76d4b775c52b06f84936ebd6208a3357148ababa8c8c3aaca727dacf4b7ed05b5f5da2bedc74d8b1d46f170ddc418d4c9e125fa
6
+ metadata.gz: '014489d9944bf9cb36c883450f654be009fccee51a3af1215e2f64e525d13ca8faed8ffed08df60368cd5c7c93e29b773626f9e6b4cccbd00593992d0465af28'
7
+ data.tar.gz: '08a0fc5a78f29fd006c3e308ab0346df3f18944d337747af8d753a213232bd9c68e1ce5985b040dd00330d8ac6233a233633e466b0d26b4f045dfd9f9835155a'
data/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ *.rb diff=ruby
@@ -15,8 +15,8 @@ jobs:
15
15
  runs-on: macos-latest
16
16
  strategy:
17
17
  matrix:
18
- ruby: ['2.7.8', '3.0.6', '3.1.4', '3.2.2', '3.3.0-preview2', 'head']
19
- duckdb: ['0.9.1', '0.8.1']
18
+ ruby: ['2.7.8', '3.0.6', '3.1.4', '3.2.2', '3.3.0-rc1', 'head']
19
+ duckdb: ['0.9.2', '0.8.1']
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v3
@@ -59,7 +59,7 @@ jobs:
59
59
 
60
60
  - name: run test with Ruby ${{ matrix.ruby }}
61
61
  run: |
62
- rake test
62
+ env RUBYOPT=-W:deprecated rake test
63
63
 
64
64
  post-test:
65
65
  name: All tests passed on macos
@@ -15,8 +15,8 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  strategy:
17
17
  matrix:
18
- ruby: ['2.7.8', '3.0.6', '3.1.4', '3.2.2', '3.3.0-preview2', 'head']
19
- duckdb: ['0.9.1', '0.8.1']
18
+ ruby: ['2.7.8', '3.0.6', '3.1.4', '3.2.2', '3.3.0-rc1', 'head']
19
+ duckdb: ['0.9.2', '0.8.1']
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v3
@@ -49,7 +49,6 @@ jobs:
49
49
  env:
50
50
  DUCKDB_VERSION: ${{ matrix.duckdb }}
51
51
  run: |
52
- gem install bundler
53
52
  bundle install --jobs 4 --retry 3
54
53
  bundle exec rake build -- --with-duckdb-include=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/src/include --with-duckdb-lib=${GITHUB_WORKSPACE}/duckdb-v${DUCKDB_VERSION}/build/release/src/
55
54
 
@@ -57,7 +56,7 @@ jobs:
57
56
  env:
58
57
  DUCKDB_VERSION: ${{ matrix.duckdb }}
59
58
  run: |
60
- rake test
59
+ env RUBYOPT=-W:deprecated rake test
61
60
 
62
61
  post-test:
63
62
  name: All tests passed on Ubuntu
@@ -16,7 +16,7 @@ jobs:
16
16
  strategy:
17
17
  matrix:
18
18
  ruby: ['2.7.8', '3.0.6', '3.1.4', '3.2.2', 'ucrt', 'mingw', 'mswin', 'head']
19
- duckdb: ['0.9.1', '0.8.1']
19
+ duckdb: ['0.9.2', '0.8.1']
20
20
 
21
21
  steps:
22
22
  - uses: actions/checkout@v3
data/CHANGELOG.md CHANGED
@@ -1,16 +1,27 @@
1
1
  # ChangeLog
2
2
 
3
+ # 0.9.2.1
4
+ - support Time column in `DuckDB#Result#chunk_each`.
5
+ - add `DuckDB::Interval#eql?`.
6
+
7
+ # 0.9.2
8
+ - add `DuckDB::Connection#async_query_stream`.
9
+ - `DuckDB::PendingResult` accepts second argument. If the second argument is
10
+ true, `PendingResult#execute_pending` returns streaming `DuckDB::Result` object.
11
+ - add `DuckDB::PreparedStatement#pending_prepared_stream`
12
+ - add `DuckDB::Result#streaming?`.
13
+
3
14
  # 0.9.1.2
4
- - add DuckDB::Connection#interrupt, DuckDB::Connection#query_progress
5
- - add DuckDB::Connection#async_query, alias method async_execute.
15
+ - add `DuckDB::Connection#interrupt`, `DuckDB::Connection#query_progress`.
16
+ - add `DuckDB::Connection#async_query`, alias method `async_execute`.
6
17
 
7
18
  # 0.9.1.1
8
19
  - change default branch to main from master.
9
- - add DuckDB::PendingResult class.
10
- - add DuckDB::PendingResult#state.
11
- - add DuckDB::PendingResult#execute_task.
12
- - add DuckDB::PendingResult#execute_pending.
13
- - add DuckDB::PreparedStatement#pending_prepared.
20
+ - add `DuckDB::PendingResult` class.
21
+ - add `DuckDB::PendingResult#state`.
22
+ - add `DuckDB::PendingResult#execute_task`.
23
+ - add `DuckDB::PendingResult#execute_pending`.
24
+ - add `DuckDB::PreparedStatement#pending_prepared`.
14
25
 
15
26
  ## Breaking Changes
16
27
  - drop duckdb v0.7.x.
@@ -21,16 +32,16 @@
21
32
 
22
33
  # 0.9.0.1
23
34
  - add `DuckDB::PreparedStatement#bind_parameter_index`.
24
- - DuckDB::Connection#query accepts SQL with named bind parameters.
35
+ - `DuckDB::Connection#query` accepts SQL with named bind parameters.
25
36
 
26
37
  # 0.9.0
27
38
  - bump duckdb to 0.9.0.
28
39
 
29
40
  ## Breaking Changes
30
- - deprecation warning when DuckDB::Result.each calling with `DuckDB::Result.use_chunk_each` is false.
41
+ - deprecation warning when `DuckDB::Result.each` calling with `DuckDB::Result.use_chunk_each` is false.
31
42
  The `each` behavior will be same as `DuckDB::Result.chunk_each` in the future.
32
43
  set `DuckDB::Result.use_chunk_each = true` to suppress the warning.
33
- - DuckDB::Result#chunck_each returns DuckDB::Interval class when the column type is INTERVAL.
44
+ - `DuckDB::Result#chunk_each` returns `DuckDB::Interval` class when the column type is INTERVAL.
34
45
 
35
46
  # 0.8.1.3
36
47
  - Fix BigDecimal conversion.
@@ -57,7 +68,7 @@
57
68
  But `DuckDB::Result#each` behavior will be changed like as `DuckDB::Result#chunk_each` in near future release.
58
69
  And there are some breaking changes.
59
70
  Write `DuckdDB::Result.use_chunk_each = true` if you want to try new behavior.
60
- ```
71
+ ```ruby
61
72
  DuckDB::Result.use_chunk_each = true
62
73
 
63
74
  result = con.query('SELECT ....')
data/Dockerfile CHANGED
@@ -1,7 +1,7 @@
1
1
  ARG RUBY_VERSION=3.2.2
2
2
  FROM ruby:${RUBY_VERSION}
3
3
 
4
- ARG DUCKDB_VERSION=0.9.1
4
+ ARG DUCKDB_VERSION=0.9.2
5
5
 
6
6
  RUN apt update -qq && \
7
7
  apt install -y build-essential curl git wget
data/Gemfile.lock CHANGED
@@ -1,24 +1,24 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- duckdb (0.9.1.2)
4
+ duckdb (0.9.2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- benchmark-ips (2.12.0)
9
+ benchmark-ips (2.13.0)
10
10
  mini_portile2 (2.8.5)
11
11
  minitest (5.20.0)
12
- nokogiri (1.15.4)
12
+ nokogiri (1.15.5)
13
13
  mini_portile2 (~> 2.8.2)
14
14
  racc (~> 1.4)
15
- nokogiri (1.15.4-x86_64-linux)
15
+ nokogiri (1.15.5-x86_64-linux)
16
16
  racc (~> 1.4)
17
17
  racc (1.7.3)
18
18
  rake (13.1.0)
19
19
  rake-compiler (1.2.5)
20
20
  rake
21
- ruby_memcheck (2.2.0)
21
+ ruby_memcheck (2.3.0)
22
22
  nokogiri
23
23
  stackprof (0.2.25)
24
24
 
data/README.md CHANGED
@@ -104,10 +104,25 @@ con.query('SELECT * FROM users WHERE name = ? AND email = ?', 'Alice', 'alice@ex
104
104
  con.query('SELECT * FROM users WHERE name = $name AND email = $email', name: 'Alice', email: 'alice@example.com')
105
105
  ```
106
106
 
107
+ ### using async query
108
+
109
+ You can use async query.
110
+
111
+ ```ruby
112
+ DuckDB::Result.use_chunk_each = true # must be true.
113
+ ...
114
+
115
+ pending_result = con.async_query_stream('SLOW QUERY')
116
+ pending_result.execute_task while pending_result.state == :not_ready
117
+
118
+ result = pending_result.execute_pending
119
+ result.each.first
120
+ ```
121
+
122
+ Here is [the benchmark](./benchmark/async_query.rb).
107
123
 
108
124
  ### using BLOB column
109
125
 
110
- BLOB is available with DuckDB v0.2.5 or later.
111
126
  Use `DuckDB::Blob.new` or use sting#force_encoding(Encoding::BINARY)
112
127
 
113
128
  ```ruby
@@ -119,6 +134,7 @@ DuckDB::Database.open do |db|
119
134
  stmt = DuckDB::PreparedStatement.new(con, 'INSERT INTO blob_table VALUES ($1)')
120
135
 
121
136
  stmt.bind(1, DuckDB::Blob.new("\0\1\2\3\4\5"))
137
+ # or
122
138
  # stmt.bind(1, "\0\1\2\3\4\5".force_encoding(Encoding::BINARY))
123
139
  stmt.execute
124
140
 
@@ -0,0 +1,90 @@
1
+ require 'bundler/setup'
2
+ require 'duckdb'
3
+ require 'benchmark/ips'
4
+
5
+
6
+ DuckDB::Result.use_chunk_each = true
7
+ DuckDB::Database.open do |db|
8
+ db.connect do |con|
9
+ con.query('SET threads=1')
10
+ con.query('CREATE TABLE tbl as SELECT range a, mod(range, 10) b FROM range(100000)')
11
+ con.query('CREATE TABLE tbl2 as SELECT range a, mod(range, 10) b FROM range(100000)')
12
+ query_sql = 'SELECT * FROM tbl where b = (SELECT min(b) FROM tbl2)'
13
+ print <<~END_OF_HEAD
14
+
15
+ Benchmark: Get first record ======================================
16
+ END_OF_HEAD
17
+
18
+ Benchmark.ips do |x|
19
+ x.report('async_query') do
20
+ pending_result = con.async_query(query_sql)
21
+
22
+ pending_result.execute_task while pending_result.state == :not_ready
23
+ result = pending_result.execute_pending
24
+ result.each.first
25
+ end
26
+ x.report('query') do
27
+ result = con.query(query_sql)
28
+ result.each.first
29
+ end
30
+ x.report('async_query_stream') do
31
+ pending_result = con.async_query_stream(query_sql)
32
+
33
+ pending_result.execute_task while pending_result.state == :not_ready
34
+ result = pending_result.execute_pending
35
+ result.each.first
36
+ end
37
+ end
38
+
39
+ print <<~END_OF_HEAD
40
+
41
+
42
+ Benchmark: Get all records ======================================
43
+ END_OF_HEAD
44
+
45
+ Benchmark.ips do |x|
46
+ x.report('async_query') do
47
+ pending_result = con.async_query(query_sql)
48
+
49
+ pending_result.execute_task while pending_result.state == :not_ready
50
+ result = pending_result.execute_pending
51
+ result.each.to_a
52
+ end
53
+ x.report('query') do
54
+ result = con.query(query_sql)
55
+ result.each.to_a
56
+ end
57
+ x.report('async_query_stream') do
58
+ pending_result = con.async_query_stream(query_sql)
59
+
60
+ pending_result.execute_task while pending_result.state == :not_ready
61
+ result = pending_result.execute_pending
62
+ result.each.to_a
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ __END__
69
+
70
+ results:
71
+ Benchmark: Get first record ======================================
72
+ Warming up --------------------------------------
73
+ async_query 70.000 i/100ms
74
+ query 88.000 i/100ms
75
+ async_query_stream 188.000 i/100ms
76
+ Calculating -------------------------------------
77
+ async_query 847.191 (± 4.6%) i/s - 4.270k in 5.051650s
78
+ query 850.509 (± 3.8%) i/s - 4.312k in 5.078167s
79
+ async_query_stream 1.757k (± 7.3%) i/s - 8.836k in 5.057142s
80
+
81
+
82
+ Benchmark: Get all records ======================================
83
+ Warming up --------------------------------------
84
+ async_query 40.000 i/100ms
85
+ query 40.000 i/100ms
86
+ async_query_stream 39.000 i/100ms
87
+ Calculating -------------------------------------
88
+ async_query 402.567 (± 0.5%) i/s - 2.040k in 5.067639s
89
+ query 406.632 (± 0.7%) i/s - 2.040k in 5.017079s
90
+ async_query_stream 395.532 (± 0.8%) i/s - 1.989k in 5.028955s
@@ -1,31 +1,64 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mkmf'
2
4
 
3
- def duckdb_library_available?(func)
4
- header = find_header('duckdb.h') || find_header('duckdb.h', '/opt/homebrew/include')
5
- library = have_func(func, 'duckdb.h') || find_library('duckdb', func, '/opt/homebrew/opt/duckdb/lib')
6
- header && library
5
+ DUCKDB_REQUIRED_VERSION = '0.8.0'
6
+
7
+ def check_duckdb_header(header, version)
8
+ found = find_header(
9
+ header,
10
+ '/opt/homebrew/include',
11
+ '/opt/homebrew/opt/duckdb/include',
12
+ '/opt/local/include'
13
+ )
14
+ return if found
15
+
16
+ msg = "#{header} is not found. Install #{header} of duckdb >= #{version}."
17
+ print_message(msg)
18
+ raise msg
7
19
  end
8
20
 
9
- def check_duckdb_library(func, version)
10
- return if duckdb_library_available?(func)
21
+ def check_duckdb_library(library, func, version)
22
+ found = find_library(
23
+ library,
24
+ func,
25
+ '/opt/homebrew/lib',
26
+ '/opt/homebrew/opt/duckdb/lib',
27
+ '/opt/local/lib'
28
+ )
29
+ return if found
11
30
 
12
- msg = "duckdb >= #{version} is not found. Install duckdb >= #{version} library and header file."
13
- puts ''
14
- puts '*' * 80
15
- puts msg
16
- puts '*' * 80
17
- puts ''
31
+ library_name = duckdb_library_name(library)
32
+ msg = "#{library_name} is not found. Install #{library_name} of duckdb >= #{version}."
33
+ print_message(msg)
18
34
  raise msg
19
35
  end
20
36
 
37
+ def duckdb_library_name(library)
38
+ "lib#{library}.(so|dylib|dll)"
39
+ end
40
+
41
+ def print_message(msg)
42
+ print <<~END_OF_MESSAGE
43
+
44
+ #{'*' * 80}
45
+ #{msg}
46
+ #{'*' * 80}
47
+
48
+ END_OF_MESSAGE
49
+ end
50
+
21
51
  dir_config('duckdb')
22
52
 
53
+ check_duckdb_header('duckdb.h', DUCKDB_REQUIRED_VERSION)
54
+
23
55
  # check duckdb >= 0.8.0
24
- check_duckdb_library('duckdb_string_is_inlined', '0.8.0')
56
+ check_duckdb_library('duckdb', 'duckdb_string_is_inlined', DUCKDB_REQUIRED_VERSION)
25
57
 
26
58
  # check duckdb >= 0.9.0
27
59
  have_func('duckdb_bind_parameter_index', 'duckdb.h')
28
60
 
61
+ # duckdb_parameter_name is not found on Windows.
29
62
  have_func('duckdb_parameter_name', 'duckdb.h')
30
63
 
31
64
  create_makefile('duckdb/duckdb_native')
@@ -5,7 +5,7 @@ static VALUE cDuckDBPendingResult;
5
5
  static void deallocate(void *ctx);
6
6
  static VALUE allocate(VALUE klass);
7
7
  static size_t memsize(const void *p);
8
- static VALUE duckdb_pending_result_initialize(VALUE self, VALUE oDuckDBPreparedStatement);
8
+ static VALUE duckdb_pending_result_initialize(int argc, VALUE *args, VALUE self);
9
9
  static VALUE duckdb_pending_result_execute_task(VALUE self);
10
10
  static VALUE duckdb_pending_result_execute_pending(VALUE self);
11
11
 
@@ -38,11 +38,27 @@ static size_t memsize(const void *p) {
38
38
  return sizeof(rubyDuckDBPendingResult);
39
39
  }
40
40
 
41
- static VALUE duckdb_pending_result_initialize(VALUE self, VALUE oDuckDBPreparedStatement) {
41
+ static VALUE duckdb_pending_result_initialize(int argc, VALUE *argv, VALUE self) {
42
+ VALUE oDuckDBPreparedStatement;
43
+ VALUE streaming_p = Qfalse;
44
+ duckdb_state state;
45
+
46
+ rb_scan_args(argc, argv, "11", &oDuckDBPreparedStatement, &streaming_p);
47
+
48
+ if (rb_obj_is_kind_of(oDuckDBPreparedStatement, cDuckDBPreparedStatement) != Qtrue) {
49
+ rb_raise(rb_eTypeError, "1st argument must be DuckDB::PreparedStatement");
50
+ }
51
+
42
52
  rubyDuckDBPendingResult *ctx = get_struct_pending_result(self);
43
53
  rubyDuckDBPreparedStatement *stmt = get_struct_prepared_statement(oDuckDBPreparedStatement);
44
54
 
45
- if (duckdb_pending_prepared(stmt->prepared_statement, &(ctx->pending_result)) == DuckDBError) {
55
+ if (!NIL_P(streaming_p) && streaming_p == Qtrue) {
56
+ state = duckdb_pending_prepared_streaming(stmt->prepared_statement, &(ctx->pending_result));
57
+ } else {
58
+ state = duckdb_pending_prepared(stmt->prepared_statement, &(ctx->pending_result));
59
+ }
60
+
61
+ if (state == DuckDBError) {
46
62
  rb_raise(eDuckDBError, "%s", duckdb_pending_error(ctx->pending_result));
47
63
  }
48
64
  return self;
@@ -112,7 +128,7 @@ void rbduckdb_init_duckdb_pending_result(void) {
112
128
  cDuckDBPendingResult = rb_define_class_under(mDuckDB, "PendingResult", rb_cObject);
113
129
  rb_define_alloc_func(cDuckDBPendingResult, allocate);
114
130
 
115
- rb_define_method(cDuckDBPendingResult, "initialize", duckdb_pending_result_initialize, 1);
131
+ rb_define_method(cDuckDBPendingResult, "initialize", duckdb_pending_result_initialize, -1);
116
132
  rb_define_method(cDuckDBPendingResult, "execute_task", duckdb_pending_result_execute_task, 0);
117
133
  rb_define_method(cDuckDBPendingResult, "execute_pending", duckdb_pending_result_execute_pending, 0);
118
134
 
@@ -1,6 +1,6 @@
1
1
  #include "ruby-duckdb.h"
2
2
 
3
- static VALUE cDuckDBPreparedStatement;
3
+ VALUE cDuckDBPreparedStatement;
4
4
 
5
5
  static void deallocate(void *ctx);
6
6
  static VALUE allocate(VALUE klass);
data/ext/duckdb/result.c CHANGED
@@ -19,6 +19,11 @@ static VALUE duckdb_result_column_count(VALUE oDuckDBResult);
19
19
  static VALUE duckdb_result_row_count(VALUE oDuckDBResult);
20
20
  static VALUE duckdb_result_rows_changed(VALUE oDuckDBResult);
21
21
  static VALUE duckdb_result_columns(VALUE oDuckDBResult);
22
+ static VALUE duckdb_result_streaming_p(VALUE oDuckDBResult);
23
+ static VALUE duckdb_result_chunk_each(VALUE oDuckDBResult);
24
+
25
+ static VALUE duckdb_result__chunk_stream(VALUE oDuckDBResult);
26
+ static void yield_rows(duckdb_data_chunk chunk, idx_t col_count);
22
27
  static VALUE duckdb_result__column_type(VALUE oDuckDBResult, VALUE col_idx);
23
28
  static VALUE duckdb_result__is_null(VALUE oDuckDBResult, VALUE row_idx, VALUE col_idx);
24
29
  static VALUE duckdb_result__to_boolean(VALUE oDuckDBResult, VALUE row_idx, VALUE col_idx);
@@ -39,6 +44,7 @@ static VALUE duckdb_result__enum_dictionary_value(VALUE oDuckDBResult, VALUE col
39
44
 
40
45
  static VALUE vector_date(void *vector_data, idx_t row_idx);
41
46
  static VALUE vector_timestamp(void* vector_data, idx_t row_idx);
47
+ static VALUE vector_time(void* vector_data, idx_t row_idx);
42
48
  static VALUE vector_interval(void* vector_data, idx_t row_idx);
43
49
  static VALUE vector_blob(void* vector_data, idx_t row_idx);
44
50
  static VALUE vector_varchar(void* vector_data, idx_t row_idx);
@@ -50,7 +56,6 @@ static VALUE vector_map(duckdb_logical_type ty, duckdb_vector vector, idx_t row_
50
56
  static VALUE vector_struct(duckdb_logical_type ty, duckdb_vector vector, idx_t row_idx);
51
57
  static VALUE vector_uuid(void* vector_data, idx_t row_idx);
52
58
  static VALUE vector_value(duckdb_vector vector, idx_t row_idx);
53
- static VALUE duckdb_result_chunk_each(VALUE oDuckDBResult);
54
59
 
55
60
  static const rb_data_type_t result_data_type = {
56
61
  "DuckDB/Result",
@@ -235,6 +240,79 @@ static VALUE duckdb_result_columns(VALUE oDuckDBResult) {
235
240
  return ary;
236
241
  }
237
242
 
243
+ /*
244
+ * call-seq:
245
+ * result.streaming? -> Boolean
246
+ *
247
+ * Returns true if the result is streaming, otherwise false.
248
+ *
249
+ */
250
+ static VALUE duckdb_result_streaming_p(VALUE oDuckDBResult) {
251
+ rubyDuckDBResult *ctx;
252
+ TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
253
+ return duckdb_result_is_streaming(ctx->result) ? Qtrue : Qfalse;
254
+ }
255
+
256
+ static VALUE duckdb_result_chunk_each(VALUE oDuckDBResult) {
257
+ rubyDuckDBResult *ctx;
258
+ idx_t col_count;
259
+ idx_t chunk_count;
260
+ idx_t chunk_idx;
261
+ duckdb_data_chunk chunk;
262
+
263
+ TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
264
+
265
+ col_count = duckdb_column_count(&(ctx->result));
266
+ chunk_count = duckdb_result_chunk_count(ctx->result);
267
+
268
+ RETURN_ENUMERATOR(oDuckDBResult, 0, 0);
269
+
270
+ for (chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
271
+ chunk = duckdb_result_get_chunk(ctx->result, chunk_idx);
272
+ yield_rows(chunk, col_count);
273
+ duckdb_destroy_data_chunk(&chunk);
274
+ }
275
+ return Qnil;
276
+ }
277
+
278
+ static VALUE duckdb_result__chunk_stream(VALUE oDuckDBResult) {
279
+ rubyDuckDBResult *ctx;
280
+ duckdb_data_chunk chunk;
281
+ idx_t col_count;
282
+
283
+ TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
284
+
285
+ RETURN_ENUMERATOR(oDuckDBResult, 0, 0);
286
+
287
+ col_count = duckdb_column_count(&(ctx->result));
288
+
289
+ while((chunk = duckdb_stream_fetch_chunk(ctx->result)) != NULL) {
290
+ yield_rows(chunk, col_count);
291
+ duckdb_destroy_data_chunk(&chunk);
292
+ }
293
+ return Qnil;
294
+ }
295
+
296
+ static void yield_rows(duckdb_data_chunk chunk, idx_t col_count) {
297
+ idx_t row_count;
298
+ idx_t row_idx;
299
+ idx_t col_idx;
300
+ duckdb_vector vector;
301
+ VALUE row;
302
+ VALUE val;
303
+
304
+ row_count = duckdb_data_chunk_get_size(chunk);
305
+ for (row_idx = 0; row_idx < row_count; row_idx++) {
306
+ row = rb_ary_new2(col_count);
307
+ for (col_idx = 0; col_idx < col_count; col_idx++) {
308
+ vector = duckdb_data_chunk_get_vector(chunk, col_idx);
309
+ val = vector_value(vector, row_idx);
310
+ rb_ary_store(row, col_idx, val);
311
+ }
312
+ rb_yield(row);
313
+ }
314
+ }
315
+
238
316
  static VALUE duckdb_result__column_type(VALUE oDuckDBResult, VALUE col_idx) {
239
317
  rubyDuckDBResult *ctx;
240
318
  TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
@@ -405,32 +483,43 @@ static VALUE vector_date(void *vector_data, idx_t row_idx) {
405
483
  duckdb_date_struct date = duckdb_from_date(((duckdb_date *) vector_data)[row_idx]);
406
484
 
407
485
  return rb_funcall(mDuckDBConverter, rb_intern("_to_date"), 3,
408
- INT2FIX(date.year),
409
- INT2FIX(date.month),
410
- INT2FIX(date.day)
411
- );
486
+ INT2FIX(date.year),
487
+ INT2FIX(date.month),
488
+ INT2FIX(date.day)
489
+ );
412
490
  }
413
491
 
414
492
  static VALUE vector_timestamp(void* vector_data, idx_t row_idx) {
415
493
  duckdb_timestamp_struct data = duckdb_from_timestamp(((duckdb_timestamp *)vector_data)[row_idx]);
416
494
  return rb_funcall(mDuckDBConverter, rb_intern("_to_time"), 7,
417
- INT2FIX(data.date.year),
418
- INT2FIX(data.date.month),
419
- INT2FIX(data.date.day),
420
- INT2FIX(data.time.hour),
421
- INT2FIX(data.time.min),
422
- INT2FIX(data.time.sec),
423
- INT2NUM(data.time.micros)
424
- );
495
+ INT2FIX(data.date.year),
496
+ INT2FIX(data.date.month),
497
+ INT2FIX(data.date.day),
498
+ INT2FIX(data.time.hour),
499
+ INT2FIX(data.time.min),
500
+ INT2FIX(data.time.sec),
501
+ INT2NUM(data.time.micros)
502
+ );
503
+ }
504
+
505
+ static VALUE vector_time(void* vector_data, idx_t row_idx) {
506
+ duckdb_time_struct data = duckdb_from_time(((duckdb_time *)vector_data)[row_idx]);
507
+ return rb_funcall(mDuckDBConverter, rb_intern("_to_time_from_duckdb_time"), 4,
508
+ INT2FIX(data.hour),
509
+ INT2FIX(data.min),
510
+ INT2FIX(data.sec),
511
+ INT2NUM(data.micros)
512
+ );
425
513
  }
426
514
 
515
+
427
516
  static VALUE vector_interval(void* vector_data, idx_t row_idx) {
428
517
  duckdb_interval data = ((duckdb_interval *)vector_data)[row_idx];
429
518
  return rb_funcall(mDuckDBConverter, rb_intern("_to_interval_from_vector"), 3,
430
- INT2NUM(data.months),
431
- INT2NUM(data.days),
432
- LL2NUM(data.micros)
433
- );
519
+ INT2NUM(data.months),
520
+ INT2NUM(data.days),
521
+ LL2NUM(data.micros)
522
+ );
434
523
  }
435
524
 
436
525
  static VALUE vector_blob(void* vector_data, idx_t row_idx) {
@@ -454,9 +543,9 @@ static VALUE vector_varchar(void* vector_data, idx_t row_idx) {
454
543
  static VALUE vector_hugeint(void* vector_data, idx_t row_idx) {
455
544
  duckdb_hugeint hugeint = ((duckdb_hugeint *)vector_data)[row_idx];
456
545
  return rb_funcall(mDuckDBConverter, rb_intern("_to_hugeint_from_vector"), 2,
457
- ULL2NUM(hugeint.lower),
458
- LL2NUM(hugeint.upper)
459
- );
546
+ ULL2NUM(hugeint.lower),
547
+ LL2NUM(hugeint.upper)
548
+ );
460
549
  }
461
550
 
462
551
  static VALUE vector_decimal(duckdb_logical_type ty, void* vector_data, idx_t row_idx) {
@@ -477,11 +566,11 @@ static VALUE vector_decimal(duckdb_logical_type ty, void* vector_data, idx_t row
477
566
  }
478
567
 
479
568
  return rb_funcall(mDuckDBConverter, rb_intern("_to_decimal_from_vector"), 4,
480
- INT2FIX(width),
481
- INT2FIX(scale),
482
- ULL2NUM(value.lower),
483
- LL2NUM(value.upper)
484
- );
569
+ INT2FIX(width),
570
+ INT2FIX(scale),
571
+ ULL2NUM(value.lower),
572
+ LL2NUM(value.upper)
573
+ );
485
574
  }
486
575
 
487
576
  static VALUE vector_enum(duckdb_logical_type ty, void* vector_data, idx_t row_idx) {
@@ -577,9 +666,9 @@ static VALUE vector_struct(duckdb_logical_type ty, duckdb_vector vector, idx_t r
577
666
  static VALUE vector_uuid(void* vector_data, idx_t row_idx) {
578
667
  duckdb_hugeint hugeint = ((duckdb_hugeint *)vector_data)[row_idx];
579
668
  return rb_funcall(mDuckDBConverter, rb_intern("_to_uuid_from_vector"), 2,
580
- ULL2NUM(hugeint.lower),
581
- LL2NUM(hugeint.upper)
582
- );
669
+ ULL2NUM(hugeint.lower),
670
+ LL2NUM(hugeint.upper)
671
+ );
583
672
  }
584
673
 
585
674
  static VALUE vector_value(duckdb_vector vector, idx_t row_idx) {
@@ -617,7 +706,7 @@ static VALUE vector_value(duckdb_vector vector, idx_t row_idx) {
617
706
  case DUCKDB_TYPE_BIGINT:
618
707
  obj = LL2NUM(((int64_t *) vector_data)[row_idx]);
619
708
  break;
620
- case DUCKDB_TYPE_UTINYINT:
709
+ case DUCKDB_TYPE_UTINYINT:
621
710
  obj = INT2FIX(((uint8_t *) vector_data)[row_idx]);
622
711
  break;
623
712
  case DUCKDB_TYPE_USMALLINT:
@@ -644,6 +733,9 @@ static VALUE vector_value(duckdb_vector vector, idx_t row_idx) {
644
733
  case DUCKDB_TYPE_TIMESTAMP:
645
734
  obj = vector_timestamp(vector_data, row_idx);
646
735
  break;
736
+ case DUCKDB_TYPE_TIME:
737
+ obj = vector_time(vector_data, row_idx);
738
+ break;
647
739
  case DUCKDB_TYPE_INTERVAL:
648
740
  obj = vector_interval(vector_data, row_idx);
649
741
  break;
@@ -680,43 +772,6 @@ static VALUE vector_value(duckdb_vector vector, idx_t row_idx) {
680
772
  return obj;
681
773
  }
682
774
 
683
- static VALUE duckdb_result_chunk_each(VALUE oDuckDBResult) {
684
- rubyDuckDBResult *ctx;
685
- VALUE row;
686
- idx_t col_count;
687
- idx_t row_count;
688
- idx_t chunk_count;
689
- idx_t col_idx;
690
- idx_t row_idx;
691
- idx_t chunk_idx;
692
- duckdb_data_chunk chunk;
693
- duckdb_vector vector;
694
- VALUE val;
695
-
696
- TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
697
-
698
- col_count = duckdb_column_count(&(ctx->result));
699
- chunk_count = duckdb_result_chunk_count(ctx->result);
700
-
701
- RETURN_ENUMERATOR(oDuckDBResult, 0, 0);
702
-
703
- for (chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) {
704
- chunk = duckdb_result_get_chunk(ctx->result, chunk_idx);
705
- row_count = duckdb_data_chunk_get_size(chunk);
706
- for (row_idx = 0; row_idx < row_count; row_idx++) {
707
- row = rb_ary_new2(col_count);
708
- for (col_idx = 0; col_idx < col_count; col_idx++) {
709
- vector = duckdb_data_chunk_get_vector(chunk, col_idx);
710
- val = vector_value(vector, row_idx);
711
- rb_ary_store(row, col_idx, val);
712
- }
713
- rb_yield(row);
714
- }
715
- duckdb_destroy_data_chunk(&chunk);
716
- }
717
- return Qnil;
718
- }
719
-
720
775
  void rbduckdb_init_duckdb_result(void) {
721
776
  cDuckDBResult = rb_define_class_under(mDuckDB, "Result", rb_cObject);
722
777
  rb_define_alloc_func(cDuckDBResult, allocate);
@@ -725,6 +780,9 @@ void rbduckdb_init_duckdb_result(void) {
725
780
  rb_define_method(cDuckDBResult, "row_count", duckdb_result_row_count, 0);
726
781
  rb_define_method(cDuckDBResult, "rows_changed", duckdb_result_rows_changed, 0);
727
782
  rb_define_method(cDuckDBResult, "columns", duckdb_result_columns, 0);
783
+ rb_define_method(cDuckDBResult, "streaming?", duckdb_result_streaming_p, 0);
784
+ rb_define_method(cDuckDBResult, "chunk_each", duckdb_result_chunk_each, 0);
785
+ rb_define_private_method(cDuckDBResult, "_chunk_stream", duckdb_result__chunk_stream, 0);
728
786
  rb_define_private_method(cDuckDBResult, "_column_type", duckdb_result__column_type, 1);
729
787
  rb_define_private_method(cDuckDBResult, "_null?", duckdb_result__is_null, 2);
730
788
  rb_define_private_method(cDuckDBResult, "_to_boolean", duckdb_result__to_boolean, 2);
@@ -742,5 +800,4 @@ void rbduckdb_init_duckdb_result(void) {
742
800
  rb_define_private_method(cDuckDBResult, "_enum_internal_type", duckdb_result__enum_internal_type, 1);
743
801
  rb_define_private_method(cDuckDBResult, "_enum_dictionary_size", duckdb_result__enum_dictionary_size, 1);
744
802
  rb_define_private_method(cDuckDBResult, "_enum_dictionary_value", duckdb_result__enum_dictionary_value, 2);
745
- rb_define_method(cDuckDBResult, "chunk_each", duckdb_result_chunk_each, 0);
746
803
  }
@@ -29,5 +29,6 @@ extern VALUE cDuckDBBlob;
29
29
  extern VALUE cDuckDBConfig;
30
30
  extern VALUE eDuckDBError;
31
31
  extern VALUE mDuckDBConverter;
32
+ extern VALUE cDuckDBPreparedStatement;
32
33
 
33
34
  #endif
@@ -42,14 +42,12 @@ module DuckDB
42
42
  # require 'duckdb'
43
43
  # db = DuckDB::Database.open('duckdb_file')
44
44
  # con = db.connect
45
- # pending_result = con.async_query('SELECT * FROM users')
46
- # sql = 'SELECT * FROM users WHERE name = ? AND email = ?'
47
- # pending_result = con.async_query(sql, 'Dave', 'dave@example.com')
48
- #
49
- # # or You can use named parameter.
50
45
  #
51
46
  # sql = 'SELECT * FROM users WHERE name = $name AND email = $email'
52
47
  # pending_result = con.async_query(sql, name: 'Dave', email: 'dave@example.com')
48
+ # pending_result.execute_task while pending_result.state == :not_ready
49
+ # result = pending_result.execute_pending
50
+ # result.each.first
53
51
  #
54
52
  def async_query(sql, *args, **kwargs)
55
53
  stmt = PreparedStatement.new(self, sql)
@@ -57,6 +55,30 @@ module DuckDB
57
55
  stmt.pending_prepared
58
56
  end
59
57
 
58
+ #
59
+ # executes sql with args asynchronously and provides streaming result.
60
+ # The first argument sql must be SQL string.
61
+ # The rest arguments are parameters of SQL string.
62
+ # This method returns DuckDB::PendingResult object.
63
+ #
64
+ # require 'duckdb'
65
+ # DuckDB::Result.use_chunk_each = true # must be true
66
+ # db = DuckDB::Database.open('duckdb_file')
67
+ # con = db.connect
68
+ #
69
+ # sql = 'SELECT * FROM users WHERE name = $name AND email = $email'
70
+ # pending_result = con.async_query_stream(sql, name: 'Dave', email: 'dave@example.com')
71
+ #
72
+ # pending_result.execute_task while pending_result.state == :not_ready
73
+ # result = pending_result.execute_pending
74
+ # result.each.first
75
+ #
76
+ def async_query_stream(sql, *args, **kwargs)
77
+ stmt = PreparedStatement.new(self, sql)
78
+ stmt.bind_args(*args, **kwargs)
79
+ stmt.pending_prepared_stream
80
+ end
81
+
60
82
  #
61
83
  # connects DuckDB database
62
84
  # The first argument is DuckDB::Database object
@@ -19,6 +19,18 @@ module DuckDB
19
19
  Time.local(year, month, day, hour, minute, second, microsecond)
20
20
  end
21
21
 
22
+ def _to_time_from_duckdb_time(hour, minute, second, microsecond)
23
+ Time.parse(
24
+ format(
25
+ '%<hour>02d:%<minute>02d:%<second>02d.%<microsecond>06d',
26
+ hour: hour,
27
+ minute: minute,
28
+ second: second,
29
+ microsecond: microsecond
30
+ )
31
+ )
32
+ end
33
+
22
34
  def _to_hugeint_from_vector(lower, upper)
23
35
  (upper << HALF_HUGEINT_BIT) + lower
24
36
  end
@@ -157,5 +157,9 @@ module DuckDB
157
157
  @interval_days == other.interval_days &&
158
158
  @interval_micros == other.interval_micros
159
159
  end
160
+
161
+ def eql?(other)
162
+ self == other
163
+ end
160
164
  end
161
165
  end
@@ -24,6 +24,12 @@ module DuckDB
24
24
  PendingResult.new(self)
25
25
  end
26
26
 
27
+ def pending_prepared_stream
28
+ raise DuckDB::Error, 'DuckDB::Result.use_chunk_each must be true.' unless DuckDB::Result.use_chunk_each?
29
+
30
+ PendingResult.new(self, true)
31
+ end
32
+
27
33
  # binds all parameters with SQL prepared statement.
28
34
  #
29
35
  # require 'duckdb'
data/lib/duckdb/result.rb CHANGED
@@ -58,9 +58,15 @@ module DuckDB
58
58
 
59
59
  def each
60
60
  if self.class.use_chunk_each?
61
- return chunk_each unless block_given?
61
+ if streaming?
62
+ return _chunk_stream unless block_given?
62
63
 
63
- chunk_each { |row| yield row }
64
+ _chunk_stream { |row| yield row }
65
+ else
66
+ return chunk_each unless block_given?
67
+
68
+ chunk_each { |row| yield row }
69
+ end
64
70
  else
65
71
  warn('this `each` behavior will be deprecated in the future. set `DuckDB::Result.use_chunk_each = true` to use new `each` behavior.')
66
72
  return to_enum { row_size } unless block_given?
@@ -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 = '0.9.1.2'
6
+ VERSION = '0.9.2.1'
7
7
  end
@@ -0,0 +1,24 @@
1
+ require 'duckdb'
2
+
3
+ DuckDB::Result.use_chunk_each = true
4
+ DuckDB::Database.open do |db|
5
+ db.connect do |con|
6
+ con.query('SET threads=1')
7
+ con.query('CREATE TABLE tbl as SELECT range a, mod(range, 10) b FROM range(10000)')
8
+ con.query('CREATE TABLE tbl2 as SELECT range a, mod(range, 10) b FROM range(10000)')
9
+ # con.query('SET ENABLE_PROGRESS_BAR=true')
10
+ # con.query('SET ENABLE_PROGRESS_BAR_PRINT=false')
11
+ pending_result = con.async_query('SELECT * FROM tbl where b = (SELECT min(b) FROM tbl2)')
12
+
13
+ # con.interrupt
14
+ while pending_result.state == :not_ready
15
+ pending_result.execute_task
16
+ print '.'
17
+ $stdout.flush
18
+ sleep 0.01
19
+ end
20
+ result = pending_result.execute_pending
21
+ puts
22
+ p result.each.first
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'duckdb'
2
+
3
+ DuckDB::Result.use_chunk_each = true
4
+ DuckDB::Database.open do |db|
5
+ db.connect do |con|
6
+ con.query('SET threads=1')
7
+ con.query('CREATE TABLE tbl as SELECT range a, mod(range, 10) b FROM range(10000)')
8
+ con.query('CREATE TABLE tbl2 as SELECT range a, mod(range, 10) b FROM range(10000)')
9
+ # con.query('SET ENABLE_PROGRESS_BAR=true')
10
+ # con.query('SET ENABLE_PROGRESS_BAR_PRINT=false')
11
+ pending_result = con.async_query_stream('SELECT * FROM tbl where b = (SELECT min(b) FROM tbl2)')
12
+
13
+ # con.interrupt
14
+ while pending_result.state == :not_ready
15
+ pending_result.execute_task
16
+ print '.'
17
+ $stdout.flush
18
+ sleep 0.01
19
+ end
20
+ result = pending_result.execute_pending
21
+ puts
22
+ p result.each.first
23
+ end
24
+ end
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: 0.9.1.2
4
+ version: 0.9.2.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: 2023-11-05 00:00:00.000000000 Z
11
+ date: 2023-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -75,6 +75,7 @@ extensions:
75
75
  - ext/duckdb/extconf.rb
76
76
  extra_rdoc_files: []
77
77
  files:
78
+ - ".gitattributes"
78
79
  - ".github/FUNDING.yml"
79
80
  - ".github/workflows/test_on_macos.yml"
80
81
  - ".github/workflows/test_on_ubuntu.yml"
@@ -88,6 +89,7 @@ files:
88
89
  - LICENSE
89
90
  - README.md
90
91
  - Rakefile
92
+ - benchmark/async_query.rb
91
93
  - benchmark/converter_hugeint_ips.rb
92
94
  - benchmark/get_converter_module_ips.rb
93
95
  - benchmark/to_bigdecimal_ips.rb
@@ -138,6 +140,8 @@ files:
138
140
  - lib/duckdb/prepared_statement.rb
139
141
  - lib/duckdb/result.rb
140
142
  - lib/duckdb/version.rb
143
+ - sample/async_query.rb
144
+ - sample/async_query_stream.rb
141
145
  homepage: https://github.com/suketa/ruby-duckdb
142
146
  licenses:
143
147
  - MIT