extralite 2.3 → 2.4
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/.editorconfig +6 -0
 - data/.github/workflows/test.yml +15 -1
 - data/.gitignore +3 -0
 - data/CHANGELOG.md +12 -0
 - data/Gemfile-bundle +5 -0
 - data/Gemfile.lock +3 -3
 - data/README.md +69 -10
 - data/bin/update_sqlite_source +10 -3
 - data/ext/extralite/common.c +67 -23
 - data/ext/extralite/database.c +74 -26
 - data/ext/extralite/extralite.h +15 -8
 - data/ext/extralite/iterator.c +5 -5
 - data/ext/extralite/query.c +37 -29
 - data/lib/extralite/version.rb +1 -1
 - data/lib/extralite.rb +27 -0
 - data/test/fixtures/image.png +0 -0
 - data/test/helper.rb +2 -0
 - data/test/issue-38.rb +80 -0
 - data/test/perf_ary.rb +6 -3
 - data/test/perf_hash.rb +7 -4
 - data/test/test_database.rb +256 -4
 - data/test/test_iterator.rb +2 -1
 - data/test/test_query.rb +40 -4
 - metadata +6 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: d8ccbfbf37744a5b90d30038c3ba4e73f7d2a094a0f18f21cd8b58d66ec32b42
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 5c6d3f9c967f7056f8eeb7ba471e2db50a26be334bdeec7ddf80dbd521efff77
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 19f10ce33c25e3b0837ec452b8eff0c4c433a729c79162d8b73e65411142f6df61703e0d7c254ac269fbf02ae39aa1c0cde5cb8f9fe3f262ab9ad0af503ceb99
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 921aeeb63e23bfc145af2c978fb3ce5a47b643cf23e265aaf581eb067e8a76ef5c63effde30c567d7565a19fa8f844868456f996d5413a70ef2ba774a927d638
         
     | 
    
        data/.editorconfig
    ADDED
    
    
    
        data/.github/workflows/test.yml
    CHANGED
    
    | 
         @@ -2,6 +2,10 @@ name: Tests 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            on: [push, pull_request]
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
            concurrency:
         
     | 
| 
      
 6 
     | 
    
         
            +
              group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
         
     | 
| 
      
 7 
     | 
    
         
            +
              cancel-in-progress: true
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
       5 
9 
     | 
    
         
             
            jobs:
         
     | 
| 
       6 
10 
     | 
    
         
             
              build:
         
     | 
| 
       7 
11 
     | 
    
         
             
                strategy:
         
     | 
| 
         @@ -15,7 +19,7 @@ jobs: 
     | 
|
| 
       15 
19 
     | 
    
         | 
| 
       16 
20 
     | 
    
         
             
                runs-on: ${{matrix.os}}
         
     | 
| 
       17 
21 
     | 
    
         
             
                steps:
         
     | 
| 
       18 
     | 
    
         
            -
                - uses: actions/checkout@ 
     | 
| 
      
 22 
     | 
    
         
            +
                - uses: actions/checkout@v4
         
     | 
| 
       19 
23 
     | 
    
         
             
                - uses: ruby/setup-ruby@v1
         
     | 
| 
       20 
24 
     | 
    
         
             
                  with:
         
     | 
| 
       21 
25 
     | 
    
         
             
                    ruby-version: ${{matrix.ruby}}
         
     | 
| 
         @@ -24,3 +28,13 @@ jobs: 
     | 
|
| 
       24 
28 
     | 
    
         
             
                  run: bundle exec rake compile
         
     | 
| 
       25 
29 
     | 
    
         
             
                - name: Run tests
         
     | 
| 
       26 
30 
     | 
    
         
             
                  run:  bundle exec rake test
         
     | 
| 
      
 31 
     | 
    
         
            +
                - name: Build bundled gem
         
     | 
| 
      
 32 
     | 
    
         
            +
                  run:  bundle exec rake build_bundled
         
     | 
| 
      
 33 
     | 
    
         
            +
                - name: Run tests with bundled gem
         
     | 
| 
      
 34 
     | 
    
         
            +
                  run: |
         
     | 
| 
      
 35 
     | 
    
         
            +
                    echo Installing extralite-bundle...
         
     | 
| 
      
 36 
     | 
    
         
            +
                    bundle config --local frozen false
         
     | 
| 
      
 37 
     | 
    
         
            +
                    mv Gemfile-bundle Gemfile
         
     | 
| 
      
 38 
     | 
    
         
            +
                    bundle install
         
     | 
| 
      
 39 
     | 
    
         
            +
                    echo Running tests...
         
     | 
