extralite-bundle 2.2 → 2.4

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