extralite-bundle 2.4 → 2.6
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-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +49 -10
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +876 -217
- data/TODO.md +2 -3
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +226 -19
- data/ext/extralite/database.c +339 -23
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +25 -5
- data/ext/extralite/extralite_ext.c +10 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +222 -22
- data/ext/sqlite3/sqlite3.c +5420 -2501
- data/ext/sqlite3/sqlite3.h +73 -18
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +64 -8
- data/test/helper.rb +8 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +672 -13
- data/test/test_query.rb +367 -2
- metadata +10 -5
- data/test/perf_prepared.rb +0 -64
    
        data/ext/extralite/database.c
    CHANGED
    
    | @@ -13,11 +13,19 @@ VALUE eArgumentError; | |
| 13 13 |  | 
| 14 14 | 
             
            ID ID_bind;
         | 
| 15 15 | 
             
            ID ID_call;
         | 
| 16 | 
            +
            ID ID_each;
         | 
| 16 17 | 
             
            ID ID_keys;
         | 
| 17 18 | 
             
            ID ID_new;
         | 
| 18 19 | 
             
            ID ID_strip;
         | 
| 20 | 
            +
            ID ID_to_s;
         | 
| 21 | 
            +
            ID ID_track;
         | 
| 19 22 |  | 
| 23 | 
            +
            VALUE SYM_gvl_release_threshold;
         | 
| 20 24 | 
             
            VALUE SYM_read_only;
         | 
| 25 | 
            +
            VALUE SYM_synchronous;
         | 
| 26 | 
            +
            VALUE SYM_wal_journal_mode;
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            #define DB_GVL_MODE(db) Database_prepare_gvl_mode(db)
         | 