| 
      
 40 
     | 
    
         
            +
                    bundle exec rake test
         
     | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,3 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # 2.4 2023-12-24
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            - Implement GVL release threshold. [#46](https://github.com/digital-fabric/extralite/pull/46)
         
     | 
| 
      
 4 
     | 
    
         
            +
            - Implement write barriers for better GC performance.
         
     | 
| 
      
 5 
     | 
    
         
            +
            - Add support for binding large numbers and symbols. [#43](https://github.com/digital-fabric/extralite/pull/43)
         
     | 
| 
      
 6 
     | 
    
         
            +
            - Implement Database#transaction. [#42](https://github.com/digital-fabric/extralite/pull/42)
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Add support for binding BLOBs. [#40](https://github.com/digital-fabric/extralite/pull/40)
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Minor fixes and cleanup of C-code.
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Fix `Database#inspect` for a closed database instance [#37](https://github.com/digital-fabric/extralite/issues/37)
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Add support for binding named parameters from Struct and Data classes [#30](https://github.com/digital-fabric/extralite/pull/30)
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Update bundled SQLite code to version 3.44.2 [#32](https://github.com/digital-fabric/extralite/pull/32)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
       1 
13 
     | 
    
         
             
            # 2.3 2023-11-12
         
     | 
| 
       2 
14 
     | 
    
         | 
| 
       3 
15 
     | 
    
         
             
            - Update bundled SQLite to version 3.44.0 (#29)
         
     | 
    
        data/Gemfile-bundle
    ADDED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,13 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
2 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
3 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                extralite (2. 
     | 
| 
      
 4 
     | 
    
         
            +
                extralite (2.4)
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            GEM
         
     | 
| 
       7 
7 
     | 
    
         
             
              remote: https://rubygems.org/
         
     | 
| 
       8 
8 
     | 
    
         
             
              specs:
         
     | 
| 
       9 
9 
     | 
    
         
             
                docile (1.4.0)
         
     | 
| 
       10 
     | 
    
         
            -
                json (2. 
     | 
| 
      
 10 
     | 
    
         
            +
                json (2.7.1)
         
     | 
| 
       11 
11 
     | 
    
         
             
                minitest (5.15.0)
         
     | 
| 
       12 
12 
     | 
    
         
             
                rake (13.1.0)
         
     | 
| 
       13 
13 
     | 
    
         
             
                rake-compiler (1.1.6)
         
     | 
| 
         @@ -34,4 +34,4 @@ DEPENDENCIES 
     | 
|
| 
       34 
34 
     | 
    
         
             
              yard (= 0.9.27)
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
            BUNDLED WITH
         
     | 
| 
       37 
     | 
    
         
            -
               2. 
     | 
| 
      
 37 
     | 
    
         
            +
               2.4.22
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -14,14 +14,13 @@ with an SQLite3 database, as well as prepared queries (prepared statements). 
     | 
|
| 
       14 
14 
     | 
    
         
             
            Extralite comes in two flavors: the `extralite` gem which uses the
         
     | 
| 
       15 
15 
     | 
    
         
             
            system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
         
     | 
| 
       16 
16 
     | 
    
         
             
            the latest version of SQLite
         
     | 
| 
       17 
     | 
    
         
            -
            ([3.44. 
     | 
| 
      
 17 
     | 
    
         
            +
            ([3.44.2](https://sqlite.org/releaselog/3_44_2.html)), offering access to the
         
     | 
| 
       18 
18 
     | 
    
         
             
            latest features and enhancements.
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
            ## Features
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            - Super fast - [up to 11x faster](#performance) than the
         
     | 
| 
       23 
     | 
    
         
            -
              [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem 
     | 
| 
       24 
     | 
    
         
            -
              [comparison](#why-not-just-use-the-sqlite3-gem).)
         
     | 
| 
      
 23 
     | 
    
         
            +
              [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem.
         
     | 
| 
       25 
24 
     | 
    
         
             
            - A variety of methods for different data access patterns: rows as hashes, rows
         
     | 
| 
       26 
25 
     | 
    
         
             
              as arrays, single row, single column, single value.
         
     | 
| 
       27 
26 
     | 
    
         
             
            - Prepared statements.
         
     | 
| 
         @@ -30,10 +29,12 @@ latest features and enhancements. 
     | 
|
| 
       30 
29 
     | 
    
         
             
            - Use system-installed sqlite3, or the [bundled latest version of
         
     | 
| 
       31 
30 
     | 
    
         
             
              SQLite3](#installing-the-extralite-sqlite3-bundle).
         
     | 
| 
       32 
31 
     | 
    
         
             
            - Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
         
     | 
| 
       33 
     | 
    
         
            -
              released while preparing SQL statements and while iterating over 
     | 
| 
      
 32 
     | 
    
         
            +
              released peridically while preparing SQL statements and while iterating over
         
     | 
| 
      
 33 
     | 
    
         
            +
              results.
         
     | 
| 
       34 
34 
     | 
    
         
             
            - Automatically execute SQL strings containing multiple semicolon-separated
         
     | 
| 
       35 
35 
     | 
    
         
             
              queries (handy for creating/modifying schemas).
         
     | 
| 
       36 
     | 
    
         
            -
            - Execute the same query with multiple parameter lists (useful for inserting 
     | 
| 
      
 36 
     | 
    
         
            +
            - Execute the same query with multiple parameter lists (useful for inserting
         
     | 
| 
      
 37 
     | 
    
         
            +
              records).
         
     | 
| 
       37 
38 
     | 
    
         
             
            - Load extensions (loading of extensions is autmatically enabled. You can find
         
     | 
| 
       38 
39 
     | 
    
         
             
              some useful extensions here: https://github.com/nalgeon/sqlean.)
         
     | 
| 
       39 
40 
     | 
    
         
             
            - Includes a [Sequel adapter](#usage-with-sequel).
         
     | 
| 
         @@ -101,12 +102,24 @@ db.query_single_value("select 'foo'") #=> "foo" 
     | 
|
| 
       101 
102 
     | 
    
         | 
| 
       102 
103 
     | 
    
         
             
            # parameter binding (works for all query_xxx methods)
         
     | 
| 
       103 
104 
     | 
    
         
             
            db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
         
     | 
| 
      
 105 
     | 
    
         
            +
            db.query_hash('select ?2 as foo, ?1 as bar, ?1 * ?2 as baz', 6, 7) #=> [{ :foo => 7, :bar => 6, :baz => 42 }]
         
     | 
| 
       104 
106 
     | 
    
         | 
| 
       105 
107 
     | 
    
         
             
            # parameter binding of named parameters
         
     | 
| 
       106 
108 
     | 
    
         
             
            db.query('select * from foo where bar = :bar', bar: 42)
         
     | 
| 
       107 
109 
     | 
    
         
             
            db.query('select * from foo where bar = :bar', 'bar' => 42)
         
     | 
| 
       108 
110 
     | 
    
         
             
            db.query('select * from foo where bar = :bar', ':bar' => 42)
         
     | 
| 
       109 
111 
     | 
    
         | 
| 
      
 112 
     | 
    
         
            +
            # parameter binding of named parameters from Struct and Data
         
     | 
| 
      
 113 
     | 
    
         
            +
            SomeStruct = Struct.new(:foo, :bar)
         
     | 
| 
      
 114 
     | 
    
         
            +
            db.query_single_column('select :bar', SomeStruct.new(41, 42)) #=> [42]
         
     | 
| 
      
 115 
     | 
    
         
            +
            SomeData = Data.define(:foo, :bar)
         
     | 
| 
      
 116 
     | 
    
         
            +
            db.query_single_column('select :bar', SomeData.new(foo: 41, bar: 42)) #=> [42]
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            # parameter binding for binary data (BLOBs)
         
     | 
| 
      
 119 
     | 
    
         
            +
            db.execute('insert into foo values (?)', File.binread('/path/to/file'))
         
     | 
| 
      
 120 
     | 
    
         
            +
            db.execute('insert into foo values (?)', Extralite::Blob.new('Hello, 世界!'))
         
     | 
| 
      
 121 
     | 
    
         
            +
            db.execute('insert into foo values (?)', 'Hello, 世界!'.force_encoding(Encoding::ASCII_8BIT))
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
       110 
123 
     | 
    
         
             
            # insert multiple rows
         
     | 
| 
       111 
124 
     | 
    
         
             
            db.execute_multi('insert into foo values (?)', ['bar', 'baz'])
         
     | 
| 
       112 
125 
     | 
    
         
             
            db.execute_multi('insert into foo values (?, ?)', [[1, 2], [3, 4]])
         
     | 
| 
         @@ -166,6 +179,13 @@ db.pragma(:journal_mode) #=> 'wal' 
     | 
|
| 
       166 
179 
     | 
    
         
             
            # load an extension
         
     | 
| 
       167 
180 
     | 
    
         
             
            db.load_extension('/path/to/extension.so')
         
     | 
| 
       168 
181 
     | 
    
         | 
| 
      
 182 
     | 
    
         
            +
            # run queries in a transaction
         
     | 
| 
      
 183 
     | 
    
         
            +
            db.transaction do
         
     | 
| 
      
 184 
     | 
    
         
            +
              db.execute('insert into foo values (?)', 1)
         
     | 
| 
      
 185 
     | 
    
         
            +
              db.execute('insert into foo values (?)', 2)
         
     | 
| 
      
 186 
     | 
    
         
            +
              db.execute('insert into foo values (?)', 3)
         
     | 
| 
      
 187 
     | 
    
         
            +
            end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
       169 
189 
     | 
    
         
             
            # close database
         
     | 
| 
       170 
190 
     | 
    
         
             
            db.close
         
     | 
| 
       171 
191 
     | 
    
         
             
            db.closed? #=> true
         
     | 
| 
         @@ -194,6 +214,23 @@ ensure 
     | 
|
| 
       194 
214 
     | 
    
         
             
            end
         
     | 
| 
       195 
215 
     | 
    
         
             
            ```
         
     | 
| 
       196 
216 
     | 
    
         | 
| 
      
 217 
     | 
    
         
            +
            ### Running transactions
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
            In order to run multiple queries in a single transaction, use
         
     | 
| 
      
 220 
     | 
    
         
            +
            `Database#transaction`, passing a block that runs the queries. You can specify
         
     | 
| 
      
 221 
     | 
    
         
            +
            the [transaction
         
     | 
| 
      
 222 
     | 
    
         
            +
            mode](https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions).
         
     | 
| 
      
 223 
     | 
    
         
            +
            The default mode is `:immediate`:
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 226 
     | 
    
         
            +
            db.transaction { ... } # Run an immediate transaction
         
     | 
| 
      
 227 
     | 
    
         
            +
            db.transaction(:deferred) { ... } # Run a deferred transaction
         
     | 
| 
      
 228 
     | 
    
         
            +
            db.transaction(:exclusive) { ... } # Run an exclusive transaction
         
     | 
| 
      
 229 
     | 
    
         
            +
            ```
         
     | 
| 
      
 230 
     | 
    
         
            +
             
     | 
| 
      
 231 
     | 
    
         
            +
            If an exception is raised in the given block, the transaction will be rolled
         
     | 
| 
      
 232 
     | 
    
         
            +
            back. Otherwise, it is committed.
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
       197 
234 
     | 
    
         
             
            ### Creating Backups
         
     | 
| 
       198 
235 
     | 
    
         | 
| 
       199 
236 
     | 
    
         
             
            You can use `Database#backup` to create backup copies of a database. The
         
     | 
| 
         @@ -295,11 +332,33 @@ p articles.to_a 
     | 
|
| 
       295 
332 
     | 
    
         | 
| 
       296 
333 
     | 
    
         
             
            ## Concurrency
         
     | 
| 
       297 
334 
     | 
    
         | 
| 
       298 
     | 
    
         
            -
            Extralite releases the GVL while making  
     | 
| 
       299 
     | 
    
         
            -
             
     | 
| 
       300 
     | 
    
         
            -
             
     | 
| 
       301 
     | 
    
         
            -
             
     | 
| 
       302 
     | 
    
         
            -
             
     | 
| 
      
 335 
     | 
    
         
            +
            Extralite releases the GVL while making calls to the sqlite3 library that might
         
     | 
| 
      
 336 
     | 
    
         
            +
            block, such as when backing up a database, or when preparing a query. Extralite
         
     | 
| 
      
 337 
     | 
    
         
            +
            also releases the GVL periodically when iterating over records. By default, the
         
     | 
| 
      
 338 
     | 
    
         
            +
            GVL is released every 1000 records iterated. The GVL release threshold can be
         
     | 
| 
      
 339 
     | 
    
         
            +
            set separately for each database:
         
     | 
| 
      
 340 
     | 
    
         
            +
             
     | 
| 
      
 341 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 342 
     | 
    
         
            +
            db.gvl_release_threshold = 10 # release GVL every 10 records
         
     | 
| 
      
 343 
     | 
    
         
            +
             
     | 
| 
      
 344 
     | 
    
         
            +
            db.gvl_release_threshold = nil # use default value (currently 1000)
         
     | 
| 
      
 345 
     | 
    
         
            +
            ```
         
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
      
 347 
     | 
    
         
            +
            For most applications, there's no need to tune the GVL threshold value, as it
         
     | 
| 
      
 348 
     | 
    
         
            +
            provides [excellent](#performance) performance characteristics for both single-threaded and
         
     | 
| 
      
 349 
     | 
    
         
            +
            multi-threaded applications.
         
     | 
| 
      
 350 
     | 
    
         
            +
             
     | 
| 
      
 351 
     | 
    
         
            +
            In a heavily multi-threaded application, releasing the GVL more often (lower
         
     | 
| 
      
 352 
     | 
    
         
            +
            threshold value) will lead to less latency (for threads not running a query),
         
     | 
| 
      
 353 
     | 
    
         
            +
            but will also hurt the throughput (for the thread running the query). Releasing
         
     | 
| 
      
 354 
     | 
    
         
            +
            the GVL less often (higher threshold value) will lead to better throughput for
         
     | 
| 
      
 355 
     | 
    
         
            +
            queries, while increasing latency for threads not running a query. The following
         
     | 
| 
      
 356 
     | 
    
         
            +
            diagram demonstrates the relationship between the GVL release threshold value,
         
     | 
| 
      
 357 
     | 
    
         
            +
            latency and throughput:
         
     | 
| 
      
 358 
     | 
    
         
            +
             
     | 
| 
      
 359 
     | 
    
         
            +
            ```
         
     | 
| 
      
 360 
     | 
    
         
            +
            less latency & throughput <<< GVL release threshold >>> more latency & throughput
         
     | 
| 
      
 361 
     | 
    
         
            +
            ```
         
     | 
| 
       303 
362 
     | 
    
         | 
| 
       304 
363 
     | 
    
         
             
            ## Performance
         
     | 
| 
       305 
364 
     | 
    
         | 
    
        data/bin/update_sqlite_source
    CHANGED
    
    | 
         @@ -9,10 +9,10 @@ require 'date' 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            FileUtils.cd '/tmp'
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            version_id = version. 
     | 
| 
      
 12 
     | 
    
         
            +
            version_id = version.split('.').each_with_index.map { |v, i| i == 0 ? v : v.rjust(2, '0') }.join
         
     | 
| 
       13 
13 
     | 
    
         
             
            version_id += '0' * (7 - version_id.length)
         
     | 
| 
       14 
14 
     | 
    
         
             
            url = "https://sqlite.org/#{Date.today.year}/sqlite-amalgamation-#{version_id}.zip"
         
     | 
| 
       15 
     | 
    
         
            -
            dest = File.expand_path('../ext/ 
     | 
| 
      
 15 
     | 
    
         
            +
            dest = File.expand_path('../ext/sqlite3', __dir__)
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
17 
     | 
    
         
             
            puts "Downloading from #{url}..."
         
     | 
| 
       18 
18 
     | 
    
         
             
            `curl #{url} > #{version_id}.zip`
         
     | 
| 
         @@ -23,4 +23,11 @@ puts "Unzipping zip file..." 
     | 
|
| 
       23 
23 
     | 
    
         
             
            puts "Copying source files"
         
     | 
| 
       24 
24 
     | 
    
         
             
            `cp sqlite-amalgamation-#{version_id}/sqlite3.* #{dest}/`
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
            puts  
     | 
| 
      
 26 
     | 
    
         
            +
            puts "Updating README"
         
     | 
| 
      
 27 
     | 
    
         
            +
            readme_path = File.expand_path('../README.md', __dir__)
         
     | 
| 
      
 28 
     | 
    
         
            +
            readme = File.read(readme_path)
         
     | 
| 
      
 29 
     | 
    
         
            +
            readme.gsub!(/\[\d+\.\d+\.\d+\]/, "[#{version}]")
         
     | 
| 
      
 30 
     | 
    
         
            +
            readme.gsub!(/\d+_\d+_\d+\.html/, "#{version.gsub('.', '_')}.html")
         
     | 
| 
      
 31 
     | 
    
         
            +
            File.write(readme_path, readme)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            puts 'Done updating source files'
         
     | 
    
        data/ext/extralite/common.c
    CHANGED
    
    | 
         @@ -3,6 +3,15 @@ 
     | 
|
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            rb_encoding *UTF8_ENCODING;
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
      
 6 
     | 
    
         
            +
            inline void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data) {
         
     | 
| 
      
 7 
     | 
    
         
            +
              switch (mode) {
         
     | 
| 
      
 8 
     | 
    
         
            +
                case GVL_RELEASE:
         
     | 
| 
      
 9 
     | 
    
         
            +
                  return rb_thread_call_without_gvl(fn, data, RUBY_UBF_IO, 0);
         
     | 
| 
      
 10 
     | 
    
         
            +
                default:
         
     | 
| 
      
 11 
     | 
    
         
            +
                  return fn(data);
         
     | 
| 
      
 12 
     | 
    
         
            +
              }
         
     | 
| 
      
 13 
     | 
    
         
            +
            }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       6 
15 
     | 
    
         
             
            static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
         
     | 
| 
       7 
16 
     | 
    
         
             
              switch (type) {
         
     | 
| 
       8 
17 
     | 
    
         
             
                case SQLITE_NULL:
         
     | 
| 
         @@ -24,37 +33,52 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) { 
     | 
|
| 
       24 
33 
     | 
    
         | 
| 
       25 
34 
     | 
    
         
             
            void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
         
     | 
| 
       26 
35 
     | 
    
         | 
| 
      
 36 
     | 
    
         
            +
            static inline void bind_key_value(sqlite3_stmt *stmt, VALUE k, VALUE v) {
         
     | 
| 
      
 37 
     | 
    
         
            +
              switch (TYPE(k)) {
         
     | 
| 
      
 38 
     | 
    
         
            +
                case T_FIXNUM:
         
     | 
| 
      
 39 
     | 
    
         
            +
                  bind_parameter_value(stmt, FIX2INT(k), v);
         
     | 
| 
      
 40 
     | 
    
         
            +
                  break;
         
     | 
| 
      
 41 
     | 
    
         
            +
                case T_SYMBOL:
         
     | 
| 
      
 42 
     | 
    
         
            +
                  k = rb_sym2str(k);
         
     | 
| 
      
 43 
     | 
    
         
            +
                case T_STRING:
         
     | 
| 
      
 44 
     | 
    
         
            +
                  if (RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
         
     | 
| 
      
 45 
     | 
    
         
            +
                  int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
         
     | 
| 
      
 46 
     | 
    
         
            +
                  bind_parameter_value(stmt, pos, v);
         
     | 
| 
      
 47 
     | 
    
         
            +
                  break;
         
     | 
| 
      
 48 
     | 
    
         
            +
                default:
         
     | 
| 
      
 49 
     | 
    
         
            +
                  rb_raise(cParameterError, "Cannot bind parameter with a key of type %"PRIsVALUE"",
         
     | 
| 
      
 50 
     | 
    
         
            +
                    rb_class_name(rb_obj_class(k)));
         
     | 
| 
      
 51 
     | 
    
         
            +
              }
         
     | 
| 
      
 52 
     | 
    
         
            +
            }
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
       27 
54 
     | 
    
         
             
            void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
         
     | 
| 
       28 
55 
     | 
    
         
             
              VALUE keys = rb_funcall(hash, ID_keys, 0);
         
     | 
| 
       29 
56 
     | 
    
         
             
              long len = RARRAY_LEN(keys);
         
     | 
| 
       30 
57 
     | 
    
         
             
              for (long i = 0; i < len; i++) {
         
     | 
| 
       31 
58 
     | 
    
         
             
                VALUE k = RARRAY_AREF(keys, i);
         
     | 
| 
       32 
59 
     | 
    
         
             
                VALUE v = rb_hash_aref(hash, k);
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                switch (TYPE(k)) {
         
     | 
| 
       35 
     | 
    
         
            -
                  case T_FIXNUM:
         
     | 
| 
       36 
     | 
    
         
            -
                    bind_parameter_value(stmt, FIX2INT(k), v);
         
     | 
| 
       37 
     | 
    
         
            -
                    break;
         
     | 
| 
       38 
     | 
    
         
            -
                  case T_SYMBOL:
         
     | 
| 
       39 
     | 
    
         
            -
                    k = rb_funcall(k, ID_to_s, 0);
         
     | 
| 
       40 
     | 
    
         
            -
                  case T_STRING:
         
     | 
| 
       41 
     | 
    
         
            -
                    if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
         
     | 
| 
       42 
     | 
    
         
            -
                    int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
         
     | 
| 
       43 
     | 
    
         
            -
                    bind_parameter_value(stmt, pos, v);
         
     | 
| 
       44 
     | 
    
         
            -
                    break;
         
     | 
| 
       45 
     | 
    
         
            -
                  default:
         
     | 
| 
       46 
     | 
    
         
            -
                    rb_raise(cError, "Cannot bind hash key value idx %ld", i);
         
     | 
| 
       47 
     | 
    
         
            -
                }
         
     | 
| 
      
 60 
     | 
    
         
            +
                bind_key_value(stmt, k, v);
         
     | 
| 
       48 
61 
     | 
    
         
             
              }
         
     | 
| 
       49 
62 
     | 
    
         
             
              RB_GC_GUARD(keys);
         
     | 
| 
       50 
63 
     | 
    
         
             
            }
         
     | 
| 
       51 
64 
     | 
    
         | 
| 
      
 65 
     | 
    
         
            +
            void bind_struct_parameter_values(sqlite3_stmt *stmt, VALUE struct_obj) {
         
     | 
| 
      
 66 
     | 
    
         
            +
              VALUE members = rb_struct_members(struct_obj);
         
     | 
| 
      
 67 
     | 
    
         
            +
              for (long i = 0; i < RSTRUCT_LEN(struct_obj); i++) {
         
     | 
| 
      
 68 
     | 
    
         
            +
                VALUE k = rb_ary_entry(members, i);
         
     | 
| 
      
 69 
     | 
    
         
            +
                VALUE v = RSTRUCT_GET(struct_obj, i);
         
     | 
| 
      
 70 
     | 
    
         
            +
                bind_key_value(stmt, k, v);
         
     | 
| 
      
 71 
     | 
    
         
            +
              }
         
     | 
| 
      
 72 
     | 
    
         
            +
              RB_GC_GUARD(members);
         
     | 
| 
      
 73 
     | 
    
         
            +
            }
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
       52 
75 
     | 
    
         
             
            inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
         
     | 
| 
       53 
76 
     | 
    
         
             
              switch (TYPE(value)) {
         
     | 
| 
       54 
77 
     | 
    
         
             
                case T_NIL:
         
     | 
| 
       55 
78 
     | 
    
         
             
                  sqlite3_bind_null(stmt, pos);
         
     | 
| 
       56 
79 
     | 
    
         
             
                  return;
         
     | 
| 
       57 
80 
     | 
    
         
             
                case T_FIXNUM:
         
     | 
| 
      
 81 
     | 
    
         
            +
                case T_BIGNUM:
         
     | 
| 
       58 
82 
     | 
    
         
             
                  sqlite3_bind_int64(stmt, pos, NUM2LL(value));
         
     | 
| 
       59 
83 
     | 
    
         
             
                  return;
         
     | 
| 
       60 
84 
     | 
    
         
             
                case T_FLOAT:
         
     | 
| 
         @@ -66,14 +90,23 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) { 
     | 
|
| 
       66 
90 
     | 
    
         
             
                case T_FALSE:
         
     | 
| 
       67 
91 
     | 
    
         
             
                  sqlite3_bind_int(stmt, pos, 0);
         
     | 
| 
       68 
92 
     | 
    
         
             
                  return;
         
     | 
| 
      
 93 
     | 
    
         
            +
                case T_SYMBOL:
         
     | 
| 
      
 94 
     | 
    
         
            +
                  value = rb_sym2str(value);
         
     | 
| 
       69 
95 
     | 
    
         
             
                case T_STRING:
         
     | 
| 
       70 
     | 
    
         
            -
                   
     | 
| 
      
 96 
     | 
    
         
            +
                  if (rb_enc_get_index(value) == rb_ascii8bit_encindex() || CLASS_OF(value) == cBlob)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    sqlite3_bind_blob(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
         
     | 
| 
      
 98 
     | 
    
         
            +
                  else
         
     | 
| 
      
 99 
     | 
    
         
            +
                    sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
         
     | 
| 
       71 
100 
     | 
    
         
             
                  return;
         
     | 
| 
       72 
101 
     | 
    
         
             
                case T_HASH:
         
     | 
| 
       73 
102 
     | 
    
         
             
                  bind_hash_parameter_values(stmt, value);
         
     | 
| 
       74 
103 
     | 
    
         
             
                  return;
         
     | 
| 
      
 104 
     | 
    
         
            +
                case T_STRUCT:
         
     | 
| 
      
 105 
     | 
    
         
            +
                  bind_struct_parameter_values(stmt, value);
         
     | 
| 
      
 106 
     | 
    
         
            +
                  return;
         
     | 
| 
       75 
107 
     | 
    
         
             
                default:
         
     | 
| 
       76 
     | 
    
         
            -
                  rb_raise( 
     | 
| 
      
 108 
     | 
    
         
            +
                  rb_raise(cParameterError, "Cannot bind parameter at position %d of type %"PRIsVALUE"",
         
     | 
| 
      
 109 
     | 
    
         
            +
                    pos, rb_class_name(rb_obj_class(value)));
         
     | 
| 
       77 
110 
     | 
    
         
             
              }
         
     | 
| 
       78 
111 
     | 
    
         
             
            }
         
     | 
| 
       79 
112 
     | 
    
         | 
| 
         @@ -126,7 +159,7 @@ typedef struct { 
     | 
|
| 
       126 
159 
     | 
    
         
             
              int rc;
         
     | 
| 
       127 
160 
     | 
    
         
             
            } prepare_stmt_ctx;
         
     | 
| 
       128 
161 
     | 
    
         | 
| 
       129 
     | 
    
         
            -
            void * 
     | 
| 
      
 162 
     | 
    
         
            +
            void *prepare_multi_stmt_impl(void *ptr) {
         
     | 
| 
       130 
163 
     | 
    
         
             
              prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
         
     | 
| 
       131 
164 
     | 
    
         
             
              const char *rest = NULL;
         
     | 
| 
       132 
165 
     | 
    
         
             
              const char *str = ctx->str;
         
     | 
| 
         @@ -163,7 +196,7 @@ is not executed, but instead handed back to the caller for looping over results. 
     | 
|
| 
       163 
196 
     | 
    
         
             
            */
         
     | 
| 
       164 
197 
     | 
    
         
             
            void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
         
     | 
| 
       165 
198 
     | 
    
         
             
              prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
         
     | 
| 
       166 
     | 
    
         
            -
               
     | 
| 
      
 199 
     | 
    
         
            +
              gvl_call(GVL_RELEASE, prepare_multi_stmt_impl, (void *)&ctx);
         
     | 
| 
       167 
200 
     | 
    
         
             
              RB_GC_GUARD(sql);
         
     | 
| 
       168 
201 
     | 
    
         | 
| 
       169 
202 
     | 
    
         
             
              switch (ctx.rc) {
         
     | 
| 
         @@ -180,7 +213,7 @@ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) { 
     | 
|
| 
       180 
213 
     | 
    
         | 
| 
       181 
214 
     | 
    
         
             
            #define SQLITE_MULTI_STMT -1
         
     | 
| 
       182 
215 
     | 
    
         | 
| 
       183 
     | 
    
         
            -
            void * 
     | 
| 
      
 216 
     | 
    
         
            +
            void *prepare_single_stmt_impl(void *ptr) {
         
     | 
| 
       184 
217 
     | 
    
         
             
              prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
         
     | 
| 
       185 
218 
     | 
    
         
             
              const char *rest = NULL;
         
     | 
| 
       186 
219 
     | 
    
         
             
              const char *str = ctx->str;
         
     | 
| 
         @@ -205,7 +238,7 @@ end: 
     | 
|
| 
       205 
238 
     | 
    
         | 
| 
       206 
239 
     | 
    
         
             
            void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
         
     | 
| 
       207 
240 
     | 
    
         
             
              prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
         
     | 
| 
       208 
     | 
    
         
            -
               
     | 
| 
      
 241 
     | 
    
         
            +
              gvl_call(GVL_RELEASE, prepare_single_stmt_impl, (void *)&ctx);
         
     | 
| 
       209 
242 
     | 
    
         
             
              RB_GC_GUARD(sql);
         
     | 
| 
       210 
243 
     | 
    
         | 
| 
       211 
244 
     | 
    
         
             
              switch (ctx.rc) {
         
     | 
| 
         @@ -227,15 +260,26 @@ struct step_ctx { 
     | 
|
| 
       227 
260 
     | 
    
         
             
              int rc;
         
     | 
| 
       228 
261 
     | 
    
         
             
            };
         
     | 
| 
       229 
262 
     | 
    
         | 
| 
       230 
     | 
    
         
            -
            void * 
     | 
| 
      
 263 
     | 
    
         
            +
            void *stmt_iterate_step(void *ptr) {
         
     | 
| 
       231 
264 
     | 
    
         
             
              struct step_ctx *ctx = (struct step_ctx *)ptr;
         
     | 
| 
       232 
265 
     | 
    
         
             
              ctx->rc = sqlite3_step(ctx->stmt);
         
     | 
| 
       233 
266 
     | 
    
         
             
              return NULL;
         
     | 
| 
       234 
267 
     | 
    
         
             
            }
         
     | 
| 
       235 
268 
     | 
    
         | 
| 
      
 269 
     | 
    
         
            +
            inline enum gvl_mode stepwise_gvl_mode(query_ctx *ctx) {
         
     | 
| 
      
 270 
     | 
    
         
            +
              // a negative or zero threshold means the GVL is always held during iteration.
         
     | 
| 
      
 271 
     | 
    
         
            +
              if (ctx->gvl_release_threshold <= 0) return GVL_HOLD;
         
     | 
| 
      
 272 
     | 
    
         
            +
              
         
     | 
| 
      
 273 
     | 
    
         
            +
              if (!sqlite3_stmt_busy(ctx->stmt)) return GVL_RELEASE;
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
      
 275 
     | 
    
         
            +
              // if positive, the GVL is normally held, and release every <threshold> steps.
         
     | 
| 
      
 276 
     | 
    
         
            +
              return (ctx->step_count % ctx->gvl_release_threshold) ? GVL_HOLD : GVL_RELEASE;
         
     | 
| 
      
 277 
     | 
    
         
            +
            }
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
       236 
279 
     | 
    
         
             
            inline int stmt_iterate(query_ctx *ctx) {
         
     | 
| 
       237 
280 
     | 
    
         
             
              struct step_ctx step_ctx = {ctx->stmt, 0};
         
     | 
| 
       238 
     | 
    
         
            -
               
     | 
| 
      
 281 
     | 
    
         
            +
              ctx->step_count += 1;
         
     | 
| 
      
 282 
     | 
    
         
            +
              gvl_call(stepwise_gvl_mode(ctx), stmt_iterate_step, (void *)&step_ctx);
         
     | 
| 
       239 
283 
     | 
    
         
             
              switch (step_ctx.rc) {
         
     | 
| 
       240 
284 
     | 
    
         
             
                case SQLITE_ROW:
         
     | 
| 
       241 
285 
     | 
    
         
             
                  return 1;
         
     |