extralite 2.6 → 2.7

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/lib/extralite.rb CHANGED
@@ -29,10 +29,10 @@ module Extralite
29
29
  class ParameterError < Error
30
30
  end
31
31
 
32
- # An SQLite database
32
+ # This class encapsulates an SQLite database connection.
33
33
  class Database
34
34
  # @!visibility private
35
- TABLES_SQL = <<~SQL
35
+ TABLES_SQL = (<<~SQL).freeze
36
36
  SELECT name FROM %<db>s.sqlite_master
37
37
  WHERE type ='table'
38
38
  AND name NOT LIKE 'sqlite_%%';
@@ -46,10 +46,11 @@ module Extralite
46
46
  # @param db [String] name of attached database
47
47
  # @return [Array] list of tables
48
48
  def tables(db = 'main')
49
- query_single_column(format(TABLES_SQL, db: db))
49
+ query_argv(format(TABLES_SQL, db: db))
50
50
  end
51
51
 
52
- # Gets or sets one or more pragmas:
52
+ # Gets or sets one or more database pragmas. For a list of available pragmas
53
+ # see: https://sqlite.org/pragma.html#toc
53
54
  #
54
55
  # db.pragma(:cache_size) # get
55
56
  # db.pragma(cache_size: -2000) # set
@@ -74,21 +75,29 @@ module Extralite
74
75
  # raise if db.query_single_value('select x from bar') > 42
75
76
  # end
76
77
  #
77
- # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
78
+ # For more information on transactions see:
79
+ # https://sqlite.org/lang_transaction.html
80
+ #
81
+ # @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive).
78
82
  # @return [Any] the given block's return value
79
83
  def transaction(mode = :immediate)
80
- execute "begin #{mode} transaction"
81
-
82
84
  abort = false
85
+ execute "begin #{mode} transaction"
83
86
  yield self
84
87
  rescue => e
85
88
  abort = true
86
- raise unless e.is_a?(Rollback)
89
+ e.is_a?(Rollback) ? nil : raise
87
90
  ensure
88
91
  execute(abort ? 'rollback' : 'commit')
89
92
  end
90
93
 
91
- # Creates a savepoint with the given name.
94
+ # Creates a savepoint with the given name. For more information on
95
+ # savepoints see: https://sqlite.org/lang_savepoint.html
96
+ #
97
+ # db.savepoint(:savepoint1)
98
+ # db.execute('insert into foo values (42)')
99
+ # db.rollback_to(:savepoint1)
100
+ # db.release(:savepoint1)
92
101
  #
93
102
  # @param name [String, Symbol] savepoint name
94
103
  # @return [Extralite::Database] database
@@ -97,7 +106,8 @@ module Extralite
97
106
  self
98
107
  end
99
108
 
100
- # Release a savepoint with the given name.
109
+ # Release a savepoint with the given name. For more information on
110
+ # savepoints see: https://sqlite.org/lang_savepoint.html
101
111
  #
102
112
  # @param name [String, Symbol] savepoint name
103
113
  # @return [Extralite::Database] database
@@ -106,7 +116,8 @@ module Extralite
106
116
  self
107
117
  end
108
118
 
109
- # Rolls back changes to a savepoint with the given name.
119
+ # Rolls back changes to a savepoint with the given name. For more
120
+ # information on savepoints see: https://sqlite.org/lang_savepoint.html
110
121
  #
111
122
  # @param name [String, Symbol] savepoint name
112
123
  # @return [Extralite::Database] database
@@ -116,14 +127,14 @@ module Extralite
116
127
  end
117
128
 
118
129
  # Rolls back the currently active transaction. This method should only be
119
- # called from within a block passed to Database#transaction. This method
130
+ # called from within a block passed to `Database#transaction`. This method
120
131
  # raises a Extralite::Rollback exception, which will stop execution of the
121
132
  # transaction block without propagating the exception.
122
133
  #
123
- # db.transaction do
124
- # db.execute('insert into foo (42)')
125
- # db.rollback!
126
- # end
134
+ # db.transaction do
135
+ # db.execute('insert into foo (42)')
136
+ # db.rollback!
137
+ # end
127
138
  #
128
139
  # @param name [String, Symbol] savepoint name
129
140
  # @return [Extralite::Database] database
@@ -139,7 +150,7 @@ module Extralite
139
150
  end
140
151
 
141
152
  def pragma_get(key)
142
- query_single_value("pragma #{key}")
153
+ query_single_argv("pragma #{key}")
143
154
  end
144
155
  end
145
156
 
@@ -362,7 +362,7 @@ module Sequel
362
362
  def fetch_rows(sql, &block)
363
363
  execute(sql) do |result, columns|
364
364
  self.columns = columns
365
- max = columns.size
365
+ # max = columns.size
366
366
  result.each(&block)
367
367
  end
368
368
  end
data/test/helper.rb CHANGED
@@ -7,7 +7,8 @@ require 'minitest/autorun'
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
8
 
9
9
  IS_LINUX = RUBY_PLATFORM =~ /linux/
10
- SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
10
+ # Ractors are kinda flaky, there's no point in testing this
11
+ SKIP_RACTOR_TESTS = true #!IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
11
12
 
12
13
  module Minitest::Assertions
13
14
  def assert_in_range exp_range, act