| 21 29 |  | 
| 22 30 | 
             
            static size_t Database_size(const void *ptr) {
         | 
| 23 31 | 
             
              return sizeof(Database_t);
         | 
| @@ -25,7 +33,14 @@ static size_t Database_size(const void *ptr) { | |
| 25 33 |  | 
| 26 34 | 
             
            static void Database_mark(void *ptr) {
         | 
| 27 35 | 
             
              Database_t *db = ptr;
         | 
| 28 | 
            -
               | 
| 36 | 
            +
              rb_gc_mark_movable(db->trace_proc);
         | 
| 37 | 
            +
              rb_gc_mark_movable(db->progress_handler_proc);
         | 
| 38 | 
            +
            }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            static void Database_compact(void *ptr) {
         | 
| 41 | 
            +
              Database_t *db = ptr;
         | 
| 42 | 
            +
              db->trace_proc = rb_gc_location(db->trace_proc);
         | 
| 43 | 
            +
              db->progress_handler_proc = rb_gc_location(db->progress_handler_proc);
         | 
| 29 44 | 
             
            }
         | 
| 30 45 |  | 
| 31 46 | 
             
            static void Database_free(void *ptr) {
         | 
| @@ -36,7 +51,7 @@ static void Database_free(void *ptr) { | |
| 36 51 |  | 
| 37 52 | 
             
            static const rb_data_type_t Database_type = {
         | 
| 38 53 | 
             
                "Database",
         | 
| 39 | 
            -
                {Database_mark, Database_free, Database_size,},
         | 
| 54 | 
            +
                {Database_mark, Database_free, Database_size, Database_compact},
         | 
| 40 55 | 
             
                0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
         | 
| 41 56 | 
             
            };
         | 
| 42 57 |  | 
| @@ -85,14 +100,40 @@ default_flags: | |
| 85 100 | 
             
              return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
         | 
| 86 101 | 
             
            }
         | 
| 87 102 |  | 
| 103 | 
            +
            VALUE Database_execute(int argc, VALUE *argv, VALUE self);
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            void Database_apply_opts(VALUE self, Database_t *db, VALUE opts) {
         | 
| 106 | 
            +
              VALUE value = Qnil;
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              value = rb_hash_aref(opts, SYM_gvl_release_threshold);
         | 
| 109 | 
            +
              if (!NIL_P(value)) db->gvl_release_threshold = NUM2INT(value);
         | 
| 110 | 
            +
             | 
| 111 | 
            +
              value = rb_hash_aref(opts, SYM_wal_journal_mode);
         | 
| 112 | 
            +
              if (RTEST(value)) {
         | 
| 113 | 
            +
                value = rb_str_new_literal("PRAGMA journal_mode=wal");
         | 
| 114 | 
            +
                Database_execute(1, &value, self);
         | 
| 115 | 
            +
              }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
              value = rb_hash_aref(opts, SYM_synchronous);
         | 
| 118 | 
            +
              if (RTEST(value)) {
         | 
| 119 | 
            +
                value = rb_str_new_literal("PRAGMA synchronous=1");
         | 
| 120 | 
            +
                Database_execute(1, &value, self);
         | 
| 121 | 
            +
              }
         | 
| 122 | 
            +
             | 
| 123 | 
            +
              RB_GC_GUARD(value);
         | 
| 124 | 
            +
            }
         | 
| 125 | 
            +
             | 
| 88 126 | 
             
            /* Initializes a new SQLite database with the given path and options.
         | 
| 89 127 | 
             
             *
         | 
| 90 128 | 
             
             * @overload initialize(path)
         | 
| 91 129 | 
             
             *   @param path [String] file path (or ':memory:' for memory database)
         | 
| 92 130 | 
             
             *   @return [void]
         | 
| 93 | 
            -
             * @overload initialize(path, read_only:  | 
| 131 | 
            +
             * @overload initialize(path, gvl_release_threshold: , read_only: , synchronous: , wal_journal_mode: )
         | 
| 94 132 | 
             
             *   @param path [String] file path (or ':memory:' for memory database)
         | 
| 133 | 
            +
             *   @param gvl_release_threshold [Integer] GVL release threshold
         | 
| 95 134 | 
             
             *   @param read_only [boolean] true for opening the database for reading only
         | 
| 135 | 
            +
             *   @param synchronous [boolean] true to set PRAGMA synchronous=1
         | 
| 136 | 
            +
             *   @param wal_journal_mode [boolean] true to set PRAGMA journal_mode=wal
         | 
| 96 137 | 
             
             *   @return [void]
         | 
| 97 138 | 
             
             */
         | 
| 98 139 | 
             
            VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
         | 
| @@ -124,9 +165,12 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) { | |
| 124 165 | 
             
              }
         | 
| 125 166 | 
             
            #endif
         | 
| 126 167 |  | 
| 127 | 
            -
              db-> | 
| 168 | 
            +
              db->trace_proc = Qnil;
         | 
| 169 | 
            +
              db->progress_handler_proc = Qnil;
         | 
| 128 170 | 
             
              db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
         | 
| 129 171 |  | 
| 172 | 
            +
              if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
         | 
| 173 | 
            +
             | 
| 130 174 | 
             
              return Qnil;
         | 
| 131 175 | 
             
            }
         | 
| 132 176 |  | 
| @@ -170,6 +214,10 @@ VALUE Database_closed_p(VALUE self) { | |
| 170 214 | 
             
              return db->sqlite3_db ? Qfalse : Qtrue;
         | 
| 171 215 | 
             
            }
         | 
| 172 216 |  | 
| 217 | 
            +
            inline enum gvl_mode Database_prepare_gvl_mode(Database_t *db) {
         | 
| 218 | 
            +
              return db->gvl_release_threshold < 0 ? GVL_HOLD : GVL_RELEASE;
         | 
| 219 | 
            +
            }
         | 
| 220 | 
            +
             | 
| 173 221 | 
             
            static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VALUE (*call)(query_ctx *)) {
         | 
| 174 222 | 
             
              Database_t *db = self_to_open_database(self);
         | 
| 175 223 | 
             
              sqlite3_stmt *stmt;
         | 
| @@ -180,9 +228,8 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA | |
| 180 228 | 
             
              sql = rb_funcall(argv[0], ID_strip, 0);
         | 
| 181 229 | 
             
              if (RSTRING_LEN(sql) == 0) return Qnil;
         | 
| 182 230 |  | 
| 183 | 
            -
               | 
| 184 | 
            -
               | 
| 185 | 
            -
              prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
         | 
| 231 | 
            +
              TRACE_SQL(db, sql);
         | 
| 232 | 
            +
              prepare_multi_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
         | 
| 186 233 | 
             
              RB_GC_GUARD(sql);
         | 
| 187 234 |  | 
| 188 235 | 
             
              bind_all_parameters(stmt, argc - 1, argv + 1);
         | 
| @@ -342,30 +389,150 @@ VALUE Database_execute(int argc, VALUE *argv, VALUE self) { | |
| 342 389 | 
             
            }
         | 
| 343 390 |  | 
| 344 391 | 
             
            /* call-seq:
         | 
| 345 | 
            -
             *   db. | 
| 392 | 
            +
             *   db.batch_execute(sql, params_array) -> changes
         | 
| 393 | 
            +
             *   db.batch_execute(sql, enumerable) -> changes
         | 
| 394 | 
            +
             *   db.batch_execute(sql, callable) -> changes
         | 
| 395 | 
            +
             *
         | 
| 396 | 
            +
             * Executes the given query for each list of parameters in the paramter source.
         | 
| 397 | 
            +
             * If an enumerable is given, it is iterated and each of its values is used as
         | 
| 398 | 
            +
             * the parameters for running the query. If a callable is given, it is called
         | 
| 399 | 
            +
             * repeatedly and each of its return values is used as the parameters, until nil
         | 
| 400 | 
            +
             * is returned.
         | 
| 346 401 | 
             
             *
         | 
| 347 | 
            -
             *  | 
| 348 | 
            -
             *  | 
| 349 | 
            -
             * multiple records.
         | 
| 402 | 
            +
             * Returns the number of changes effected. This method is designed for inserting
         | 
| 403 | 
            +
             * multiple records or performing other mass operations.
         | 
| 350 404 | 
             
             *
         | 
| 351 405 | 
             
             *     records = [
         | 
| 352 406 | 
             
             *       [1, 2, 3],
         | 
| 353 407 | 
             
             *       [4, 5, 6]
         | 
| 354 408 | 
             
             *     ]
         | 
| 355 | 
            -
             *     db. | 
| 409 | 
            +
             *     db.batch_execute('insert into foo values (?, ?, ?)', records)
         | 
| 356 410 | 
             
             *
         | 
| 411 | 
            +
             *     source = [
         | 
| 412 | 
            +
             *       [1, 2, 3],
         | 
| 413 | 
            +
             *       [4, 5, 6]
         | 
| 414 | 
            +
             *     ]
         | 
| 415 | 
            +
             *     db.batch_execute('insert into foo values (?, ?, ?)', -> { records.shift })
         | 
| 416 | 
            +
             *
         | 
| 417 | 
            +
             * @param sql [String] query SQL
         | 
| 418 | 
            +
             * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
         | 
| 419 | 
            +
             * @return [Integer] Total number of changes effected
         | 
| 357 420 | 
             
             */
         | 
| 358 | 
            -
            VALUE  | 
| 421 | 
            +
            VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
         | 
| 359 422 | 
             
              Database_t *db = self_to_open_database(self);
         | 
| 360 423 | 
             
              sqlite3_stmt *stmt;
         | 
| 361 424 |  | 
| 362 425 | 
             
              if (RSTRING_LEN(sql) == 0) return Qnil;
         | 
| 363 426 |  | 
| 364 | 
            -
               | 
| 365 | 
            -
               | 
| 366 | 
            -
             | 
| 427 | 
            +
              prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
         | 
| 428 | 
            +
              query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
         | 
| 429 | 
            +
             | 
| 430 | 
            +
              return rb_ensure(SAFE(safe_batch_execute), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
         | 
| 431 | 
            +
            }
         | 
| 432 | 
            +
             | 
| 433 | 
            +
            /* call-seq:
         | 
| 434 | 
            +
             *   db.batch_query(sql, params_array) -> rows
         | 
| 435 | 
            +
             *   db.batch_query(sql, enumerable) -> rows
         | 
| 436 | 
            +
             *   db.batch_query(sql, callable) -> rows
         | 
| 437 | 
            +
             *   db.batch_query(sql, params_array) { |rows| ... } -> changes
         | 
| 438 | 
            +
             *   db.batch_query(sql, enumerable) { |rows| ... } -> changes
         | 
| 439 | 
            +
             *   db.batch_query(sql, callable) { |rows| ... } -> changes
         | 
| 440 | 
            +
             *
         | 
| 441 | 
            +
             * Executes the given query for each list of parameters in the given paramter
         | 
| 442 | 
            +
             * source. If a block is given, it is called with the resulting rows for each
         | 
| 443 | 
            +
             * invocation of the query, and the total number of changes is returned.
         | 
| 444 | 
            +
             * Otherwise, an array containing the resulting rows for each invocation is
         | 
| 445 | 
            +
             * returned.
         | 
| 446 | 
            +
             *
         | 
| 447 | 
            +
             *     records = [
         | 
| 448 | 
            +
             *       [1, 2],
         | 
| 449 | 
            +
             *       [3, 4]
         | 
| 450 | 
            +
             *     ]
         | 
| 451 | 
            +
             *     db.batch_query('insert into foo values (?, ?) returning bar, baz', records)
         | 
| 452 | 
            +
             *     #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
         | 
| 453 | 
            +
             * *
         | 
| 454 | 
            +
             * @param sql [String] query SQL
         | 
| 455 | 
            +
             * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
         | 
| 456 | 
            +
             * @return [Array<Hash>, Integer] Total number of changes effected
         | 
| 457 | 
            +
             */
         | 
| 458 | 
            +
            VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
         | 
| 459 | 
            +
              Database_t *db = self_to_open_database(self);
         | 
| 460 | 
            +
              sqlite3_stmt *stmt;
         | 
| 461 | 
            +
             | 
| 462 | 
            +
              prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
         | 
| 463 | 
            +
              query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
         | 
| 464 | 
            +
             | 
| 465 | 
            +
              return rb_ensure(SAFE(safe_batch_query), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
         | 
| 466 | 
            +
            }
         | 
| 467 | 
            +
             | 
| 468 | 
            +
            /* call-seq:
         | 
| 469 | 
            +
             *   db.batch_query_ary(sql, params_array) -> rows
         | 
| 470 | 
            +
             *   db.batch_query_ary(sql, enumerable) -> rows
         | 
| 471 | 
            +
             *   db.batch_query_ary(sql, callable) -> rows
         | 
| 472 | 
            +
             *   db.batch_query_ary(sql, params_array) { |rows| ... } -> changes
         | 
| 473 | 
            +
             *   db.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
         | 
| 474 | 
            +
             *   db.batch_query_ary(sql, callable) { |rows| ... } -> changes
         | 
| 475 | 
            +
             *
         | 
| 476 | 
            +
             * Executes the given query for each list of parameters in the given paramter
         | 
| 477 | 
            +
             * source. If a block is given, it is called with the resulting rows for each
         | 
| 478 | 
            +
             * invocation of the query, and the total number of changes is returned.
         | 
| 479 | 
            +
             * Otherwise, an array containing the resulting rows for each invocation is
         | 
| 480 | 
            +
             * returned. Rows are represented as arrays.
         | 
| 481 | 
            +
             *
         | 
| 482 | 
            +
             *     records = [
         | 
| 483 | 
            +
             *       [1, 2],
         | 
| 484 | 
            +
             *       [3, 4]
         | 
| 485 | 
            +
             *     ]
         | 
| 486 | 
            +
             *     db.batch_query_ary('insert into foo values (?, ?) returning bar, baz', records)
         | 
| 487 | 
            +
             *     #=> [[1, 2], [3, 4]]
         | 
| 488 | 
            +
             * *
         | 
| 489 | 
            +
             * @param sql [String] query SQL
         | 
| 490 | 
            +
             * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
         | 
| 491 | 
            +
             * @return [Array<Array>, Integer] Total number of changes effected
         | 
| 492 | 
            +
             */
         | 
| 493 | 
            +
            VALUE Database_batch_query_ary(VALUE self, VALUE sql, VALUE parameters) {
         | 
| 494 | 
            +
              Database_t *db = self_to_open_database(self);
         | 
| 495 | 
            +
              sqlite3_stmt *stmt;
         | 
| 496 | 
            +
             | 
| 497 | 
            +
              prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
         | 
| 498 | 
            +
              query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
         | 
| 499 | 
            +
             | 
| 500 | 
            +
              return rb_ensure(SAFE(safe_batch_query_ary), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
         | 
| 501 | 
            +
            }
         | 
| 502 | 
            +
             | 
| 503 | 
            +
            /* call-seq:
         | 
| 504 | 
            +
             *   db.batch_query_single_column(sql, params_array) -> rows
         | 
| 505 | 
            +
             *   db.batch_query_single_column(sql, enumerable) -> rows
         | 
| 506 | 
            +
             *   db.batch_query_single_column(sql, callable) -> rows
         | 
| 507 | 
            +
             *   db.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
         | 
| 508 | 
            +
             *   db.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
         | 
| 509 | 
            +
             *   db.batch_query_single_column(sql, callable) { |rows| ... } -> changes
         | 
| 510 | 
            +
             *
         | 
| 511 | 
            +
             * Executes the given query for each list of parameters in the given paramter
         | 
| 512 | 
            +
             * source. If a block is given, it is called with the resulting rows for each
         | 
| 513 | 
            +
             * invocation of the query, and the total number of changes is returned.
         | 
| 514 | 
            +
             * Otherwise, an array containing the resulting rows for each invocation is
         | 
| 515 | 
            +
             * returned. Rows are single values.
         | 
| 516 | 
            +
             *
         | 
| 517 | 
            +
             *     records = [
         | 
| 518 | 
            +
             *       [1, 2],
         | 
| 519 | 
            +
             *       [3, 4]
         | 
| 520 | 
            +
             *     ]
         | 
| 521 | 
            +
             *     db.batch_query_ary('insert into foo values (?, ?) returning baz', records)
         | 
| 522 | 
            +
             *     #=> [2, 4]
         | 
| 523 | 
            +
             * *
         | 
| 524 | 
            +
             * @param sql [String] query SQL
         | 
| 525 | 
            +
             * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
         | 
| 526 | 
            +
             * @return [Array<any>, Integer] Total number of changes effected
         | 
| 527 | 
            +
             */
         | 
| 528 | 
            +
            VALUE Database_batch_query_single_column(VALUE self, VALUE sql, VALUE parameters) {
         | 
| 529 | 
            +
              Database_t *db = self_to_open_database(self);
         | 
| 530 | 
            +
              sqlite3_stmt *stmt;
         | 
| 531 | 
            +
             | 
| 532 | 
            +
              prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
         | 
| 533 | 
            +
              query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
         | 
| 367 534 |  | 
| 368 | 
            -
              return rb_ensure(SAFE( | 
| 535 | 
            +
              return rb_ensure(SAFE(safe_batch_query_single_column), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
         | 
| 369 536 | 
             
            }
         | 
| 370 537 |  | 
| 371 538 | 
             
            /* call-seq:
         | 
| @@ -449,8 +616,10 @@ VALUE Database_load_extension(VALUE self, VALUE path) { | |
| 449 616 |  | 
| 450 617 | 
             
            /* call-seq:
         | 
| 451 618 | 
             
             *   db.prepare(sql) -> Extralite::Query
         | 
| 619 | 
            +
             *   db.prepare(sql, ...) -> Extralite::Query
         | 
| 452 620 | 
             
             *
         | 
| 453 | 
            -
             * Creates a prepared statement with the given SQL query.
         | 
| 621 | 
            +
             * Creates a prepared statement with the given SQL query. If query parameters
         | 
| 622 | 
            +
             * are given, they are bound to the query.
         | 
| 454 623 | 
             
             */
         | 
| 455 624 | 
             
            VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
         | 
| 456 625 | 
             
              rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
         | 
| @@ -697,7 +866,127 @@ VALUE Database_total_changes(VALUE self) { | |
| 697 866 | 
             
            VALUE Database_trace(VALUE self) {
         | 
| 698 867 | 
             
              Database_t *db = self_to_open_database(self);
         | 
| 699 868 |  | 
| 700 | 
            -
              RB_OBJ_WRITE(self, &db-> | 
| 869 | 
            +
              RB_OBJ_WRITE(self, &db->trace_proc, rb_block_given_p() ? rb_block_proc() : Qnil);
         | 
| 870 | 
            +
              return self;
         | 
| 871 | 
            +
            }
         | 
| 872 | 
            +
             | 
| 873 | 
            +
            #ifdef EXTRALITE_ENABLE_CHANGESET
         | 
| 874 | 
            +
            /* Tracks changes to the database and returns a changeset. The changeset can
         | 
| 875 | 
            +
             * then be used to store the changes to a file, apply them to another database,
         | 
| 876 | 
            +
             * or undo the changes. The given table names specify which tables should be
         | 
| 877 | 
            +
             * tracked for changes. Passing a value of nil causes all tables to be tracked.
         | 
| 878 | 
            +
             * 
         | 
| 879 | 
            +
             *   changeset = db.track_changes(:foo, :bar) do
         | 
| 880 | 
            +
             *     perform_a_bunch_of_queries
         | 
| 881 | 
            +
             *   end
         | 
| 882 | 
            +
             * 
         | 
| 883 | 
            +
             *   File.open('my.changes', 'w+') { |f| f << changeset.to_blob }
         | 
| 884 | 
            +
             * 
         | 
| 885 | 
            +
             * @param table [String, Symbol] table to track
         | 
| 886 | 
            +
             * @return [Extralite::Changeset] changeset
         | 
| 887 | 
            +
            */
         | 
| 888 | 
            +
            VALUE Database_track_changes(int argc, VALUE *argv, VALUE self) {
         | 
| 889 | 
            +
              self_to_open_database(self);
         | 
| 890 | 
            +
             | 
| 891 | 
            +
              VALUE changeset = rb_funcall(cChangeset, ID_new, 0);
         | 
| 892 | 
            +
              VALUE tables = rb_ary_new_from_values(argc, argv);
         | 
| 893 | 
            +
             | 
| 894 | 
            +
              rb_funcall(changeset, ID_track, 2, self, tables);
         | 
| 895 | 
            +
             | 
| 896 | 
            +
              RB_GC_GUARD(changeset);
         | 
| 897 | 
            +
              RB_GC_GUARD(tables);
         | 
| 898 | 
            +
              return changeset;
         | 
| 899 | 
            +
            }
         | 
| 900 | 
            +
            #endif
         | 
| 901 | 
            +
             | 
| 902 | 
            +
            int Database_progress_handler(void *ptr) {
         | 
| 903 | 
            +
              Database_t *db = (Database_t *)ptr;
         | 
| 904 | 
            +
              rb_funcall(db->progress_handler_proc, ID_call, 0);
         | 
| 905 | 
            +
              return 0;
         | 
| 906 | 
            +
            }
         | 
| 907 | 
            +
             | 
| 908 | 
            +
            int Database_busy_handler(void *ptr, int v) {
         | 
| 909 | 
            +
              Database_t *db = (Database_t *)ptr;
         | 
| 910 | 
            +
              rb_funcall(db->progress_handler_proc, ID_call, 0);
         | 
| 911 | 
            +
              return 1;
         | 
| 912 | 
            +
            }
         | 
| 913 | 
            +
             | 
| 914 | 
            +
            void Database_reset_progress_handler(VALUE self, Database_t *db) {
         | 
| 915 | 
            +
              RB_OBJ_WRITE(self, &db->progress_handler_proc, Qnil);
         | 
| 916 | 
            +
              sqlite3_progress_handler(db->sqlite3_db, 0, NULL, NULL);
         | 
| 917 | 
            +
              sqlite3_busy_handler(db->sqlite3_db, NULL, NULL);
         | 
| 918 | 
            +
            }
         | 
| 919 | 
            +
             | 
| 920 | 
            +
            /* call-seq:
         | 
| 921 | 
            +
             *   db.on_progress(period) { } -> db
         | 
| 922 | 
            +
             *   db.on_progress(0) -> db
         | 
| 923 | 
            +
             *
         | 
| 924 | 
            +
             * Installs or removes a progress handler that will be executed periodically
         | 
| 925 | 
            +
             * while a query is running. This method can be used to support switching
         | 
| 926 | 
            +
             * between fibers and threads or implementing timeouts for running queries.
         | 
| 927 | 
            +
             *
         | 
| 928 | 
            +
             * The given period parameter specifies the approximate number of SQLite virtual
         | 
| 929 | 
            +
             * machine instructions that are evaluated between successive invocations of the
         | 
| 930 | 
            +
             * progress handler. A period of less than 1 removes the progress handler.
         | 
| 931 | 
            +
             *
         | 
| 932 | 
            +
             * The progress handler is called also when the database is busy. This lets the
         | 
| 933 | 
            +
             * application perform work while waiting for the database to become unlocked,
         | 
| 934 | 
            +
             * or implement a timeout. Note that setting the database's busy_timeout _after_
         | 
| 935 | 
            +
             * setting a progress handler may lead to undefined behaviour in a concurrent
         | 
| 936 | 
            +
             * application.
         | 
| 937 | 
            +
             *
         | 
| 938 | 
            +
             * When the progress handler is set, the gvl release threshold value is set to
         | 
| 939 | 
            +
             * -1, which means that the GVL will not be released at all when preparing or
         | 
| 940 | 
            +
             * running queries. It is the application's responsibility to let other threads
         | 
| 941 | 
            +
             * or fibers run by calling e.g. Thread.pass:
         | 
| 942 | 
            +
             * 
         | 
| 943 | 
            +
             *     db.on_progress(1000) do
         | 
| 944 | 
            +
             *       do_something_interesting
         | 
| 945 | 
            +
             *       Thread.pass # let other threads run
         | 
| 946 | 
            +
             *     end
         | 
| 947 | 
            +
             * 
         | 
| 948 | 
            +
             * Note that the progress handler is set globally for the database and that 
         | 
| 949 | 
            +
             * Extralite does provide any hooks for telling which queries are currently
         | 
| 950 | 
            +
             * running or at what time they were started. This means that you'll need
         | 
| 951 | 
            +
             * to wrap the stock #query_xxx and #execute methods with your own code that
         | 
| 952 | 
            +
             * calculates timeouts, for example:
         | 
| 953 | 
            +
             * 
         | 
| 954 | 
            +
             *     def setup_progress_handler
         | 
| 955 | 
            +
             *       @db.on_progress(1000) do
         | 
| 956 | 
            +
             *         raise TimeoutError if Time.now - @t0 >= @timeout
         | 
| 957 | 
            +
             *         Thread.pass
         | 
| 958 | 
            +
             *       end
         | 
| 959 | 
            +
             *     end
         | 
| 960 | 
            +
             * 
         | 
| 961 | 
            +
             *     def query(sql, *)
         | 
| 962 | 
            +
             *       @t0 = Time.now
         | 
| 963 | 
            +
             *       @db.query(sql, *)
         | 
| 964 | 
            +
             *     end
         | 
| 965 | 
            +
             * 
         | 
| 966 | 
            +
             * If the gvl release threshold is set to a value equal to or larger than 0
         | 
| 967 | 
            +
             * after setting the progress handler, the progress handler will be reset.
         | 
| 968 | 
            +
             *
         | 
| 969 | 
            +
             * @param period [Integer] progress handler period
         | 
| 970 | 
            +
             * @returns [Extralite::Database] database
         | 
| 971 | 
            +
             */
         | 
| 972 | 
            +
            VALUE Database_on_progress(VALUE self, VALUE period) {
         | 
| 973 | 
            +
              Database_t *db = self_to_open_database(self);
         | 
| 974 | 
            +
              int period_int = NUM2INT(period);
         | 
| 975 | 
            +
             | 
| 976 | 
            +
              if (period_int > 0 && rb_block_given_p()) {
         | 
| 977 | 
            +
                RB_OBJ_WRITE(self, &db->progress_handler_proc, rb_block_proc());
         | 
| 978 | 
            +
                db->gvl_release_threshold = -1;
         | 
| 979 | 
            +
             | 
| 980 | 
            +
                sqlite3_progress_handler(db->sqlite3_db, period_int, &Database_progress_handler, db);
         | 
| 981 | 
            +
                sqlite3_busy_handler(db->sqlite3_db, &Database_busy_handler, db);
         | 
| 982 | 
            +
              }
         | 
| 983 | 
            +
              else {
         | 
| 984 | 
            +
                RB_OBJ_WRITE(self, &db->progress_handler_proc, Qnil);
         | 
| 985 | 
            +
                db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
         | 
| 986 | 
            +
                sqlite3_progress_handler(db->sqlite3_db, 0, NULL, NULL);
         | 
| 987 | 
            +
                sqlite3_busy_handler(db->sqlite3_db, NULL, NULL);
         | 
| 988 | 
            +
              }
         | 
| 989 | 
            +
             | 
| 701 990 | 
             
              return self;
         | 
| 702 991 | 
             
            }
         | 
| 703 992 |  | 
| @@ -777,8 +1066,16 @@ VALUE Database_gvl_release_threshold_set(VALUE self, VALUE value) { | |
| 777 1066 |  | 
| 778 1067 | 
             
              switch (TYPE(value)) {
         | 
| 779 1068 | 
             
                case T_FIXNUM:
         | 
| 780 | 
            -
                   | 
| 781 | 
            -
             | 
| 1069 | 
            +
                  {
         | 
| 1070 | 
            +
                    int value_int = NUM2INT(value);
         | 
| 1071 | 
            +
                    if (value_int < -1)
         | 
| 1072 | 
            +
                      rb_raise(eArgumentError, "Invalid GVL release threshold value (expect integer >= -1)");
         | 
| 1073 | 
            +
             | 
| 1074 | 
            +
                    if (value_int > -1 && !NIL_P(db->progress_handler_proc))
         | 
| 1075 | 
            +
                      Database_reset_progress_handler(self, db);
         | 
| 1076 | 
            +
                    db->gvl_release_threshold = value_int;
         | 
| 1077 | 
            +
                    break;
         | 
| 1078 | 
            +
                  }
         | 
| 782 1079 | 
             
                case T_NIL:
         | 
| 783 1080 | 
             
                  db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
         | 
| 784 1081 | 
             
                  break;
         | 
| @@ -811,7 +1108,10 @@ void Init_ExtraliteDatabase(void) { | |
| 811 1108 | 
             
              #endif
         | 
| 812 1109 |  | 
| 813 1110 | 
             
              rb_define_method(cDatabase, "execute", Database_execute, -1);
         | 
| 814 | 
            -
              rb_define_method(cDatabase, " | 
| 1111 | 
            +
              rb_define_method(cDatabase, "batch_execute", Database_batch_execute, 2);
         | 
| 1112 | 
            +
              rb_define_method(cDatabase, "batch_query", Database_batch_query, 2);
         | 
| 1113 | 
            +
              rb_define_method(cDatabase, "batch_query_ary", Database_batch_query_ary, 2);
         | 
| 1114 | 
            +
              rb_define_method(cDatabase, "batch_query_single_column", Database_batch_query_single_column, 2);
         | 
| 815 1115 | 
             
              rb_define_method(cDatabase, "filename", Database_filename, -1);
         | 
| 816 1116 | 
             
              rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
         | 
| 817 1117 | 
             
              rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
         | 
| @@ -820,6 +1120,7 @@ void Init_ExtraliteDatabase(void) { | |
| 820 1120 | 
             
              rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
         | 
| 821 1121 | 
             
              rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
         | 
| 822 1122 | 
             
              rb_define_method(cDatabase, "limit", Database_limit, -1);
         | 
| 1123 | 
            +
              rb_define_method(cDatabase, "on_progress", Database_on_progress, 1);
         | 
| 823 1124 | 
             
              rb_define_method(cDatabase, "prepare", Database_prepare, -1);
         | 
| 824 1125 | 
             
              rb_define_method(cDatabase, "query", Database_query_hash, -1);
         | 
| 825 1126 | 
             
              rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
         | 
| @@ -831,6 +1132,11 @@ void Init_ExtraliteDatabase(void) { | |
| 831 1132 | 
             
              rb_define_method(cDatabase, "status", Database_status, -1);
         | 
| 832 1133 | 
             
              rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
         | 
| 833 1134 | 
             
              rb_define_method(cDatabase, "trace", Database_trace, 0);
         | 
| 1135 | 
            +
             | 
| 1136 | 
            +
              #ifdef EXTRALITE_ENABLE_CHANGESET
         | 
| 1137 | 
            +
              rb_define_method(cDatabase, "track_changes", Database_track_changes, -1);
         | 
| 1138 | 
            +
              #endif
         | 
| 1139 | 
            +
              
         | 
| 834 1140 | 
             
              rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
         | 
| 835 1141 |  | 
| 836 1142 | 
             
            #ifdef HAVE_SQLITE3_LOAD_EXTENSION
         | 
| @@ -849,12 +1155,22 @@ void Init_ExtraliteDatabase(void) { | |
| 849 1155 |  | 
| 850 1156 | 
             
              ID_bind   = rb_intern("bind");
         | 
| 851 1157 | 
             
              ID_call   = rb_intern("call");
         | 
| 1158 | 
            +
              ID_each   = rb_intern("each");
         | 
| 852 1159 | 
             
              ID_keys   = rb_intern("keys");
         | 
| 853 1160 | 
             
              ID_new    = rb_intern("new");
         | 
| 854 1161 | 
             
              ID_strip  = rb_intern("strip");
         | 
| 1162 | 
            +
              ID_to_s   = rb_intern("to_s");
         | 
| 1163 | 
            +
              ID_track  = rb_intern("track");
         | 
| 1164 | 
            +
             | 
| 1165 | 
            +
              SYM_gvl_release_threshold = ID2SYM(rb_intern("gvl_release_threshold"));
         | 
| 1166 | 
            +
              SYM_read_only             = ID2SYM(rb_intern("read_only"));
         | 
| 1167 | 
            +
              SYM_synchronous           = ID2SYM(rb_intern("synchronous"));
         | 
| 1168 | 
            +
              SYM_wal_journal_mode      = ID2SYM(rb_intern("wal_journal_mode"));
         | 
| 855 1169 |  | 
| 856 | 
            -
               | 
| 1170 | 
            +
              rb_gc_register_mark_object(SYM_gvl_release_threshold);
         | 
| 857 1171 | 
             
              rb_gc_register_mark_object(SYM_read_only);
         | 
| 1172 | 
            +
              rb_gc_register_mark_object(SYM_synchronous);
         | 
| 1173 | 
            +
              rb_gc_register_mark_object(SYM_wal_journal_mode);
         | 
| 858 1174 |  | 
| 859 1175 | 
             
              UTF8_ENCODING = rb_utf8_encoding();
         | 
| 860 1176 | 
             
            }
         | 
| @@ -6,11 +6,17 @@ $CFLAGS << ' -Wno-undef' | |
| 6 6 | 
             
            $CFLAGS << ' -Wno-discarded-qualifiers'
         | 
| 7 7 | 
             
            $CFLAGS << ' -Wno-unused-function'
         | 
| 8 8 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
            $defs <<  | 
| 11 | 
            -
            $defs <<  | 
| 9 | 
            +
            # enable the session extension
         | 
| 10 | 
            +
            $defs << '-DSQLITE_ENABLE_SESSION'
         | 
| 11 | 
            +
            $defs << '-DSQLITE_ENABLE_PREUPDATE_HOOK'
         | 
| 12 | 
            +
            $defs << '-DEXTRALITE_ENABLE_CHANGESET'
         | 
| 12 13 |  | 
| 13 | 
            -
             | 
| 14 | 
            +
            $defs << '-DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION'
         | 
| 15 | 
            +
            $defs << '-DHAVE_SQLITE3_LOAD_EXTENSION'
         | 
| 16 | 
            +
            $defs << '-DHAVE_SQLITE3_PREPARE_V2'
         | 
| 17 | 
            +
            $defs << '-DHAVE_SQLITE3_ERROR_OFFSET'
         | 
| 18 | 
            +
            $defs << '-DHAVE_SQLITE3SESSION_CHANGESET'
         | 
| 14 19 |  | 
| 20 | 
            +
            have_func('usleep')
         | 
| 15 21 | 
             
            dir_config('extralite_ext')
         | 
| 16 22 | 
             
            create_makefile('extralite_ext')
         | 
    
        data/ext/extralite/extconf.rb
    CHANGED
    
    | @@ -52,43 +52,47 @@ else | |
| 52 52 | 
             
                $CFLAGS << ' -W3'
         | 
| 53 53 | 
             
              end
         | 
| 54 54 |  | 
| 55 | 
            -
              if RUBY_VERSION < '2.7'
         | 
| 56 | 
            -
                $CFLAGS << ' -DTAINTING_SUPPORT'
         | 
| 57 | 
            -
              end
         | 
| 58 | 
            -
             | 
| 59 55 | 
             
              # @!visibility private
         | 
| 60 56 | 
             
              def asplode missing
         | 
| 61 57 | 
             
                if RUBY_PLATFORM =~ /mingw|mswin/
         | 
| 62 58 | 
             
                  abort "#{missing} is missing. Install SQLite3 from " +
         | 
| 63 59 | 
             
                  "http://www.sqlite.org/ first."
         | 
| 64 60 | 
             
                else
         | 
| 65 | 
            -
                  abort  | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
                     | 
| 70 | 
            -
             | 
| 71 | 
            -
                  end
         | 
| 61 | 
            +
                  abort <<~error
         | 
| 62 | 
            +
                    #{missing} is missing. Try 'brew install sqlite3',
         | 
| 63 | 
            +
                    'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
         | 
| 64 | 
            +
                    and check your shared library search path (the location where
         | 
| 65 | 
            +
                    your sqlite3 shared library is located).
         | 
| 66 | 
            +
                  error
         | 
| 72 67 | 
             
                end
         | 
| 68 | 
            +
              end
         | 
| 73 69 |  | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 70 | 
            +
              asplode('sqlite3.h') unless find_header('sqlite3.h')
         | 
| 71 | 
            +
              # find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
         | 
| 76 72 |  | 
| 77 | 
            -
             | 
| 73 | 
            +
              have_library 'dl' # for static builds
         | 
| 78 74 |  | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 75 | 
            +
              if with_config('sqlcipher')
         | 
| 76 | 
            +
                asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
         | 
| 77 | 
            +
              else
         | 
| 78 | 
            +
                asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              have_func('sqlite3_enable_load_extension')
         | 
| 82 | 
            +
              have_func('sqlite3_load_extension')
         | 
| 83 | 
            +
              have_func('sqlite3_prepare_v2')
         | 
| 84 | 
            +
              have_func('sqlite3_error_offset')
         | 
| 85 | 
            +
              have_func('sqlite3session_changeset')
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              if have_type('sqlite3_session', 'sqlite.h')
         | 
| 88 | 
            +
                $defs << '-DEXTRALITE_ENABLE_CHANGESET'
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
              # have_macro('__SQLITESESSION_H_')
         | 
| 91 | 
            +
              # have_macro('SQLITE3_H')
         | 
| 84 92 |  | 
| 85 | 
            -
                have_func('sqlite3_enable_load_extension')
         | 
| 86 | 
            -
                have_func('sqlite3_load_extension')
         | 
| 87 | 
            -
                have_func('sqlite3_prepare_v2')
         | 
| 88 | 
            -
                have_func('sqlite3_error_offset')
         | 
| 89 93 |  | 
| 90 | 
            -
             | 
| 94 | 
            +
              $defs << "-DEXTRALITE_NO_BUNDLE"
         | 
| 91 95 |  | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 96 | 
            +
              dir_config('extralite_ext')
         | 
| 97 | 
            +
              create_makefile('extralite_ext')
         | 
| 98 | 
            +
            end
         | 
    
        data/ext/extralite/extralite.h
    CHANGED
    
    | @@ -23,6 +23,7 @@ | |
| 23 23 | 
             
            extern VALUE cDatabase;
         | 
| 24 24 | 
             
            extern VALUE cQuery;
         | 
| 25 25 | 
             
            extern VALUE cIterator;
         | 
| 26 | 
            +
            extern VALUE cChangeset;
         | 
| 26 27 | 
             
            extern VALUE cBlob;
         | 
| 27 28 |  | 
| 28 29 | 
             
            extern VALUE cError;
         | 
| @@ -32,17 +33,21 @@ extern VALUE cInterruptError; | |
| 32 33 | 
             
            extern VALUE cParameterError;
         | 
| 33 34 |  | 
| 34 35 | 
             
            extern ID ID_call;
         | 
| 36 | 
            +
            extern ID ID_each;
         | 
| 35 37 | 
             
            extern ID ID_keys;
         | 
| 36 38 | 
             
            extern ID ID_new;
         | 
| 37 39 | 
             
            extern ID ID_strip;
         | 
| 40 | 
            +
            extern ID ID_to_s;
         | 
| 41 | 
            +
            extern ID ID_track;
         | 
| 38 42 |  | 
| 39 | 
            -
            extern VALUE SYM_hash;
         | 
| 40 43 | 
             
            extern VALUE SYM_ary;
         | 
| 44 | 
            +
            extern VALUE SYM_hash;
         | 
| 41 45 | 
             
            extern VALUE SYM_single_column;
         | 
| 42 46 |  | 
| 43 47 | 
             
            typedef struct {
         | 
| 44 48 | 
             
              sqlite3 *sqlite3_db;
         | 
| 45 | 
            -
              VALUE    | 
| 49 | 
            +
              VALUE   trace_proc;
         | 
| 50 | 
            +
              VALUE   progress_handler_proc;
         | 
| 46 51 | 
             
              int     gvl_release_threshold;
         | 
| 47 52 | 
             
            } Database_t;
         | 
| 48 53 |  | 
| @@ -67,6 +72,13 @@ typedef struct { | |
| 67 72 | 
             
              enum iterator_mode  mode;
         | 
| 68 73 | 
             
            } Iterator_t;
         | 
| 69 74 |  | 
| 75 | 
            +
            #ifdef EXTRALITE_ENABLE_CHANGESET
         | 
| 76 | 
            +
            typedef struct {
         | 
| 77 | 
            +
              int             changeset_len;
         | 
| 78 | 
            +
              void            *changeset_ptr;
         | 
| 79 | 
            +
            } Changeset_t;
         | 
| 80 | 
            +
            #endif
         | 
| 81 | 
            +
             | 
| 70 82 | 
             
            enum query_mode {
         | 
| 71 83 | 
             
              QUERY_YIELD,
         | 
| 72 84 | 
             
              QUERY_MULTI_ROW,
         | 
| @@ -96,11 +108,18 @@ enum gvl_mode { | |
| 96 108 | 
             
            #define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
         | 
| 97 109 | 
             
            #define QUERY_CTX(self, db, stmt, params, mode, max_rows) \
         | 
| 98 110 | 
             
              { self, db->sqlite3_db, stmt, params, mode, max_rows, 0, db->gvl_release_threshold, 0 }
         | 
| 111 | 
            +
            #define TRACE_SQL(db, sql) \
         | 
| 112 | 
            +
                if (db->trace_proc != Qnil) rb_funcall(db->trace_proc, ID_call, 1, sql);
         | 
| 113 | 
            +
             | 
| 99 114 | 
             
            #define DEFAULT_GVL_RELEASE_THRESHOLD 1000
         | 
| 100 115 |  | 
| 116 | 
            +
             | 
| 101 117 | 
             
            extern rb_encoding *UTF8_ENCODING;
         | 
| 102 118 |  | 
| 103 | 
            -
            VALUE  | 
| 119 | 
            +
            VALUE safe_batch_execute(query_ctx *ctx);
         | 
| 120 | 
            +
            VALUE safe_batch_query(query_ctx *ctx);
         | 
| 121 | 
            +
            VALUE safe_batch_query_ary(query_ctx *ctx);
         | 
| 122 | 
            +
            VALUE safe_batch_query_single_column(query_ctx *ctx);
         | 
| 104 123 | 
             
            VALUE safe_query_ary(query_ctx *ctx);
         | 
| 105 124 | 
             
            VALUE safe_query_changes(query_ctx *ctx);
         | 
| 106 125 | 
             
            VALUE safe_query_columns(query_ctx *ctx);
         | 
| @@ -121,14 +140,15 @@ VALUE Query_to_a_hash(VALUE self); | |
| 121 140 | 
             
            VALUE Query_to_a_ary(VALUE self);
         | 
| 122 141 | 
             
            VALUE Query_to_a_single_column(VALUE self);
         | 
| 123 142 |  | 
| 124 | 
            -
            void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
         | 
| 125 | 
            -
            void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
         | 
| 143 | 
            +
            void prepare_single_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
         | 
| 144 | 
            +
            void prepare_multi_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
         | 
| 126 145 | 
             
            void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv);
         | 
| 127 146 | 
             
            void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj);
         | 
| 128 147 | 
             
            int stmt_iterate(query_ctx *ctx);
         | 
| 129 148 | 
             
            VALUE cleanup_stmt(query_ctx *ctx);
         | 
| 130 149 |  | 
| 131 150 | 
             
            sqlite3 *Database_sqlite3_db(VALUE self);
         | 
| 151 | 
            +
            enum gvl_mode Database_prepare_gvl_mode(Database_t *db);
         | 
| 132 152 | 
             
            Database_t *self_to_database(VALUE self);
         | 
| 133 153 |  | 
| 134 154 | 
             
            void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);
         |