extralite-bundle 2.2 → 2.4

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.
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.2'
3
+ VERSION = '2.4'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -25,6 +25,10 @@ module Extralite
25
25
  class InterruptError < Error
26
26
  end
27
27
 
28
+ # An exception raised when an Extralite doesn't know how to bind a parameter to a query
29
+ class ParameterError < Error
30
+ end
31
+
28
32
  # An SQLite database
29
33
  class Database
30
34
  # @!visibility private
@@ -52,6 +56,29 @@ module Extralite
52
56
  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
53
57
  end
54
58
 
59
+ # Starts a transaction and runs the given block. If an exception is raised
60
+ # in the block, the transaction is rolled back. Otherwise, the transaction
61
+ # is commited after running the block.
62
+ #
63
+ # db.transaction do
64
+ # db.execute('insert into foo values (1, 2, 3)')
65
+ # raise if db.query_single_value('select x from bar') > 42
66
+ # end
67
+ #
68
+ # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
69
+ # @return [Any] the given block's return value
70
+ def transaction(mode = :immediate)
71
+ execute "begin #{mode} transaction"
72
+
73
+ abort = false
74
+ yield self
75
+ rescue
76
+ abort = true
77
+ raise
78
+ ensure
79
+ execute(abort ? 'rollback' : 'commit')
80
+ end
81
+
55
82
  private
56
83
 
57
84
  def pragma_set(values)
Binary file
data/test/helper.rb CHANGED
@@ -5,3 +5,5 @@ require 'extralite'
5
5
  require 'minitest/autorun'
6
6
 
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
+
9
+ IS_LINUX = RUBY_PLATFORM =~ /linux/
data/test/issue-38.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sqlite3"
4
+ require "./lib/extralite"
5
+ require "benchmark"
6
+
7
+ # Setup
8
+
9
+ File.delete("benchmark.sqlite3") if File.exist?("benchmark.sqlite3")
10
+
11
+ POOL_SIZE = 10
12
+
13
+ EXTRALITE_CONNECTIONS = POOL_SIZE.times.map do
14
+ db = Extralite::Database.new("benchmark.sqlite3")
15
+ db.execute("PRAGMA journal_mode = WAL")
16
+ db.execute("PRAGMA synchronous = NORMAL")
17
+ db.execute("PRAGMA journal_size_limit = 64000000")
18
+ db.execute("PRAGMA mmap_size = 128000000")
19
+ db.execute("PRAGMA cache_size = 2000")
20
+ db.execute("PRAGMA busy_timeout = 5000")
21
+ db
22
+ end
23
+
24
+ SQLITE3_CONNECTIONS = POOL_SIZE.times.map do
25
+ db = SQLite3::Database.new("benchmark.sqlite3")
26
+ db.execute("PRAGMA journal_mode = WAL")
27
+ db.execute("PRAGMA synchronous = NORMAL")
28
+ db.execute("PRAGMA journal_size_limit = 64000000")
29
+ db.execute("PRAGMA mmap_size = 128000000")
30
+ db.execute("PRAGMA cache_size = 2000")
31
+ db.execute("PRAGMA busy_timeout = 5000")
32
+ db
33
+ end
34
+
35
+ EXTRALITE_CONNECTIONS[0].execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at TEXT, updated_at TEXT) STRICT")
36
+ insert_statement = EXTRALITE_CONNECTIONS[0].prepare("INSERT INTO users (name, created_at, updated_at) VALUES (?, ?, ?)")
37
+ 1000.times do
38
+ insert_statement.execute("John Doe", Time.now.iso8601, Time.now.iso8601)
39
+ end
40
+
41
+ # Benchmark variations
42
+
43
+ THREAD_COUNTS = [1, 2, 4, 8]
44
+ LIMITS = [10, 100, 1000]
45
+ CLIENTS = %w[extralite sqlite3]
46
+
47
+ # Benchmark
48
+
49
+ GC.disable
50
+ Benchmark.bm do |x|
51
+ LIMITS.each do |limit|
52
+ THREAD_COUNTS.each do |thread_count|
53
+ CLIENTS.each do |client|
54
+ GC.start
55
+
56
+ x.report("#{client.rjust('extralite'.length)} - limit: #{limit}, threads: #{thread_count}") do
57
+ threads = thread_count.times.map do |thread_number|
58
+ Thread.new do
59
+ start = Time.now
60
+ if client == "extralite"
61
+ 1_000.times do
62
+ records = EXTRALITE_CONNECTIONS[thread_number].query_ary("SELECT * FROM users LIMIT #{limit}")
63
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
64
+ end
65
+ else
66
+ 1_000.times do
67
+ records = SQLITE3_CONNECTIONS[thread_number].query("SELECT * FROM users LIMIT #{limit}").entries
68
+ raise "Expected #{limit} but got #{length}" unless records.length == limit
69
+ end
70
+ end
71
+ end
72
+ end
73
+ threads.each(&:join)
74
+ end
75
+ end
76
+ puts
77
+ end
78
+ end
79
+ end
80
+ GC.enable
data/test/perf_ary.rb CHANGED
@@ -12,15 +12,18 @@ end
12
12
  require 'benchmark/ips'
