duckdb 0.9.1.2 → 0.9.2.1

Sign up to get free protection for your applications and to get access to all the features.
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