extralite-bundle 2.14 → 3.0.0

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.
data/gemspec.rb CHANGED
@@ -15,11 +15,11 @@ def common_spec(s)
15
15
  s.rdoc_options = ['--title', 'Extralite', '--main', 'README.md']
16
16
  s.extra_rdoc_files = ['README.md']
17
17
  s.require_paths = ['lib']
18
- s.required_ruby_version = '>= 3.2'
18
+ s.required_ruby_version = '>= 3.4'
19
19
 
20
20
  s.add_development_dependency 'rake-compiler', '1.3.1'
21
- s.add_development_dependency 'minitest', '6.0.1'
21
+ s.add_development_dependency 'minitest'
22
22
  s.add_development_dependency 'simplecov', '0.22.0'
23
23
  s.add_development_dependency 'yard', '0.9.38'
24
- s.add_development_dependency 'sequel', '5.100.0'
24
+ s.add_development_dependency 'sequel', '5.105.0'
25
25
  end
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.14'
3
+ VERSION = '3.0.0'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -236,7 +236,7 @@ module Extralite
236
236
  # @return [Any] the given block's return value
237
237
  def transaction(mode = :immediate)
238
238
  abort = false
239
- execute "begin #{mode} transaction"
239
+ execute "begin #{mode} transaction"
240
240
  yield self
241
241
  rescue => e
242
242
  abort = true
@@ -296,6 +296,10 @@ module Extralite
296
296
  raise Rollback
297
297
  end
298
298
 
299
+ def quote(str)
300
+ str.gsub("'", "''")
301
+ end
302
+
299
303
  private
300
304
 
301
305
  def pragma_set(values)
@@ -311,4 +315,101 @@ module Extralite
311
315
  class Query
312
316
  alias_method :execute_multi, :batch_execute
313
317
  end
318
+
319
+ class Transform
320
+ alias_method :orig_initialize, :initialize
321
+
322
+ # call-seq:
323
+ # Extralite::Transform.new(literal_spec) -> transform
324
+ # Extralite::Transform.new { dsl_spec } -> transform
325
+ #
326
+ # Initializes a new transform with with the given spec. The spec may be
327
+ # expressed as a hash containing the columns and any nested entities, or as a
328
+ # block with the transform DSL.
329
+ #
330
+ # # literal transform spec
331
+ # transform = Extralite::Transform.new(
332
+ # columns: {
333
+ # id: { type: :integer, identity: true },
334
+ # content: { type: :text },
335
+ # author: {
336
+ # type: :relation,
337
+ # columns: {
338
+ # id: { type: :integer, identity: true },
339
+ # name: { type: :text },
340
+ # }
341
+ # }
342
+ # }
343
+ # )
344
+ #
345
+ # # DSL spec
346
+ # transform = Extralite::Transform.new do
347
+ # {
348
+ # id: integer.identity,
349
+ # content: text,
350
+ # author: {
351
+ # id: integer.identity,
352
+ # name: text
353
+ # }
354
+ # }
355
+ # end
356
+ #
357
+ # @param spec [Hash, nil] literal spec
358
+ # @param block [Proc, nil] DSL spec
359
+ # @return [void]
360
+ def initialize(spec = nil, &block)
361
+ return orig_initialize(spec) if spec
362
+ raise "No spec given" if !block
363
+
364
+ orig_initialize(dsl_to_spec(block))
365
+ end
366
+
367
+ private
368
+
369
+ class DSLContext
370
+ attr_reader :hash
371
+
372
+ def initialize(hash = {})
373
+ @hash = hash
374
+ end
375
+
376
+ def auto = mutate(type: nil)
377
+ def integer = mutate(type: :integer)
378
+ def float = mutate(type: :float)
379
+ def text = mutate(type: :text)
380
+ def bool = mutate(type: :bool)
381
+ def json = mutate(type: :json)
382
+ def identity = mutate(identity: true)
383
+
384
+ private
385
+ def mutate(opts)
386
+ DSLContext.new(@hash.merge(opts))
387
+ end
388
+ end
389
+
390
+ def dsl_to_spec(block)
391
+ intermediate = DSLContext.new.instance_eval(&block)
392
+ dsl_intermediate_to_spec(intermediate)
393
+ end
394
+
395
+ def dsl_intermediate_to_spec(intermediate)
396
+ columns = intermediate.each_with_object({}) do |(k, v), h|
397
+ h[k] = dsl_translate_intermediate_value(v)
398
+ end
399
+ { type: :relation, columns: }
400
+ end
401
+
402
+ def dsl_translate_intermediate_value(v)
403
+ case v
404
+ when DSLContext
405
+ v.hash
406
+ when Hash
407
+ dsl_intermediate_to_spec(v)
408
+ when Array
409
+ [dsl_intermediate_to_spec(v[0])]
410
+ else
411
+ raise Extralite::Error, 'Invalid transform spec'
412
+ end
413
+ end
414
+ end
314
415
  end
