extralite 0.2 → 0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 407877ae6a216daacd70b87735171f77845fe57ec6a0b109a96e4cc31c00f97b
4
- data.tar.gz: 2273841ec089faac02d3e7901d0e9ffd91cf1bc95e47e0b7c912cfbb218f6f55
3
+ metadata.gz: 89b99ba7c6d26fc887574bf943f8e07f4c27f0c058a99723be73d7b64a90b3d5
4
+ data.tar.gz: 41c9bc62b91d78d0507d58b6a3e150608aae813b7b929410f23b0389a8b8c98f
5
5
  SHA512:
6
- metadata.gz: eed74e69024c0e714e1a1e8449e3e2503e8dde5e2ed807a64ce46cec079b58c9c7417014ac6a4600958e355e0218b0ccc63def43b1ba98c5d5fe0de11b4d4559
7
- data.tar.gz: dd20969cb233586cbaf998fda0d65cafe703d5cdaa3f1ff9377a9a679f7b9dd602d71525259cd459bfabddc6d2a4c01b330c59a673b23de6ed37abe9920b7aab
6
+ metadata.gz: 6c29b92468cdf3a2fe9f65088c579a901ff981b11c68d8dc32c99508c4ab7fae5a8d9a348e2310dcf18b0980edac9f3503889de76e097c6d27c6c3fb20480245
7
+ data.tar.gz: 49c703000e9285cff90dadd0d64f40690d96a6f6c59305021dca0561d41aff6fedcc64a4918ce16337acbb7537fd91c73f2b1d65953cdff8be591773844c3465
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- ## 0.2 2021-05-24
1
+ ## 0.6 2021-05-25
2
+
3
+ - Add more specific errors: `SQLError`, `BusyError`
4
+
5
+ ## 0.5 2021-05-25
6
+
7
+ - Implement `Database#query_single_row`
8
+
9
+ ## 0.4 2021-05-24
10
+
11
+ - Add support for loading extensions
12
+
13
+ ## 0.3 2021-05-24
14
+
15
+ - Add support for running multiple statements
16
+
17
+ ## 0.2 2021-05-23
2
18
 
3
19
  - Implement `Database#transaction_active?`
4
20
  - Add tests
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (0.2)
4
+ extralite (0.6)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -5,12 +5,13 @@ class with a minimal set of methods to interact with an SQLite3 database.
5
5
 
6
6
  ### Features
7
7
 
8
- - A variety of ways to get back query results: row as hash, row as array, single
9
- column, single value.
8
+ - A variety of methods for different data access patterns: row as hash, row as array, single
9
+ single row, single column, single value.
10
10
  - Iterate over records with a block, or collect records into an array.
11
11
  - Parameter binding.
12
12
  - Get last insert rowid.
13
13
  - Get number of rows changed by last query.
14
+ - Load extensions.
14
15
 
15
16
  ### Usage
16
17
 
@@ -34,6 +35,9 @@ db.query_ary('select 1, 2, 3') #=> [[1, 2, 3]]
34
35
  db.query_ary('select 1, 2, 3') { |r| p r }
35
36
  # [1, 2, 3]
36
37
 
38
+ # get a single row as a hash
39
+ db.query_single_row("select 1 as foo") #=> { :foo => 1 }
40
+
37
41
  # get single column query results as array of values
38
42
  db.query_single_column('select 42') #=> [42]
39
43
  # or iterate over results
@@ -3,6 +3,9 @@
3
3
  #include "../sqlite3/sqlite3.h"
4
4
 
5
5
  VALUE cError;
6
+ VALUE cSQLError;
7
+ VALUE cBusyError;
8
+ ID ID_STRIP;
6
9
 
7
10
  typedef struct Database_t {
8
11
  sqlite3 *sqlite3_db;
@@ -44,10 +47,14 @@ VALUE Database_initialize(VALUE self, VALUE path) {
44
47
 
45
48
  rc = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
46
49
  if (rc) {
47
- fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db->sqlite3_db));
48
50
  sqlite3_close(db->sqlite3_db);
49
- // TODO: raise error
50
- return Qfalse;
51
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
52
+ }
53
+
54
+ rc = sqlite3_enable_load_extension(db->sqlite3_db, 1);
55
+ if (rc) {
56
+ sqlite3_close(db->sqlite3_db);
57
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
51
58
  }
52
59
 
53
60
  return Qnil;
@@ -123,6 +130,41 @@ static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE colu
123
130
  return row;