13
13
  require 'fileutils'
14
14
 
15
- DB_PATH = '/tmp/extralite_sqlite3_perf.db'
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
17
+
16
18
 
17
19
  def prepare_database(count)
18
- FileUtils.rm(DB_PATH) rescue nil
19
20
  db = Extralite::Database.new(DB_PATH)
20
- db.query('create table foo ( a integer primary key, b text )')
21
+ db.query('create table if not exists foo ( a integer primary key, b text )')
22
+ db.query('delete from foo')
21
23
  db.query('begin')
22
24
  count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
25
  db.query('commit')
26
+ db.close
24
27
  end
25
28
 
26
29
  def sqlite3_run(count)
data/test/perf_hash.rb CHANGED
@@ -12,15 +12,17 @@ end
12
12
  require 'benchmark/ips'
13
13
  require 'fileutils'
14
14
 
15
- DB_PATH = '/tmp/extralite_sqlite3_perf.db'
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
16
17
 
17
18
  def prepare_database(count)
18
- FileUtils.rm(DB_PATH) rescue nil
19
19
  db = Extralite::Database.new(DB_PATH)
20
- db.query('create table foo ( a integer primary key, b text )')
20
+ db.query('create table if not exists foo ( a integer primary key, b text )')
21
+ db.query('delete from foo')
21
22
  db.query('begin')
22
23
  count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
24
  db.query('commit')
25
+ db.close
24
26
  end
25
27
 
26
28
  def sqlite3_run(count)
@@ -36,7 +38,7 @@ def extralite_run(count)
36
38
  end
37
39
 
38
40
  [10, 1000, 100000].each do |c|
39
- puts; puts; puts "Record count: #{c}"
41
+ puts "Record count: #{c}"
40
42
 
41
43
  prepare_database(c)
42
44
 
@@ -48,4 +50,5 @@ end
48
50
 
49
51
  x.compare!
50
52
  end
53
+ puts; puts;
51
54
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative 'helper'
4
4
 
5
+ require 'date'
6
+ require 'tempfile'
7
+
5
8
  class DatabaseTest < MiniTest::Test
6
9
  def setup
7
10
  @db = Extralite::Database.new(':memory:')
@@ -123,6 +126,9 @@ end
123
126
 
124
127
  r = @db.query('select x, y, z from t where z = ?', 6)
125
128
  assert_equal [{ x: 4, y: 5, z: 6 }], r
129
+
130
+ error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Date.today) }
131
+ assert_equal error.message, 'Cannot bind parameter at position 1 of type Date'
126
132
  end
127
133
 
128
134
  def test_parameter_binding_with_index
@@ -152,6 +158,94 @@ end
152
158
  assert_equal [{ x: 4, y: 5, z: 6 }], r
153
159
  end
154
160
 
161
+ class Foo; end
162
+
163
+ def test_parameter_binding_from_hash
164
+ assert_equal 42, @db.query_single_value('select :bar', foo: 41, bar: 42)
165
+ assert_equal 42, @db.query_single_value('select :bar', 'foo' => 41, 'bar' => 42)
166
+ assert_equal 42, @db.query_single_value('select ?8', 7 => 41, 8 => 42)
167
+ assert_nil @db.query_single_value('select :bar', foo: 41)
168
+
169
+ error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', Foo.new => 42) }
170
+ assert_equal error.message, 'Cannot bind parameter with a key of type DatabaseTest::Foo'
171
+
172
+ error = assert_raises(Extralite::ParameterError) { @db.query_single_value('select ?', %w[a b] => 42) }
173
+ assert_equal error.message, 'Cannot bind parameter with a key of type Array'
174
+ end
175
+
176
+ def test_parameter_binding_from_struct
177
+ foo_bar = Struct.new(:":foo", :bar)
178
+ value = foo_bar.new(41, 42)
179
+ assert_equal 41, @db.query_single_value('select :foo', value)
180
+ assert_equal 42, @db.query_single_value('select :bar', value)
181
+ assert_nil @db.query_single_value('select :baz', value)
182
+ end
183
+
184
+ def test_parameter_binding_from_data_class
185
+ skip "Data isn't supported in Ruby < 3.2" if RUBY_VERSION < '3.2'
186
+
187
+ foo_bar = Data.define(:":foo", :bar)
188
+ value = foo_bar.new(":foo": 41, bar: 42)
189
+ assert_equal 42, @db.query_single_value('select :bar', value)
190
+ assert_nil @db.query_single_value('select :baz', value)
191
+ end
192
+
193
+ def test_parameter_binding_for_blobs
194
+ sql = 'SELECT typeof(data) AS type, data FROM blobs WHERE ROWID = ?'
195
+ blob_path = File.expand_path('fixtures/image.png', __dir__)
196
+ @db.execute('CREATE TABLE blobs (data BLOB)')
197
+
198
+ # it's a string, not a blob
199
+ @db.execute('INSERT INTO blobs VALUES (?)', 'Hello, 世界!')
200
+ result = @db.query_single_row(sql, @db.last_insert_rowid)
201
+ assert_equal 'text', result[:type]
202
+ assert_equal Encoding::UTF_8, result[:data].encoding
203
+
204
+ data = File.binread(blob_path)
205
+ @db.execute('INSERT INTO blobs VALUES (?)', data)
206
+ result = @db.query_single_row(sql, @db.last_insert_rowid)
207
+ assert_equal 'blob', result[:type]
208
+ assert_equal data, result[:data]
209
+
210
+ data = (+'Hello, 世界!').force_encoding(Encoding::ASCII_8BIT)
211
+ @db.execute('INSERT INTO blobs VALUES (?)', data)
212
+ result = @db.query_single_row(sql, @db.last_insert_rowid)
213
+ assert_equal 'blob', result[:type]
214
+ assert_equal Encoding::ASCII_8BIT, result[:data].encoding
215
+ assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
216
+
217
+ data = Extralite::Blob.new('Hello, 世界!')
218
+ @db.execute('INSERT INTO blobs VALUES (?)', data)
219
+ result = @db.query_single_row(sql, @db.last_insert_rowid)
220
+ assert_equal 'blob', result[:type]
221
+ assert_equal Encoding::ASCII_8BIT, result[:data].encoding
222
+ assert_equal 'Hello, 世界!', result[:data].force_encoding(Encoding::UTF_8)
223
+ end
224
+
225
+ def test_parameter_binding_for_simple_types
226
+ assert_nil @db.query_single_value('select ?', nil)
227
+
228
+ # 32-bit integers
229
+ assert_equal -2** 31, @db.query_single_value('select ?', -2**31)
230
+ assert_equal 2**31 - 1, @db.query_single_value('select ?', 2**31 - 1)
231
+
232
+ # 64-bit integers
233
+ assert_equal -2 ** 63, @db.query_single_value('select ?', -2 ** 63)
234
+ assert_equal 2**63 - 1, @db.query_single_value('select ?', 2**63 - 1)
235
+
236
+ # floats
237
+ assert_equal Float::MIN, @db.query_single_value('select ?', Float::MIN)
238
+ assert_equal Float::MAX, @db.query_single_value('select ?', Float::MAX)
239
+
240
+ # boolean
241
+ assert_equal 1, @db.query_single_value('select ?', true)
242
+ assert_equal 0, @db.query_single_value('select ?', false)
243
+
244
+ # strings and symbols
245
+ assert_equal 'foo', @db.query_single_value('select ?', 'foo')
246
+ assert_equal 'foo', @db.query_single_value('select ?', :foo)
247
+ end
248
+
155
249
  def test_value_casting
156
250
  r = @db.query_single_value("select 'abc'")
157
251
  assert_equal 'abc', r
@@ -293,7 +387,7 @@ end
293
387
  end
294
388
 
295
389
  def test_database_busy_timeout
296
- fn = "/tmp/extralite-#{rand(10000)}.db"
390
+ fn = Tempfile.new('extralite_test_database_busy_timeout').path
297
391
  db1 = Extralite::Database.new(fn)
298
392
  db2 = Extralite::Database.new(fn)
