extralite 0.6 → 1.0
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +37 -9
- data/ext/extralite/extralite.c +126 -152
- data/lib/extralite/version.rb +1 -1
- metadata +2 -3
- data/ext/sqlite3/sqlite3.h +0 -12264
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c69948d51f689ead3e4ccf008bc7b0670085d0c987d39359bb38172b9993b58
|
4
|
+
data.tar.gz: 776ed4e4f95cdb405b9b3be277d0aaa5a27fcd0e84aff8f6ebb3aeece531b1cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 472db7ae94e195e647712ce04213538b229bcfebc3f9ec22b3f8e69dceb0a5cbba9000e14147a27ed2e26231aa7c73b43585a45be369f94d8cd2507643dd88b9
|
7
|
+
data.tar.gz: 6475f99ebe990b9b073f77379f58ddcd4fa0f678fb32e64b39021e3aa7b6d6f9343c8f02d69cce1826a9d156c8b0918985536732af5a11be2bda28524524e8c2
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
## Extralite
|
2
2
|
|
3
|
-
Extralite is an extra-lightweight
|
4
|
-
class with a minimal set of methods to interact with
|
3
|
+
Extralite is an extra-lightweight (~365 lines of C-code) SQLite3 wrapper for
|
4
|
+
Ruby. It provides a single class with a minimal set of methods to interact with
|
5
|
+
an SQLite3 database.
|
5
6
|
|
6
7
|
### Features
|
7
8
|
|
8
|
-
- A variety of methods for different data access patterns: row as hash, row as
|
9
|
-
single row, single column, single value.
|
9
|
+
- A variety of methods for different data access patterns: row as hash, row as
|
10
|
+
array, single single row, single column, single value.
|
10
11
|
- Iterate over records with a block, or collect records into an array.
|
11
12
|
- Parameter binding.
|
13
|
+
- Correctly execute strings with multiple semicolon-separated queries (handy for
|
14
|
+
creating/modifying schemas).
|
12
15
|
- Get last insert rowid.
|
13
16
|
- Get number of rows changed by last query.
|
14
|
-
- Load extensions.
|
17
|
+
- Load extensions (loading of extensions is autmatically enabled. You can find
|
18
|
+
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
15
19
|
|
16
20
|
### Usage
|
17
21
|
|
@@ -19,7 +23,7 @@ class with a minimal set of methods to interact with an SQLite3 database.
|
|
19
23
|
require 'extralite'
|
20
24
|
|
21
25
|
# open a database
|
22
|
-
db = Extralite::Database.new('
|
26
|
+
db = Extralite::Database.new('/tmp/my.db')
|
23
27
|
|
24
28
|
# get query results as array of hashes
|
25
29
|
db.query('select 1 as foo') #=> [{ :foo => 1 }]
|
@@ -53,9 +57,33 @@ db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
|
53
57
|
# get last insert rowid
|
54
58
|
rowid = db.last_insert_id
|
55
59
|
|
56
|
-
# get rows changed in last query
|
57
|
-
|
60
|
+
# get number of rows changed in last query
|
61
|
+
number_of_rows_affected = db.changes
|
58
62
|
|
59
63
|
# get db filename
|
60
|
-
|
64
|
+
db.filename #=> "/tmp/my.db"
|
65
|
+
|
66
|
+
# load an extension
|
67
|
+
db.load_extension('/path/to/extension.so')
|
61
68
|
```
|
69
|
+
|
70
|
+
### Why not just use the sqlite3 gem?
|
71
|
+
|
72
|
+
The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
|
73
|
+
thousands of developers. I've been doing a lot of work with SQLite3 lately, and
|
74
|
+
wanted to have a simpler API that gives me query results in a variety of ways.
|
75
|
+
Thus extralite was born.
|
76
|
+
|
77
|
+
### What about concurrency?
|
78
|
+
|
79
|
+
Extralite currently does not release the GVL. This means that even if queries
|
80
|
+
are executed on a separate thread, no other Ruby threads will be scheduled while
|
81
|
+
SQLite3 is busy fetching the next record.
|
82
|
+
|
83
|
+
In the future Extralite might be changed to release the GVL each time
|
84
|
+
`sqlite3_step` is called.
|
85
|
+
|
86
|
+
### Can I use it with an ORM like ActiveRecord or Sequel?
|
87
|
+
|
88
|
+
Not yet, but you are welcome to contribute adapters for those projects. I will
|
89
|
+
be releasing my own not-an-ORM tool in the near future.
|
data/ext/extralite/extralite.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include "ruby.h"
|
3
|
-
#include
|
3
|
+
#include <sqlite3.h>
|
4
4
|
|
5
5
|
VALUE cError;
|
6
6
|
VALUE cSQLError;
|
@@ -165,235 +165,209 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
165
165
|
}
|
166
166
|
}
|
167
167
|
|
168
|
-
|
168
|
+
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
169
169
|
int rc;
|
170
|
-
sqlite3_stmt* stmt;
|
171
|
-
int column_count;
|
172
|
-
Database_t *db;
|
173
|
-
VALUE result = self;
|
174
|
-
int yield_to_block = rb_block_given_p();
|
175
|
-
VALUE row;
|
176
|
-
VALUE column_names;
|
177
|
-
VALUE sql;
|
178
|
-
|
179
|
-
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
180
|
-
sql = rb_funcall(argv[0], ID_STRIP, 0);
|
181
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
182
|
-
|
183
|
-
GetDatabase(self, db);
|
184
|
-
|
185
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
186
|
-
bind_all_parameters(stmt, argc, argv);
|
187
|
-
column_count = sqlite3_column_count(stmt);
|
188
|
-
column_names = get_column_names(stmt, column_count);
|
189
|
-
|
190
|
-
// block not given, so prepare the array of records to be returned
|
191
|
-
if (!yield_to_block) result = rb_ary_new();
|
192
|
-
|
193
|
-
step:
|
194
170
|
rc = sqlite3_step(stmt);
|
195
171
|
switch (rc) {
|
196
172
|
case SQLITE_ROW:
|
197
|
-
|
198
|
-
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
199
|
-
goto step;
|
173
|
+
return 1;
|
200
174
|
case SQLITE_DONE:
|
201
|
-
|
175
|
+
return 0;
|
202
176
|
case SQLITE_BUSY:
|
203
|
-
sqlite3_finalize(stmt);
|
204
177
|
rb_raise(cBusyError, "Database is busy");
|
205
178
|
case SQLITE_ERROR:
|
206
|
-
|
207
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
179
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
208
180
|
default:
|
209
|
-
sqlite3_finalize(stmt);
|
210
181
|
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
211
182
|
}
|
212
|
-
|
213
|
-
|
183
|
+
|
184
|
+
return 0;
|
185
|
+
}
|
186
|
+
|
187
|
+
typedef struct query_ctx {
|
188
|
+
VALUE self;
|
189
|
+
int argc;
|
190
|
+
VALUE *argv;
|
191
|
+
sqlite3_stmt *stmt;
|
192
|
+
} query_ctx;
|
193
|
+
|
194
|
+
VALUE cleanup_stmt(VALUE arg) {
|
195
|
+
query_ctx *ctx = (query_ctx *)arg;
|
196
|
+
sqlite3_finalize(ctx->stmt);
|
197
|
+
return Qnil;
|
198
|
+
}
|
199
|
+
|
200
|
+
#define check_arity_and_prepare_sql(argc, argv, sql) { \
|
201
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); \
|
202
|
+
sql = rb_funcall(argv[0], ID_STRIP, 0); \
|
203
|
+
if (RSTRING_LEN(sql) == 0) return Qnil; \
|
204
|
+
}
|
205
|
+
|
206
|
+
VALUE safe_query_hash(VALUE arg) {
|
207
|
+
query_ctx *ctx = (query_ctx *)arg;
|
208
|
+
Database_t *db;
|
209
|
+
VALUE result = ctx->self;
|
210
|
+
int yield_to_block = rb_block_given_p();
|
211
|
+
VALUE row;
|
212
|
+
VALUE sql;
|
213
|
+
int column_count;
|
214
|
+
VALUE column_names;
|
215
|
+
|
216
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
217
|
+
GetDatabase(ctx->self, db);
|
218
|
+
|
219
|
+
prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
|
220
|
+
bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
|
221
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
222
|
+
column_names = get_column_names(ctx->stmt, column_count);
|
223
|
+
|
224
|
+
// block not given, so prepare the array of records to be returned
|
225
|
+
if (!yield_to_block) result = rb_ary_new();
|
226
|
+
|
227
|
+
while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
|
228
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
229
|
+
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
230
|
+
}
|
231
|
+
|
214
232
|
RB_GC_GUARD(column_names);
|
215
233
|
RB_GC_GUARD(row);
|
216
234
|
RB_GC_GUARD(result);
|
217
235
|
return result;
|
218
236
|
}
|
219
237
|
|
220
|
-
VALUE
|
221
|
-
|
222
|
-
|
223
|
-
|
238
|
+
VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
|
239
|
+
query_ctx ctx = { self, argc, argv, 0 };
|
240
|
+
return rb_ensure(safe_query_hash, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
|
241
|
+
}
|
242
|
+
|
243
|
+
VALUE safe_query_ary(VALUE arg) {
|
244
|
+
query_ctx *ctx = (query_ctx *)arg;
|
224
245
|
Database_t *db;
|
225
|
-
|
246
|
+
int column_count;
|
247
|
+
VALUE result = ctx->self;
|
226
248
|
int yield_to_block = rb_block_given_p();
|
227
249
|
VALUE row;
|
228
250
|
VALUE sql;
|
229
251
|
|
230
|
-
|
231
|
-
|
232
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
233
|
-
GetDatabase(self, db);
|
252
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
253
|
+
GetDatabase(ctx->self, db);
|
234
254
|
|
235
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
236
|
-
bind_all_parameters(stmt, argc, argv);
|
237
|
-
column_count = sqlite3_column_count(stmt);
|
255
|
+
prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
|
256
|
+
bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
|
257
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
238
258
|
|
239
259
|
// block not given, so prepare the array of records to be returned
|
240
260
|
if (!yield_to_block) result = rb_ary_new();
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
row = row_to_ary(stmt, column_count);
|
246
|
-
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
247
|
-
goto step;
|
248
|
-
case SQLITE_DONE:
|
249
|
-
break;
|
250
|
-
case SQLITE_BUSY:
|
251
|
-
sqlite3_finalize(stmt);
|
252
|
-
rb_raise(cBusyError, "Database is busy");
|
253
|
-
case SQLITE_ERROR:
|
254
|
-
sqlite3_finalize(stmt);
|
255
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
256
|
-
default:
|
257
|
-
sqlite3_finalize(stmt);
|
258
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
261
|
+
|
262
|
+
while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
|
263
|
+
row = row_to_ary(ctx->stmt, column_count);
|
264
|
+
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
259
265
|
}
|
260
|
-
|
266
|
+
|
261
267
|
RB_GC_GUARD(row);
|
262
268
|
RB_GC_GUARD(result);
|
263
269
|
return result;
|
264
270
|
}
|
265
271
|
|
266
|
-
VALUE
|
267
|
-
|
268
|
-
|
269
|
-
|
272
|
+
VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
|
273
|
+
query_ctx ctx = { self, argc, argv, 0 };
|
274
|
+
return rb_ensure(safe_query_ary, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
|
275
|
+
}
|
276
|
+
|
277
|
+
VALUE safe_query_single_row(VALUE arg) {
|
278
|
+
query_ctx *ctx = (query_ctx *)arg;
|
270
279
|
Database_t *db;
|
280
|
+
int column_count;
|
271
281
|
VALUE sql;
|
272
282
|
VALUE row = Qnil;
|
273
283
|
VALUE column_names;
|
274
284
|
|
275
|
-
|
276
|
-
|
277
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
285
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
286
|
+
GetDatabase(ctx->self, db);
|
278
287
|
|
279
|
-
|
288
|
+
prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
|
289
|
+
bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
|
290
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
291
|
+
column_names = get_column_names(ctx->stmt, column_count);
|
280
292
|
|
281
|
-
|
282
|
-
|
283
|
-
column_count = sqlite3_column_count(stmt);
|
284
|
-
column_names = get_column_names(stmt, column_count);
|
293
|
+
if (stmt_iterate(ctx->stmt, db->sqlite3_db))
|
294
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
285
295
|
|
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
296
|
RB_GC_GUARD(row);
|
303
297
|
RB_GC_GUARD(column_names);
|
304
298
|
return row;
|
305
299
|
}
|
306
300
|
|
307
|
-
VALUE
|
308
|
-
|
309
|
-
|
301
|
+
VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
|
302
|
+
query_ctx ctx = { self, argc, argv, 0 };
|
303
|
+
return rb_ensure(safe_query_single_row, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
|
304
|
+
}
|
305
|
+
|
306
|
+
VALUE safe_query_single_column(VALUE arg) {
|
307
|
+
query_ctx *ctx = (query_ctx *)arg;
|
308
|
+
|
310
309
|
int column_count;
|
311
310
|
Database_t *db;
|
312
|
-
VALUE result = self;
|
311
|
+
VALUE result = ctx->self;
|
313
312
|
int yield_to_block = rb_block_given_p();
|
314
313
|
VALUE sql;
|
315
314
|
VALUE value;
|
316
315
|
|
317
|
-
|
318
|
-
|
319
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
316
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
317
|
+
GetDatabase(ctx->self, db);
|
320
318
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
bind_all_parameters(stmt, argc, argv);
|
325
|
-
column_count = sqlite3_column_count(stmt);
|
319
|
+
prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
|
320
|
+
bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
|
321
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
326
322
|
if (column_count != 1)
|
327
323
|
rb_raise(cError, "Expected query result to have 1 column");
|
328
324
|
|
329
325
|
// block not given, so prepare the array of records to be returned
|
330
326
|
if (!yield_to_block) result = rb_ary_new();
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
336
|
-
if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
|
337
|
-
goto step;
|
338
|
-
case SQLITE_DONE:
|
339
|
-
break;
|
340
|
-
case SQLITE_BUSY:
|
341
|
-
sqlite3_finalize(stmt);
|
342
|
-
rb_raise(cBusyError, "Database is busy");
|
343
|
-
case SQLITE_ERROR:
|
344
|
-
sqlite3_finalize(stmt);
|
345
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
346
|
-
default:
|
347
|
-
sqlite3_finalize(stmt);
|
348
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
327
|
+
|
328
|
+
while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
|
329
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
330
|
+
if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
|
349
331
|
}
|
350
332
|
|
351
|
-
sqlite3_finalize(stmt);
|
352
333
|
RB_GC_GUARD(value);
|
353
334
|
RB_GC_GUARD(result);
|
354
335
|
return result;
|
355
336
|
}
|
356
337
|
|
357
|
-
VALUE
|
358
|
-
|
359
|
-
|
338
|
+
VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
|
339
|
+
query_ctx ctx = { self, argc, argv, 0 };
|
340
|
+
return rb_ensure(safe_query_single_column, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
|
341
|
+
}
|
342
|
+
|
343
|
+
VALUE safe_query_single_value(VALUE arg) {
|
344
|
+
query_ctx *ctx = (query_ctx *)arg;
|
360
345
|
int column_count;
|
361
346
|
Database_t *db;
|
362
347
|
VALUE sql;
|
363
348
|
VALUE value = Qnil;
|
364
349
|
|
365
|
-
|
366
|
-
|
367
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
368
|
-
|
369
|
-
GetDatabase(self, db);
|
350
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
351
|
+
GetDatabase(ctx->self, db);
|
370
352
|
|
371
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
372
|
-
bind_all_parameters(stmt, argc, argv);
|
373
|
-
column_count = sqlite3_column_count(stmt);
|
353
|
+
prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
|
354
|
+
bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
|
355
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
374
356
|
if (column_count != 1)
|
375
357
|
rb_raise(cError, "Expected query result to have 1 column");
|
376
358
|
|
377
|
-
|
378
|
-
|
379
|
-
case SQLITE_ROW:
|
380
|
-
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
381
|
-
break;
|
382
|
-
case SQLITE_DONE:
|
383
|
-
break;
|
384
|
-
case SQLITE_BUSY:
|
385
|
-
rb_raise(cBusyError, "Database is busy");
|
386
|
-
case SQLITE_ERROR:
|
387
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
388
|
-
default:
|
389
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
390
|
-
}
|
359
|
+
if (stmt_iterate(ctx->stmt, db->sqlite3_db))
|
360
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
391
361
|
|
392
|
-
sqlite3_finalize(stmt);
|
393
362
|
RB_GC_GUARD(value);
|
394
363
|
return value;
|
395
364
|
}
|
396
365
|
|
366
|
+
VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
|
367
|
+
query_ctx ctx = { self, argc, argv, 0 };
|
368
|
+
return rb_ensure(safe_query_single_value, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
|
369
|
+
}
|
370
|
+
|
397
371
|
VALUE Database_last_insert_rowid(VALUE self) {
|
398
372
|
Database_t *db;
|
399
373
|
GetDatabase(self, db);
|