extralite 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.
@@ -1,9 +1,19 @@
1
+ #include "ruby.h"
2
+
1
3
  void Init_ExtraliteDatabase();
2
4
  void Init_ExtraliteQuery();
3
5
  void Init_ExtraliteIterator();
6
+ #ifdef EXTRALITE_ENABLE_CHANGESET
7
+ void Init_ExtraliteChangeset();
8
+ #endif
4
9
 
5
10
  void Init_extralite_ext(void) {
11
+ rb_ext_ractor_safe(true);
12
+
6
13
  Init_ExtraliteDatabase();
7
14
  Init_ExtraliteQuery();
8
15
  Init_ExtraliteIterator();
16
+ #ifdef EXTRALITE_ENABLE_CHANGESET
17
+ Init_ExtraliteChangeset();
18
+ #endif
9
19
  }
@@ -19,13 +19,18 @@ static size_t Iterator_size(const void *ptr) {
19
19
 
20
20
  static void Iterator_mark(void *ptr) {
21
21
  Iterator_t *iterator = ptr;
22
- rb_gc_mark(iterator->query);
22
+ rb_gc_mark_movable(iterator->query);
23
+ }
24
+
25
+ static void Iterator_compact(void *ptr) {
26
+ Iterator_t *iterator = ptr;
27
+ iterator->query = rb_gc_location(iterator->query);
23
28
  }
24
29
 
25
30
  static const rb_data_type_t Iterator_type = {
26
31
  "Iterator",
27
- {Iterator_mark, free, Iterator_size,},
28
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
32
+ {Iterator_mark, free, Iterator_size, Iterator_compact},
33
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
29
34
  };
30
35
 
31
36
  static VALUE Iterator_allocate(VALUE klass) {
@@ -14,14 +14,22 @@ VALUE cQuery;
14
14
  ID ID_inspect;
15
15
  ID ID_slice;
16
16
 
17
+ #define DB_GVL_MODE(query) Database_prepare_gvl_mode(query->db_struct)
18
+
17
19
  static size_t Query_size(const void *ptr) {
18
20
  return sizeof(Query_t);
19
21
  }
20
22
 
21
23
  static void Query_mark(void *ptr) {
22
24
  Query_t *query = ptr;
23
- rb_gc_mark(query->db);
24
- rb_gc_mark(query->sql);
25
+ rb_gc_mark_movable(query->db);
26
+ rb_gc_mark_movable(query->sql);
27
+ }
28
+
29
+ static void Query_compact(void *ptr) {
30
+ Query_t *query = ptr;
31
+ query->db = rb_gc_location(query->db);
32
+ query->sql = rb_gc_location(query->sql);
25
33
  }
26
34
 
27
35
  static void Query_free(void *ptr) {
@@ -32,7 +40,7 @@ static void Query_free(void *ptr) {
32
40
 
33
41
  static const rb_data_type_t Query_type = {
34
42
  "Query",
35
- {Query_mark, Query_free, Query_size,},
43
+ {Query_mark, Query_free, Query_size, Query_compact},
36
44
  0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
37
45
  };
38
46
 
@@ -82,19 +90,17 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
82
90
 
83
91
  static inline void query_reset(Query_t *query) {
84
92
  if (!query->stmt)
85
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
86
- if (query->db_struct->trace_block != Qnil)
87
- rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
93
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
94
+ TRACE_SQL(query->db_struct, query->sql);
88
95
  sqlite3_reset(query->stmt);
89
96
  query->eof = 0;
90
97
  }
91
98
 
92
99
  static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv) {
93
100
  if (!query->stmt)
94
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
101
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
95
102
 
96
- if (query->db_struct->trace_block != Qnil)
97
- rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
103
+ TRACE_SQL(query->db_struct, query->sql);
98
104
 
99
105
  sqlite3_reset(query->stmt);
100
106
  query->eof = 0;
@@ -122,8 +128,7 @@ VALUE Query_reset(VALUE self) {
122
128
  if (query->closed) rb_raise(cError, "Query is closed");
123
129
 
124
130
  query_reset(query);
125
- if (query->db_struct->trace_block != Qnil)
126
- rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
131
+ TRACE_SQL(query->db_struct, query->sql);
127
132
 
128
133
  return self;
129
134
  }
@@ -367,27 +372,115 @@ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
367
372
  return Query_perform_next(self, ALL_ROWS, safe_query_changes);
368
373
  }
369
374
 
370
- /* Executes the query for each set of parameters in the given array. Parameters
371
- * can be specified as either an array (for unnamed parameters) or a hash (for
372
- * named parameters). Returns the number of changes effected. This method is
373
- * designed for inserting multiple records.
375
+ /* call-seq:
376
+ * query << [...] -> query
377
+ * query << { ... } -> query
378
+ *
379
+ * Runs the with the given parameters, returning the total changes effected.
380
+ * This method should be used for data- or schema-manipulation queries.
381
+ *
382
+ * Query parameters to be bound to placeholders in the query can be specified as
383
+ * a list of values or as a hash mapping parameter names to values. When
384
+ * parameters are given as an array, the query should specify parameters using
385
+ * `?`:
386
+ *
387
+ * query = db.prepare('update foo set x = ? where y = ?')
388
+ * query << [42, 43]
389
+ *
390
+ * Named placeholders are specified using `:`. The placeholder values are
391
+ * specified using a hash, where keys are either strings are symbols. String
392
+ * keys can include or omit the `:` prefix. The following are equivalent:
393
+ *
394
+ * query = db.prepare('update foo set x = :bar')
395
+ * query << { bar: 42 }
396
+ * query << { 'bar' => 42 }
397
+ * query << { ':bar' => 42 }
398
+ */
399
+ VALUE Query_execute_chevrons(VALUE self, VALUE params) {
400
+ Query_execute(1, &params, self);
401
+ return self;
402
+ }
403
+
404
+ /* call-seq:
405
+ * query.batch_execute(params_array) -> changes
406
+ * query.batch_execute(enumerable) -> changes
407
+ * query.batch_execute(callable) -> changes
408
+ *
409
+ * Executes the query for each set of parameters in the paramter source. If an
410
+ * enumerable is given, it is iterated and each of its values is used as the
411
+ * parameters for running the query. If a callable is given, it is called
412
+ * repeatedly and each of its return values is used as the parameters, until nil
413
+ * is returned.
414
+ *
415
+ * Returns the number of changes effected. This method is designed for inserting
416
+ * multiple records.
374
417
  *
375
418
  * query = db.prepare('insert into foo values (?, ?, ?)')
376
419
  * records = [
377
420
  * [1, 2, 3],
378
421
  * [4, 5, 6]
379
422
  * ]
380
- * query.execute_multi(records)
423
+ * query.batch_execute(records)
424
+ *
425
+ * source = [
426
+ * [1, 2, 3],
427
+ * [4, 5, 6]
428
+ * ]
429
+ * query.batch_execute { records.shift }
381
430
  *
382
- * @param parameters [Array<Array, Hash>] array of parameters to run query with
431
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] array of parameters to run query with
383
432
  * @return [Integer] number of changes effected
384
433
  */
385
- VALUE Query_execute_multi(VALUE self, VALUE parameters) {
434
+ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
435
+ Query_t *query = self_to_query(self);
436
+ if (query->closed) rb_raise(cError, "Query is closed");
437
+
438
+ if (!query->stmt)
439
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
440
+
441
+ query_ctx ctx = QUERY_CTX(
442
+ self,
443
+ query->db_struct,
444
+ query->stmt,
445
+ parameters,
446
+ QUERY_MODE(QUERY_MULTI_ROW),
447
+ ALL_ROWS
448
+ );
449
+ return safe_batch_execute(&ctx);
450
+ }
451
+
452
+ /* call-seq:
453
+ * query.batch_query(sql, params_array) -> rows
454
+ * query.batch_query(sql, enumerable) -> rows
455
+ * query.batch_query(sql, callable) -> rows
456
+ * query.batch_query(sql, params_array) { |rows| ... } -> changes
457
+ * query.batch_query(sql, enumerable) { |rows| ... } -> changes
458
+ * query.batch_query(sql, callable) { |rows| ... } -> changes
459
+ *
460
+ * Executes the prepared query for each list of parameters in the given paramter
461
+ * source. If a block is given, it is called with the resulting rows for each
462
+ * invocation of the query, and the total number of changes is returned.
463
+ * Otherwise, an array containing the resulting rows for each invocation is
464
+ * returned.
465
+ *
466
+ * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
467
+ * records = [
468
+ * [1, 2],
469
+ * [3, 4]
470
+ * ]
471
+ * q.batch_query(records)
472
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
473
+ * *
474
+ * @param sql [String] query SQL
475
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
476
+ * @return [Array<Hash>, Integer] Total number of changes effected
477
+ */
478
+ VALUE Query_batch_query(VALUE self, VALUE parameters) {
386
479
  Query_t *query = self_to_query(self);
387
480
  if (query->closed) rb_raise(cError, "Query is closed");
388
481
 
389
482
  if (!query->stmt)
390
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
483
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
391
484
 
392
485
  query_ctx ctx = QUERY_CTX(
393
486
  self,
@@ -397,7 +490,95 @@ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
397
490
  QUERY_MODE(QUERY_MULTI_ROW),
398
491
  ALL_ROWS
399
492
  );
400
- return safe_execute_multi(&ctx);
493
+ return safe_batch_query(&ctx);
494
+ }
495
+
496
+ /* call-seq:
497
+ * query.batch_query_ary(sql, params_array) -> rows
498
+ * query.batch_query_ary(sql, enumerable) -> rows
499
+ * query.batch_query_ary(sql, callable) -> rows
500
+ * query.batch_query_ary(sql, params_array) { |rows| ... } -> changes
501
+ * query.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
502
+ * query.batch_query_ary(sql, callable) { |rows| ... } -> changes
503
+ *
504
+ * Executes the prepared query for each list of parameters in the given paramter
505
+ * source. If a block is given, it is called with the resulting rows for each
506
+ * invocation of the query, and the total number of changes is returned.
507
+ * Otherwise, an array containing the resulting rows for each invocation is
508
+ * returned. Rows are represented as arrays.
509
+ *
510
+ * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
511
+ * records = [
512
+ * [1, 2],
513
+ * [3, 4]
514
+ * ]
515
+ * q.batch_query_ary(records)
516
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
517
+ * *
518
+ * @param sql [String] query SQL
519
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
520
+ * @return [Array<Hash>, Integer] Total number of changes effected
521
+ */
522
+ VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
523
+ Query_t *query = self_to_query(self);
524
+ if (query->closed) rb_raise(cError, "Query is closed");
525
+
526
+ if (!query->stmt)
527
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
528
+
529
+ query_ctx ctx = QUERY_CTX(
530
+ self,
531
+ query->db_struct,
532
+ query->stmt,
533
+ parameters,
534
+ QUERY_MODE(QUERY_MULTI_ROW),
535
+ ALL_ROWS
536
+ );
537
+ return safe_batch_query_ary(&ctx);
538
+ }
539
+
540
+ /* call-seq:
541
+ * query.batch_query_single_column(sql, params_array) -> rows
542
+ * query.batch_query_single_column(sql, enumerable) -> rows
543
+ * query.batch_query_single_column(sql, callable) -> rows
544
+ * query.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
545
+ * query.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
546
+ * query.batch_query_single_column(sql, callable) { |rows| ... } -> changes
547
+ *
548
+ * Executes the prepared query for each list of parameters in the given paramter
549
+ * source. If a block is given, it is called with the resulting rows for each
550
+ * invocation of the query, and the total number of changes is returned.
551
+ * Otherwise, an array containing the resulting rows for each invocation is
552
+ * returned. Rows are represented as single values.
553
+ *
554
+ * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
555
+ * records = [
556
+ * [1, 2],
557
+ * [3, 4]
558
+ * ]
559
+ * q.batch_query_single_column(records)
560
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
561
+ * *
562
+ * @param sql [String] query SQL
563
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
564
+ * @return [Array<Hash>, Integer] Total number of changes effected
565
+ */
566
+ VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
567
+ Query_t *query = self_to_query(self);
568
+ if (query->closed) rb_raise(cError, "Query is closed");
569
+
570
+ if (!query->stmt)
571
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
572
+
573
+ query_ctx ctx = QUERY_CTX(
574
+ self,
575
+ query->db_struct,
576
+ query->stmt,
577
+ parameters,
578
+ QUERY_MODE(QUERY_MULTI_ROW),
579
+ ALL_ROWS
580
+ );
581
+ return safe_batch_query_single_column(&ctx);
401
582
  }
402
583
 
403
584
  /* Returns the database associated with the query.
@@ -431,6 +612,19 @@ VALUE Query_columns(VALUE self) {
431
612
  return Query_perform_next(self, ALL_ROWS, safe_query_columns);
432
613
  }
433
614
 
615
+ /* call-seq:
616
+ * query.clone -> copy
617
+ * query.dup -> copy
618
+ *
619
+ * Returns a new query instance for the same SQL as the original query.
620
+ *
621
+ * @return [Extralite::Query] copy of query
622
+ */
623
+ VALUE Query_clone(VALUE self) {
624
+ Query_t *query = self_to_query(self);
625
+ return rb_funcall(cQuery, ID_new, 2, query->db, query->sql);
626
+ }
627
+
434
628
  /* Closes the query. Attempting to run a closed query will raise an error.
435
629
  *
436
630
  * @return [Extralite::Query] self
@@ -475,7 +669,7 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
475
669
  if (query->closed) rb_raise(cError, "Query is closed");
476
670
 
477
671
  if (!query->stmt)
478
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
672
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
479
673
 
480
674
  int value = sqlite3_stmt_status(query->stmt, NUM2INT(op), RTEST(reset) ? 1 : 0);
481
675
  return INT2NUM(value);
@@ -509,8 +703,10 @@ void Init_ExtraliteQuery(void) {
509
703
  rb_define_method(cQuery, "close", Query_close, 0);
510
704
  rb_define_method(cQuery, "closed?", Query_closed_p, 0);
511
705
  rb_define_method(cQuery, "columns", Query_columns, 0);
706
+ rb_define_method(cQuery, "clone", Query_clone, 0);
512
707
  rb_define_method(cQuery, "database", Query_database, 0);
513
708
  rb_define_method(cQuery, "db", Query_database, 0);
709
+ rb_define_method(cQuery, "dup", Query_clone, 0);
514
710
 
515
711
  rb_define_method(cQuery, "each", Query_each_hash, 0);
516
712
  rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
@@ -519,7 +715,11 @@ void Init_ExtraliteQuery(void) {
519
715
 
520
716
  rb_define_method(cQuery, "eof?", Query_eof_p, 0);
521
717
  rb_define_method(cQuery, "execute", Query_execute, -1);
522
- rb_define_method(cQuery, "execute_multi", Query_execute_multi, 1);
718
+ rb_define_method(cQuery, "<<", Query_execute_chevrons, 1);
719
+ rb_define_method(cQuery, "batch_execute", Query_batch_execute, 1);
720
+ rb_define_method(cQuery, "batch_query", Query_batch_query, 1);
721
+ rb_define_method(cQuery, "batch_query_ary", Query_batch_query_ary, 1);
722
+ rb_define_method(cQuery, "batch_query_single_column", Query_batch_query_single_column, 1);
523
723
  rb_define_method(cQuery, "initialize", Query_initialize, 2);
524
724
  rb_define_method(cQuery, "inspect", Query_inspect, 0);
525
725
 
data/gemspec.rb CHANGED
@@ -16,7 +16,7 @@ def common_spec(s)
16
16
  s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
17
17
  s.extra_rdoc_files = ["README.md"]
18
18
  s.require_paths = ["lib"]
19
- s.required_ruby_version = '>= 2.7'
19
+ s.required_ruby_version = '>= 3.0'
20
20
 
21
21
  s.add_development_dependency 'rake-compiler', '1.1.6'
22
22
  s.add_development_dependency 'minitest', '5.15.0'
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.4'
3
+ VERSION = '2.6'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -33,16 +33,20 @@ module Extralite
33
33
  class Database
34
34
  # @!visibility private
35
35
  TABLES_SQL = <<~SQL
36
- SELECT name FROM sqlite_master
36
+ SELECT name FROM %<db>s.sqlite_master
37
37
  WHERE type ='table'
38
- AND name NOT LIKE 'sqlite_%';
38
+ AND name NOT LIKE 'sqlite_%%';
39
39
  SQL
40
40
 
41
- # Returns the list of currently defined tables.
41
+ alias_method :execute_multi, :batch_execute
42
+
43
+ # Returns the list of currently defined tables. If a database name is given,
44
+ # returns the list of tables for the relevant attached database.
42
45
  #
46
+ # @param db [String] name of attached database
43
47
  # @return [Array] list of tables
44
- def tables
45
- query_single_column(TABLES_SQL)
48
+ def tables(db = 'main')
49
+ query_single_column(format(TABLES_SQL, db: db))
46
50
  end
47
51
 
48
52
  # Gets or sets one or more pragmas:
@@ -56,6 +60,11 @@ module Extralite
56
60
  value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
57
61
  end
58
62
 
63
+ # Error class used to roll back a transaction without propagating an
64
+ # exception.
65
+ class Rollback < Error
66
+ end
67
+
59
68
  # Starts a transaction and runs the given block. If an exception is raised
60
69
  # in the block, the transaction is rolled back. Otherwise, the transaction
61
70
  # is commited after running the block.
@@ -72,13 +81,56 @@ module Extralite
72
81
 
73
82
  abort = false
74
83
  yield self
75
- rescue
84
+ rescue => e
76
85
  abort = true
77
- raise
86
+ raise unless e.is_a?(Rollback)
78
87
  ensure
79
88
  execute(abort ? 'rollback' : 'commit')
80
89
  end
81
90
 
91
+ # Creates a savepoint with the given name.
92
+ #
93
+ # @param name [String, Symbol] savepoint name
94
+ # @return [Extralite::Database] database
95
+ def savepoint(name)
96
+ execute "savepoint #{name}"
97
+ self
98
+ end
99
+
100
+ # Release a savepoint with the given name.
101
+ #
102
+ # @param name [String, Symbol] savepoint name
103
+ # @return [Extralite::Database] database
104
+ def release(name)
105
+ execute "release #{name}"
106
+ self
107
+ end
108
+
109
+ # Rolls back changes to a savepoint with the given name.
110
+ #
111
+ # @param name [String, Symbol] savepoint name
112
+ # @return [Extralite::Database] database
113
+ def rollback_to(name)
114
+ execute "rollback to #{name}"
115
+ self
116
+ end
117
+
118
+ # Rolls back the currently active transaction. This method should only be
119
+ # called from within a block passed to Database#transaction. This method
120
+ # raises a Extralite::Rollback exception, which will stop execution of the
121
+ # transaction block without propagating the exception.
122
+ #
123
+ # db.transaction do
124
+ # db.execute('insert into foo (42)')
125
+ # db.rollback!
126
+ # end
127
+ #
128
+ # @param name [String, Symbol] savepoint name
129
+ # @return [Extralite::Database] database
130
+ def rollback!
131
+ raise Rollback
132
+ end
133
+
82
134
  private
83
135
 
84
136
  def pragma_set(values)
@@ -87,7 +139,11 @@ module Extralite
87
139
  end
88
140
 
89
141
  def pragma_get(key)
90
- query("pragma #{key}")
142
+ query_single_value("pragma #{key}")
91
143
  end
92
144
  end
145
+
146
+ class Query
147
+ alias_method :execute_multi, :batch_execute
148
+ end
93
149
  end
data/test/helper.rb CHANGED
@@ -7,3 +7,11 @@ require 'minitest/autorun'
7
7
  puts "sqlite3 version: #{Extralite.sqlite3_version}"
8
8
 
9
9
  IS_LINUX = RUBY_PLATFORM =~ /linux/
10
+ SKIP_RACTOR_TESTS = !IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
11
+
12
+ module Minitest::Assertions
13
+ def assert_in_range exp_range, act
14
+ msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
15
+ assert exp_range.include?(act), msg
16
+ end
17
+ end
data/test/issue-54.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "./lib/extralite"
4
+
5
+ puts 'Connecting to database...'
6
+
7
+ connection_1 = Extralite::Database.new("test.sqlite3")
8
+ puts "#{connection_1} connected"
9
+ connection_2 = Extralite::Database.new("test.sqlite3")
10
+ connection_2.busy_timeout = 0
11
+ puts "#{connection_2} connected"
12
+
13
+ [connection_1, connection_2].each do |connection|
14
+ puts "#{connection} beginning transaction..."
15
+ connection.execute "begin immediate transaction"
16
+ end
17
+
18
+ [connection_1, connection_2].each do |connection|
19
+ puts "#{connection} rolling back transaction..."
20
+ connection.execute "rollback transaction"
21
+ end
data/test/issue-59.rb ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "./lib/extralite"
4
+ require "benchmark"
5
+ require "tempfile"
6
+ require "fileutils"
7
+
8
+ p sqlite_version: Extralite.sqlite3_version
9
+
10
+ N = (ENV['N'] || 1000).to_i
11
+ p N: N
12
+
13
+ fn1 = '/tmp/db1'
14
+ fn2 = '/tmp/db2'
15
+
16
+ FileUtils.rm(fn1) rescue nil
17
+ FileUtils.rm(fn2) rescue nil
18
+
19
+ p fn1: fn1
20
+ p fn2: fn2
21
+
22
+ db1 = Extralite::Database.new fn1
23
+ db1.execute "pragma journal_mode = wal;"
24
+ db1.transaction do
25
+ db1.execute "create table t1 ( a integer primary key, b text );"
26
+ values = N.times.map { |i| "#{i}-#{rand(1000)}" }
27
+ db1.execute_multi "insert into t1 ( b ) values ( ? );", values
28
+
29
+ p count: db1.query_single_value("select count(*) from t1")
30
+ p some_rows: db1.query("select * from t1 limit 5")
31
+ end
32
+
33
+ db2 = Extralite::Database.new fn2
34
+ db2.execute "pragma journal_mode = wal;"
35
+ db2.execute "attach '#{fn1}' as db1;"
36
+ db2.execute "create table t2 ( a integer primary key, b text );"
37
+
38
+ p main_tables: db2.tables
39
+ p db1_tables: db2.tables('db1')
40
+
41
+ overall = Benchmark.realtime do
42
+ t1 = Thread.new do
43
+ time1 = Benchmark.realtime do
44
+ db2.execute "create unique index db1.t1_b_unique on t1 (b);"
45
+ end
46
+ p({ indexing: time1 })
47
+ end
48
+
49
+ t2 = Thread.new do
50
+ time2 = Benchmark.realtime do
51
+ (N / 10000).times do |i|
52
+ values = 10000.times.map { |i| "#{i}-#{rand(1000)}" }
53
+ db2.transaction do
54
+ db2.execute_multi "insert into main.t2 ( b ) values ( ? );", values
55
+ end
56
+ end
57
+ end
58
+ p({ inserting: time2 })
59
+ p count_t2: db2.query_single_value("select count(*) from main.t2")
60
+ p some_rows_t2: db2.query("select * from main.t2 limit 5")
61
+ end
62
+
63
+ t1.join
64
+ t2.join
65
+ end
66
+
67
+ p({ overall: overall })
68
+
69
+ db1.close
70
+ db2.close
data/test/perf_ary.rb CHANGED
@@ -15,26 +15,25 @@ require 'fileutils'
15
15
  DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
16
16
  puts "DB_PATH = #{DB_PATH.inspect}"
17
17
 
18
+ $sqlite3_db = SQLite3::Database.new(DB_PATH)
19
+ $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
18
20
 
19
21
  def prepare_database(count)
20
22
  db = Extralite::Database.new(DB_PATH)
21
- db.query('create table if not exists foo ( a integer primary key, b text )')
22
- db.query('delete from foo')
23
- db.query('begin')
24
- count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
25
- db.query('commit')
26
- db.close
23
+ $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
24
+ $extralite_db.query('delete from foo')
25
+ $extralite_db.query('begin')
26
+ count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
27
+ $extralite_db.query('commit')
27
28
  end
28
29
 
29
30
  def sqlite3_run(count)
30
- db = SQLite3::Database.new(DB_PATH)
31
- results = db.execute('select * from foo')
31
+ results = $sqlite3_db.execute('select * from foo')
32
32
  raise unless results.size == count
33
33
  end
34
34
 
35
35
  def extralite_run(count)
36
- db = Extralite::Database.new(DB_PATH)
37
- results = db.query_ary('select * from foo')
36
+ results = $extralite_db.query('select * from foo')
38
37
  raise unless results.size == count
39
38
  end
40
39
 
@@ -43,12 +42,15 @@ end
43
42
 
44
43
  prepare_database(c)
45
44
 
46
- Benchmark.ips do |x|
47
- x.config(:time => 3, :warmup => 1)
45
+ bm = Benchmark.ips do |x|
46
+ x.config(:time => 5, :warmup => 2)
48
47
 
49
48
  x.report("sqlite3") { sqlite3_run(c) }
50
49
  x.report("extralite") { extralite_run(c) }
51
50
 
52
51
  x.compare!
53
52
  end
53
+ puts;
54
+ bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
55
+ puts;
54
56
  end