extralite-bundle 1.22 → 1.23
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/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/README.md +47 -40
- data/ext/extralite/common.c +4 -4
- data/ext/extralite/database.c +85 -1
- data/ext/extralite/extralite.h +4 -0
- data/ext/extralite/prepared_statement.c +3 -0
- data/lib/extralite/sqlite3_constants.rb +50 -0
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +3 -38
- data/test/test_database.rb +86 -0
- data/test/test_prepared_statement.rb +5 -0
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6622a72a5d4e7ea9670be11b21708f2f8eedda7d94e870ea283d6e8b0d13471e
         | 
| 4 | 
            +
              data.tar.gz: 1eed8cd7ca3fe7844dead0024463efe285f4979eef2be94741c96bf42b447a42
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3300b9369bffded248aa4291db0f18da92a2c95015b0fbd4afc668c2783c223cb72d4cfb207ca1dace057a41c3e61a38ee37e0b8ffe5a1199a147143374c9f6d
         | 
| 7 | 
            +
              data.tar.gz: 17d532b9c92238f3b94f073b2e7c13d793a872511778f7138e371a8e0a8f4e776d830a00874df203de514d2143e8340d42f479e2f267105a907226892540d74b
         | 
    
        data/.github/workflows/test.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            # Extralite -  | 
| 1 | 
            +
            # Extralite - a Super Fast Ruby Gem for Working with SQLite3 Databases
         | 
| 2 2 |  | 
| 3 3 | 
             
            * Source code: https://github.com/digital-fabric/extralite
         | 
| 4 4 | 
             
            * Documentation: http://www.rubydoc.info/gems/extralite
         | 
| @@ -19,7 +19,7 @@ latest features and enhancements. | |
| 19 19 |  | 
| 20 20 | 
             
            ## Features
         | 