data/test/perf_array.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- gem 'sqlite3', '2.6.0'
7
+ gem 'sqlite3', '2.9.5'
8
8
  gem 'extralite', path: '..'
9
9
  gem 'benchmark-ips'
10
10
  end
data/test/perf_hash.rb CHANGED
@@ -7,7 +7,7 @@ require 'bundler/inline'
7
7
  gemfile do
8
8
  source 'https://rubygems.org'
9
9
  gem 'extralite', path: '..'
10
- gem 'sqlite3', '2.6.0'
10
+ gem 'sqlite3', '2.9.5'
11
11
  gem 'benchmark-ips'
12
12
  end
13
13
 
@@ -5,7 +5,7 @@ require 'bundler/inline'
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
7
  gem 'extralite', path: '..'
8
- gem 'sqlite3', '2.6.0'
8
+ gem 'sqlite3', '2.9.5'
9
9
  gem 'benchmark-ips'
10
10
  end
11
11
 
@@ -18,7 +18,7 @@ puts "DB_PATH = #{DB_PATH.inspect}"
18
18
  def prepare_database(count)
19
19
  $sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
20
20
  $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
21
-
21
+
22
22
  $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
23
23
  $extralite_db.query('delete from foo')
24
24
  $extralite_db.query('begin')
data/test/perf_splat.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- gem 'sqlite3', '2.6.0'
7
+ gem 'sqlite3', '2.9.5'
8
8
  gem 'extralite', path: '..'
9
9
  gem 'benchmark-ips'
10
10
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Run on Ruby 3.3 with YJIT enabled
4
+
5
+ require 'bundler/inline'
6
+
7
+ gemfile do
8
+ source 'https://rubygems.org'
9
+ gem 'extralite', path: '..'
10
+ gem 'benchmark-ips'
11
+ end
12
+
13
+ require 'benchmark/ips'
14
+ require 'fileutils'
15
+
16
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
17
+ puts "DB_PATH = #{DB_PATH.inspect}"
18
+
19
+ $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
20
+
21
+ def prepare_database(count)
22
+ $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
23
+ $extralite_db.query('delete from foo')
24
+ $extralite_db.query('begin')
25
+ count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
26
+ $extralite_db.query('commit')
27
+ end
28
+
29
+ def run_normal(count)
30
+ results = $extralite_db.query('select * from foo')
31
+ raise unless results.size == count
32
+ end
33
+
34
+ @transform = Extralite::Transform.new {
35
+ { a: integer, b: text }
36
+ }
37
+
38
+ def run_transform(count)
39
+ results = $extralite_db.query(@transform, 'select * from foo')
40
+ raise unless results.size == count
41
+ end
42
+
43
+ [10, 1000, 100000].each do |c|
44
+ puts "Record count: #{c}"
45
+ prepare_database(c)
46
+
47
+ bm = Benchmark.ips do |x|
48
+ x.config(:time => 5, :warmup => 2)
49
+
50
+ x.report("normal") { run_normal(c) }
51
+ x.report("transform") { run_transform(c) }
52
+
53
+ x.compare!
54
+ end
55
+ puts;
56
+ bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
57
+ puts;
58
+ end
@@ -302,6 +302,15 @@ class DatabaseTest < Minitest::Test
302
302
 
303
303
  r = @db.query_single_splat("select reverse('abcd')")
304
304
  assert_equal 'dcba', r
305
+
306
+ e = nil
307
+ begin
308
+ @db.execute("select load_extension('/extensions/foo')")
309
+ rescue => e
310
+ end
311
+
312
+ assert_kind_of Extralite::SQLError, e
313
+ assert_equal 'not authorized', e.message
305
314
  end
306
315
 
307
316
  def test_tables
@@ -341,7 +350,7 @@ class DatabaseTest < Minitest::Test
341
350
 
342
351
  def test_pragma
343
352
  assert_equal 'memory', @db.pragma('journal_mode')
344
- assert_equal 2, @db.pragma('synchronous')
353
+ assert_equal 1, @db.pragma('synchronous')
345
354
 
346
355
  assert_equal 1, @db.pragma(:schema_version)
347
356
  assert_equal 0, @db.pragma(:recursive_triggers)
@@ -866,14 +875,17 @@ class DatabaseTest < Minitest::Test
866
875
  db = Extralite::Database.new(':memory:', gvl_release_threshold: 23)
867
876
  assert_equal 23, db.gvl_release_threshold
868
877
 
869
- fn = Tempfile.new('extralite_test_database_initialize_options_wal').path
870
- db = Extralite::Database.new(fn, wal: true)
878
+ fn = Tempfile.new('extralite_test_database_initialize_modern').path
879
+ db = Extralite::Database.new(fn)
871
880
  assert_equal 'wal', db.pragma(:journal_mode)
