extralite 1.6 → 1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2924788672c7c3960a5baeaeacf14d297cfe461cfb1d20db828d7a67b5536c8
4
- data.tar.gz: fb4da5551c3b1ddca95ef05520eee477bfb7a29383b9c7e5f5c0638392eba3e9
3
+ metadata.gz: 874f2ebc573b23e4336b69409252b2cbb8c71c1275b1ca34d1d1924999d8ed31
4
+ data.tar.gz: 5b304d82a818c3e0da2dd72bca6fa590ff8be84241c6dc54fdb9f3bee49e498a
5
5
  SHA512:
6
- metadata.gz: 63b73c0d05cb294b75d79bef5c1362e2c422f16b0e7ae76c3d2b577af20f612007a4fe272942958b38d545efabe4d975c1a0475ee8dec4a68e957d61800ffca7
7
- data.tar.gz: b79c50cb5c696cf1eb505bbd126b197f1160f56af70e669cfc432e2e16d5423848c139ff9e942fb26c06c3d15f94e1a43f22ab4c317dae4fb0c2290f8d431aa6
6
+ metadata.gz: dd039c09306a04eb1e1cd42eb968c58cbf7693fd09c31587e9b7853a061c2a607af9d594859510f6cabc2ef5f166426a5a0ae4459b40168b10ea3fc4c6e3063f
7
+ data.tar.gz: 2b65161b0ec69a8072e8e2f343386d8178194d1dae3bfc2ac16e414f5682d2ca1da501057f1da38cf875238e0314aa35032dcdbbf3d610affe14fb572713435a
@@ -0,0 +1 @@
1
+ github: ciconia
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest]
11
- ruby: [2.6, 2.7, 3.0]
11
+ ruby: [2.6, 2.7, '3.0']
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
@@ -16,15 +16,10 @@ jobs:
16
16
  runs-on: ${{matrix.os}}
17
17
  steps:
18
18
  - uses: actions/checkout@v1
19
- - uses: actions/setup-ruby@v1
19
+ - uses: ruby/setup-ruby@v1
20
20
  with:
21
21
  ruby-version: ${{matrix.ruby}}
22
- - name: Install dependencies
23
- run: |
24
- gem install bundler
25
- bundle install
26
- - name: Show Linux kernel version
27
- run: uname -r
22
+ bundler-cache: true # 'bundle install' and cache
28
23
  - name: Compile C-extension
29
24
  run: bundle exec rake compile
30
25
  - name: Run tests
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 1.9 2021-12-15
2
+
3
+ - Add support for reading BLOBs
4
+
5
+ ## 1.8.2 2021-12-15
6
+
7
+ - Add documentation
8
+
9
+ ## 1.7 2021-12-13
10
+
11
+ - Add extralite Sequel adapter
12
+ - Add support for binding hash parameters
13
+
1
14
  ## 1.6 2021-12-13
2
15
 
3
16
  - Release GVL while fetching rows
data/Gemfile.lock CHANGED
@@ -1,58 +1,37 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.6)
4
+ extralite (1.9)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ast (2.4.2)
10
- coderay (1.1.3)
11
9
  docile (1.4.0)
12
10
  json (2.5.1)
13
- method_source (1.0.0)
14
- minitest (5.14.4)
15
- parallel (1.20.1)
16
- parser (3.0.1.1)
17
- ast (~> 2.4.1)
18
- pry (0.13.1)
19
- coderay (~> 1.1)
20
- method_source (~> 1.0)
21
- rainbow (3.0.0)
22
- rake (13.0.3)
23
- rake-compiler (1.1.1)
11
+ minitest (5.15.0)
12
+ rake (13.0.6)
13
+ rake-compiler (1.1.6)
24
14
  rake
25
- regexp_parser (2.1.1)
26
- rexml (3.2.5)
27
- rubocop (0.85.1)
28
- parallel (~> 1.10)
29
- parser (>= 2.7.0.1)
30
- rainbow (>= 2.2.2, < 4.0)
31
- regexp_parser (>= 1.7)
32
- rexml
33
- rubocop-ast (>= 0.0.3)
34
- ruby-progressbar (~> 1.7)
35
- unicode-display_width (>= 1.4.0, < 2.0)
36
- rubocop-ast (1.5.0)
37
- parser (>= 3.0.1.1)
38
- ruby-progressbar (1.11.0)
15
+ sequel (5.51.0)
39
16
  simplecov (0.17.1)
40
17
  docile (~> 1.1)
41
18
  json (>= 1.8, < 3)
42
19
  simplecov-html (~> 0.10.0)
43
20
  simplecov-html (0.10.2)
44
- unicode-display_width (1.7.0)
21
+ webrick (1.7.0)
22
+ yard (0.9.27)
23
+ webrick (~> 1.7.0)
45
24
 
46
25
  PLATFORMS
47
26
  ruby
48
27
 
49
28
  DEPENDENCIES
50
29
  extralite!
51
- minitest (= 5.14.4)
52
- pry (= 0.13.1)
53
- rake-compiler (= 1.1.1)
54
- rubocop (= 0.85.1)
30
+ minitest (= 5.15.0)
31
+ rake-compiler (= 1.1.6)
32
+ sequel (= 5.51.0)
55
33
  simplecov (= 0.17.1)
34
+ yard (= 0.9.27)
56
35
 
57
36
  BUNDLED WITH
58
37
  2.1.4
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Extralite - a Ruby gem for working with SQLite3 databases
1
+ # Extralite - a fast Ruby gem for working with SQLite3 databases
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/extralite.svg)](http://rubygems.org/gems/extralite)
4
4
  [![Modulation Test](https://github.com/digital-fabric/extralite/workflows/Tests/badge.svg)](https://github.com/digital-fabric/extralite/actions?query=workflow%3ATests)
@@ -6,9 +6,9 @@
6
6
 
7
7
  ## What is Extralite?
8
8
 
9
- Extralite is an extra-lightweight (less than 430 lines of C-code) SQLite3
10
- wrapper for Ruby. It provides a single class with a minimal set of methods to
11
- interact with an SQLite3 database.
9
+ Extralite is a fast, extra-lightweight (less than 460 lines of C-code) SQLite3
10
+ wrapper for Ruby. It provides a single class with a minimal set of methods for
11
+ interacting with an SQLite3 database.
12
12
 
13
13
  ## Features
14
14
 
@@ -17,8 +17,9 @@ interact with an SQLite3 database.
17
17
  - Super fast - [up to 12.5x faster](#performance) than the
18
18
  [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
19
19
  [comparison](#why-not-just-use-the-sqlite3-gem).)
20
- - Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
21
- released while preparing SQL statements and while iterating over results.
20
+ - Improved [concurrency](#what-about-concurrency) for multithreaded apps: the
21
+ Ruby GVL is released while preparing SQL statements and while iterating over
22
+ results.
22
23
  - Iterate over records with a block, or collect records into an array.
23
24
  - Parameter binding.
24
25
  - Automatically execute SQL strings containing multiple semicolon-separated
@@ -27,6 +28,7 @@ interact with an SQLite3 database.
27
28
  - Get number of rows changed by last query.
28
29
  - Load extensions (loading of extensions is autmatically enabled. You can find
29
30
  some useful extensions here: https://github.com/nalgeon/sqlean.)
31
+ - Includes a [Sequel adapter](#usage-with-sequel) (an ActiveRecord)
30
32
 
31
33
  ## Usage
32
34
 
@@ -65,8 +67,13 @@ db.query_single_value("select 'foo'") #=> "foo"
65
67
  # parameter binding (works for all query_xxx methods)
66
68
  db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
67
69
 
70
+ # parameter binding of named parameters
71
+ db.query('select * from foo where bar = :bar', bar: 42)
72
+ db.query('select * from foo where bar = :bar', 'bar' => 42)
73
+ db.query('select * from foo where bar = :bar', ':bar' => 42)
74
+
68
75
  # get last insert rowid
69
- rowid = db.last_insert_id
76
+ rowid = db.last_insert_rowid
70
77
 
71
78
  # get number of rows changed in last query
72
79
  number_of_rows_affected = db.changes
@@ -82,25 +89,45 @@ db.close
82
89
  db.closed? #=> true
83
90
  ```
84
91
 
92
+ ## Usage with Sequel
93
+
94
+ Extralite includes an adapter for
95
+ [Sequel](https://github.com/jeremyevans/sequel). To use the Extralite adapter,
96
+ just use the `extralite` scheme instead of `sqlite`:
97
+
98
+ ```ruby
99
+ DB = Sequel.connect('extralite:blog.db')
100
+ articles = DB[:articles]
101
+ p articles.to_a
102
+ ```
103
+
104
+ (Make sure you include `extralite` as a dependency in your `Gemfile`.)
105
+
85
106
  ## Why not just use the sqlite3 gem?
86
107
 
87
- The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
88
- thousands of developers. I've been doing a lot of work with SQLite3 databases
89
- lately, and wanted to have a simpler API that gives me query results in a
90
- variety of ways. Thus extralite was born.
108
+ The [sqlite3-ruby](https://github.com/sparklemotion/sqlite3-ruby) gem is a
109
+ popular, solid, well-maintained project, used by thousands of developers. I've
110
+ been doing a lot of work with SQLite3 databases lately, and wanted to have a
111
+ simpler API that gives me query results in a variety of ways. Thus extralite was
112
+ born.
113
+
114
+ Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
115
+ [thread-friendly](#what-about-concurrency). On the other hand, Extralite does
116
+ not have support for defining custom functions, aggregates and collations. If
117
+ you're using those features, you'll need to stick with sqlite3-ruby.
91
118
 
92
119
  Here's a table summarizing the differences between the two gems:
93
120
 
94
121
  | |sqlite3-ruby|Extralite|
95
122
  |-|-|-|
96
123
  |API design|multiple classes|single class|
97
- |Query results|row as hash, row as array, single row, single value|row as hash, row as array, single column, single row, single value|
124
+ |Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
98
125
  |execute multiple statements|separate API (#execute_batch)|integrated|
99
126
  |custom functions in Ruby|yes|no|
100
127
  |custom collations|yes|no|
101
128
  |custom aggregate functions|yes|no|
102
- |Multithread friendly|no|[yes](#concurrency)|
103
- |Code size|~2650LoC|~500LoC|
129
+ |Multithread friendly|no|[yes](#what-about-concurrency)|
130
+ |Code size|~2650LoC|~530LoC|
104
131
  |Performance|1x|1.5x to 12.5x (see [below](#performance))|
105
132
 
106
133
  ## What about concurrency?
@@ -113,27 +140,33 @@ performance:
113
140
 
114
141
  ## Performance
115
142
 
116
- A benchmark script is
117
- [included](https://github.com/digital-fabric/extralite/blob/main/test/perf.rb),
118
- creating a table of various row counts, then fetching the entire table using
119
- either `sqlite3` or `extralite`. This benchmark shows Extralite to be up to 12.5
120
- times faster than `sqlite3` when fetching a large number of rows. Here are the
121
- results (using the `sqlite3` gem performance as baseline):
143
+ A benchmark script is included, creating a table of various row counts, then
144
+ fetching the entire table using either `sqlite3` or `extralite`. This benchmark
145
+ shows Extralite to be up to 12.5 times faster than `sqlite3` when fetching a
146
+ large number of rows. Here are the [results for fetching rows as hashes](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb):
122
147
 
123
- |Row count|sqlite3-ruby (baseline)|Extralite (relative - rounded)|
124
- |-:|-:|-:|
125
- |10|1x|1.5x|
126
- |1K|1x|7x|
127
- |100K|1x|12.5x|
148
+ |Row count|sqlite3-ruby|Extralite|Advantage|
149
+ |-:|-:|-:|-:|
150
+ |10|57620 rows/s|95340 rows/s|__1.65x__|
151
+ |1K|286.8K rows/s|2106.4 rows/s|__7.35x__|
152
+ |100K|181K rows/s|2275.3K rows/s|__12.53x__|
128
153
 
129
- (If you're interested in checking this yourself, just run the script and let me
130
- know if your results are different.)
154
+ When [fetching rows as arrays](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb) Extralite also significantly outperforms sqlite3-ruby:
131
155
 
132
- ## Can I use it with an ORM like ActiveRecord or Sequel?
156
+ |Row count|sqlite3-ruby|Extralite|Advantage|
157
+ |-:|-:|-:|-:|
158
+ |10|64365 rows/s|94031 rows/s|__1.46x__|
159
+ |1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
160
+ |100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
161
+
162
+ (If you're interested in checking this yourself, just run the script and let me
163
+ know if your results are better/worse.)
133
164
 
134
- Not yet, but you are welcome to contribute adapters for those projects.
165
+ As those benchmarks show, Extralite is capabale of reading more than 3M
166
+ rows/second (when fetching rows as arrays), and more than 2.2M rows/second (when
167
+ fetching rows as hashes.)
135
168
 
136
169
  ## Contributing
137
170
 
138
171
  Contributions in the form of issues, PRs or comments will be greatly
139
- appreciated!
172
+ appreciated!
data/Rakefile CHANGED
@@ -1,18 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rake/clean"
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/clean'
5
5
 
6
- require "rake/extensiontask"
7
- Rake::ExtensionTask.new("extralite_ext") do |ext|
8
- ext.ext_dir = "ext/extralite"
6
+ require 'rake/extensiontask'
7
+ Rake::ExtensionTask.new('extralite_ext') do |ext|
8
+ ext.ext_dir = 'ext/extralite'
9
9
  end
10
10
 
11
11
  task :recompile => [:clean, :compile]
12
12
 
13
- task :default => [:compile, :test]
13
+ task :default => [:compile, :doc, :test]
14
+ task :doc => :yard
14
15
  task :test do
15
- exec 'ruby test/test_database.rb'
16
+ exec 'ruby test/run.rb'
16
17
  end
17
18
 
18
- CLEAN.include "**/*.o", "**/*.so", "**/*.so.*", "**/*.a", "**/*.bundle", "**/*.jar", "pkg", "tmp"
19
+ CLEAN.include '**/*.o', '**/*.so', '**/*.so.*', '**/*.a', '**/*.bundle', '**/*.jar', 'pkg', 'tmp'
20
+
21
+ require 'yard'
22
+ YARD_FILES = FileList['ext/extralite/extralite.c', 'lib/extralite.rb', 'lib/sequel/adapters/extralite.rb']
23
+
24
+ YARD::Rake::YardocTask.new do |t|
25
+ t.files = YARD_FILES
26
+ t.options = %w(-o doc --readme README.md)
27
+ end
@@ -6,7 +6,10 @@
6
6
  VALUE cError;
7
7
  VALUE cSQLError;
8
8
  VALUE cBusyError;
9
+
10
+ ID ID_KEYS;
9
11
  ID ID_STRIP;
12
+ ID ID_TO_S;
10
13
 
11
14
  typedef struct Database_t {
12
15
  sqlite3 *sqlite3_db;
@@ -48,6 +51,10 @@ static VALUE Database_allocate(VALUE klass) {
48
51
  } \
49
52
  }
50
53
 
54
+ /* call-seq: initialize(path)
55
+ *
56
+ * Initializes a new SQLite database with the given path.
57
+ */
51
58
 
52
59
  VALUE Database_initialize(VALUE self, VALUE path) {
53
60
  int rc;
@@ -69,6 +76,10 @@ VALUE Database_initialize(VALUE self, VALUE path) {
69
76
  return Qnil;
70
77
  }
71
78
 
79
+ /* call-seq: close
80
+ *
81
+ * Closes the database.
82
+ */
72
83
  VALUE Database_close(VALUE self) {
73
84
  int rc;
74
85
  Database_t *db;
@@ -83,6 +94,10 @@ VALUE Database_close(VALUE self) {
83
94
  return self;
84
95
  }
85
96
 
97
+ /* call-seq: closed?
98
+ *
99
+ * Returns true if the database is closed.
100
+ */
86
101
  VALUE Database_closed_p(VALUE self) {
87
102
  Database_t *db;
88
103
  GetDatabase(self, db);
@@ -101,7 +116,7 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
101
116
  case SQLITE_TEXT:
102
117
  return rb_str_new_cstr((char *)sqlite3_column_text(stmt, col));
103
118
  case SQLITE_BLOB:
104
- rb_raise(cError, "BLOB reading not yet implemented");
119
+ return rb_str_new((const char *)sqlite3_column_blob(stmt, col), (long)sqlite3_column_bytes(stmt, col));
105
120
  default:
106
121
  rb_raise(cError, "Unknown column type: %d", type);
107
122
  }
@@ -109,6 +124,33 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
109
124
  return Qnil;
110
125
  }
111
126
 
127
+ static void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
128
+
129
+ static inline void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
130
+ VALUE keys = rb_funcall(hash, ID_KEYS, 0);
131
+ int len = RARRAY_LEN(keys);
132
+ for (int i = 0; i < len; i++) {
133
+ VALUE k = RARRAY_AREF(keys, i);
134
+ VALUE v = rb_hash_aref(hash, k);
135
+
136
+ switch (TYPE(k)) {
137
+ case T_FIXNUM:
138
+ bind_parameter_value(stmt, NUM2INT(k), v);
139
+ return;
140
+ case T_SYMBOL:
141
+ k = rb_funcall(k, ID_TO_S, 0);
142
+ case T_STRING:
143
+ if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
144
+ int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
145
+ bind_parameter_value(stmt, pos, v);
146
+ return;
147
+ default:
148
+ rb_raise(cError, "Cannot bind hash key value idx %d", i);
149
+ }
150
+ }
151
+ RB_GC_GUARD(keys);
152
+ }
153
+
112
154
  static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
113
155
  switch (TYPE(value)) {
114
156
  case T_NIL:
@@ -129,6 +171,9 @@ static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value
129
171
  case T_STRING:
130
172
  sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
131
173
  return;
174
+ case T_HASH:
175
+ bind_hash_parameter_values(stmt, value);
176
+ return;
132
177
  default:
133
178
  rb_raise(cError, "Cannot bind parameter at position %d", pos);
134
179
  }
@@ -309,6 +354,29 @@ VALUE safe_query_hash(VALUE arg) {
309
354
  return result;
310
355
  }
311
356
 
357
+ /* call-seq:
358
+ * query(sql, *parameters, &block)
359
+ * query_hash(sql, *parameters, &block)
360
+ *
361
+ * Runs a query returning rows as hashes (with symbol keys). If a block is
362
+ * given, it will be called for each row. Otherwise, an array containing all
363
+ * rows is returned.
364
+ *
365
+ * Query parameters to be bound to placeholders in the query can be specified as
366
+ * a list of values or as a hash mapping parameter names to values. When
367
+ * parameters are given as a least, the query should specify parameters using
368
+ * `?`:
369
+ *
370
+ * db.query('select * from foo where x = ?', 42)
371
+ *
372
+ * Named placeholders are specified using `:`. The placeholder values are
373
+ * specified using a hash, where keys are either strings are symbols. String
374
+ * keys can include or omit the `:` prefix. The following are equivalent:
375
+ *
376
+ * db.query('select * from foo where x = :bar', bar: 42)
377
+ * db.query('select * from foo where x = :bar', 'bar' => 42)
378
+ * db.query('select * from foo where x = :bar', ':bar' => 42)
379
+ */
312
380
  VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
313
381
  query_ctx ctx = { self, argc, argv, 0 };
314
382
  return rb_ensure(safe_query_hash, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -343,6 +411,26 @@ VALUE safe_query_ary(VALUE arg) {
343
411
  return result;
344
412
  }
345
413
 
414
+ /* call-seq: query_ary(sql, *parameters, &block)
415
+ *
416
+ * Runs a query returning rows as arrays. If a block is given, it will be called
417
+ * for each row. Otherwise, an array containing all rows is returned.
418
+ *
419
+ * Query parameters to be bound to placeholders in the query can be specified as
420
+ * a list of values or as a hash mapping parameter names to values. When
421
+ * parameters are given as a least, the query should specify parameters using
422
+ * `?`:
423
+ *
424
+ * db.query_ary('select * from foo where x = ?', 42)
425
+ *
426
+ * Named placeholders are specified using `:`. The placeholder values are
427
+ * specified using a hash, where keys are either strings are symbols. String
428
+ * keys can include or omit the `:` prefix. The following are equivalent:
429
+ *
430
+ * db.query_ary('select * from foo where x = :bar', bar: 42)
431
+ * db.query_ary('select * from foo where x = :bar', 'bar' => 42)
432
+ * db.query_ary('select * from foo where x = :bar', ':bar' => 42)
433
+ */
346
434
  VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
347
435
  query_ctx ctx = { self, argc, argv, 0 };
348
436
  return rb_ensure(safe_query_ary, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -372,6 +460,25 @@ VALUE safe_query_single_row(VALUE arg) {
372
460
  return row;
373
461
  }
374
462
 
463
+ /* call-seq: query_single_row(sql, *parameters)
464
+ *
465
+ * Runs a query returning a single row as a hash.
466
+ *
467
+ * Query parameters to be bound to placeholders in the query can be specified as
468
+ * a list of values or as a hash mapping parameter names to values. When
469
+ * parameters are given as a least, the query should specify parameters using
470
+ * `?`:
471
+ *
472
+ * db.query_single_row('select * from foo where x = ?', 42)
473
+ *
474
+ * Named placeholders are specified using `:`. The placeholder values are
475
+ * specified using a hash, where keys are either strings are symbols. String
476
+ * keys can include or omit the `:` prefix. The following are equivalent:
477
+ *
478
+ * db.query_single_row('select * from foo where x = :bar', bar: 42)
479
+ * db.query_single_row('select * from foo where x = :bar', 'bar' => 42)
480
+ * db.query_single_row('select * from foo where x = :bar', ':bar' => 42)
481
+ */
375
482
  VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
376
483
  query_ctx ctx = { self, argc, argv, 0 };
377
484
  return rb_ensure(safe_query_single_row, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -409,6 +516,26 @@ VALUE safe_query_single_column(VALUE arg) {
409
516
  return result;
410
517
  }
411
518
 
519
+ /* call-seq: query_single_column(sql, *parameters, &block)
520
+ *
521
+ * Runs a query returning single column values. If a block is given, it will be called
522
+ * for each value. Otherwise, an array containing all values is returned.
523
+ *
524
+ * Query parameters to be bound to placeholders in the query can be specified as
525
+ * a list of values or as a hash mapping parameter names to values. When
526
+ * parameters are given as a least, the query should specify parameters using
527
+ * `?`:
528
+ *
529
+ * db.query_single_column('select x from foo where x = ?', 42)
530
+ *
531
+ * Named placeholders are specified using `:`. The placeholder values are
532
+ * specified using a hash, where keys are either strings are symbols. String
533
+ * keys can include or omit the `:` prefix. The following are equivalent:
534
+ *
535
+ * db.query_single_column('select x from foo where x = :bar', bar: 42)
536
+ * db.query_single_column('select x from foo where x = :bar', 'bar' => 42)
537
+ * db.query_single_column('select x from foo where x = :bar', ':bar' => 42)
538
+ */
412
539
  VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
413
540
  query_ctx ctx = { self, argc, argv, 0 };
414
541
  return rb_ensure(safe_query_single_column, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -437,11 +564,34 @@ VALUE safe_query_single_value(VALUE arg) {
437
564
  return value;
438
565
  }
439
566
 
567
+ /* call-seq: query_single_value(sql, *parameters)
568
+ *
569
+ * Runs a query returning a single value from the first row.
570
+ *
571
+ * Query parameters to be bound to placeholders in the query can be specified as
572
+ * a list of values or as a hash mapping parameter names to values. When
573
+ * parameters are given as a least, the query should specify parameters using
574
+ * `?`:
575
+ *
576
+ * db.query_single_value('select x from foo where x = ?', 42)
577
+ *
578
+ * Named placeholders are specified using `:`. The placeholder values are
579
+ * specified using a hash, where keys are either strings are symbols. String
580
+ * keys can include or omit the `:` prefix. The following are equivalent:
581
+ *
582
+ * db.query_single_value('select x from foo where x = :bar', bar: 42)
583
+ * db.query_single_value('select x from foo where x = :bar', 'bar' => 42)
584
+ * db.query_single_value('select x from foo where x = :bar', ':bar' => 42)
585
+ */
440
586
  VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
441
587
  query_ctx ctx = { self, argc, argv, 0 };
442
588
  return rb_ensure(safe_query_single_value, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
443
589
  }
444
590
 
591
+ /* call-seq: last_insert_rowid
592
+ *
593
+ * Returns the rowid of the last inserted row.
594
+ */
445
595
  VALUE Database_last_insert_rowid(VALUE self) {
446
596
  Database_t *db;
447
597
  GetOpenDatabase(self, db);
@@ -449,6 +599,10 @@ VALUE Database_last_insert_rowid(VALUE self) {
449
599
  return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
450
600
  }
451
601
 
602
+ /* call-seq: changes
603
+ *
604
+ * Returns the number of changes made to the database by the last operation.
605
+ */
452
606
  VALUE Database_changes(VALUE self) {
453
607
  Database_t *db;
454
608
  GetOpenDatabase(self, db);
@@ -456,6 +610,10 @@ VALUE Database_changes(VALUE self) {
456
610
  return INT2NUM(sqlite3_changes(db->sqlite3_db));
457
611
  }
458
612
 
613
+ /* call-seq: filename
614
+ *
615
+ * Returns the database filename.
616
+ */
459
617
  VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
460
618
  const char *db_name;
461
619
  const char *filename;
@@ -468,6 +626,10 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
468
626
  return filename ? rb_str_new_cstr(filename) : Qnil;
469
627
  }
470
628
 
629
+ /* call-seq: transaction_active?
630
+ *
631
+ * Returns true if a transaction is currently in progress.
632
+ */
471
633
  VALUE Database_transaction_active_p(VALUE self) {
472
634
  Database_t *db;
473
635
  GetOpenDatabase(self, db);
@@ -475,6 +637,10 @@ VALUE Database_transaction_active_p(VALUE self) {
475
637
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
476
638
  }
477
639
 
640
+ /* call-seq: load_extension(path)
641
+ *
642
+ * Loads an extension with the given path.
643
+ */
478
644
  VALUE Database_load_extension(VALUE self, VALUE path) {
479
645
  Database_t *db;
480
646
  GetOpenDatabase(self, db);
@@ -519,5 +685,7 @@ void Init_Extralite() {
519
685
  rb_gc_register_mark_object(cSQLError);
520
686
  rb_gc_register_mark_object(cBusyError);
521
687
 
522
- ID_STRIP = rb_intern("strip");
688
+ ID_KEYS = rb_intern("keys");
689
+ ID_STRIP = rb_intern("strip");
690
+ ID_TO_S = rb_intern("to_s");
523
691
  }
data/extralite.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.homepage = 'https://github.com/digital-fabric/extralite'
12
12
  s.metadata = {
13
13
  "source_code_uri" => "https://github.com/digital-fabric/extralite",
14
- "documentation_uri" => "https://github.com/digital-fabric/extralite",
14
+ "documentation_uri" => "https://www.rubydoc.info/gems/extralite",
15
15
  "homepage_uri" => "https://github.com/digital-fabric/extralite",
16
16
  "changelog_uri" => "https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md"
17
17
  }
@@ -21,9 +21,10 @@ Gem::Specification.new do |s|
21
21
  s.require_paths = ["lib"]
22
22
  s.required_ruby_version = '>= 2.6'
23
23
 
24
- s.add_development_dependency 'rake-compiler', '1.1.1'
25
- s.add_development_dependency 'minitest', '5.14.4'
24
+ s.add_development_dependency 'rake-compiler', '1.1.6'
25
+ s.add_development_dependency 'minitest', '5.15.0'
26
26
  s.add_development_dependency 'simplecov', '0.17.1'
27
- s.add_development_dependency 'rubocop', '0.85.1'
28
- s.add_development_dependency 'pry', '0.13.1'
27
+ s.add_development_dependency 'yard', '0.9.27'
28
+
29
+ s.add_development_dependency 'sequel', '5.51.0'
29
30
  end
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '1.6'
2
+ VERSION = '1.9'
3
3
  end
data/lib/extralite.rb CHANGED
@@ -1 +1,21 @@
1
1
  require_relative './extralite_ext'
2
+
3
+ # Extralite is a Ruby gem for working with SQLite databases
4
+ module Extralite
5
+ # A base class for Extralite exceptions
6
+ class Error < RuntimeError
7
+ end
8
+
9
+ # An exception representing an SQL error emitted by SQLite
10
+ class SQLError < Error
11
+ end
12
+
13
+ # An exception raised when an SQLite database is busy (locked by another
14
+ # thread or process)
15
+ class BusyError < Error
16
+ end
17
+
18
+ # An SQLite database
19
+ class Database
20
+ end
21
+ end
@@ -0,0 +1,380 @@
1
+ # frozen-string-literal: true
2
+
3
+ # This file was adapted from the SQLite adapter included in Sequel:
4
+ # https://github.com/jeremyevans/sequel
5
+ # (distributed under the MIT license)
6
+
7
+ require 'extralite'
8
+ require 'sequel/adapters/shared/sqlite'
9
+
10
+ module Sequel
11
+ module Extralite
12
+ FALSE_VALUES = (%w'0 false f no n'.each(&:freeze) + [0]).freeze
13
+
14
+ blob = Object.new
15
+ def blob.call(s)
16
+ Sequel::SQL::Blob.new(s.to_s)
17
+ end
18
+
19
+ boolean = Object.new
20
+ def boolean.call(s)
21
+ s = s.downcase if s.is_a?(String)
22
+ !FALSE_VALUES.include?(s)
23
+ end
24
+
25
+ date = Object.new
26
+ def date.call(s)
27
+ case s
28
+ when String
29
+ Sequel.string_to_date(s)
30
+ when Integer
31
+ Date.jd(s)
32
+ when Float
33
+ Date.jd(s.to_i)
34
+ else
35
+ raise Sequel::Error, "unhandled type when converting to date: #{s.inspect} (#{s.class.inspect})"
36
+ end
37
+ end
38
+
39
+ integer = Object.new
40
+ def integer.call(s)
41
+ s.to_i
42
+ end
43
+
44
+ float = Object.new
45
+ def float.call(s)
46
+ s.to_f
47
+ end
48
+
49
+ numeric = Object.new
50
+ def numeric.call(s)
51
+ s = s.to_s unless s.is_a?(String)
52
+ BigDecimal(s) rescue s
53
+ end
54
+
55
+ time = Object.new
56
+ def time.call(s)
57
+ case s
58
+ when String
59
+ Sequel.string_to_time(s)
60
+ when Integer
61
+ Sequel::SQLTime.create(s/3600, (s % 3600)/60, s % 60)
62
+ when Float
63
+ s, f = s.divmod(1)
64
+ Sequel::SQLTime.create(s/3600, (s % 3600)/60, s % 60, (f*1000000).round)
65
+ else
66
+ raise Sequel::Error, "unhandled type when converting to date: #{s.inspect} (#{s.class.inspect})"
67
+ end
68
+ end
69
+
70
+ # Hash with string keys and callable values for converting SQLite types.
71
+ SQLITE_TYPES = {}
72
+ {
73
+ %w'date' => date,
74
+ %w'time' => time,
75
+ %w'bit bool boolean' => boolean,
76
+ %w'integer smallint mediumint int bigint' => integer,
77
+ %w'numeric decimal money' => numeric,
78
+ %w'float double real dec fixed' + ['double precision'] => float,
79
+ %w'blob' => blob
80
+ }.each do |k,v|
81
+ k.each{|n| SQLITE_TYPES[n] = v}
82
+ end
83
+ SQLITE_TYPES.freeze
84
+
85
+ USE_EXTENDED_RESULT_CODES = false
86
+
87
+ class Database < Sequel::Database
88
+ include ::Sequel::SQLite::DatabaseMethods
89
+
90
+ set_adapter_scheme :extralite
91
+
92
+ # Mimic the file:// uri, by having 2 preceding slashes specify a relative
93
+ # path, and 3 preceding slashes specify an absolute path.
94
+ def self.uri_to_options(uri) # :nodoc:
95
+ { :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
96
+ end
97
+
98
+ private_class_method :uri_to_options
99
+
100
+ # The conversion procs to use for this database
101
+ attr_reader :conversion_procs
102
+
103
+ # Connect to the database. Since SQLite is a file based database,
104
+ # available options are limited:
105
+ #
106
+ # :database :: database name (filename or ':memory:' or file: URI)
107
+ # :readonly :: open database in read-only mode; useful for reading
108
+ # static data that you do not want to modify
109
+ # :timeout :: how long to wait for the database to be available if it
110
+ # is locked, given in milliseconds (default is 5000)
111
+ def connect(server)
112
+ opts = server_opts(server)
113
+ opts[:database] = ':memory:' if blank_object?(opts[:database])
114
+ # sqlite3_opts = {}
115
+ # sqlite3_opts[:readonly] = typecast_value_boolean(opts[:readonly]) if opts.has_key?(:readonly)
116
+ db = ::Extralite::Database.new(opts[:database].to_s)#, sqlite3_opts)
117
+ # db.busy_timeout(typecast_value_integer(opts.fetch(:timeout, 5000)))
118
+
119
+ # if USE_EXTENDED_RESULT_CODES
120
+ # db.extended_result_codes = true
121
+ # end
122
+
123
+ connection_pragmas.each{|s| log_connection_yield(s, db){db.query(s)}}
124
+
125
+ # class << db
126
+ # attr_reader :prepared_statements
127
+ # end
128
+ # db.instance_variable_set(:@prepared_statements, {})
129
+
130
+ db
131
+ end
132
+
133
+ # Disconnect given connections from the database.
134
+ def disconnect_connection(c)
135
+ # c.prepared_statements.each_value{|v| v.first.close}
136
+ c.close
137
+ end
138
+
139
+ # Run the given SQL with the given arguments and yield each row.
140
+ def execute(sql, opts=OPTS, &block)
141
+ _execute(:select, sql, opts, &block)
142
+ end
143
+
144
+ # Run the given SQL with the given arguments and return the number of changed rows.
145
+ def execute_dui(sql, opts=OPTS)
146
+ _execute(:update, sql, opts)
147
+ end
148
+
149
+ # Drop any prepared statements on the connection when executing DDL. This is because
150
+ # prepared statements lock the table in such a way that you can't drop or alter the
151
+ # table while a prepared statement that references it still exists.
152
+ # def execute_ddl(sql, opts=OPTS)
153
+ # synchronize(opts[:server]) do |conn|
154
+ # conn.prepared_statements.values.each{|cps, s| cps.close}
155
+ # conn.prepared_statements.clear
156
+ # super
157
+ # end
158
+ # end
159
+
160
+ def execute_insert(sql, opts=OPTS)
161
+ _execute(:insert, sql, opts)
162
+ end
163
+
164
+ def freeze
165
+ @conversion_procs.freeze
166
+ super
167
+ end
168
+
169
+ # Handle Integer and Float arguments, since SQLite can store timestamps as integers and floats.
170
+ def to_application_timestamp(s)
171
+ case s
172
+ when String
173
+ super
174
+ when Integer
175
+ super(Time.at(s).to_s)
176
+ when Float
177
+ super(DateTime.jd(s).to_s)
178
+ else
179
+ raise Sequel::Error, "unhandled type when converting to : #{s.inspect} (#{s.class.inspect})"
180
+ end
181
+ end
182
+
183
+ private
184
+
185
+ def adapter_initialize
186
+ @conversion_procs = SQLITE_TYPES.dup
187
+ @conversion_procs['datetime'] = @conversion_procs['timestamp'] = method(:to_application_timestamp)
188
+ set_integer_booleans
189
+ end
190
+
191
+ # Yield an available connection. Rescue any Extralite::Error and turn
192
+ # them into DatabaseErrors.
193
+ def _execute(type, sql, opts, &block)
194
+ begin
195
+ synchronize(opts[:server]) do |conn|
196
+ # return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
197
+ log_args = opts[:arguments]
198
+ args = {}
199
+ opts.fetch(:arguments, OPTS).each{|k, v| args[k] = prepared_statement_argument(v) }
200
+ case type
201
+ when :select
202
+ log_connection_yield(sql, conn, log_args){conn.query(sql, args, &block)}
203
+ when :insert
204
+ log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
205
+ conn.last_insert_rowid
206
+ when :update
207
+ log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
208
+ conn.changes
209
+ end
210
+ end
211
+ rescue ::Extralite::Error => e
212
+ raise_error(e)
213
+ end
214
+ end
215
+
216
+ # The SQLite adapter does not need the pool to convert exceptions.
217
+ # Also, force the max connections to 1 if a memory database is being
218
+ # used, as otherwise each connection gets a separate database.
219
+ def connection_pool_default_options
220
+ o = super.dup
221
+ # Default to only a single connection if a memory database is used,
222
+ # because otherwise each connection will get a separate database
223
+ o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
224
+ o
225
+ end
226
+
227
+ def prepared_statement_argument(arg)
228
+ case arg
229
+ when Date, DateTime, Time
230
+ literal(arg)[1...-1]
231
+ when SQL::Blob
232
+ arg.to_blob
233
+ when true, false
234
+ if integer_booleans
235
+ arg ? 1 : 0
236
+ else
237
+ literal(arg)[1...-1]
238
+ end
239
+ else
240
+ arg
241
+ end
242
+ end
243
+
244
+ # Execute a prepared statement on the database using the given name.
245
+ def execute_prepared_statement(conn, type, name, opts, &block)
246
+ ps = prepared_statement(name)
247
+ sql = ps.prepared_sql
248
+ args = opts[:arguments]
249
+ ps_args = {}
250
+ args.each{|k, v| ps_args[k] = prepared_statement_argument(v)}
251
+ if cpsa = conn.prepared_statements[name]
252
+ cps, cps_sql = cpsa
253
+ if cps_sql != sql
254
+ cps.close
255
+ cps = nil
256
+ end
257
+ end
258
+ unless cps
259
+ cps = log_connection_yield("PREPARE #{name}: #{sql}", conn){conn.prepare(sql)}
260
+ conn.prepared_statements[name] = [cps, sql]
261
+ end
262
+ log_sql = String.new
263
+ log_sql << "EXECUTE #{name}"
264
+ if ps.log_sql
265
+ log_sql << " ("
266
+ log_sql << sql
267
+ log_sql << ")"
268
+ end
269
+ if block
270
+ log_connection_yield(log_sql, conn, args){cps.execute(ps_args, &block)}
271
+ else
272
+ log_connection_yield(log_sql, conn, args){cps.execute!(ps_args){|r|}}
273
+ case type
274
+ when :insert
275
+ conn.last_insert_rowid
276
+ when :update
277
+ conn.changes
278
+ end
279
+ end
280
+ end
281
+
282
+ # # SQLite3 raises ArgumentError in addition to SQLite3::Exception in
283
+ # # some cases, such as operations on a closed database.
284
+ def database_error_classes
285
+ #[Extralite::Error, ArgumentError]
286
+ [::Extralite::Error]
287
+ end
288
+
289
+ def dataset_class_default
290
+ Dataset
291
+ end
292
+
293
+ if USE_EXTENDED_RESULT_CODES
294
+ # Support SQLite exception codes if ruby-sqlite3 supports them.
295
+ def sqlite_error_code(exception)
296
+ exception.code if exception.respond_to?(:code)
297
+ end
298
+ end
299
+ end
300
+
301
+ class Dataset < Sequel::Dataset
302
+ include ::Sequel::SQLite::DatasetMethods
303
+
304
+ module ArgumentMapper
305
+ include Sequel::Dataset::ArgumentMapper
306
+
307
+ protected
308
+
309
+ # Return a hash with the same values as the given hash,
310
+ # but with the keys converted to strings.
311
+ def map_to_prepared_args(hash)
312
+ args = {}
313
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
314
+ args
315
+ end
316
+
317
+ private
318
+
319
+ # SQLite uses a : before the name of the argument for named
320
+ # arguments.
321
+ def prepared_arg(k)
322
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
323
+ end
324
+ end
325
+
326
+ BindArgumentMethods = prepared_statements_module(:bind, ArgumentMapper)
327
+ PreparedStatementMethods = prepared_statements_module(:prepare, BindArgumentMethods)
328
+
329
+ def fetch_rows(sql, &block)
330
+ execute(sql, &block)
331
+ # execute(sql) do |result|
332
+ # cps = db.conversion_procs
333
+ # type_procs = result.types.map{|t| cps[base_type_name(t)]}
334
+ # j = -1
335
+ # cols = result.columns.map{|c| [output_identifier(c), type_procs[(j+=1)]]}
336
+ # self.columns = cols.map(&:first)
337
+ # max = cols.length
338
+ # result.each do |values|
339
+ # row = {}
340
+ # i = -1
341
+ # while (i += 1) < max
342
+ # name, type_proc = cols[i]
343
+ # v = values[i]
344
+ # if type_proc && v
345
+ # v = type_proc.call(v)
346
+ # end
347
+ # row[name] = v
348
+ # end
349
+ # yield row
350
+ # end
351
+ # end
352
+ end
353
+
354
+ private
355
+
356
+ # The base type name for a given type, without any parenthetical part.
357
+ def base_type_name(t)
358
+ (t =~ /^(.*?)\(/ ? $1 : t).downcase if t
359
+ end
360
+
361
+ # Quote the string using the adapter class method.
362
+ def literal_string_append(sql, v)
363
+ sql << "'" << v.gsub(/'/, "''") << "'"
364
+ end
365
+
366
+ def bound_variable_modules
367
+ [BindArgumentMethods]
368
+ end
369
+
370
+ def prepared_statement_modules
371
+ [PreparedStatementMethods]
372
+ end
373
+
374
+ # SQLite uses a : before the name of the argument as a placeholder.
375
+ def prepared_arg_placeholder
376
+ ':'
377
+ end
378
+ end
379
+ end
380
+ end
data/test/perf_ary.rb ADDED
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'sqlite3'
8
+ gem 'extralite', path: '..'
9
+ gem 'benchmark-ips'
10
+ end
11
+
12
+ require 'benchmark/ips'
13
+ require 'fileutils'
14
+
15
+ DB_PATH = '/tmp/extralite_sqlite3_perf.db'
16
+
17
+ def prepare_database(count)
18
+ FileUtils.rm(DB_PATH) rescue nil
19
+ db = Extralite::Database.new(DB_PATH)
20
+ db.query('create table foo ( a integer primary key, b text )')
21
+ db.query('begin')
22
+ count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
23
+ db.query('commit')
24
+ end
25
+
26
+ def sqlite3_run(count)
27
+ db = SQLite3::Database.new(DB_PATH)
28
+ results = db.execute('select * from foo')
29
+ raise unless results.size == count
30
+ end
31
+
32
+ def extralite_run(count)
33
+ db = Extralite::Database.new(DB_PATH)
34
+ results = db.query_ary('select * from foo')
35
+ raise unless results.size == count
36
+ end
37
+
38
+ [10, 1000, 100000].each do |c|
39
+ puts; puts; puts "Record count: #{c}"
40
+
41
+ prepare_database(c)
42
+
43
+ Benchmark.ips do |x|
44
+ x.config(:time => 3, :warmup => 1)
45
+
46
+ x.report("sqlite3") { sqlite3_run(c) }
47
+ x.report("extralite") { extralite_run(c) }
48
+
49
+ x.compare!
50
+ end
51
+ end
File without changes
data/test/run.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{__dir__}/test_*.rb").each do |path|
4
+ require(path)
5
+ end
@@ -111,6 +111,50 @@ end
111
111
 
112
112
  assert_raises(Extralite::Error) { @db.query_single_value('select 42') }
113
113
  end
114
+
115
+ def test_parameter_binding_simple
116
+ r = @db.query('select x, y, z from t where x = ?', 1)
117
+ assert_equal [{ x: 1, y: 2, z: 3 }], r
118
+
119
+ r = @db.query('select x, y, z from t where z = ?', 6)
120
+ assert_equal [{ x: 4, y: 5, z: 6 }], r
121
+ end
122
+
123
+ def test_parameter_binding_with_index
124
+ r = @db.query('select x, y, z from t where x = ?2', 0, 1)
125
+ assert_equal [{ x: 1, y: 2, z: 3 }], r
126
+
127
+ r = @db.query('select x, y, z from t where z = ?3', 3, 4, 6)
128
+ assert_equal [{ x: 4, y: 5, z: 6 }], r
129
+ end
130
+
131
+ def test_parameter_binding_with_name
132
+ r = @db.query('select x, y, z from t where x = :x', x: 1, y: 2)
133
+ assert_equal [{ x: 1, y: 2, z: 3 }], r
134
+
135
+ r = @db.query('select x, y, z from t where z = :zzz', 'zzz' => 6)
136
+ assert_equal [{ x: 4, y: 5, z: 6 }], r
137
+
138
+ r = @db.query('select x, y, z from t where z = :bazzz', ':bazzz' => 6)
139
+ assert_equal [{ x: 4, y: 5, z: 6 }], r
140
+ end
141
+
142
+ def test_value_casting
143
+ r = @db.query_single_value("select 'abc'")
144
+ assert_equal 'abc', r
145
+
146
+ r = @db.query_single_value('select 123')
147
+ assert_equal 123, r
148
+
149
+ r = @db.query_single_value('select 12.34')
150
+ assert_equal 12.34, r
151
+
152
+ r = @db.query_single_value('select zeroblob(4)')
153
+ assert_equal "\x00\x00\x00\x00", r
154
+
155
+ r = @db.query_single_value('select null')
156
+ assert_nil r
157
+ end
114
158
  end
115
159
 
116
160
  class ScenarioTest < MiniTest::Test
@@ -179,4 +223,3 @@ class ScenarioTest < MiniTest::Test
179
223
  assert_equal [1, 4, 7], result
180
224
  end
181
225
  end
182
-
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'sequel'
5
+
6
+ class SequelExtraliteTest < MiniTest::Test
7
+ def test_sequel
8
+ db = Sequel.connect('extralite::memory:')
9
+ db.create_table :items do
10
+ primary_key :id
11
+ String :name, unique: true, null: false
12
+ Float :price, null: false
13
+ end
14
+
15
+ items = db[:items]
16
+
17
+ items.insert(name: 'abc', price: 123)
18
+ items.insert(name: 'def', price: 456)
19
+ items.insert(name: 'ghi', price: 789)
20
+
21
+ assert_equal 3, items.count
22
+ assert_equal (123+456+789) / 3, items.avg(:price)
23
+ end
24
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extralite
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.6'
4
+ version: '1.9'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-13 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 1.1.1
19
+ version: 1.1.6
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 1.1.1
26
+ version: 1.1.6
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 5.14.4
33
+ version: 5.15.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 5.14.4
40
+ version: 5.15.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: simplecov
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -53,33 +53,33 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.17.1
55
55
  - !ruby/object:Gem::Dependency
56
- name: rubocop
56
+ name: yard
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 0.85.1
61
+ version: 0.9.27
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 0.85.1
68
+ version: 0.9.27
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry
70
+ name: sequel
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: 0.13.1
75
+ version: 5.51.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 0.13.1
82
+ version: 5.51.0
83
83
  description:
84
84
  email: sharon@noteflakes.com
85
85
  executables: []
@@ -88,6 +88,7 @@ extensions:
88
88
  extra_rdoc_files:
89
89
  - README.md
90
90
  files:
91
+ - ".github/FUNDING.yml"
91
92
  - ".github/workflows/test.yml"
92
93
  - ".gitignore"
93
94
  - CHANGELOG.md
@@ -103,15 +104,19 @@ files:
103
104
  - extralite.gemspec
104
105
  - lib/extralite.rb
105
106
  - lib/extralite/version.rb
107
+ - lib/sequel/adapters/extralite.rb
106
108
  - test/helper.rb
107
- - test/perf.rb
109
+ - test/perf_ary.rb
110
+ - test/perf_hash.rb
111
+ - test/run.rb
108
112
  - test/test_database.rb
113
+ - test/test_sequel.rb
109
114
  homepage: https://github.com/digital-fabric/extralite
110
115
  licenses:
111
116
  - MIT
112
117
  metadata:
113
118
  source_code_uri: https://github.com/digital-fabric/extralite
114
- documentation_uri: https://github.com/digital-fabric/extralite
119
+ documentation_uri: https://www.rubydoc.info/gems/extralite
115
120
  homepage_uri: https://github.com/digital-fabric/extralite
116
121
  changelog_uri: https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md
117
122
  post_install_message: