duckdb 0.9.1.1 → 0.9.2

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/test_on_macos.yml +6 -6
  4. data/.github/workflows/test_on_ubuntu.yml +6 -6
  5. data/.github/workflows/test_on_windows.yml +4 -4
  6. data/CHANGELOG.md +11 -0
  7. data/Dockerfile +1 -1
  8. data/Gemfile.lock +5 -5
  9. data/README.md +17 -1
  10. data/benchmark/async_query.rb +90 -0
  11. data/ext/duckdb/appender.c +5 -5
  12. data/ext/duckdb/appender.h +1 -1
  13. data/ext/duckdb/blob.c +1 -1
  14. data/ext/duckdb/blob.h +1 -4
  15. data/ext/duckdb/column.c +2 -2
  16. data/ext/duckdb/column.h +2 -2
  17. data/ext/duckdb/config.c +1 -1
  18. data/ext/duckdb/config.h +1 -1
  19. data/ext/duckdb/connection.c +66 -6
  20. data/ext/duckdb/connection.h +2 -2
  21. data/ext/duckdb/converter.h +1 -1
  22. data/ext/duckdb/conveter.c +1 -1
  23. data/ext/duckdb/database.c +3 -3
  24. data/ext/duckdb/database.h +2 -2
  25. data/ext/duckdb/duckdb.c +11 -11
  26. data/ext/duckdb/error.c +1 -1
  27. data/ext/duckdb/error.h +1 -1
  28. data/ext/duckdb/extconf.rb +46 -13
  29. data/ext/duckdb/pending_result.c +45 -6
  30. data/ext/duckdb/pending_result.h +1 -1
  31. data/ext/duckdb/prepared_statement.c +7 -7
  32. data/ext/duckdb/prepared_statement.h +1 -1
  33. data/ext/duckdb/result.c +112 -70
  34. data/ext/duckdb/result.h +2 -2
  35. data/ext/duckdb/ruby-duckdb.h +1 -0
  36. data/ext/duckdb/util.c +4 -4
  37. data/ext/duckdb/util.h +4 -4
  38. data/lib/duckdb/appender.rb +1 -10
  39. data/lib/duckdb/connection.rb +50 -8
  40. data/lib/duckdb/converter.rb +27 -1
  41. data/lib/duckdb/prepared_statement.rb +42 -40
  42. data/lib/duckdb/result.rb +8 -2
  43. data/lib/duckdb/version.rb +1 -1
  44. data/sample/async_query.rb +24 -0
  45. data/sample/async_query_stream.rb +24 -0
  46. metadata +6 -2
@@ -25,19 +25,60 @@ module DuckDB
25
25
  # sql = 'SELECT * FROM users WHERE name = $name AND email = $email'
26
26
  # dave = con.query(sql, name: 'Dave', email: 'dave@example.com')
27
27
  #
28
- def query(sql, *args, **hash)
29
- return query_sql(sql) if args.empty? && hash.empty?
28
+ def query(sql, *args, **kwargs)
29
+ return query_sql(sql) if args.empty? && kwargs.empty?
30
30
 
31
31
  stmt = PreparedStatement.new(self, sql)
32
- args.each.with_index(1) do |arg, i|
33
- stmt.bind(i, arg)
34
- end
35
- hash.each do |key, value|
36
- stmt.bind(key, value)
37
- end
32
+ stmt.bind_args(*args, **kwargs)
38
33
  stmt.execute
39
34
  end
40
35
 
36
+ #
37
+ # executes sql with args asynchronously.
38
+ # The first argument sql must be SQL string.
39
+ # The rest arguments are parameters of SQL string.
40
+ # This method returns DuckDB::PendingResult object.
41
+ #
42
+ # require 'duckdb'
43
+ # db = DuckDB::Database.open('duckdb_file')
44
+ # con = db.connect
45
+ #
46
+ # sql = 'SELECT * FROM users WHERE name = $name AND email = $email'
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
51
+ #
52
+ def async_query(sql, *args, **kwargs)
53
+ stmt = PreparedStatement.new(self, sql)
54
+ stmt.bind_args(*args, **kwargs)
55
+ stmt.pending_prepared
56
+ end
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
+
41
82
  #
42
83
  # connects DuckDB database
43
84
  # The first argument is DuckDB::Database object
@@ -83,6 +124,7 @@ module DuckDB
83
124
  end
84
125
 
85
126
  alias execute query
127
+ alias async_execute async_query
86
128
  alias open connect
87
129
  alias close disconnect
88
130
  end
@@ -42,6 +42,32 @@ module DuckDB
42
42
  "#{str[0, 8]}-#{str[8, 4]}-#{str[12, 4]}-#{str[16, 4]}-#{str[20, 12]}"
43
43
  end
44
44
 
45
+ def _parse_date(value)
46
+ case value
47
+ when Date, Time
48
+ value
49
+ else
50
+ begin
51
+ Date.parse(value)
52
+ rescue StandardError => e
53
+ raise(ArgumentError, "Cannot parse `#{value.inspect}` to Date object. #{e.message}")
54
+ end
55
+ end
56
+ end
57
+
58
+ def _parse_time(value)
59
+ case value
60
+ when Time
61
+ value
62
+ else
63
+ begin
64
+ Time.parse(value)
65
+ rescue StandardError => e
66
+ raise(ArgumentError, "Cannot parse `#{value.inspect}` to Time object. #{e.message}")
67
+ end
68
+ end
69
+ end
70
+
45
71
  private
46
72
 
47
73
  def integer_to_hugeint(value)
@@ -51,7 +77,7 @@ module DuckDB
51
77
  lower = value - (upper << HALF_HUGEINT_BIT)
52
78
  [lower, upper]
53
79
  else
54
- raise(ArgumentError, "The argument `#{value}` must be Integer.")
80
+ raise(ArgumentError, "The argument `#{value.inspect}` must be Integer.")
55
81
  end
56
82
  end
57
83
  end
@@ -24,21 +24,49 @@ 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
+
33
+ # binds all parameters with SQL prepared statement.
34
+ #
35
+ # require 'duckdb'
36
+ # db = DuckDB::Database.open('duckdb_database')
37
+ # con = db.connect
38
+ # sql ='SELECT name FROM users WHERE id = ?'
39
+ # # or
40
+ # # sql ='SELECT name FROM users WHERE id = $id'
41
+ # stmt = PreparedStatement.new(con, sql)
42
+ # stmt.bind_args([1])
43
+ # # or
44
+ # # stmt.bind_args(id: 1)
45
+ def bind_args(*args, **kwargs)
46
+ args.each.with_index(1) do |arg, i|
47
+ bind(i, arg)
48
+ end
49
+ kwargs.each do |key, value|
50
+ bind(key, value)
51
+ end
52
+ end
53
+
27
54
  # binds i-th parameter with SQL prepared statement.
28
55
  # The first argument is index of parameter.
29
56
  # The index of first parameter is 1 not 0.
30
57
  # The second argument value is to expected Integer value.
31
58
  # This method uses bind_varchar internally.
59
+ #
32
60
  # require 'duckdb'
33
61
  # db = DuckDB::Database.open('duckdb_database')
34
62
  # con = db.connect
35
63
  # sql ='SELECT name FROM users WHERE bigint_col = ?'
36
64
  # stmt = PreparedStatement.new(con, sql)
37
65
  # stmt.bind_hugeint(1, 1_234_567_890_123_456_789_012_345)
38
- def bind_hugeint(i, value)
66
+ def bind_hugeint(index, value)
39
67
  case value
40
68
  when Integer
41
- bind_varchar(i, value.to_s)
69
+ bind_varchar(index, value.to_s)
42
70
  else
43
71
  raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
44
72
  end
@@ -49,6 +77,7 @@ module DuckDB
49
77
  # The index of first parameter is 1 not 0.
50
78
  # The second argument value must be Integer value.
51
79
  # This method uses duckdb_bind_hugeint internally.
80
+ #
52
81
  # require 'duckdb'
53
82
  # db = DuckDB::Database.open('duckdb_database')
54
83
  # con = db.connect
@@ -73,19 +102,10 @@ module DuckDB
73
102
  # stmt.bind(1, Date.today)
74
103
  # # or you can specify date string.
75
104
  # # stmt.bind(1, '2021-02-23')
76
- def bind_date(i, value)
77
- case value
78
- when Date, Time
79
- date = value
80
- else
81
- begin
82
- date = Date.parse(value)
83
- rescue => e
84
- raise(ArgumentError, "Cannot parse argument value to date. #{e.message}")
85
- end
86
- end
105
+ def bind_date(index, value)
106
+ date = _parse_date(value)
87
107
 
88
- _bind_date(i, date.year, date.month, date.day)
108
+ _bind_date(index, date.year, date.month, date.day)
89
109
  end
90
110
 
91
111
  # binds i-th parameter with SQL prepared statement.
@@ -101,19 +121,10 @@ module DuckDB
101
121
  # stmt.bind(1, Time.now)
102
122
  # # or you can specify time string.
103
123
  # # stmt.bind(1, '07:39:45')
104
- def bind_time(i, value)
105
- case value
106
- when Time
107
- time = value
108
- else
109
- begin
110
- time = Time.parse(value)
111
- rescue => e
112
- raise(ArgumentError, "Cannot parse argument value to time. #{e.message}")
113
- end
114
- end
124
+ def bind_time(index, value)
125
+ time = _parse_time(value)
115
126
 
116
- _bind_time(i, time.hour, time.min, time.sec, time.usec)
127
+ _bind_time(index, time.hour, time.min, time.sec, time.usec)
117
128
  end
118
129
 
119
130
  # binds i-th parameter with SQL prepared statement.
@@ -129,19 +140,10 @@ module DuckDB
129
140
  # stmt.bind(1, Time.now)
130
141
  # # or you can specify timestamp string.
131
142
  # # stmt.bind(1, '2022-02-23 07:39:45')
132
- def bind_timestamp(i, value)
133
- case value
134
- when Time
135
- time = value
136
- else
137
- begin
138
- time = Time.parse(value)
139
- rescue => e
140
- raise(ArgumentError, "Cannot parse argument value to time. #{e.message}")
141
- end
142
- end
143
+ def bind_timestamp(index, value)
144
+ time = _parse_time(value)
143
145
 
144
- _bind_timestamp(i, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
146
+ _bind_timestamp(index, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
145
147
  end
146
148
 
147
149
  # binds i-th parameter with SQL prepared statement.
@@ -155,9 +157,9 @@ module DuckDB
155
157
  # sql ='SELECT value FROM intervals WHERE interval = ?'
156
158
  # stmt = PreparedStatement.new(con, sql)
157
159
  # stmt.bind(1, 'P1Y2D')
158
- def bind_interval(i, value)
160
+ def bind_interval(index, value)
159
161
  value = Interval.to_interval(value)
160
- _bind_interval(i, value.interval_months, value.interval_days, value.interval_micros)
162
+ _bind_interval(index, value.interval_months, value.interval_days, value.interval_micros)
161
163
  end
162
164
 
163
165
  # binds i-th parameter with SQL prepared statement.
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.1'
6
+ VERSION = '0.9.2'
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.1
4
+ version: 0.9.2
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-10-28 00:00:00.000000000 Z
11
+ date: 2023-11-25 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