299
393
 
@@ -388,17 +482,77 @@ end
388
482
  assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
389
483
  end
390
484
 
485
+ def test_database_inspect_on_closed_database
486
+ db = Extralite::Database.new(':memory:')
487
+ assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
488
+ db.close
489
+ assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ \(closed\)\>$/, db.inspect
490
+ end
491
+
391
492
  def test_string_encoding
392
493
  db = Extralite::Database.new(':memory:')
393
494
  v = db.query_single_value("select 'foo'")
394
495
  assert_equal 'foo', v
395
496
  assert_equal 'UTF-8', v.encoding.name
396
497
  end
498
+
499
+ def test_database_transaction_commit
500
+ path = Tempfile.new('extralite_test_database_transaction_commit').path
501
+ db1 = Extralite::Database.new(path)
502
+ db2 = Extralite::Database.new(path)
503
+
504
+ db1.execute('create table foo(x)')
505
+ assert_equal ['foo'], db1.tables
506
+ assert_equal ['foo'], db2.tables
507
+
508
+ q1 = Queue.new
509
+ q2 = Queue.new
510
+ th = Thread.new do
511
+ db1.transaction do
512
+ assert_equal true, db1.transaction_active?
513
+ db1.execute('insert into foo values (42)')
514
+ q1 << true
515
+ q2.pop
516
+ end
517
+ assert_equal false, db1.transaction_active?
518
+ end
519
+ q1.pop
520
+ # transaction not yet committed
521
+ assert_equal false, db2.transaction_active?
522
+ assert_equal [], db2.query('select * from foo')
523
+
524
+ q2 << true
525
+ th.join
526
+ # transaction now committed
527
+ assert_equal [{ x: 42 }], db2.query('select * from foo')
528
+ end
529
+
530
+ def test_database_transaction_rollback
531
+ db = Extralite::Database.new(':memory:')
532
+ db.execute('create table foo(x)')
533
+
534
+ assert_equal [], db.query('select * from foo')
535
+
536
+ exception = nil
537
+ begin
538
+ db.transaction do
539
+ db.execute('insert into foo values (42)')
540
+ raise 'bar'
541
+ end
542
+ rescue => e
543
+ exception = e
544
+ end
545
+
546
+ assert_equal [], db.query('select * from foo')
547
+ assert_kind_of RuntimeError, exception
548
+ assert_equal 'bar', exception.message
549
+ end
397
550
  end
398
551
 
399
552
  class ScenarioTest < MiniTest::Test
400
553
  def setup
401
- @db = Extralite::Database.new('/tmp/extralite.db')
554
+ @fn = Tempfile.new('extralite_scenario_test').path
555
+ @db = Extralite::Database.new(@fn)
402
556
  @db.query('create table if not exists t (x,y,z)')
403
557
  @db.query('delete from t')
404
558
  @db.query('insert into t values (1, 2, 3)')
@@ -408,7 +562,7 @@ class ScenarioTest < MiniTest::Test
408
562
  def test_concurrent_transactions
409
563
  done = false
410
564
  t = Thread.new do
411
- db = Extralite::Database.new('/tmp/extralite.db')
565
+ db = Extralite::Database.new(@fn)
412
566
  db.query 'begin immediate'
413
567
  sleep 0.01 until done
414
568
 
@@ -465,6 +619,7 @@ class ScenarioTest < MiniTest::Test
465
619
  def test_database_trace
466
620
  sqls = []
467
621
  @db.trace { |sql| sqls << sql }
622
+ GC.start
468
623
 
469
624
  @db.query('select 1')
470
625
  assert_equal ['select 1'], sqls
@@ -515,10 +670,107 @@ class BackupTest < MiniTest::Test
515
670
  end
516
671
 
517
672
  def test_backup_with_fn
518
- tmp_fn = "/tmp/#{rand(86400)}.db"
673
+ tmp_fn = Tempfile.new('extralite_test_backup_with_fn').path
519
674
  @src.backup(tmp_fn)
520
675
 
521
676
  db = Extralite::Database.new(tmp_fn)
522
677
  assert_equal [[1, 2, 3], [4, 5, 6]], db.query_ary('select * from t')
523
678
  end
524
679
  end