@@ -0,0 +1,74 @@
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 (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
+ class Model
30
+ def initialize(h)
31
+ @h = h
32
+ end
33
+
34
+ def values
35
+ @h
36
+ end
37
+ end
38
+
39
+ TRANSFORM = ->(b) { { b: b } }
40
+
41
+ def extralite_run_ary_map(count)
42
+ results = []
43
+ $extralite_db.query_ary('select * from foo') { |(b)| results << { b: b } }
44
+ raise unless results.size == count
45
+ end
46
+
47
+ def extralite_run_argv_map(count)
48
+ results = []
49
+ $extralite_db.query_argv('select * from foo') { |b| results << { b: b } }
50
+ raise unless results.size == count
51
+ end
52
+
53
+ def extralite_run_transform(count)
54
+ results = $extralite_db.query_argv(TRANSFORM, 'select * from foo')
55
+ raise unless results.size == count
56
+ end
57
+
58
+ [10, 1000, 100000].each do |c|
59
+ puts "Record count: #{c}"
60
+ prepare_database(c)
61
+
62
+ bm = Benchmark.ips do |x|
63
+ x.config(:time => 5, :warmup => 2)
64
+
65
+ x.report("ary_map") { extralite_run_ary_map(c) }
66
+ x.report("argv_map") { extralite_run_argv_map(c) }
67
+ x.report("transform") { extralite_run_transform(c) }
68
+
69
+ x.compare!
70
+ end
71
+ puts;
72
+ bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
73
+ puts;
74
+ end
@@ -0,0 +1,66 @@
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
+ class Model
30
+ def initialize(h)
31
+ @h = h
32
+ end
33
+
34
+ def values
35
+ @h
36
+ end
37
+ end
38
+
39
+ TRANSFORM = ->(h) { Model.new(h) }
40
+
41
+ def extralite_run_map(count)
42
+ results = $extralite_db.query('select * from foo').map(&TRANSFORM)
43
+ raise unless results.size == count
44
+ end
45
+
46
+ def extralite_run_transform(count)
47
+ results = $extralite_db.query(TRANSFORM, 'select * from foo')
48
+ raise unless results.size == count
49
+ end
50
+
51
+ [10, 1000, 100000].each do |c|
52
+ puts "Record count: #{c}"
53
+ prepare_database(c)
54
+
55
+ bm = Benchmark.ips do |x|
56
+ x.config(:time => 5, :warmup => 2)
57
+
58
+ x.report("map") { extralite_run_map(c) }
59
+ x.report("transform") { extralite_run_transform(c) }
60
+
61
+ x.compare!
62
+ end
63
+ puts;
64
+ bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
65
+ puts;
66
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Run on Ruby 3.3 with YJIT enabled
4
+
5
+ require 'bundler/inline'
6
+ gemfile do
7
+ gem 'polyphony'
8
+ gem 'extralite', path: '.'
9
+ gem 'benchmark-ips'
10
+ end
11
+
12
+ require 'benchmark/ips'
13
+ require 'polyphony'
14
+
15
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
+ puts "DB_PATH = #{DB_PATH.inspect}"
17
+
18
+ $db1 = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
19
+ $db2 = Extralite::Database.new(DB_PATH, gvl_release_threshold: 0)
20
+ $db3 = Extralite::Database.new(DB_PATH)
21
+
22
+ $snooze_count = 0
23
+ $db3.on_progress(25) { $snooze_count += 1; snooze }
24
+
25
+ def prepare_database(count)
26
+ $db1.execute('create table if not exists foo ( a integer primary key, b text )')
27
+ $db1.transaction do
28
+ $db1.execute('delete from foo')
29
+ rows = count.times.map { "hello#{rand(1000)}" }
30
+ $db1.batch_execute('insert into foo (b) values (?)', rows)
31
+ end
32
+ end
33
+
34
+ def extralite_run1(count)
35
+ results = $db1.query('select * from foo')
36
+ raise unless results.size == count
37
+ end
38
+
39
+ def extralite_run2(count)
40
+ results = $db2.query('select * from foo')
41
+ raise unless results.size == count
42
+ end
43
+
44
+ def extralite_run3(count)
45
+ results = $db3.query('select * from foo')
46
+ raise unless results.size == count
47
+ end
48
+
49
+ [10, 1000, 100000].each do |c|
50
+ puts "Record count: #{c}"
51
+ prepare_database(c)
52
+
53
+ bm = Benchmark.ips do |x|
54
+ x.config(:time => 3, :warmup => 1)
55
+
56
+ x.report('GVL threshold -1') { extralite_run1(c) }
57
+ x.report('GVL threshold 0') { extralite_run2(c) }
58
+ $snooze_count = 0
59
+ x.report('on_progress 1000') { extralite_run3(c) }
60
+
61
+ x.compare!
62
+ end
63
+ puts;
64
+ bm.entries.each do |e|
65
+ score = (e.ips * c).round.to_i
66
+ if e.label == 'on_progress 1000'
67
+ snooze_rate = ($snooze_count / e.seconds).to_i
68
+ puts "#{e.label}: #{score} rows/s snoozes: #{snooze_rate} i/s"
69
+ else
70
+ puts "#{e.label}: #{score} rows/s"
71
+ end
72
+ end
73
+ puts;
74
+ end
@@ -5,7 +5,7 @@ require_relative 'helper'
5
5
  require 'date'
6
6
  require 'tempfile'
7
7
 
8
- class ChangesetTest < MiniTest::Test
8
+ class ChangesetTest < Minitest::Test
9
9
  def setup
10
10
  @db = Extralite::Database.new(':memory:')
11
11
  skip if !@db.respond_to?(:track_changes)
@@ -130,7 +130,7 @@ class ChangesetTest < MiniTest::Test
130
130
 
131
131
  def test_blob
132
132
  changeset = Extralite::Changeset.new
133
- assert_equal "", changeset.to_blob
133
+ assert_equal '', changeset.to_blob
134
134
 
135
135
  changeset.track(@db, [:t]) do
136
136
  @db.execute('insert into t values (1, 2, 3)')