872
881
  assert_equal 1, db.pragma(:synchronous)
882
+ assert_equal 1, db.pragma(:foreign_keys)
873
883
 
874
- fn = Tempfile.new('extralite_test_database_initialize_options_pragma').path
875
- db = Extralite::Database.new(fn, pragma: { application_id: 42 })
876
- assert_equal 42, db.pragma(:application_id)
884
+ fn = Tempfile.new('extralite_test_database_initialize_legacy').path
885
+ db = Extralite::Database.new(fn, legacy: true)
886
+ assert_equal 'delete', db.pragma(:journal_mode)
887
+ assert_equal 2, db.pragma(:synchronous)
888
+ assert_equal 0, db.pragma(:foreign_keys)
877
889
  end
878
890
 
879
891
  def test_database_inspect
@@ -1052,7 +1064,7 @@ class DatabaseTest < Minitest::Test
1052
1064
  def test_wal_checkpoint
1053
1065
  fn = Tempfile.new('extralite_test_wal_checkpoint').path
1054
1066
 
1055
- db = Extralite::Database.new(fn, wal: true)
1067
+ db = Extralite::Database.new(fn)
1056
1068
 
1057
1069
  db.execute 'create table t (x, y, z)'
1058
1070
  rows = (1..1000).map { [rand, rand, rand] }
@@ -1121,12 +1133,27 @@ class DatabaseTest < Minitest::Test
1121
1133
  assert_nil result
1122
1134
  assert_equal [7, 8], @db.query_splat('SELECT x FROM t ORDER BY x')
1123
1135
  end
1136
+
1137
+ def test_quote
1138
+ assert_equal '"abc', @db.quote('"abc')
1139
+ assert_equal "''abc''def", @db.quote("'abc'def")
1140
+
1141
+ assert_equal "'abc'def", @db.query_single_splat("select '#{@db.quote("'abc'def")}'")
1142
+ end
1143
+
1144
+ def test_cache_flush
1145
+ assert_equal @db, @db.cache_flush
1146
+ end
1147
+
1148
+ def test_release_memory
1149
+ assert_equal @db, @db.release_memory
1150
+ end
1124
1151
  end
1125
1152
 
1126
1153
  class ScenarioTest < Minitest::Test
1127
1154
  def setup
1128
1155
  @fn = Tempfile.new('extralite_scenario_test').path
1129
- @db = Extralite::Database.new(@fn)
1156
+ @db = Extralite::Database.new(@fn, legacy: true)
1130
1157
  @db.query('create table if not exists t (x,y,z)')
1131
1158
  @db.query('delete from t')
1132
1159
  @db.query('insert into t values (1, 2, 3)')
@@ -1136,7 +1163,7 @@ class ScenarioTest < Minitest::Test
1136
1163
  def test_concurrent_transactions
1137
1164
  done = false
1138
1165
  t = Thread.new do
1139
- db = Extralite::Database.new(@fn)
1166
+ db = Extralite::Database.new(@fn, legacy: true)
1140
1167
  db.query 'begin immediate'
1141
1168
  sleep 0.01 until done
1142
1169
 
@@ -1806,7 +1833,7 @@ class RactorTest < Minitest::Test
1806
1833
  assert_equal [10] * 10, buf
1807
1834
 
1808
1835
  final_check = Ractor.new(fn) do |rfn|
1809
- rdb = Extralite::Database.new(rfn, wal: true)
1836
+ rdb = Extralite::Database.new(rfn)
1810
1837
  count = rdb.query_single_splat('select count(*) from stress_test')
1811
1838
  Ractor.yield count
1812
1839
  end
data/test/test_query.rb CHANGED
@@ -27,7 +27,7 @@ class QueryTest < Minitest::Test
27
27
  def test_mode
28
28
  query = @db.prepare('select 1')
29
29
  assert_equal :hash, query.mode
30
-
30
+
31
31
  query.mode = :splat
32
32
  assert_equal :splat, query.mode
33
33
 
@@ -554,7 +554,7 @@ class QueryTest < Minitest::Test
554
554
  def test_query_execute_with_mixed_params
555
555
  @db.execute 'delete from t'
556
556
  q = @db.prepare('insert into t values (?, ?, ?)')
557
-
557
+
558
558
  q.execute(1, [2], 3)
559
559
  q.execute([4, 5], 6)
560
560
  q.execute([7], 8, [9])
@@ -1014,7 +1014,7 @@ class QueryTransformTest < Minitest::Test
1014
1014
 
1015
1015
  @q1 = @db.prepare_splat('select c from t where a = ?')
1016
1016
  @q2 = @db.prepare_splat('select c from t order by a')
1017
-
1017
+
1018
1018
  @q3 = @db.prepare('select * from t where a = ?')