680
+
681
+ class GVLReleaseThresholdTest < Minitest::Test
682
+ def setup
683
+ @sql = <<~SQL
684
+ WITH RECURSIVE r(i) AS (
685
+ VALUES(0)
686
+ UNION ALL
687
+ SELECT i FROM r
688
+ LIMIT 3000000
689
+ )
690
+ SELECT i FROM r WHERE i = 1;
691
+ SQL
692
+ end
693
+
694
+ def test_default_gvl_release_threshold
695
+ db = Extralite::Database.new(':memory:')
696
+ assert_equal 1000, db.gvl_release_threshold
697
+ end
698
+
699
+ def test_gvl_always_release
700
+ skip if !IS_LINUX
701
+
702
+ delays = []
703
+ running = true
704
+ t1 = Thread.new do
705
+ last = Time.now
706
+ while running
707
+ sleep 0.1
708
+ now = Time.now
709
+ delays << (now - last)
710
+ last = now
711
+ end
712
+ end
713
+ t2 = Thread.new do
714
+ db = Extralite::Database.new(':memory:')
715
+ db.gvl_release_threshold = 1
716
+ db.query(@sql)
717
+ ensure
718
+ running = false
719
+ end
720
+ t2.join
721
+ t1.join
722
+
723
+ assert delays.size > 4
724
+ assert_equal 0, delays.select { |d| d > 0.15 }.size
725
+ end
726
+
727
+ def test_gvl_always_hold
728
+ skip if !IS_LINUX
729
+
730
+ delays = []
731
+ running = true
732
+
733
+ signal = Queue.new
734
+ db = Extralite::Database.new(':memory:')
735
+ db.gvl_release_threshold = 0
736
+
737
+ t1 = Thread.new do
738
+ last = Time.now
739
+ while running
740
+ signal << true
741
+ sleep 0.1
742
+ now = Time.now
743
+ delays << (now - last)
744
+ last = now
745
+ end
746
+ end
747
+
748
+ t2 = Thread.new do
749
+ signal.pop
750
+ db.query(@sql)
751
+ ensure
752
+ running = false
753
+ end
754
+ t2.join
755
+ t1.join
756
+
757
+ assert delays.size >= 1
758
+ assert delays.first > 0.2
759
+ end
760
+
761
+ def test_gvl_mode_get_set
762
+ db = Extralite::Database.new(':memory:')
763
+ assert_equal 1000, db.gvl_release_threshold
764
+
765
+ db.gvl_release_threshold = 42
766
+ assert_equal 42, db.gvl_release_threshold
767
+
768
+ db.gvl_release_threshold = 0
769
+ assert_equal 0, db.gvl_release_threshold
770
+
771
+ assert_raises(ArgumentError) { db.gvl_release_threshold = :foo }
772
+
773
+ db.gvl_release_threshold = nil
774
+ assert_equal 1000, db.gvl_release_threshold
775
+ end
776
+ end
@@ -103,7 +103,8 @@ class IteratorTest < MiniTest::Test
103
103
  end
104
104
 
105
105
  def test_return_from_block_issue_26
106
- db = Extralite::Database.new('/tmp/locked.db')
106
+ fn = Tempfile.new('extralite_test_return_from_block_issue_26').path
107
+ db = Extralite::Database.new(fn)
107
108
 
108
109
  λ = ->(sql) {
109
110
  db.prepare(sql).each { |r| r.each { |_, v| return v } }
data/test/test_query.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'helper'
4
+ require 'date'
4
5
 
5
6
  class QueryTest < MiniTest::Test
6
7
  def setup
@@ -267,7 +268,7 @@ class QueryTest < MiniTest::Test
267
268
  end
268
269
 
269
270
  def test_query_each_without_block
270
- query = @db.prepare('select * from t')
271
+ query = @db.prepare('select * from t')
271
272
  iter = query.each
272
273
  assert_kind_of Extralite::Iterator, iter
273
274
 
@@ -294,7 +295,7 @@ class QueryTest < MiniTest::Test
294
295
  end
295
296
 
296
297
  def test_query_each_ary_without_block
297
- query = @db.prepare('select * from t')
298
+ query = @db.prepare('select * from t')
298
299
  iter = query.each_ary
299
300
  assert_kind_of Extralite::Iterator, iter
300
301
 
@@ -322,7 +323,7 @@ class QueryTest < MiniTest::Test
322
323
  end
323
324
 
324
325
  def test_query_each_single_column_without_block
325
- query = @db.prepare('select x from t')
326
+ query = @db.prepare('select x from t')
326
327
  iter = query.each_single_column
327
328
  assert_kind_of Extralite::Iterator, iter
328
329
 
@@ -375,6 +376,9 @@ class QueryTest < MiniTest::Test
375
376
  def test_query_parameter_binding_simple
376
377
  r = @db.prepare('select x, y, z from t where x = ?').bind(1).next
377
378
  assert_equal({ x: 1, y: 2, z: 3 }, r)
379
+
380
+ error = assert_raises(Extralite::ParameterError) { @db.prepare('select ?').bind(Date.today).next }
381
+ assert_equal error.message, 'Cannot bind parameter at position 1 of type Date'
378
382
  end
379
383
 
380
384
  def test_query_parameter_binding_with_index
@@ -404,6 +408,38 @@ class QueryTest < MiniTest::Test
404
408
  assert_equal({ x: 4, y: 5, z: 6 }, r)
405
409
  end
406
410
 
411
+ class Foo; end
412
+
413
+ def test_parameter_binding_from_hash
414
+ assert_equal 42, @db.prepare('select :bar').bind(foo: 41, bar: 42).next_single_column
415
+ assert_equal 42, @db.prepare('select :bar').bind('foo' => 41, 'bar' => 42).next_single_column
416
+ assert_equal 42, @db.prepare('select ?8').bind(7 => 41, 8 => 42).next_single_column
417
+ assert_nil @db.prepare('select :bar').bind(foo: 41).next_single_column
418
+
419
+ error = assert_raises(Extralite::ParameterError) { @db.prepare('select ?').bind(Foo.new => 42).next_single_column }
420
+ assert_equal error.message, 'Cannot bind parameter with a key of type QueryTest::Foo'
421
+
422
+ error = assert_raises(Extralite::ParameterError) { @db.prepare('select ?').bind(%w[a b] => 42).next_single_column }
423
+ assert_equal error.message, 'Cannot bind parameter with a key of type Array'
424
+ end
425
+
426
+ def test_parameter_binding_from_struct
427
+ foo_bar = Struct.new(:":foo", :bar)
428
+ value = foo_bar.new(41, 42)
429
+ assert_equal 41, @db.prepare('select :foo').bind(value).next_single_column
430
+ assert_equal 42, @db.prepare('select :bar').bind(value).next_single_column
431
+ assert_nil @db.prepare('select :baz').bind(value).next_single_column
432
+ end
433
+
434
+ def test_parameter_binding_from_data_class
435
+ skip "Data isn't supported in Ruby < 3.2" if RUBY_VERSION < '3.2'
436
+
437
+ foo_bar = Data.define(:":foo", :bar)
438
+ value = foo_bar.new(":foo": 41, bar: 42)
439
+ assert_equal 42, @db.prepare('select :bar').bind(value).next_single_column
440
+ assert_nil @db.prepare('select :baz').bind(value).next_single_column
441
+ end
442
+
407
443
  def test_query_columns
408
444
  r = @db.prepare("select 'abc' as a, 'def' as b").columns
409
445
  assert_equal [:a, :b], r
@@ -459,7 +495,7 @@ class QueryTest < MiniTest::Test
459
495
  { a: 1, b: '2', c: 3 },
460
496
  { a: '4', b: 5, c: 6 }
461
497
  ], @db.query('select * from foo')
462
- end
498
+ end
463
499
 
464
500
  def test_query_status
465
501
  assert_equal 0, @query.status(Extralite::SQLITE_STMTSTATUS_RUN)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extralite-bundle
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.2'
4
+ version: '2.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-14 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: rake-compiler
@@ -88,12 +88,14 @@ extensions:
88
88
  extra_rdoc_files:
89
89
  - README.md
90
90
  files:
91
+ - ".editorconfig"
91
92
  - ".github/FUNDING.yml"
92
93
  - ".github/workflows/test.yml"
93
94
  - ".gitignore"
94
95
  - ".yardopts"
95
96
  - CHANGELOG.md
96
97
  - Gemfile
98
+ - Gemfile-bundle
97
99
  - Gemfile.lock
98
100
  - LICENSE
99
101
  - README.md
@@ -120,7 +122,9 @@ files:
120
122
  - lib/sequel/adapters/extralite.rb
121
123
  - test/extensions/text.dylib
122
124
  - test/extensions/text.so
125
+ - test/fixtures/image.png
123
126
  - test/helper.rb
127
+ - test/issue-38.rb
124
128
  - test/perf_ary.rb
125
129
  - test/perf_hash.rb
126
130
  - test/perf_prepared.rb