124
131
  }
125
132
 
133
+ static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
134
+ VALUE row = rb_ary_new2(column_count);
135
+ for (int i = 0; i < column_count; i++) {
136
+ VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
137
+ rb_ary_push(row, value);
138
+ }
139
+ return row;
140
+ }
141
+
142
+ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
143
+ const char *rest = 0;
144
+ const char *ptr = RSTRING_PTR(sql);
145
+ const char *end = ptr + RSTRING_LEN(sql);
146
+ while (1) {
147
+ int rc = sqlite3_prepare(db, ptr, end - ptr, stmt, &rest);
148
+ if (rc) {
149
+ sqlite3_finalize(*stmt);
150
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
151
+ }
152
+
153
+ if (rest == end) return;
154
+
155
+ // perform current query, but discard its results
156
+ rc = sqlite3_step(*stmt);
157
+ sqlite3_finalize(*stmt);
158
+ switch (rc) {
159
+ case SQLITE_BUSY:
160
+ rb_raise(cBusyError, "Database is busy");
161
+ case SQLITE_ERROR:
162
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
163
+ }
164
+ ptr = rest;
165
+ }
166
+ }
167
+
126
168
  VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
127
169
  int rc;
128
170
  sqlite3_stmt* stmt;
@@ -135,16 +177,12 @@ VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
135
177
  VALUE sql;
136
178
 
137
179
  rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
138
- sql = argv[0];
139
- GetDatabase(self, db);
180
+ sql = rb_funcall(argv[0], ID_STRIP, 0);
181
+ if (RSTRING_LEN(sql) == 0) return Qnil;
140
182
 
141
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
142
- if (rc) {
143
- sqlite3_finalize(stmt);
144
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
145
- return Qnil;
146
- }
183
+ GetDatabase(self, db);
147
184
 
185
+ prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
148
186
  bind_all_parameters(stmt, argc, argv);
149
187
  column_count = sqlite3_column_count(stmt);
150
188
  column_names = get_column_names(stmt, column_count);
@@ -163,10 +201,10 @@ step:
163
201
  break;
164
202
  case SQLITE_BUSY:
165
203
  sqlite3_finalize(stmt);
166
- rb_raise(cError, "Database is busy");
204
+ rb_raise(cBusyError, "Database is busy");
167
205
  case SQLITE_ERROR:
168
206
  sqlite3_finalize(stmt);
169
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
207
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
170
208
  default:
171
209
  sqlite3_finalize(stmt);
172
210
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
@@ -179,15 +217,6 @@ step:
179
217
  return result;
180
218
  }
181
219
 
182
- static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
183
- VALUE row = rb_ary_new2(column_count);
184
- for (int i = 0; i < column_count; i++) {
185
- VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
186
- rb_ary_push(row, value);
187
- }
188
- return row;
189
- }
190
-
191
220
  VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
192
221
  int rc;
193
222
  sqlite3_stmt* stmt;
@@ -199,17 +228,11 @@ VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
199
228
  VALUE sql;
200
229
 
201
230
  rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
202
- sql = argv[0];
231
+ sql = rb_funcall(argv[0], ID_STRIP, 0);
232
+ if (RSTRING_LEN(sql) == 0) return Qnil;
203
233
  GetDatabase(self, db);
204
234
 
205
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
206
- if (rc) {
207
- fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
208
- sqlite3_finalize(stmt);
209
- // TODO: raise error
210
- return Qfalse;
211
- }
212
-
235
+ prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
213
236
  bind_all_parameters(stmt, argc, argv);
214
237
  column_count = sqlite3_column_count(stmt);
215
238
 
@@ -225,10 +248,13 @@ step:
225
248
  case SQLITE_DONE:
226
249
  break;
227
250
  case SQLITE_BUSY:
228
- rb_raise(cError, "Database is busy");
251
+ sqlite3_finalize(stmt);
252
+ rb_raise(cBusyError, "Database is busy");
229
253
  case SQLITE_ERROR:
230
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
254
+ sqlite3_finalize(stmt);
255
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
231
256
  default:
257
+ sqlite3_finalize(stmt);
232
258
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
233
259
  }
234
260
  sqlite3_finalize(stmt);
@@ -237,6 +263,47 @@ step:
237
263
  return result;
238
264
  }
239
265
 
266
+ VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
267
+ int rc;
268
+ sqlite3_stmt* stmt;
269
+ int column_count;
270
+ Database_t *db;
271
+ VALUE sql;
272
+ VALUE row = Qnil;
273
+ VALUE column_names;
274
+
275
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
276
+ sql = rb_funcall(argv[0], ID_STRIP, 0);
277
+ if (RSTRING_LEN(sql) == 0) return Qnil;
278
+
279
+ GetDatabase(self, db);
280
+
281
+ prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
282
+ bind_all_parameters(stmt, argc, argv);
283
+ column_count = sqlite3_column_count(stmt);
284
+ column_names = get_column_names(stmt, column_count);
285
+
286
+ rc = sqlite3_step(stmt);
287
+ switch (rc) {
288
+ case SQLITE_ROW:
289
+ row = row_to_hash(stmt, column_count, column_names);
290
+ break;
291
+ case SQLITE_DONE:
292
+ break;
293
+ case SQLITE_BUSY:
294
+ rb_raise(cBusyError, "Database is busy");
295
+ case SQLITE_ERROR:
296
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
297
+ default:
298
+ rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
299
+ }
300
+
301
+ sqlite3_finalize(stmt);
302
+ RB_GC_GUARD(row);
303
+ RB_GC_GUARD(column_names);
304
+ return row;
305
+ }
306
+
240
307
  VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
241
308
  int rc;
242
309
  sqlite3_stmt* stmt;
@@ -248,17 +315,12 @@ VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
248
315
  VALUE value;
249
316
 
250
317
  rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
251
- sql = argv[0];
252
- GetDatabase(self, db);
318
+ sql = rb_funcall(argv[0], ID_STRIP, 0);
319
+ if (RSTRING_LEN(sql) == 0) return Qnil;
253
320
 
254
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
255
- if (rc) {
256
- fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
257
- sqlite3_finalize(stmt);
258
- // TODO: raise error
259
- return Qfalse;
260
- }
321
+ GetDatabase(self, db);
261
322
 
323
+ prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
262
324
  bind_all_parameters(stmt, argc, argv);
263
325
  column_count = sqlite3_column_count(stmt);
264
326
  if (column_count != 1)
@@ -276,10 +338,13 @@ step:
276
338
  case SQLITE_DONE:
277
339
  break;
278
340
  case SQLITE_BUSY:
279
- rb_raise(cError, "Database is busy");
341
+ sqlite3_finalize(stmt);
342
+ rb_raise(cBusyError, "Database is busy");
280
343
  case SQLITE_ERROR:
281
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
344
+ sqlite3_finalize(stmt);
345
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
282
346
  default:
347
+ sqlite3_finalize(stmt);
283
348
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
284
349
  }
285
350
 
@@ -298,17 +363,12 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
298
363
  VALUE value = Qnil;
299
364
 
300
365
  rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
301
- sql = argv[0];
302
- GetDatabase(self, db);
366
+ sql = rb_funcall(argv[0], ID_STRIP, 0);
367
+ if (RSTRING_LEN(sql) == 0) return Qnil;
303
368
 
304
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
305
- if (rc) {
306
- fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
307
- sqlite3_finalize(stmt);
308
- // TODO: raise error
309
- return Qfalse;
310
- }
369
+ GetDatabase(self, db);
311
370
 
371
+ prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
312
372
  bind_all_parameters(stmt, argc, argv);
313
373
  column_count = sqlite3_column_count(stmt);
314
374
  if (column_count != 1)
@@ -319,10 +379,12 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
319
379
  case SQLITE_ROW:
320
380
  value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
321
381
  break;
382
+ case SQLITE_DONE:
383
+ break;
322
384
  case SQLITE_BUSY:
323
- rb_raise(cError, "Database is busy");
385
+ rb_raise(cBusyError, "Database is busy");
324
386
  case SQLITE_ERROR:
325
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
387
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
326
388
  default:
327
389
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
328
390
  }
@@ -365,6 +427,21 @@ VALUE Database_transaction_active_p(VALUE self) {
365
427
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
366
428
  }
367
429
 
430
+ VALUE Database_load_extension(VALUE self, VALUE path) {
431
+ Database_t *db;
432
+ GetDatabase(self, db);
433
+ char *err_msg;
434
+
435
+ int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
436
+ if (rc != SQLITE_OK) {
437
+ VALUE error = rb_exc_new2(cError, err_msg);
438
+ sqlite3_free(err_msg);
439
+ rb_exc_raise(error);
440
+ }
441
+
442
+ return self;
443
+ }
444
+
368
445
  void Init_Extralite() {
369
446
  VALUE mExtralite = rb_define_module("Extralite");
370
447
  VALUE cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
@@ -375,6 +452,7 @@ void Init_Extralite() {
375
452
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
376
453
  rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
377
454
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
455
+ rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
378
456
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
379
457
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
380
458
 
@@ -382,6 +460,11 @@ void Init_Extralite() {
382
460
  rb_define_method(cDatabase, "changes", Database_changes, 0);
383
461
  rb_define_method(cDatabase, "filename", Database_filename, -1);
384
462
  rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
463
+ rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
385
464
 
386
465
  cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
466
+ cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
467
+ cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
468
+
469
+ ID_STRIP = rb_intern("strip");
387
470
  }
data/extralite.gemspec CHANGED
@@ -11,8 +11,8 @@ 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://digital-fabric.github.io/extralite/",
15
- "homepage_uri" => "https://digital-fabric.github.io/extralite/",
14
+ "documentation_uri" => "https://github.com/digital-fabric/extralite",
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
  }
18
18
  s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '0.2'
2
+ VERSION = '0.6'
3
3
  end
@@ -19,26 +19,53 @@ class DatabaseTest < MiniTest::Test
19
19
  def test_query
20
20
  r = @db.query('select * from t')
21
21
  assert_equal [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}], r
22
+
23
+ r = @db.query('select * from t where x = 2')
24
+ assert_equal [], r
25
+ end
26
+
27
+ def test_invalid_query
28
+ assert_raises(Extralite::SQLError) { @db.query('blah') }
22
29
  end
23
30
 
24
31
  def test_query_hash
25
32
  r = @db.query_hash('select * from t')
26
33
  assert_equal [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}], r
34
+
35
+ r = @db.query_hash('select * from t where x = 2')
36
+ assert_equal [], r
27
37
  end
28
38
 
29
39
  def test_query_ary
30
40
  r = @db.query_ary('select * from t')
31
41
  assert_equal [[1, 2, 3], [4, 5, 6]], r
42
+
43
+ r = @db.query_ary('select * from t where x = 2')
44
+ assert_equal [], r
45
+ end
46
+
47
+ def test_query_single_row
48
+ r = @db.query_single_row('select * from t order by x desc limit 1')
49
+ assert_equal({ x: 4, y: 5, z: 6 }, r)
50
+
51
+ r = @db.query_single_row('select * from t where x = 2')
52
+ assert_nil r
32
53
  end
33
54
 
34
55
  def test_query_single_column
35
56
  r = @db.query_single_column('select y from t')
36
57
  assert_equal [2, 5], r
37
- end
58
+
59
+ r = @db.query_single_column('select y from t where x = 2')
60
+ assert_equal [], r
61
+ end
38
62
 
39
63
  def test_query_single_value
40
64
  r = @db.query_single_value('select z from t order by Z desc limit 1')
41
65
  assert_equal 6, r
66
+
67
+ r = @db.query_single_value('select z from t where x = 2')
68
+ assert_nil r
42
69
  end
43
70
 
44
71
  def test_transaction_active?
@@ -48,4 +75,18 @@ class DatabaseTest < MiniTest::Test
48
75
  @db.query('rollback')
49
76
  assert_equal false, @db.transaction_active?
50
77
  end
78
+
79
+ def test_multiple_statements
80
+ @db.query("insert into t values ('a', 'b', 'c'); insert into t values ('d', 'e', 'f');")
81
+
82
+ assert_equal [1, 4, 'a', 'd'], @db.query_single_column('select x from t order by x')
83
+ end
84
+
85
+ def test_empty_sql
86
+ r = @db.query(' ')
87
+ assert_nil r
88
+
89
+ r = @db.query('select 1 as foo; ')
90
+ assert_equal [{ foo: 1 }], r
91
+ end
51
92
  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: '0.2'
4
+ version: '0.6'
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-05-23 00:00:00.000000000 Z
11
+ date: 2021-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -125,8 +125,8 @@ licenses:
125
125
  - MIT
126
126
  metadata:
127
127
  source_code_uri: https://github.com/digital-fabric/extralite
128
- documentation_uri: https://digital-fabric.github.io/extralite/
129
- homepage_uri: https://digital-fabric.github.io/extralite/
128
+ documentation_uri: https://github.com/digital-fabric/extralite
129
+ homepage_uri: https://github.com/digital-fabric/extralite
130
130
  changelog_uri: https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md
131
131
  post_install_message:
132
132
  rdoc_options: