duckdb 0.9.1.1 → 0.9.2

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