1019
1019
  @q4 = @db.prepare('select * from t order by a')
1020
1020
 
@@ -1038,8 +1038,7 @@ class QueryTransformTest < Minitest::Test
1038
1038
  end
1039
1039
 
1040
1040
  def test_transform_hash
1041
- q = @q5.transform { |h| MyModel.new(h) }
1042
- assert_equal @q5, q
1041
+ @q5.transform = ->(h) { MyModel.new(h) }
1043
1042
 
1044
1043
  o = @q5.bind(1).next
1045
1044
  assert_kind_of MyModel, o
@@ -1054,7 +1053,7 @@ class QueryTransformTest < Minitest::Test
1054
1053
  [{ a: 4, b: 5 }]
1055
1054
  ], @q5.batch_query([[1], [4]]).map { |a| a.map(&:values) }
1056
1055
 
1057
- @q6.transform { |h| MyModel.new(h) }
1056
+ @q6.transform = ->(h) { MyModel.new(h) }
1058
1057
  assert_equal [
1059
1058
  { a: 1, b: 2 },
1060
1059
  { a: 4, b: 5 }
@@ -1070,8 +1069,7 @@ class QueryTransformTest < Minitest::Test
1070
1069
 
1071
1070
  def test_transform_array
1072
1071
  @q5.mode = :array
1073
- q = @q5.transform { |h| MyModel.new(h) }
1074
- assert_equal @q5, q
1072
+ @q5.transform = ->(h) { MyModel.new(h) }
1075
1073
 
1076
1074
  o = @q5.bind(1).next
1077
1075
  assert_kind_of MyModel, o
@@ -1087,7 +1085,7 @@ class QueryTransformTest < Minitest::Test
1087
1085
  ], @q5.batch_query([[1], [4]]).map { |a| a.map(&:values) }
1088
1086
 
1089
1087
  @q6.mode = :array
1090
- @q6.transform { |h| MyModel.new(h) }
1088
+ @q6.transform = ->(h) { MyModel.new(h) }
1091
1089
  assert_equal [
1092
1090
  [1, 2],
1093
1091
  [4, 5]
@@ -1102,8 +1100,7 @@ class QueryTransformTest < Minitest::Test
1102
1100
  end
1103
1101
 
1104
1102
  def test_transform_splat_single_column
1105
- q = @q1.transform { |c| JSON.parse(c, symbolize_names: true) }
1106
- assert_equal @q1, q
1103
+ @q1.transform = ->(c) { JSON.parse(c, symbolize_names: true) }
1107
1104
 
1108
1105
  assert_equal({ foo: 42, bar: 43 }, @q1.bind(1).next)
1109
1106
  assert_equal({ foo: 45, bar: 46 }, @q1.bind(4).next)
@@ -1113,7 +1110,7 @@ class QueryTransformTest < Minitest::Test
1113
1110
  [{ foo: 45, bar: 46 }]
1114
1111
  ], @q1.batch_query([[1], [4]])
1115
1112
 
1116
- @q2.transform { |c| JSON.parse(c, symbolize_names: true) }
1113
+ @q2.transform = ->(c) { JSON.parse(c, symbolize_names: true) }
1117
1114
  assert_equal [
1118
1115
  { foo: 42, bar: 43 },
1119
1116
  { foo: 45, bar: 46 }
@@ -1129,8 +1126,7 @@ class QueryTransformTest < Minitest::Test
1129
1126
 
1130
1127
  def test_transform_splat_multi_column
1131
1128
  @q3.mode = :splat
1132
- q = @q3.transform { |a, b, c| { a: a, b: b, c: JSON.parse(c, symbolize_names: true) } }
1133
- assert_equal @q3, q
1129
+ @q3.transform = ->(a, b, c) { { a: a, b: b, c: JSON.parse(c, symbolize_names: true) } }
1134
1130
 
1135
1131
  assert_equal({ a: 1, b: 2, c: { foo: 42, bar: 43 }}, @q3.bind(1).next)
1136
1132
  assert_equal({ a: 4, b: 5, c: { foo: 45, bar: 46 }}, @q3.bind(4).next)
@@ -1141,7 +1137,7 @@ class QueryTransformTest < Minitest::Test
1141
1137
  ], @q3.batch_query([[1], [4]])
1142
1138
 
1143
1139
  @q4.mode = :splat
1144
- @q4.transform { |a, b, c| { a: a, b: b, c: JSON.parse(c, symbolize_names: true) } }
1140
+ @q4.transform = ->(a, b, c) { { a: a, b: b, c: JSON.parse(c, symbolize_names: true) } }
1145
1141
  assert_equal [
1146
1142
  { a: 1, b: 2, c: { foo: 42, bar: 43 }},
1147
1143
  { a: 4, b: 5, c: { foo: 45, bar: 46 }}