extralite 2.4 → 2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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