| 21 21 |  | 
| 22 | 
            -
            - Super fast - [up to  | 
| 22 | 
            +
            - Super fast - [up to 11x faster](#performance) than the
         | 
| 23 23 | 
             
              [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
         | 
| 24 24 | 
             
              [comparison](#why-not-just-use-the-sqlite3-gem).)
         | 
| 25 25 | 
             
            - A variety of methods for different data access patterns: rows as hashes, rows
         | 
| @@ -48,7 +48,7 @@ gem 'extralite' | |
| 48 48 |  | 
| 49 49 | 
             
            You can also run `gem install extralite` if you just want to check it out.
         | 
| 50 50 |  | 
| 51 | 
            -
            ### Installing the Extralite-SQLite3  | 
| 51 | 
            +
            ### Installing the Extralite-SQLite3 Bundle
         | 
| 52 52 |  | 
| 53 53 | 
             
            If you don't have sqlite3 installed on your system, do not want to use the
         | 
| 54 54 | 
             
            system-installed version of SQLite3, or would like to use the latest version of
         | 
| @@ -145,9 +145,9 @@ db.close | |
| 145 145 | 
             
            db.closed? #=> true
         | 
| 146 146 | 
             
            ```
         | 
| 147 147 |  | 
| 148 | 
            -
            ## More  | 
| 148 | 
            +
            ## More Features
         | 
| 149 149 |  | 
| 150 | 
            -
            ### Interrupting  | 
| 150 | 
            +
            ### Interrupting Long-running Queries
         | 
| 151 151 |  | 
| 152 152 | 
             
            When running long-running queries, you can use `Database#interrupt` to interrupt
         | 
| 153 153 | 
             
            the query:
         | 
| @@ -168,7 +168,7 @@ ensure | |
| 168 168 | 
             
            end
         | 
| 169 169 | 
             
            ```
         | 
| 170 170 |  | 
| 171 | 
            -
            ### Creating  | 
| 171 | 
            +
            ### Creating Backups
         | 
| 172 172 |  | 
| 173 173 | 
             
            You can use `Database#backup` to create backup copies of a database. The
         | 
| 174 174 | 
             
            `#backup` method takes either a filename or a database instance:
         | 
| @@ -191,7 +191,7 @@ db.backup('backup.db') do |remaining, total| | |
| 191 191 | 
             
            end
         | 
| 192 192 | 
             
            ```
         | 
| 193 193 |  | 
| 194 | 
            -
            ###  | 
| 194 | 
            +
            ### Retrieving Status Information
         | 
| 195 195 |  | 
| 196 196 | 
             
            Extralite provides methods for retrieving status information about the sqlite
         | 
| 197 197 | 
             
            runtime, database-specific status and prepared statement-specific status,
         | 
| @@ -217,6 +217,42 @@ current, high_watermark = db.status(Extralite::SQLITE_DBSTATUS_CACHE_USED) | |
| 217 217 | 
             
            value = stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
         | 
| 218 218 | 
             
            ```
         | 
| 219 219 |  | 
| 220 | 
            +
            ### Working with Database Limits
         | 
| 221 | 
            +
             | 
| 222 | 
            +
            The `Database#limit` can be used to get and set various database limits, as
         | 
| 223 | 
            +
            [discussed in the SQLite docs](https://www.sqlite.org/limits.html):
         | 
| 224 | 
            +
             | 
| 225 | 
            +
            ```ruby
         | 
| 226 | 
            +
            # get limit
         | 
| 227 | 
            +
            value = db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
         | 
| 228 | 
            +
             | 
| 229 | 
            +
            # set limit
         | 
| 230 | 
            +
            db.limit(Extralite::SQLITE_LIMIT_ATTACHED, new_value)
         | 
| 231 | 
            +
            ```
         | 
| 232 | 
            +
             | 
| 233 | 
            +
            ### Setting the Busy Timeout
         | 
| 234 | 
            +
             | 
| 235 | 
            +
            When accessing a database concurrently it can be handy to set a busy timeout, in
         | 
| 236 | 
            +
            order to not have to deal with rescuing `Extralite::BusyError` exceptions. The
         | 
| 237 | 
            +
            timeout is given in seconds:
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            ```ruby
         | 
| 240 | 
            +
            db.busy_timeout = 5
         | 
| 241 | 
            +
            ```
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            ### Tracing SQL Statements
         | 
| 244 | 
            +
             | 
| 245 | 
            +
            To trace all SQL statements executed on the database, pass a block to
         | 
| 246 | 
            +
            `Database#trace`. To disable tracing, call `Database#trace` without a block:
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            ```ruby
         | 
| 249 | 
            +
            # enable tracing
         | 
| 250 | 
            +
            db.trace { |sql| puts sql: sql }
         | 
| 251 | 
            +
             | 
| 252 | 
            +
            # disable tracing
         | 
| 253 | 
            +
            db.trace
         | 
| 254 | 
            +
            ```
         | 
| 255 | 
            +
             | 
| 220 256 | 
             
            ## Usage with Sequel
         | 
| 221 257 |  | 
| 222 258 | 
             
            Extralite includes an adapter for
         | 
| @@ -231,35 +267,6 @@ p articles.to_a | |
| 231 267 |  | 
| 232 268 | 
             
            (Make sure you include `extralite` as a dependency in your `Gemfile`.)
         | 
| 233 269 |  | 
| 234 | 
            -
            ## Why not just use the sqlite3 gem?
         | 
| 235 | 
            -
             | 
| 236 | 
            -
            The [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem is a
         | 
| 237 | 
            -
            popular, solid, well-maintained project, used by thousands of developers. I've
         | 
| 238 | 
            -
            been doing a lot of work with SQLite3 databases lately, and wanted to have a
         | 
| 239 | 
            -
            simpler API that gives me query results in a variety of ways. Thus extralite was
         | 
| 240 | 
            -
            born.
         | 
| 241 | 
            -
             | 
| 242 | 
            -
            Extralite is significantly [faster](#performance) than the `sqlite3` gem and is
         | 
| 243 | 
            -
            also [thread-friendly](#concurrency). On the other hand, Extralite does not have
         | 
| 244 | 
            -
            support for defining custom functions, aggregates and collations. If you're
         | 
| 245 | 
            -
            using any of those features, you'll have to stick to the `sqlite3` gem.
         | 
| 246 | 
            -
             | 
| 247 | 
            -
            Here's a table summarizing the differences between the two gems:
         | 
| 248 | 
            -
             | 
| 249 | 
            -
            | |sqlite3 1.6.0|Extralite 1.21|
         | 
| 250 | 
            -
            |-|-|-|
         | 
| 251 | 
            -
            |SQLite3 dependency|depends on OS-installed libsqlite3|Use either system sqlite3 or [bundled latest version of SQLite3](#installing-the-extralite-sqlite3-bundle)|
         | 
| 252 | 
            -
            |API design|multiple classes|single class|
         | 
| 253 | 
            -
            |Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
         | 
| 254 | 
            -
            |Execute multiple statements|separate API (#execute_batch)|integrated|
         | 
| 255 | 
            -
            |Prepared statements|yes|yes|
         | 
| 256 | 
            -
            |custom functions in Ruby|yes|no|
         | 
| 257 | 
            -
            |custom collations|yes|no|
         | 
| 258 | 
            -
            |custom aggregate functions|yes|no|
         | 
| 259 | 
            -
            |Multithread friendly|no|[yes](#concurrency)|
         | 
| 260 | 
            -
            |Code size|~2650LoC|~1300LoC|
         | 
| 261 | 
            -
            |Performance|1x|1.5x to 10x (see [below](#performance))|
         | 
| 262 | 
            -
             | 
| 263 270 | 
             
            ## Concurrency
         | 
| 264 271 |  | 
| 265 272 | 
             
            Extralite releases the GVL while making blocking calls to the sqlite3 library,
         | 
| @@ -272,10 +279,10 @@ performance, as you can see: | |
| 272 279 |  | 
| 273 280 | 
             
            A benchmark script is included, creating a table of various row counts, then
         | 
| 274 281 | 
             
            fetching the entire table using either `sqlite3` or `extralite`. This benchmark
         | 
| 275 | 
            -
            shows Extralite to be up to ~ | 
| 282 | 
            +
            shows Extralite to be up to ~11 times faster than `sqlite3` when fetching a
         | 
| 276 283 | 
             
            large number of rows.
         | 
| 277 284 |  | 
| 278 | 
            -
            ### Rows as  | 
| 285 | 
            +
            ### Rows as Hashes
         | 
| 279 286 |  | 
| 280 287 | 
             
             [Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
         | 
| 281 288 |  | 
| @@ -285,7 +292,7 @@ large number of rows. | |
| 285 292 | 
             
            |1K|299.2K rows/s|1.983M rows/s|__6.63x__|
         | 
| 286 293 | 
             
            |100K|185.4K rows/s|2.033M rows/s|__10.97x__|
         | 
| 287 294 |  | 
| 288 | 
            -
            ### Rows as  | 
| 295 | 
            +
            ### Rows as Arrays
         | 
| 289 296 |  | 
| 290 297 | 
             
            [Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
         | 
| 291 298 |  | 
| @@ -295,7 +302,7 @@ large number of rows. | |
| 295 302 | 
             
            |1K|502.1K rows/s|2.065M rows/s|__4.11x__|
         | 
| 296 303 | 
             
            |100K|455.7K rows/s|2.511M rows/s|__5.51x__|
         | 
| 297 304 |  | 
| 298 | 
            -
            ### Prepared  | 
| 305 | 
            +
            ### Prepared Statements
         | 
| 299 306 |  | 
| 300 307 | 
             
            [Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
         | 
| 301 308 |  | 
    
        data/ext/extralite/common.c
    CHANGED
    
    | @@ -174,7 +174,7 @@ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) { | |
| 174 174 | 
             
              case SQLITE_ERROR:
         | 
| 175 175 | 
             
                rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
         | 
| 176 176 | 
             
              default:
         | 
| 177 | 
            -
                rb_raise(cError, " | 
| 177 | 
            +
                rb_raise(cError, "%s", sqlite3_errmsg(db));
         | 
| 178 178 | 
             
              }
         | 
| 179 179 | 
             
            }
         | 
| 180 180 |  | 
| @@ -216,9 +216,9 @@ void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) { | |
| 216 216 | 
             
              case SQLITE_ERROR:
         | 
| 217 217 | 
             
                rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
         | 
| 218 218 | 
             
              case SQLITE_MULTI_STMT:
         | 
| 219 | 
            -
                rb_raise( | 
| 219 | 
            +
                rb_raise(cError, "A prepared statement does not accept SQL strings with multiple queries");
         | 
| 220 220 | 
             
              default:
         | 
| 221 | 
            -
                rb_raise(cError, " | 
| 221 | 
            +
                rb_raise(cError, "%s", sqlite3_errmsg(db));
         | 
| 222 222 | 
             
              }
         | 
| 223 223 | 
             
            }
         | 
| 224 224 |  | 
| @@ -248,7 +248,7 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) { | |
| 248 248 | 
             
                case SQLITE_ERROR:
         | 
| 249 249 | 
             
                  rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
         | 
| 250 250 | 
             
                default:
         | 
| 251 | 
            -
                  rb_raise(cError, " | 
| 251 | 
            +
                  rb_raise(cError, "%s", sqlite3_errmsg(db));
         | 
| 252 252 | 
             
              }
         | 
| 253 253 |  | 
| 254 254 | 
             
              return 0;
         | 
    
        data/ext/extralite/database.c
    CHANGED
    
    | @@ -7,6 +7,7 @@ VALUE cSQLError; | |
| 7 7 | 
             
            VALUE cBusyError;
         | 
| 8 8 | 
             
            VALUE cInterruptError;
         | 
| 9 9 |  | 
| 10 | 
            +
            ID ID_CALL;
         | 
| 10 11 | 
             
            ID ID_KEYS;
         | 
| 11 12 | 
             
            ID ID_NEW;
         | 
| 12 13 | 
             
            ID ID_STRIP;
         | 
| @@ -45,6 +46,12 @@ static VALUE Database_allocate(VALUE klass) { | |
| 45 46 | 
             
              } \
         | 
| 46 47 | 
             
            }
         | 
| 47 48 |  | 
| 49 | 
            +
            Database_t *Database_struct(VALUE self) {
         | 
| 50 | 
            +
              Database_t *db;
         | 
| 51 | 
            +
              GetDatabase(self, db);
         | 
| 52 | 
            +
              return db;
         | 
| 53 | 
            +
            }
         | 
| 54 | 
            +
             | 
| 48 55 | 
             
            sqlite3 *Database_sqlite3_db(VALUE self) {
         | 
| 49 56 | 
             
              Database_t *db;
         | 
| 50 57 | 
             
              GetDatabase(self, db);
         | 
| @@ -86,6 +93,8 @@ VALUE Database_initialize(VALUE self, VALUE path) { | |
| 86 93 | 
             
              }
         | 
| 87 94 | 
             
            #endif
         | 
| 88 95 |  | 
| 96 | 
            +
              db->trace_block = Qnil;
         | 
| 97 | 
            +
             | 
| 89 98 | 
             
              return Qnil;
         | 
| 90 99 | 
             
            }
         | 
| 91 100 |  | 
| @@ -134,6 +143,7 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA | |
| 134 143 |  | 
| 135 144 | 
             
              // prepare query ctx
         | 
| 136 145 | 
             
              GetOpenDatabase(self, db);
         | 
| 146 | 
            +
              if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_CALL, 1, sql);
         | 
| 137 147 | 
             
              prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
         | 
| 138 148 | 
             
              bind_all_parameters(stmt, argc - 1, argv + 1);
         | 
| 139 149 | 
             
              query_ctx ctx = { self, db->sqlite3_db, stmt };
         | 
| @@ -550,7 +560,7 @@ VALUE Extralite_runtime_status(int argc, VALUE* argv, VALUE self) { | |
| 550 560 | 
             
             * current value and the high water mark value. To reset the high water mark,
         | 
| 551 561 | 
             
             * pass true as reset.
         | 
| 552 562 | 
             
             */
         | 
| 553 | 
            -
            VALUE Database_status(int argc, VALUE* | 
| 563 | 
            +
            VALUE Database_status(int argc, VALUE *argv, VALUE self) {
         | 
| 554 564 | 
             
              VALUE op, reset;
         | 
| 555 565 | 
             
              int cur, hwm;
         | 
| 556 566 |  | 
| @@ -565,6 +575,75 @@ VALUE Database_status(int argc, VALUE* argv, VALUE self) { | |
| 565 575 | 
             
              return rb_ary_new3(2, INT2NUM(cur), INT2NUM(hwm));
         | 
| 566 576 | 
             
            }
         | 
| 567 577 |  | 
| 578 | 
            +
            /* call-seq:
         | 
| 579 | 
            +
             *   db.limit(category) -> value
         | 
| 580 | 
            +
             *   db.limit(category, new_value) -> prev_value
         | 
| 581 | 
            +
             *
         | 
| 582 | 
            +
             * Returns the current limit for the given category. If a new value is given,
         | 
| 583 | 
            +
             * sets the limit to the new value and returns the previous value.
         | 
| 584 | 
            +
             */
         | 
| 585 | 
            +
            VALUE Database_limit(int argc, VALUE *argv, VALUE self) {
         | 
| 586 | 
            +
              VALUE category, new_value;
         | 
| 587 | 
            +
             | 
| 588 | 
            +
              rb_scan_args(argc, argv, "11", &category, &new_value);
         | 
| 589 | 
            +
             | 
| 590 | 
            +
              Database_t *db;
         | 
| 591 | 
            +
              GetOpenDatabase(self, db);
         | 
| 592 | 
            +
             | 
| 593 | 
            +
              int value = sqlite3_limit(db->sqlite3_db, NUM2INT(category), RTEST(new_value) ? NUM2INT(new_value) : -1);
         | 
| 594 | 
            +
             | 
| 595 | 
            +
              if (value == -1) rb_raise(cError, "Invalid limit category");
         | 
| 596 | 
            +
             | 
| 597 | 
            +
              return INT2NUM(value);
         | 
| 598 | 
            +
            }
         | 
| 599 | 
            +
             | 
| 600 | 
            +
            /* call-seq:
         | 
| 601 | 
            +
             *   db.busy_timeout=(sec) -> db
         | 
| 602 | 
            +
             *   db.busy_timeout=nil -> db
         | 
| 603 | 
            +
             *
         | 
| 604 | 
            +
             * Sets the busy timeout for the database, in seconds or fractions thereof. To
         | 
| 605 | 
            +
             * disable the busy timeout, set it to 0 or nil.
         | 
| 606 | 
            +
             */
         | 
| 607 | 
            +
            VALUE Database_busy_timeout_set(VALUE self, VALUE sec) {
         | 
| 608 | 
            +
              Database_t *db;
         | 
| 609 | 
            +
              GetOpenDatabase(self, db);
         | 
| 610 | 
            +
             | 
| 611 | 
            +
              int ms = (sec == Qnil) ? 0 : (int)(NUM2DBL(sec) * 1000);
         | 
| 612 | 
            +
             | 
| 613 | 
            +
              int rc = sqlite3_busy_timeout(db->sqlite3_db, ms);
         | 
| 614 | 
            +
              if (rc != SQLITE_OK) rb_raise(cError, "Failed to set busy timeout");
         | 
| 615 | 
            +
             | 
| 616 | 
            +
              return self;
         | 
| 617 | 
            +
            }
         | 
| 618 | 
            +
             | 
| 619 | 
            +
            /* call-seq:
         | 
| 620 | 
            +
             *   db.total_changes -> value
         | 
| 621 | 
            +
             *
         | 
| 622 | 
            +
             * Returns the total number of changes made to the database since opening it.
         | 
| 623 | 
            +
             */
         | 
| 624 | 
            +
            VALUE Database_total_changes(VALUE self) {
         | 
| 625 | 
            +
              Database_t *db;
         | 
| 626 | 
            +
              GetOpenDatabase(self, db);
         | 
| 627 | 
            +
             | 
| 628 | 
            +
              int value = sqlite3_total_changes(db->sqlite3_db);
         | 
| 629 | 
            +
              return INT2NUM(value);
         | 
| 630 | 
            +
            }
         | 
| 631 | 
            +
             | 
| 632 | 
            +
            /* call-seq:
         | 
| 633 | 
            +
             *   db.trace { |sql| } -> db
         | 
| 634 | 
            +
             *   db.trace -> db
         | 
| 635 | 
            +
             *
         | 
| 636 | 
            +
             * Installs or removes a block that will be invoked for every SQL statement
         | 
| 637 | 
            +
             * executed.
         | 
| 638 | 
            +
             */
         | 
| 639 | 
            +
            VALUE Database_trace(VALUE self) {
         | 
| 640 | 
            +
              Database_t *db;
         | 
| 641 | 
            +
              GetOpenDatabase(self, db);
         | 
| 642 | 
            +
             | 
| 643 | 
            +
              db->trace_block = rb_block_given_p() ? rb_block_proc() : Qnil;
         | 
| 644 | 
            +
              return self;
         | 
| 645 | 
            +
            }
         | 
| 646 | 
            +
             | 
| 568 647 | 
             
            void Init_ExtraliteDatabase(void) {
         | 
| 569 648 | 
             
              VALUE mExtralite = rb_define_module("Extralite");
         | 
| 570 649 | 
             
              rb_define_singleton_method(mExtralite, "runtime_status", Extralite_runtime_status, -1);
         | 
| @@ -574,6 +653,7 @@ void Init_ExtraliteDatabase(void) { | |
| 574 653 | 
             
              rb_define_alloc_func(cDatabase, Database_allocate);
         | 
| 575 654 |  | 
| 576 655 | 
             
              rb_define_method(cDatabase, "backup", Database_backup, -1);
         | 
| 656 | 
            +
              rb_define_method(cDatabase, "busy_timeout=", Database_busy_timeout_set, 1);
         | 
| 577 657 | 
             
              rb_define_method(cDatabase, "changes", Database_changes, 0);
         | 
| 578 658 | 
             
              rb_define_method(cDatabase, "close", Database_close, 0);
         | 
| 579 659 | 
             
              rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
         | 
| @@ -583,6 +663,7 @@ void Init_ExtraliteDatabase(void) { | |
| 583 663 | 
             
              rb_define_method(cDatabase, "initialize", Database_initialize, 1);
         | 
| 584 664 | 
             
              rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
         | 
| 585 665 | 
             
              rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
         | 
| 666 | 
            +
              rb_define_method(cDatabase, "limit", Database_limit, -1);
         | 
| 586 667 | 
             
              rb_define_method(cDatabase, "prepare", Database_prepare, 1);
         | 
| 587 668 | 
             
              rb_define_method(cDatabase, "query", Database_query_hash, -1);
         | 
| 588 669 | 
             
              rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
         | 
| @@ -591,6 +672,8 @@ void Init_ExtraliteDatabase(void) { | |
| 591 672 | 
             
              rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
         | 
| 592 673 | 
             
              rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
         | 
| 593 674 | 
             
              rb_define_method(cDatabase, "status", Database_status, -1);
         | 
| 675 | 
            +
              rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
         | 
| 676 | 
            +
              rb_define_method(cDatabase, "trace", Database_trace, 0);
         | 
| 594 677 | 
             
              rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
         | 
| 595 678 |  | 
| 596 679 | 
             
            #ifdef HAVE_SQLITE3_LOAD_EXTENSION
         | 
| @@ -606,6 +689,7 @@ void Init_ExtraliteDatabase(void) { | |
| 606 689 | 
             
              rb_gc_register_mark_object(cBusyError);
         | 
| 607 690 | 
             
              rb_gc_register_mark_object(cInterruptError);
         | 
| 608 691 |  | 
| 692 | 
            +
              ID_CALL   = rb_intern("call");
         | 
| 609 693 | 
             
              ID_KEYS   = rb_intern("keys");
         | 
| 610 694 | 
             
              ID_NEW    = rb_intern("new");
         | 
| 611 695 | 
             
              ID_STRIP  = rb_intern("strip");
         | 
    
        data/ext/extralite/extralite.h
    CHANGED
    
    | @@ -27,6 +27,7 @@ extern VALUE cSQLError; | |
| 27 27 | 
             
            extern VALUE cBusyError;
         | 
| 28 28 | 
             
            extern VALUE cInterruptError;
         | 
| 29 29 |  | 
| 30 | 
            +
            extern ID ID_CALL;
         | 
| 30 31 | 
             
            extern ID ID_KEYS;
         | 
| 31 32 | 
             
            extern ID ID_NEW;
         | 
| 32 33 | 
             
            extern ID ID_STRIP;
         | 
| @@ -34,11 +35,13 @@ extern ID ID_TO_S; | |
| 34 35 |  | 
| 35 36 | 
             
            typedef struct {
         | 
| 36 37 | 
             
              sqlite3 *sqlite3_db;
         | 
| 38 | 
            +
              VALUE trace_block;
         | 
| 37 39 | 
             
            } Database_t;
         | 
| 38 40 |  | 
| 39 41 | 
             
            typedef struct {
         | 
| 40 42 | 
             
              VALUE db;
         | 
| 41 43 | 
             
              VALUE sql;
         | 
| 44 | 
            +
              Database_t *db_struct;
         | 
| 42 45 | 
             
              sqlite3 *sqlite3_db;
         | 
| 43 46 | 
             
              sqlite3_stmt *stmt;
         | 
| 44 47 | 
             
            } PreparedStatement_t;
         | 
| @@ -72,5 +75,6 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db); | |
| 72 75 | 
             
            VALUE cleanup_stmt(query_ctx *ctx);
         | 
| 73 76 |  | 
| 74 77 | 
             
            sqlite3 *Database_sqlite3_db(VALUE self);
         | 
| 78 | 
            +
            Database_t *Database_struct(VALUE self);
         | 
| 75 79 |  | 
| 76 80 | 
             
            #endif /* EXTRALITE_H */
         | 
| @@ -50,6 +50,7 @@ VALUE PreparedStatement_initialize(VALUE self, VALUE db, VALUE sql) { | |
| 50 50 | 
             
                rb_raise(cError, "Cannot prepare an empty SQL query");
         | 
| 51 51 |  | 
| 52 52 | 
             
              stmt->db = db;
         | 
| 53 | 
            +
              stmt->db_struct = Database_struct(db);
         | 
| 53 54 | 
             
              stmt->sqlite3_db = Database_sqlite3_db(db);
         | 
| 54 55 | 
             
              stmt->sql = sql;
         | 
| 55 56 |  | 
| @@ -65,6 +66,8 @@ static inline VALUE PreparedStatement_perform_query(int argc, VALUE *argv, VALUE | |
| 65 66 | 
             
              if (!stmt->stmt)
         | 
| 66 67 | 
             
                rb_raise(cError, "Prepared statement is closed");
         | 
| 67 68 |  | 
| 69 | 
            +
              if (stmt->db_struct->trace_block != Qnil) rb_funcall(stmt->db_struct->trace_block, ID_CALL, 1, stmt->sql);
         | 
| 70 | 
            +
             | 
| 68 71 | 
             
              sqlite3_reset(stmt->stmt);
         | 
| 69 72 | 
             
              sqlite3_clear_bindings(stmt->stmt);
         | 
| 70 73 | 
             
              bind_all_parameters(stmt->stmt, argc, argv);
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module Extralite
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              SQLITE_STATUS_MEMORY_USED           =  0
         | 
| 4 | 
            +
              SQLITE_STATUS_PAGECACHE_USED        =  1
         | 
| 5 | 
            +
              SQLITE_STATUS_PAGECACHE_OVERFLOW    =  2
         | 
| 6 | 
            +
              SQLITE_STATUS_SCRATCH_USED          =  3 # NOT USED
         | 
| 7 | 
            +
              SQLITE_STATUS_SCRATCH_OVERFLOW      =  4 # NOT USED
         | 
| 8 | 
            +
              SQLITE_STATUS_MALLOC_SIZE           =  5
         | 
| 9 | 
            +
              SQLITE_STATUS_PARSER_STACK          =  6
         | 
| 10 | 
            +
              SQLITE_STATUS_PAGECACHE_SIZE        =  7
         | 
| 11 | 
            +
              SQLITE_STATUS_SCRATCH_SIZE          =  8 # NOT USED
         | 
| 12 | 
            +
              SQLITE_STATUS_MALLOC_COUNT          =  9
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              SQLITE_DBSTATUS_LOOKASIDE_USED      =  0
         | 
| 15 | 
            +
              SQLITE_DBSTATUS_CACHE_USED          =  1
         | 
| 16 | 
            +
              SQLITE_DBSTATUS_SCHEMA_USED         =  2
         | 
| 17 | 
            +
              SQLITE_DBSTATUS_STMT_USED           =  3
         | 
| 18 | 
            +
              SQLITE_DBSTATUS_LOOKASIDE_HIT       =  4
         | 
| 19 | 
            +
              SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE =  5
         | 
| 20 | 
            +
              SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL =  6
         | 
| 21 | 
            +
              SQLITE_DBSTATUS_CACHE_HIT           =  7
         | 
| 22 | 
            +
              SQLITE_DBSTATUS_CACHE_MISS          =  8
         | 
| 23 | 
            +
              SQLITE_DBSTATUS_CACHE_WRITE         =  9
         | 
| 24 | 
            +
              SQLITE_DBSTATUS_DEFERRED_FKS        = 10
         | 
| 25 | 
            +
              SQLITE_DBSTATUS_CACHE_USED_SHARED   = 11
         | 
| 26 | 
            +
              SQLITE_DBSTATUS_CACHE_SPILL         = 12
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              SQLITE_STMTSTATUS_FULLSCAN_STEP     =  1
         | 
| 29 | 
            +
              SQLITE_STMTSTATUS_SORT              =  2
         | 
| 30 | 
            +
              SQLITE_STMTSTATUS_AUTOINDEX         =  3
         | 
| 31 | 
            +
              SQLITE_STMTSTATUS_VM_STEP           =  4
         | 
| 32 | 
            +
              SQLITE_STMTSTATUS_REPREPARE         =  5
         | 
| 33 | 
            +
              SQLITE_STMTSTATUS_RUN               =  6
         | 
| 34 | 
            +
              SQLITE_STMTSTATUS_FILTER_MISS       =  7
         | 
| 35 | 
            +
              SQLITE_STMTSTATUS_FILTER_HIT        =  8
         | 
| 36 | 
            +
              SQLITE_STMTSTATUS_MEMUSED           = 99
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              SQLITE_LIMIT_LENGTH                 =  0
         | 
| 39 | 
            +
              SQLITE_LIMIT_SQL_LENGTH             =  1
         | 
| 40 | 
            +
              SQLITE_LIMIT_COLUMN                 =  2
         | 
| 41 | 
            +
              SQLITE_LIMIT_EXPR_DEPTH             =  3
         | 
| 42 | 
            +
              SQLITE_LIMIT_COMPOUND_SELECT        =  4
         | 
| 43 | 
            +
              SQLITE_LIMIT_VDBE_OP                =  5
         | 
| 44 | 
            +
              SQLITE_LIMIT_FUNCTION_ARG           =  6
         | 
| 45 | 
            +
              SQLITE_LIMIT_ATTACHED               =  7
         | 
| 46 | 
            +
              SQLITE_LIMIT_LIKE_PATTERN_LENGTH    =  8
         | 
| 47 | 
            +
              SQLITE_LIMIT_VARIABLE_NUMBER        =  9
         | 
| 48 | 
            +
              SQLITE_LIMIT_TRIGGER_DEPTH          = 10
         | 
| 49 | 
            +
              SQLITE_LIMIT_WORKER_THREADS         = 11  
         | 
| 50 | 
            +
            end
         | 
    
        data/lib/extralite/version.rb
    CHANGED
    
    
    
        data/lib/extralite.rb
    CHANGED
    
    | @@ -1,46 +1,11 @@ | |
| 1 1 | 
             
            require_relative './extralite_ext'
         | 
| 2 | 
            +
            require_relative './extralite/sqlite3_constants'
         | 
| 2 3 |  | 
| 3 4 | 
             
            # Extralite is a Ruby gem for working with SQLite databases
         | 
| 4 5 | 
             
            module Extralite
         | 
| 5 | 
            -
              
         | 
| 6 | 
            -
              SQLITE_STATUS_MEMORY_USED           =  0
         | 
| 7 | 
            -
              SQLITE_STATUS_PAGECACHE_USED        =  1
         | 
| 8 | 
            -
              SQLITE_STATUS_PAGECACHE_OVERFLOW    =  2
         | 
| 9 | 
            -
              SQLITE_STATUS_SCRATCH_USED          =  3 # NOT USED
         | 
| 10 | 
            -
              SQLITE_STATUS_SCRATCH_OVERFLOW      =  4 # NOT USED
         | 
| 11 | 
            -
              SQLITE_STATUS_MALLOC_SIZE           =  5
         | 
| 12 | 
            -
              SQLITE_STATUS_PARSER_STACK          =  6
         | 
| 13 | 
            -
              SQLITE_STATUS_PAGECACHE_SIZE        =  7
         | 
| 14 | 
            -
              SQLITE_STATUS_SCRATCH_SIZE          =  8 # NOT USED
         | 
| 15 | 
            -
              SQLITE_STATUS_MALLOC_COUNT          =  9
         | 
| 16 6 |  | 
| 17 | 
            -
               | 
| 18 | 
            -
               | 
| 19 | 
            -
              SQLITE_DBSTATUS_SCHEMA_USED         =  2
         | 
| 20 | 
            -
              SQLITE_DBSTATUS_STMT_USED           =  3
         | 
| 21 | 
            -
              SQLITE_DBSTATUS_LOOKASIDE_HIT       =  4
         | 
| 22 | 
            -
              SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE =  5
         | 
| 23 | 
            -
              SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL =  6
         | 
| 24 | 
            -
              SQLITE_DBSTATUS_CACHE_HIT           =  7
         | 
| 25 | 
            -
              SQLITE_DBSTATUS_CACHE_MISS          =  8
         | 
| 26 | 
            -
              SQLITE_DBSTATUS_CACHE_WRITE         =  9
         | 
| 27 | 
            -
              SQLITE_DBSTATUS_DEFERRED_FKS        = 10
         | 
| 28 | 
            -
              SQLITE_DBSTATUS_CACHE_USED_SHARED   = 11
         | 
| 29 | 
            -
              SQLITE_DBSTATUS_CACHE_SPILL         = 12
         | 
| 30 | 
            -
             | 
| 31 | 
            -
              SQLITE_STMTSTATUS_FULLSCAN_STEP     =  1
         | 
| 32 | 
            -
              SQLITE_STMTSTATUS_SORT              =  2
         | 
| 33 | 
            -
              SQLITE_STMTSTATUS_AUTOINDEX         =  3
         | 
| 34 | 
            -
              SQLITE_STMTSTATUS_VM_STEP           =  4
         | 
| 35 | 
            -
              SQLITE_STMTSTATUS_REPREPARE         =  5
         | 
| 36 | 
            -
              SQLITE_STMTSTATUS_RUN               =  6
         | 
| 37 | 
            -
              SQLITE_STMTSTATUS_FILTER_MISS       =  7
         | 
| 38 | 
            -
              SQLITE_STMTSTATUS_FILTER_HIT        =  8
         | 
| 39 | 
            -
              SQLITE_STMTSTATUS_MEMUSED           = 99
         | 
| 40 | 
            -
             | 
| 41 | 
            -
              # The following class definitions are not really needed, as they're already
         | 
| 42 | 
            -
              # defined in the C extension. We put them here for the sake of generating
         | 
| 43 | 
            -
              # docs.
         | 
| 7 | 
            +
              # The following error classes are already defined in the C extension. We put
         | 
| 8 | 
            +
              # them here for the sake of generating docs.
         | 
| 44 9 |  | 
| 45 10 | 
             
              # A base class for Extralite exceptions
         | 
| 46 11 | 
             
              class Error < ::StandardError
         | 
    
        data/test/test_database.rb
    CHANGED
    
    | @@ -269,6 +269,68 @@ end | |
| 269 269 | 
             
              def test_database_status
         | 
| 270 270 | 
             
                assert_operator 0, :<, @db.status(Extralite::SQLITE_DBSTATUS_SCHEMA_USED).first
         | 
| 271 271 | 
             
              end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
              def test_database_limit
         | 
| 274 | 
            +
                result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
         | 
| 275 | 
            +
                assert_equal 10, result
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED, 5)
         | 
| 278 | 
            +
                assert_equal 10, result
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
         | 
| 281 | 
            +
                assert_equal 5, result
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                assert_raises(Extralite::Error) { @db.limit(-999) }
         | 
| 284 | 
            +
              end
         | 
| 285 | 
            +
             | 
| 286 | 
            +
              def test_database_busy_timeout
         | 
| 287 | 
            +
                fn = "/tmp/extralite-#{rand(10000)}.db"
         | 
| 288 | 
            +
                db1 = Extralite::Database.new(fn)
         | 
| 289 | 
            +
                db2 = Extralite::Database.new(fn)
         | 
| 290 | 
            +
                
         | 
| 291 | 
            +
                db1.query('begin exclusive')
         | 
| 292 | 
            +
                assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                db2.busy_timeout = 0.3
         | 
| 295 | 
            +
                t0 = Time.now
         | 
| 296 | 
            +
                t = Thread.new { sleep 0.1; db1.query('rollback') }
         | 
| 297 | 
            +
                result = db2.query('begin exclusive')
         | 
| 298 | 
            +
                t1 = Time.now
         | 
| 299 | 
            +
             | 
| 300 | 
            +
                assert_equal [], result
         | 
| 301 | 
            +
                assert t1 - t0 >= 0.1
         | 
| 302 | 
            +
                db2.query('rollback')
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                # try to provoke a timeout
         | 
| 305 | 
            +
                db1.query('begin exclusive')
         | 
| 306 | 
            +
                db2.busy_timeout = 0.1
         | 
| 307 | 
            +
                t0 = Time.now
         | 
| 308 | 
            +
                t = Thread.new do
         | 
| 309 | 
            +
                  sleep 0.5
         | 
| 310 | 
            +
                ensure
         | 
| 311 | 
            +
                  db1.query('rollback')
         | 
| 312 | 
            +
                end
         | 
| 313 | 
            +
                assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
         | 
| 314 | 
            +
                t1 = Time.now
         | 
| 315 | 
            +
                assert t1 - t0 >= 0.1
         | 
| 316 | 
            +
                t.kill
         | 
| 317 | 
            +
                t.join
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                db1.query('begin exclusive')
         | 
| 320 | 
            +
                db2.busy_timeout = 0
         | 
| 321 | 
            +
                assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                db2.busy_timeout = nil
         | 
| 324 | 
            +
                assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
         | 
| 325 | 
            +
              end
         | 
| 326 | 
            +
             | 
| 327 | 
            +
              def test_database_total_changes
         | 
| 328 | 
            +
                assert_equal 2, @db.total_changes
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                @db.query('insert into t values (7, 8, 9)')
         | 
| 331 | 
            +
             | 
| 332 | 
            +
                assert_equal 3, @db.total_changes
         | 
| 333 | 
            +
              end
         | 
| 272 334 | 
             
            end
         | 
| 273 335 |  | 
| 274 336 | 
             
            class ScenarioTest < MiniTest::Test
         | 
| @@ -336,6 +398,30 @@ class ScenarioTest < MiniTest::Test | |
| 336 398 | 
             
                result = @db.query_single_column('select x from t')
         | 
| 337 399 | 
             
                assert_equal [1, 4, 7], result
         | 
| 338 400 | 
             
              end
         | 
| 401 | 
            +
             | 
| 402 | 
            +
              def test_database_trace
         | 
| 403 | 
            +
                sqls = []
         | 
| 404 | 
            +
                @db.trace { |sql| sqls << sql }
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                @db.query('select 1')
         | 
| 407 | 
            +
                assert_equal ['select 1'], sqls
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                @db.query('select 2')
         | 
| 410 | 
            +
                assert_equal ['select 1', 'select 2'], sqls
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                stmt = @db.prepare('select 3')
         | 
| 413 | 
            +
                
         | 
| 414 | 
            +
                stmt.query
         | 
| 415 | 
            +
                assert_equal ['select 1', 'select 2', 'select 3'], sqls
         | 
| 416 | 
            +
             | 
| 417 | 
            +
                # turn off
         | 
| 418 | 
            +
                @db.trace
         | 
| 419 | 
            +
             | 
| 420 | 
            +
                stmt.query
         | 
| 421 | 
            +
             | 
| 422 | 
            +
                @db.query('select 4')
         | 
| 423 | 
            +
                assert_equal ['select 1', 'select 2', 'select 3'], sqls
         | 
| 424 | 
            +
              end
         | 
| 339 425 | 
             
            end
         | 
| 340 426 |  | 
| 341 427 | 
             
            class BackupTest < MiniTest::Test
         | 
| @@ -37,6 +37,11 @@ class PreparedStatementTest < MiniTest::Test | |
| 37 37 | 
             
                assert_raises(Extralite::SQLError) { @db.prepare('blah') }
         | 
| 38 38 | 
             
              end
         | 
| 39 39 |  | 
| 40 | 
            +
              def test_prepared_statement_with_multiple_queries
         | 
| 41 | 
            +
                error = begin; @db.prepare('select 1; select 2'); rescue => e; error = e; end
         | 
| 42 | 
            +
                assert_equal Extralite::Error, error.class
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 40 45 | 
             
              def test_prepared_statement_query_hash
         | 
| 41 46 | 
             
                r = @stmt.query_hash(4)
         | 
| 42 47 | 
             
                assert_equal [{x: 4, y: 5, z: 6}], r
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: extralite-bundle
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: '1. | 
| 4 | 
            +
              version: '1.23'
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Sharon Rosner
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2023-01- | 
| 11 | 
            +
            date: 2023-01-26 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rake-compiler
         | 
| @@ -113,6 +113,7 @@ files: | |
| 113 113 | 
             
            - extralite.gemspec
         | 
| 114 114 | 
             
            - gemspec.rb
         | 
| 115 115 | 
             
            - lib/extralite.rb
         | 
| 116 | 
            +
            - lib/extralite/sqlite3_constants.rb
         | 
| 116 117 | 
             
            - lib/extralite/version.rb
         | 
| 117 118 | 
             
            - lib/sequel/adapters/extralite.rb
         | 
| 118 119 | 
             
            - test/extensions/text.dylib
         |