extralite 0.2 → 0.6

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: 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: