extralite 2.5 → 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +34 -13
- data/Gemfile +4 -0
- data/Gemfile-bundle +1 -1
- data/LICENSE +1 -1
- data/README.md +1059 -247
- data/Rakefile +18 -0
- data/TODO.md +0 -7
- data/examples/kv_store.rb +49 -0
- data/examples/multi_fiber.rb +16 -0
- data/examples/on_progress.rb +9 -0
- data/examples/pubsub_store_polyphony.rb +194 -0
- data/examples/pubsub_store_threads.rb +204 -0
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +177 -91
- data/ext/extralite/database.c +745 -276
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +34 -34
- data/ext/extralite/extralite.h +104 -47
- data/ext/extralite/extralite_ext.c +6 -0
- data/ext/extralite/iterator.c +14 -86
- data/ext/extralite/query.c +171 -264
- data/extralite-bundle.gemspec +1 -1
- data/extralite.gemspec +1 -1
- data/gemspec.rb +10 -11
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +69 -10
- data/lib/sequel/adapters/extralite.rb +1 -1
- data/test/helper.rb +9 -1
- data/test/perf_argv_transform.rb +74 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/perf_hash_transform.rb +66 -0
- data/test/perf_polyphony.rb +74 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +720 -104
- data/test/test_extralite.rb +2 -2
- data/test/test_iterator.rb +28 -13
- data/test/test_query.rb +352 -110
- data/test/test_sequel.rb +4 -4
- metadata +24 -16
- data/Gemfile.lock +0 -37
- data/test/perf_prepared.rb +0 -64
    
        data/gemspec.rb
    CHANGED
    
    | @@ -8,19 +8,18 @@ def common_spec(s) | |
| 8 8 | 
             
              s.files       = `git ls-files`.split
         | 
| 9 9 | 
             
              s.homepage    = 'https://github.com/digital-fabric/extralite'
         | 
| 10 10 | 
             
              s.metadata    = {
         | 
| 11 | 
            -
                 | 
| 12 | 
            -
                 | 
| 13 | 
            -
                 | 
| 14 | 
            -
                "changelog_uri" => "https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md"
         | 
| 11 | 
            +
                'homepage_uri' => 'https://github.com/digital-fabric/extralite',
         | 
| 12 | 
            +
                'documentation_uri' => 'https://www.rubydoc.info/gems/extralite',
         | 
| 13 | 
            +
                'changelog_uri' => 'https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md'
         | 
| 15 14 | 
             
              }
         | 
| 16 | 
            -
              s.rdoc_options = [ | 
| 17 | 
            -
              s.extra_rdoc_files = [ | 
| 18 | 
            -
              s.require_paths = [ | 
| 15 | 
            +
              s.rdoc_options = ['--title', 'Extralite', '--main', 'README.md']
         | 
| 16 | 
            +
              s.extra_rdoc_files = ['README.md']
         | 
| 17 | 
            +
              s.require_paths = ['lib']
         | 
| 19 18 | 
             
              s.required_ruby_version = '>= 3.0'
         | 
| 20 19 |  | 
| 21 | 
            -
              s.add_development_dependency  'rake-compiler',        '1. | 
| 22 | 
            -
              s.add_development_dependency  'minitest',             '5. | 
| 20 | 
            +
              s.add_development_dependency  'rake-compiler',        '1.2.7'
         | 
| 21 | 
            +
              s.add_development_dependency  'minitest',             '5.21.2'
         | 
| 23 22 | 
             
              s.add_development_dependency  'simplecov',            '0.17.1'
         | 
| 24 | 
            -
              s.add_development_dependency  'yard',                 '0.9. | 
| 25 | 
            -
              s.add_development_dependency  'sequel',               '5. | 
| 23 | 
            +
              s.add_development_dependency  'yard',                 '0.9.34'
         | 
| 24 | 
            +
              s.add_development_dependency  'sequel',               '5.77.0'
         | 
| 26 25 | 
             
            end
         | 
    
        data/lib/extralite/version.rb
    CHANGED
    
    
    
        data/lib/extralite.rb
    CHANGED
    
    | @@ -29,10 +29,10 @@ module Extralite | |
| 29 29 | 
             
              class ParameterError < Error
         | 
| 30 30 | 
             
              end
         | 
| 31 31 |  | 
| 32 | 
            -
              #  | 
| 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 | 
            -
                   | 
| 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
         | 
| @@ -60,6 +61,11 @@ module Extralite | |
| 60 61 | 
             
                  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
         | 
| 61 62 | 
             
                end
         | 
| 62 63 |  | 
| 64 | 
            +
                # Error class used to roll back a transaction without propagating an
         | 
| 65 | 
            +
                # exception.
         | 
| 66 | 
            +
                class Rollback < Error
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 63 69 | 
             
                # Starts a transaction and runs the given block. If an exception is raised
         | 
| 64 70 | 
             
                # in the block, the transaction is rolled back. Otherwise, the transaction
         | 
| 65 71 | 
             
                # is commited after running the block.
         | 
| @@ -69,20 +75,73 @@ module Extralite | |
| 69 75 | 
             
                #       raise if db.query_single_value('select x from bar') > 42
         | 
| 70 76 | 
             
                #     end
         | 
| 71 77 | 
             
                #
         | 
| 72 | 
            -
                #  | 
| 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).
         | 
| 73 82 | 
             
                # @return [Any] the given block's return value
         | 
| 74 83 | 
             
                def transaction(mode = :immediate)
         | 
| 75 | 
            -
                  execute "begin #{mode} transaction"
         | 
| 76 | 
            -
                
         | 
| 77 84 | 
             
                  abort = false
         | 
| 85 | 
            +
                  execute "begin #{mode} transaction"    
         | 
| 78 86 | 
             
                  yield self
         | 
| 79 | 
            -
                rescue
         | 
| 87 | 
            +
                rescue => e
         | 
| 80 88 | 
             
                  abort = true
         | 
| 81 | 
            -
                  raise
         | 
| 89 | 
            +
                  e.is_a?(Rollback) ? nil : raise
         | 
| 82 90 | 
             
                ensure
         | 
| 83 91 | 
             
                  execute(abort ? 'rollback' : 'commit')
         | 
| 84 92 | 
             
                end
         | 
| 85 93 |  | 
| 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)
         | 
| 101 | 
            +
                #
         | 
| 102 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 103 | 
            +
                # @return [Extralite::Database] database
         | 
| 104 | 
            +
                def savepoint(name)
         | 
| 105 | 
            +
                  execute "savepoint #{name}"
         | 
| 106 | 
            +
                  self
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                # Release a savepoint with the given name. For more information on
         | 
| 110 | 
            +
                # savepoints see: https://sqlite.org/lang_savepoint.html
         | 
| 111 | 
            +
                #
         | 
| 112 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 113 | 
            +
                # @return [Extralite::Database] database
         | 
| 114 | 
            +
                def release(name)
         | 
| 115 | 
            +
                  execute "release #{name}"
         | 
| 116 | 
            +
                  self
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                # Rolls back changes to a savepoint with the given name. For more
         | 
| 120 | 
            +
                # information on savepoints see: https://sqlite.org/lang_savepoint.html
         | 
| 121 | 
            +
                #
         | 
| 122 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 123 | 
            +
                # @return [Extralite::Database] database
         | 
| 124 | 
            +
                def rollback_to(name)
         | 
| 125 | 
            +
                  execute "rollback to #{name}"
         | 
| 126 | 
            +
                  self
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                # Rolls back the currently active transaction. This method should only be
         | 
| 130 | 
            +
                # called from within a block passed to `Database#transaction`. This method
         | 
| 131 | 
            +
                # raises a Extralite::Rollback exception, which will stop execution of the
         | 
| 132 | 
            +
                # transaction block without propagating the exception.
         | 
| 133 | 
            +
                #
         | 
| 134 | 
            +
                #     db.transaction do
         | 
| 135 | 
            +
                #       db.execute('insert into foo (42)')
         | 
| 136 | 
            +
                #       db.rollback!
         | 
| 137 | 
            +
                #     end
         | 
| 138 | 
            +
                #
         | 
| 139 | 
            +
                # @param name [String, Symbol] savepoint name
         | 
| 140 | 
            +
                # @return [Extralite::Database] database
         | 
| 141 | 
            +
                def rollback!
         | 
| 142 | 
            +
                  raise Rollback
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 86 145 | 
             
                private
         | 
| 87 146 |  | 
| 88 147 | 
             
                def pragma_set(values)
         | 
| @@ -91,7 +150,7 @@ module Extralite | |
| 91 150 | 
             
                end
         | 
| 92 151 |  | 
| 93 152 | 
             
                def pragma_get(key)
         | 
| 94 | 
            -
                   | 
| 153 | 
            +
                  query_single_argv("pragma #{key}")
         | 
| 95 154 | 
             
                end
         | 
| 96 155 | 
             
              end
         | 
| 97 156 |  | 
    
        data/test/helper.rb
    CHANGED
    
    | @@ -7,4 +7,12 @@ require 'minitest/autorun' | |
| 7 7 | 
             
            puts "sqlite3 version: #{Extralite.sqlite3_version}"
         | 
| 8 8 |  | 
| 9 9 | 
             
            IS_LINUX = RUBY_PLATFORM =~ /linux/
         | 
| 10 | 
            -
             | 
| 10 | 
            +
            # Ractors are kinda flaky, there's no point in testing this
         | 
| 11 | 
            +
            SKIP_RACTOR_TESTS = true #!IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module Minitest::Assertions
         | 
| 14 | 
            +
              def assert_in_range exp_range, act
         | 
| 15 | 
            +
                msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
         | 
| 16 | 
            +
                assert exp_range.include?(act), msg
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            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 | 
            +
             | 
| 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
         | 
    
        data/test/perf_ary.rb
    CHANGED
    
    | @@ -15,26 +15,25 @@ require 'fileutils' | |
| 15 15 | 
             
            DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
         | 
| 16 16 | 
             
            puts "DB_PATH = #{DB_PATH.inspect}"
         | 
| 17 17 |  | 
| 18 | 
            +
            $sqlite3_db = SQLite3::Database.new(DB_PATH)
         | 
| 19 | 
            +
            $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
         | 
| 18 20 |  | 
| 19 21 | 
             
            def prepare_database(count)
         | 
| 20 22 | 
             
              db = Extralite::Database.new(DB_PATH)
         | 
| 21 | 
            -
               | 
| 22 | 
            -
               | 
| 23 | 
            -
               | 
| 24 | 
            -
              count.times {  | 
| 25 | 
            -
               | 
| 26 | 
            -
              db.close
         | 
| 23 | 
            +
              $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
         | 
| 24 | 
            +
              $extralite_db.query('delete from foo')
         | 
| 25 | 
            +
              $extralite_db.query('begin')
         | 
| 26 | 
            +
              count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
         | 
| 27 | 
            +
              $extralite_db.query('commit')
         | 
| 27 28 | 
             
            end
         | 
| 28 29 |  | 
| 29 30 | 
             
            def sqlite3_run(count)
         | 
| 30 | 
            -
               | 
| 31 | 
            -
              results = db.execute('select * from foo')
         | 
| 31 | 
            +
              results = $sqlite3_db.execute('select * from foo')
         | 
| 32 32 | 
             
              raise unless results.size == count
         | 
| 33 33 | 
             
            end
         | 
| 34 34 |  | 
| 35 35 | 
             
            def extralite_run(count)
         | 
| 36 | 
            -
               | 
| 37 | 
            -
              results = db.query_ary('select * from foo')
         | 
| 36 | 
            +
              results = $extralite_db.query('select * from foo')
         | 
| 38 37 | 
             
              raise unless results.size == count
         | 
| 39 38 | 
             
            end
         | 
| 40 39 |  | 
| @@ -43,12 +42,15 @@ end | |
| 43 42 |  | 
| 44 43 | 
             
              prepare_database(c)
         | 
| 45 44 |  | 
| 46 | 
            -
              Benchmark.ips do |x|
         | 
| 47 | 
            -
                x.config(:time =>  | 
| 45 | 
            +
              bm = Benchmark.ips do |x|
         | 
| 46 | 
            +
                x.config(:time => 5, :warmup => 2)
         | 
| 48 47 |  | 
| 49 48 | 
             
                x.report("sqlite3") { sqlite3_run(c) }
         | 
| 50 49 | 
             
                x.report("extralite") { extralite_run(c) }
         | 
| 51 50 |  | 
| 52 51 | 
             
                x.compare!
         | 
| 53 52 | 
             
              end
         | 
| 53 | 
            +
              puts;
         | 
| 54 | 
            +
              bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
         | 
| 55 | 
            +
              puts;
         | 
| 54 56 | 
             
            end
         | 
    
        data/test/perf_hash.rb
    CHANGED
    
    | @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            # Run on Ruby 3.3 with YJIT enabled
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            require 'bundler/inline'
         | 
| 4 6 |  | 
| 5 7 | 
             
            gemfile do
         | 
| @@ -15,40 +17,40 @@ require 'fileutils' | |
| 15 17 | 
             
            DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
         | 
| 16 18 | 
             
            puts "DB_PATH = #{DB_PATH.inspect}"
         | 
| 17 19 |  | 
| 20 | 
            +
            $sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
         | 
| 21 | 
            +
            $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
         | 
| 22 | 
            +
             | 
| 18 23 | 
             
            def prepare_database(count)
         | 
| 19 | 
            -
               | 
| 20 | 
            -
               | 
| 21 | 
            -
               | 
| 22 | 
            -
               | 
| 23 | 
            -
               | 
| 24 | 
            -
              db.query('commit')
         | 
| 25 | 
            -
              db.close
         | 
| 24 | 
            +
              $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
         | 
| 25 | 
            +
              $extralite_db.query('delete from foo')
         | 
| 26 | 
            +
              $extralite_db.query('begin')
         | 
| 27 | 
            +
              count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
         | 
| 28 | 
            +
              $extralite_db.query('commit')
         | 
| 26 29 | 
             
            end
         | 
| 27 30 |  | 
| 28 31 | 
             
            def sqlite3_run(count)
         | 
| 29 | 
            -
               | 
| 30 | 
            -
              results = db.execute('select * from foo')
         | 
| 32 | 
            +
              results = $sqlite3_db.execute('select * from foo')
         | 
| 31 33 | 
             
              raise unless results.size == count
         | 
| 32 34 | 
             
            end
         | 
| 33 35 |  | 
| 34 36 | 
             
            def extralite_run(count)
         | 
| 35 | 
            -
               | 
| 36 | 
            -
              results = db.query('select * from foo')
         | 
| 37 | 
            +
              results = $extralite_db.query('select * from foo')
         | 
| 37 38 | 
             
              raise unless results.size == count
         | 
| 38 39 | 
             
            end
         | 
| 39 40 |  | 
| 40 41 | 
             
            [10, 1000, 100000].each do |c|
         | 
| 41 42 | 
             
              puts "Record count: #{c}"
         | 
| 42 | 
            -
             | 
| 43 43 | 
             
              prepare_database(c)
         | 
| 44 44 |  | 
| 45 | 
            -
              Benchmark.ips do |x|
         | 
| 46 | 
            -
                x.config(:time =>  | 
| 45 | 
            +
              bm = Benchmark.ips do |x|
         | 
| 46 | 
            +
                x.config(:time => 5, :warmup => 2)
         | 
| 47 47 |  | 
| 48 48 | 
             
                x.report("sqlite3") { sqlite3_run(c) }
         | 
| 49 49 | 
             
                x.report("extralite") { extralite_run(c) }
         | 
| 50 50 |  | 
| 51 51 | 
             
                x.compare!
         | 
| 52 52 | 
             
              end
         | 
| 53 | 
            -
              puts; | 
| 53 | 
            +
              puts;
         | 
| 54 | 
            +
              bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
         | 
| 55 | 
            +
              puts;
         | 
| 54 56 | 
             
            end
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'bundler/inline'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            gemfile do
         | 
| 6 | 
            +
              source 'https://rubygems.org'
         | 
| 7 | 
            +
              gem 'extralite', path: '..'
         | 
| 8 | 
            +
              gem 'sqlite3'
         | 
| 9 | 
            +
              gem 'benchmark-ips'
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            require 'benchmark/ips'
         | 
| 13 | 
            +
            require 'fileutils'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
         | 
| 16 | 
            +
            puts "DB_PATH = #{DB_PATH.inspect}"
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            def prepare_database(count)
         | 
| 19 | 
            +
              $sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
         | 
| 20 | 
            +
              $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
         | 
| 21 | 
            +
                
         | 
| 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 | 
            +
             | 
| 28 | 
            +
              $sqlite3_stmt = $sqlite3_db.prepare('select * from foo')
         | 
| 29 | 
            +
              $extralite_q = $extralite_db.prepare('select * from foo')
         | 
| 30 | 
            +
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            def sqlite3_run(count)
         | 
| 33 | 
            +
              results = $sqlite3_stmt.execute
         | 
| 34 | 
            +
              raise unless results.to_a.size == count
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            def extralite_run(count)
         | 
| 38 | 
            +
              results = $extralite_q.to_a
         | 
| 39 | 
            +
              raise unless results.size == count
         | 
| 40 | 
            +
            end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            [10, 1000, 100000].each do |c|
         | 
| 43 | 
            +
              puts "Record count: #{c}"
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              prepare_database(c)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              bm = Benchmark.ips do |x|
         | 
| 48 | 
            +
                x.config(:time => 5, :warmup => 2)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                x.report("sqlite3") { sqlite3_run(c) }
         | 
| 51 | 
            +
                x.report("extralite") { extralite_run(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
         | 
| @@ -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
         |