extralite 2.6 → 2.7

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