extralite-bundle 2.0 → 2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +20 -4
- data/ext/extralite/common.c +8 -1
- data/ext/extralite/database.c +119 -66
- data/ext/extralite/extralite.h +5 -1
- data/ext/extralite/iterator.c +33 -5
- data/ext/extralite/query.c +75 -20
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +0 -2
- data/lib/sequel/adapters/extralite.rb +5 -1
- data/test/test_database.rb +33 -0
- data/test/test_iterator.rb +18 -0
- data/test/test_query.rb +17 -0
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6366e2122d0ba28606033c9bb0b546946f60b822f4c002d0449aa6822e4ee3a
|
4
|
+
data.tar.gz: 21d410d22a27abf3ab8b23ca6d6da1422c9a0642eebba0c79de13a1d489b4123
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60d0af3f487d4c91989046928aef667ec26bb869e4345d6587c74fbb16c6690d0a721b4c6769d5f3f9cd207a3aa6c86e86d3f86fc07428c4df1614d67b93c2ba
|
7
|
+
data.tar.gz: c1c862e8eba40affb7c36d129292cbf554be2460918f6ddc143bad241d0de3d5dbefa1f7cca42746802646acd4b166fec90d8ce6de04bf350d214fbd87e92143
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# 2.2 2023-10-14
|
2
|
+
|
3
|
+
- Set correct encoding for strings values in query results (#27)
|
4
|
+
- Reset query after running it in Sequel adapter (#26)
|
5
|
+
|
6
|
+
# 2.1 2023-07-11
|
7
|
+
|
8
|
+
- Implement `Database#execute`, `Query#execute` for data-manipulation queries
|
9
|
+
- Add option for opening databases for read only access
|
10
|
+
|
1
11
|
# 2.0 2023-07-08
|
2
12
|
|
3
13
|
- Fix Sequel migrations (#8)
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
|
-
- Add option to open database in readonly mode, use sqlite3_open_v2
|
2
|
-
https://sqlite.org/c3ref/open.html
|
3
|
-
|
4
1
|
- Improve tracing
|
5
|
-
-
|
2
|
+
- Transactions and savepoints:
|
3
|
+
|
4
|
+
- `DB#transaction {}` - does a `BEGIN..COMMIT` - non-reentrant!
|
5
|
+
- `DB#savepoint(name)` - creates a savepoint
|
6
|
+
- `DB#release(name)` - releases a savepoint
|
7
|
+
- `DB#rollback` - raises `Extralite::Rollback`, which is rescued by `DB#transaction`
|
8
|
+
- `DB#rollback_to(name)` - rolls back to a savepoint
|
9
|
+
|
10
|
+
- More database methods:
|
11
|
+
|
12
|
+
- `Database#quote`
|
13
|
+
- `Database#busy_timeout=` https://sqlite.org/c3ref/busy_timeout.html
|
14
|
+
- `Database#cache_flush` https://sqlite.org/c3ref/db_cacheflush.html
|
15
|
+
- `Database#release_memory` https://sqlite.org/c3ref/db_release_memory.html
|
16
|
+
|
17
|
+
- Security
|
18
|
+
|
19
|
+
- Enable extension loading by using
|
20
|
+
[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION](https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension)
|
21
|
+
in order to prevent usage of `load_extension()` SQL function.
|
data/ext/extralite/common.c
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include "extralite.h"
|
3
3
|
|
4
|
+
rb_encoding *UTF8_ENCODING;
|
5
|
+
|
4
6
|
static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
5
7
|
switch (type) {
|
6
8
|
case SQLITE_NULL:
|
@@ -10,7 +12,7 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
10
12
|
case SQLITE_FLOAT:
|
11
13
|
return DBL2NUM(sqlite3_column_double(stmt, col));
|
12
14
|
case SQLITE_TEXT:
|
13
|
-
return
|
15
|
+
return rb_enc_str_new((char *)sqlite3_column_text(stmt, col), (long)sqlite3_column_bytes(stmt, col), UTF8_ENCODING);
|
14
16
|
case SQLITE_BLOB:
|
15
17
|
return rb_str_new((const char *)sqlite3_column_blob(stmt, col), (long)sqlite3_column_bytes(stmt, col));
|
16
18
|
default:
|
@@ -396,3 +398,8 @@ VALUE safe_execute_multi(query_ctx *ctx) {
|
|
396
398
|
VALUE safe_query_columns(query_ctx *ctx) {
|
397
399
|
return get_column_names(ctx->stmt, sqlite3_column_count(ctx->stmt));
|
398
400
|
}
|
401
|
+
|
402
|
+
VALUE safe_query_changes(query_ctx *ctx) {
|
403
|
+
while (stmt_iterate(ctx));
|
404
|
+
return INT2FIX(sqlite3_changes(ctx->sqlite3_db));
|
405
|
+
}
|
data/ext/extralite/database.c
CHANGED
@@ -6,6 +6,7 @@ VALUE cError;
|
|
6
6
|
VALUE cSQLError;
|
7
7
|
VALUE cBusyError;
|
8
8
|
VALUE cInterruptError;
|
9
|
+
VALUE eArgumentError;
|
9
10
|
|
10
11
|
ID ID_bind;
|
11
12
|
ID ID_call;
|
@@ -14,6 +15,8 @@ ID ID_new;
|
|
14
15
|
ID ID_strip;
|
15
16
|
ID ID_to_s;
|
16
17
|
|
18
|
+
VALUE SYM_read_only;
|
19
|
+
|
17
20
|
static size_t Database_size(const void *ptr) {
|
18
21
|
return sizeof(Database_t);
|
19
22
|
}
|
@@ -36,27 +39,21 @@ static VALUE Database_allocate(VALUE klass) {
|
|
36
39
|
return TypedData_Wrap_Struct(klass, &Database_type, db);
|
37
40
|
}
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
#define GetOpenDatabase(obj, database) { \
|
44
|
-
TypedData_Get_Struct((obj), Database_t, &Database_type, (database)); \
|
45
|
-
if (!(database)->sqlite3_db) { \
|
46
|
-
rb_raise(cError, "Database is closed"); \
|
47
|
-
} \
|
42
|
+
inline Database_t *self_to_database(VALUE self) {
|
43
|
+
Database_t *db;
|
44
|
+
TypedData_Get_Struct(self, Database_t, &Database_type, db);
|
45
|
+
return db;
|
48
46
|
}
|
49
47
|
|
50
|
-
Database_t *
|
51
|
-
Database_t *db;
|
52
|
-
|
48
|
+
inline Database_t *self_to_open_database(VALUE self) {
|
49
|
+
Database_t *db = self_to_database(self);
|
50
|
+
if (!(db)->sqlite3_db) rb_raise(cError, "Database is closed");
|
51
|
+
|
53
52
|
return db;
|
54
53
|
}
|
55
54
|
|
56
|
-
sqlite3 *Database_sqlite3_db(VALUE self) {
|
57
|
-
|
58
|
-
GetDatabase(self, db);
|
59
|
-
return db->sqlite3_db;
|
55
|
+
inline sqlite3 *Database_sqlite3_db(VALUE self) {
|
56
|
+
return self_to_database(self)->sqlite3_db;
|
60
57
|
}
|
61
58
|
|
62
59
|
/* call-seq:
|
@@ -69,18 +66,37 @@ VALUE Extralite_sqlite3_version(VALUE self) {
|
|
69
66
|
return rb_str_new_cstr(sqlite3_version);
|
70
67
|
}
|
71
68
|
|
72
|
-
|
73
|
-
|
69
|
+
static inline int db_open_flags_from_opts(VALUE opts) {
|
70
|
+
if (opts == Qnil) goto default_flags;
|
71
|
+
|
72
|
+
if (TYPE(opts) != T_HASH)
|
73
|
+
rb_raise(eArgumentError, "Expected hash as database initialization options");
|
74
|
+
|
75
|
+
VALUE read_only = rb_hash_aref(opts, SYM_read_only);
|
76
|
+
if (RTEST(read_only)) return SQLITE_OPEN_READONLY;
|
77
|
+
default_flags:
|
78
|
+
return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
79
|
+
}
|
80
|
+
|
81
|
+
/* Initializes a new SQLite database with the given path and options.
|
74
82
|
*
|
75
|
-
*
|
83
|
+
* @overload initialize(path)
|
84
|
+
* @param path [String] file path (or ':memory:' for memory database)
|
85
|
+
* @return [void]
|
86
|
+
* @overload initialize(path, read_only: false)
|
87
|
+
* @param path [String] file path (or ':memory:' for memory database)
|
88
|
+
* @param read_only [boolean] true for opening the database for reading only
|
89
|
+
* @return [void]
|
76
90
|
*/
|
91
|
+
VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
92
|
+
Database_t *db = self_to_database(self);
|
93
|
+
VALUE path;
|
94
|
+
VALUE opts = Qnil;
|
77
95
|
|
78
|
-
|
79
|
-
int
|
80
|
-
Database_t *db;
|
81
|
-
GetDatabase(self, db);
|
96
|
+
rb_scan_args(argc, argv, "11", &path, &opts);
|
97
|
+
int flags = db_open_flags_from_opts(opts);
|
82
98
|
|
83
|
-
rc =
|
99
|
+
int rc = sqlite3_open_v2(StringValueCStr(path), &db->sqlite3_db, flags, NULL);
|
84
100
|
if (rc) {
|
85
101
|
sqlite3_close_v2(db->sqlite3_db);
|
86
102
|
rb_raise(cError, "%s", sqlite3_errstr(rc));
|
@@ -106,6 +122,16 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
106
122
|
return Qnil;
|
107
123
|
}
|
108
124
|
|
125
|
+
/* Returns true if the database was open for read only access.
|
126
|
+
*
|
127
|
+
* @return [boolean] true if database is open for read only access
|
128
|
+
*/
|
129
|
+
VALUE Database_read_only_p(VALUE self) {
|
130
|
+
Database_t *db = self_to_database(self);
|
131
|
+
int open = sqlite3_db_readonly(db->sqlite3_db, "main");
|
132
|
+
return (open == 1) ? Qtrue : Qfalse;
|
133
|
+
}
|
134
|
+
|
109
135
|
/* call-seq:
|
110
136
|
* db.close -> db
|
111
137
|
*
|
@@ -113,8 +139,7 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
113
139
|
*/
|
114
140
|
VALUE Database_close(VALUE self) {
|
115
141
|
int rc;
|
116
|
-
Database_t *db;
|
117
|
-
GetDatabase(self, db);
|
142
|
+
Database_t *db = self_to_database(self);
|
118
143
|
|
119
144
|
rc = sqlite3_close_v2(db->sqlite3_db);
|
120
145
|
if (rc) {
|
@@ -133,14 +158,12 @@ VALUE Database_close(VALUE self) {
|
|
133
158
|
* @return [bool] is database closed
|
134
159
|
*/
|
135
160
|
VALUE Database_closed_p(VALUE self) {
|
136
|
-
Database_t *db;
|
137
|
-
GetDatabase(self, db);
|
138
|
-
|
161
|
+
Database_t *db = self_to_database(self);
|
139
162
|
return db->sqlite3_db ? Qfalse : Qtrue;
|
140
163
|
}
|
141
164
|
|
142
165
|
static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VALUE (*call)(query_ctx *)) {
|
143
|
-
Database_t *db;
|
166
|
+
Database_t *db = self_to_open_database(self);
|
144
167
|
sqlite3_stmt *stmt;
|
145
168
|
VALUE sql;
|
146
169
|
|
@@ -150,7 +173,6 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
150
173
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
151
174
|
|
152
175
|
// prepare query ctx
|
153
|
-
GetOpenDatabase(self, db);
|
154
176
|
if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
|
155
177
|
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
156
178
|
RB_GC_GUARD(sql);
|
@@ -286,6 +308,31 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
|
|
286
308
|
return Database_perform_query(argc, argv, self, safe_query_single_value);
|
287
309
|
}
|
288
310
|
|
311
|
+
/* call-seq:
|
312
|
+
* db.execute(sql, *parameters) -> changes
|
313
|
+
*
|
314
|
+
* Runs a query returning the total changes effected. This method should be used
|
315
|
+
* for data- or schema-manipulation queries.
|
316
|
+
*
|
317
|
+
* Query parameters to be bound to placeholders in the query can be specified as
|
318
|
+
* a list of values or as a hash mapping parameter names to values. When
|
319
|
+
* parameters are given as an array, the query should specify parameters using
|
320
|
+
* `?`:
|
321
|
+
*
|
322
|
+
* db.execute('update foo set x = ? where y = ?', 42, 43)
|
323
|
+
*
|
324
|
+
* Named placeholders are specified using `:`. The placeholder values are
|
325
|
+
* specified using a hash, where keys are either strings are symbols. String
|
326
|
+
* keys can include or omit the `:` prefix. The following are equivalent:
|
327
|
+
*
|
328
|
+
* db.execute('update foo set x = :bar', bar: 42)
|
329
|
+
* db.execute('update foo set x = :bar', 'bar' => 42)
|
330
|
+
* db.execute('update foo set x = :bar', ':bar' => 42)
|
331
|
+
*/
|
332
|
+
VALUE Database_execute(int argc, VALUE *argv, VALUE self) {
|
333
|
+
return Database_perform_query(argc, argv, self, safe_query_changes);
|
334
|
+
}
|
335
|
+
|
289
336
|
/* call-seq:
|
290
337
|
* db.execute_multi(sql, params_array) -> changes
|
291
338
|
*
|
@@ -301,13 +348,12 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
|
|
301
348
|
*
|
302
349
|
*/
|
303
350
|
VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
|
304
|
-
Database_t *db;
|
351
|
+
Database_t *db = self_to_open_database(self);
|
305
352
|
sqlite3_stmt *stmt;
|
306
353
|
|
307
354
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
308
355
|
|
309
356
|
// prepare query ctx
|
310
|
-
GetOpenDatabase(self, db);
|
311
357
|
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
312
358
|
query_ctx ctx = { self, db->sqlite3_db, stmt, params_array, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
|
313
359
|
|
@@ -329,8 +375,7 @@ VALUE Database_columns(VALUE self, VALUE sql) {
|
|
329
375
|
* Returns the rowid of the last inserted row.
|
330
376
|
*/
|
331
377
|
VALUE Database_last_insert_rowid(VALUE self) {
|
332
|
-
Database_t *db;
|
333
|
-
GetOpenDatabase(self, db);
|
378
|
+
Database_t *db = self_to_open_database(self);
|
334
379
|
|
335
380
|
return INT2FIX(sqlite3_last_insert_rowid(db->sqlite3_db));
|
336
381
|
}
|
@@ -341,8 +386,7 @@ VALUE Database_last_insert_rowid(VALUE self) {
|
|
341
386
|
* Returns the number of changes made to the database by the last operation.
|
342
387
|
*/
|
343
388
|
VALUE Database_changes(VALUE self) {
|
344
|
-
Database_t *db;
|
345
|
-
GetOpenDatabase(self, db);
|
389
|
+
Database_t *db = self_to_open_database(self);
|
346
390
|
|
347
391
|
return INT2FIX(sqlite3_changes(db->sqlite3_db));
|
348
392
|
}
|
@@ -355,8 +399,7 @@ VALUE Database_changes(VALUE self) {
|
|
355
399
|
VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
356
400
|
const char *db_name;
|
357
401
|
const char *filename;
|
358
|
-
Database_t *db;
|
359
|
-
GetOpenDatabase(self, db);
|
402
|
+
Database_t *db = self_to_open_database(self);
|
360
403
|
|
361
404
|
rb_check_arity(argc, 0, 1);
|
362
405
|
db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
|
@@ -370,8 +413,7 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
|
370
413
|
* Returns true if a transaction is currently in progress.
|
371
414
|
*/
|
372
415
|
VALUE Database_transaction_active_p(VALUE self) {
|
373
|
-
Database_t *db;
|
374
|
-
GetOpenDatabase(self, db);
|
416
|
+
Database_t *db = self_to_open_database(self);
|
375
417
|
|
376
418
|
return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
|
377
419
|
}
|
@@ -383,8 +425,7 @@ VALUE Database_transaction_active_p(VALUE self) {
|
|
383
425
|
* Loads an extension with the given path.
|
384
426
|
*/
|
385
427
|
VALUE Database_load_extension(VALUE self, VALUE path) {
|
386
|
-
Database_t *db;
|
387
|
-
GetOpenDatabase(self, db);
|
428
|
+
Database_t *db = self_to_open_database(self);
|
388
429
|
char *err_msg;
|
389
430
|
|
390
431
|
int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
|
@@ -422,8 +463,7 @@ VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
|
|
422
463
|
* For more information, consult the [sqlite3 API docs](https://sqlite.org/c3ref/interrupt.html).
|
423
464
|
*/
|
424
465
|
VALUE Database_interrupt(VALUE self) {
|
425
|
-
Database_t *db;
|
426
|
-
GetOpenDatabase(self, db);
|
466
|
+
Database_t *db = self_to_open_database(self);
|
427
467
|
|
428
468
|
sqlite3_interrupt(db->sqlite3_db);
|
429
469
|
return self;
|
@@ -515,8 +555,7 @@ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
|
515
555
|
|
516
556
|
int dst_is_fn = TYPE(dst) == T_STRING;
|
517
557
|
|
518
|
-
Database_t *src;
|
519
|
-
GetOpenDatabase(self, src);
|
558
|
+
Database_t *src = self_to_open_database(self);
|
520
559
|
sqlite3 *dst_db;
|
521
560
|
|
522
561
|
if (dst_is_fn) {
|
@@ -527,8 +566,7 @@ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
|
527
566
|
}
|
528
567
|
}
|
529
568
|
else {
|
530
|
-
Database_t *dst_struct;
|
531
|
-
GetOpenDatabase(dst, dst_struct);
|
569
|
+
Database_t *dst_struct = self_to_open_database(dst);
|
532
570
|
dst_db = dst_struct->sqlite3_db;
|
533
571
|
}
|
534
572
|
|
@@ -583,8 +621,7 @@ VALUE Database_status(int argc, VALUE *argv, VALUE self) {
|
|
583
621
|
|
584
622
|
rb_scan_args(argc, argv, "11", &op, &reset);
|
585
623
|
|
586
|
-
Database_t *db;
|
587
|
-
GetOpenDatabase(self, db);
|
624
|
+
Database_t *db = self_to_open_database(self);
|
588
625
|
|
589
626
|
int rc = sqlite3_db_status(db->sqlite3_db, NUM2INT(op), &cur, &hwm, RTEST(reset) ? 1 : 0);
|
590
627
|
if (rc != SQLITE_OK) rb_raise(cError, "%s", sqlite3_errstr(rc));
|
@@ -604,8 +641,7 @@ VALUE Database_limit(int argc, VALUE *argv, VALUE self) {
|
|
604
641
|
|
605
642
|
rb_scan_args(argc, argv, "11", &category, &new_value);
|
606
643
|
|
607
|
-
Database_t *db;
|
608
|
-
GetOpenDatabase(self, db);
|
644
|
+
Database_t *db = self_to_open_database(self);
|
609
645
|
|
610
646
|
int value = sqlite3_limit(db->sqlite3_db, NUM2INT(category), RTEST(new_value) ? NUM2INT(new_value) : -1);
|
611
647
|
|
@@ -622,8 +658,7 @@ VALUE Database_limit(int argc, VALUE *argv, VALUE self) {
|
|
622
658
|
* disable the busy timeout, set it to 0 or nil.
|
623
659
|
*/
|
624
660
|
VALUE Database_busy_timeout_set(VALUE self, VALUE sec) {
|
625
|
-
Database_t *db;
|
626
|
-
GetOpenDatabase(self, db);
|
661
|
+
Database_t *db = self_to_open_database(self);
|
627
662
|
|
628
663
|
int ms = (sec == Qnil) ? 0 : (int)(NUM2DBL(sec) * 1000);
|
629
664
|
int rc = sqlite3_busy_timeout(db->sqlite3_db, ms);
|
@@ -638,8 +673,7 @@ VALUE Database_busy_timeout_set(VALUE self, VALUE sec) {
|
|
638
673
|
* Returns the total number of changes made to the database since opening it.
|
639
674
|
*/
|
640
675
|
VALUE Database_total_changes(VALUE self) {
|
641
|
-
Database_t *db;
|
642
|
-
GetOpenDatabase(self, db);
|
676
|
+
Database_t *db = self_to_open_database(self);
|
643
677
|
|
644
678
|
int value = sqlite3_total_changes(db->sqlite3_db);
|
645
679
|
return INT2NUM(value);
|
@@ -653,8 +687,7 @@ VALUE Database_total_changes(VALUE self) {
|
|
653
687
|
* executed.
|
654
688
|
*/
|
655
689
|
VALUE Database_trace(VALUE self) {
|
656
|
-
Database_t *db;
|
657
|
-
GetOpenDatabase(self, db);
|
690
|
+
Database_t *db = self_to_open_database(self);
|
658
691
|
|
659
692
|
db->trace_block = rb_block_given_p() ? rb_block_proc() : Qnil;
|
660
693
|
return self;
|
@@ -666,8 +699,7 @@ VALUE Database_trace(VALUE self) {
|
|
666
699
|
* Returns the last error code for the database.
|
667
700
|
*/
|
668
701
|
VALUE Database_errcode(VALUE self) {
|
669
|
-
Database_t *db;
|
670
|
-
GetOpenDatabase(self, db);
|
702
|
+
Database_t *db = self_to_open_database(self);
|
671
703
|
|
672
704
|
return INT2NUM(sqlite3_errcode(db->sqlite3_db));
|
673
705
|
}
|
@@ -678,8 +710,7 @@ VALUE Database_errcode(VALUE self) {
|
|
678
710
|
* Returns the last error message for the database.
|
679
711
|
*/
|
680
712
|
VALUE Database_errmsg(VALUE self) {
|
681
|
-
Database_t *db;
|
682
|
-
GetOpenDatabase(self, db);
|
713
|
+
Database_t *db = self_to_open_database(self);
|
683
714
|
|
684
715
|
return rb_str_new2(sqlite3_errmsg(db->sqlite3_db));
|
685
716
|
}
|
@@ -691,13 +722,25 @@ VALUE Database_errmsg(VALUE self) {
|
|
691
722
|
* Returns the offset for the last error
|
692
723
|
*/
|
693
724
|
VALUE Database_error_offset(VALUE self) {
|
694
|
-
Database_t *db;
|
695
|
-
GetOpenDatabase(self, db);
|
725
|
+
Database_t *db = self_to_open_database(self);
|
696
726
|
|
697
727
|
return INT2NUM(sqlite3_error_offset(db->sqlite3_db));
|
698
728
|
}
|
699
729
|
#endif
|
700
730
|
|
731
|
+
/* Returns a short string representation of the database instance, including the
|
732
|
+
* database filename.
|
733
|
+
*
|
734
|
+
* @return [String] string representation
|
735
|
+
*/
|
736
|
+
VALUE Database_inspect(VALUE self) {
|
737
|
+
VALUE cname = rb_class_name(CLASS_OF(self));
|
738
|
+
VALUE filename = Database_filename(0, NULL, self);
|
739
|
+
if (RSTRING_LEN(filename) == 0) filename = rb_str_new_literal(":memory:");
|
740
|
+
|
741
|
+
return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, filename);
|
742
|
+
}
|
743
|
+
|
701
744
|
void Init_ExtraliteDatabase(void) {
|
702
745
|
VALUE mExtralite = rb_define_module("Extralite");
|
703
746
|
rb_define_singleton_method(mExtralite, "runtime_status", Extralite_runtime_status, -1);
|
@@ -719,9 +762,11 @@ void Init_ExtraliteDatabase(void) {
|
|
719
762
|
rb_define_method(cDatabase, "error_offset", Database_error_offset, 0);
|
720
763
|
#endif
|
721
764
|
|
765
|
+
rb_define_method(cDatabase, "execute", Database_execute, -1);
|
722
766
|
rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
|
723
767
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
724
|
-
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
768
|
+
rb_define_method(cDatabase, "initialize", Database_initialize, -1);
|
769
|
+
rb_define_method(cDatabase, "inspect", Database_inspect, 0);
|
725
770
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
726
771
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
727
772
|
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
@@ -732,6 +777,7 @@ void Init_ExtraliteDatabase(void) {
|
|
732
777
|
rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
|
733
778
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
734
779
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
780
|
+
rb_define_method(cDatabase, "read_only?", Database_read_only_p, 0);
|
735
781
|
rb_define_method(cDatabase, "status", Database_status, -1);
|
736
782
|
rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
|
737
783
|
rb_define_method(cDatabase, "trace", Database_trace, 0);
|
@@ -750,10 +796,17 @@ void Init_ExtraliteDatabase(void) {
|
|
750
796
|
rb_gc_register_mark_object(cBusyError);
|
751
797
|
rb_gc_register_mark_object(cInterruptError);
|
752
798
|
|
799
|
+
eArgumentError = rb_const_get(rb_cObject, rb_intern("ArgumentError"));
|
800
|
+
|
753
801
|
ID_bind = rb_intern("bind");
|
754
802
|
ID_call = rb_intern("call");
|
755
803
|
ID_keys = rb_intern("keys");
|
756
804
|
ID_new = rb_intern("new");
|
757
805
|
ID_strip = rb_intern("strip");
|
758
806
|
ID_to_s = rb_intern("to_s");
|
807
|
+
|
808
|
+
SYM_read_only = ID2SYM(rb_intern("read_only"));
|
809
|
+
rb_gc_register_mark_object(SYM_read_only);
|
810
|
+
|
811
|
+
UTF8_ENCODING = rb_utf8_encoding();
|
759
812
|
}
|
data/ext/extralite/extralite.h
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
#include "ruby.h"
|
5
5
|
#include "ruby/thread.h"
|
6
|
+
#include "ruby/encoding.h"
|
6
7
|
|
7
8
|
#ifdef EXTRALITE_NO_BUNDLE
|
8
9
|
#include <sqlite3.h>
|
@@ -92,8 +93,11 @@ typedef struct {
|
|
92
93
|
#define QUERY_MODE(default) (rb_block_given_p() ? QUERY_YIELD : default)
|
93
94
|
#define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
|
94
95
|
|
96
|
+
extern rb_encoding *UTF8_ENCODING;
|
97
|
+
|
95
98
|
VALUE safe_execute_multi(query_ctx *ctx);
|
96
99
|
VALUE safe_query_ary(query_ctx *ctx);
|
100
|
+
VALUE safe_query_changes(query_ctx *ctx);
|
97
101
|
VALUE safe_query_columns(query_ctx *ctx);
|
98
102
|
VALUE safe_query_hash(query_ctx *ctx);
|
99
103
|
VALUE safe_query_single_column(query_ctx *ctx);
|
@@ -120,6 +124,6 @@ int stmt_iterate(query_ctx *ctx);
|
|
120
124
|
VALUE cleanup_stmt(query_ctx *ctx);
|
121
125
|
|
122
126
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
123
|
-
Database_t *
|
127
|
+
Database_t *self_to_database(VALUE self);
|
124
128
|
|
125
129
|
#endif /* EXTRALITE_H */
|
data/ext/extralite/iterator.c
CHANGED
@@ -34,7 +34,7 @@ static VALUE Iterator_allocate(VALUE klass) {
|
|
34
34
|
return TypedData_Wrap_Struct(klass, &Iterator_type, iterator);
|
35
35
|
}
|
36
36
|
|
37
|
-
static inline Iterator_t *
|
37
|
+
static inline Iterator_t *self_to_iterator(VALUE obj) {
|
38
38
|
Iterator_t *iterator;
|
39
39
|
TypedData_Get_Struct((obj), Iterator_t, &Iterator_type, (iterator));
|
40
40
|
return iterator;
|
@@ -61,7 +61,7 @@ static inline enum iterator_mode symbol_to_mode(VALUE sym) {
|
|
61
61
|
* @return [void]
|
62
62
|
*/
|
63
63
|
VALUE Iterator_initialize(VALUE self, VALUE query, VALUE mode) {
|
64
|
-
Iterator_t *iterator =
|
64
|
+
Iterator_t *iterator = self_to_iterator(self);
|
65
65
|
|
66
66
|
iterator->query = query;
|
67
67
|
iterator->mode = symbol_to_mode(mode);
|
@@ -92,7 +92,7 @@ inline each_method mode_to_each_method(enum iterator_mode mode) {
|
|
92
92
|
*/
|
93
93
|
VALUE Iterator_each(VALUE self) {
|
94
94
|
if (rb_block_given_p()) {
|
95
|
-
Iterator_t *iterator =
|
95
|
+
Iterator_t *iterator = self_to_iterator(self);
|
96
96
|
each_method method = mode_to_each_method(iterator->mode);
|
97
97
|
method(iterator->query);
|
98
98
|
}
|
@@ -130,7 +130,7 @@ inline next_method mode_to_next_method(enum iterator_mode mode) {
|
|
130
130
|
* @return [Array, Extralite::Iterator] next rows or self if block is given
|
131
131
|
*/
|
132
132
|
VALUE Iterator_next(int argc, VALUE *argv, VALUE self) {
|
133
|
-
Iterator_t *iterator =
|
133
|
+
Iterator_t *iterator = self_to_iterator(self);
|
134
134
|
next_method method = mode_to_next_method(iterator->mode);
|
135
135
|
VALUE result = method(argc, argv, iterator->query);
|
136
136
|
|
@@ -156,11 +156,34 @@ inline to_a_method mode_to_to_a_method(enum iterator_mode mode) {
|
|
156
156
|
* @return [Array] array of query result set rows
|
157
157
|
*/
|
158
158
|
VALUE Iterator_to_a(VALUE self) {
|
159
|
-
Iterator_t *iterator =
|
159
|
+
Iterator_t *iterator = self_to_iterator(self);
|
160
160
|
to_a_method method = mode_to_to_a_method(iterator->mode);
|
161
161
|
return method(iterator->query);
|
162
162
|
}
|
163
163
|
|
164
|
+
inline VALUE mode_to_symbol(Iterator_t *iterator) {
|
165
|
+
switch (iterator->mode) {
|
166
|
+
case ITERATOR_ARY:
|
167
|
+
return SYM_ary;
|
168
|
+
case ITERATOR_SINGLE_COLUMN:
|
169
|
+
return SYM_single_column;
|
170
|
+
default:
|
171
|
+
return SYM_hash;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
/* Returns a short string representation of the iterator instance, including the
|
176
|
+
* SQL string.
|
177
|
+
*
|
178
|
+
* @return [String] string representation
|
179
|
+
*/
|
180
|
+
VALUE Iterator_inspect(VALUE self) {
|
181
|
+
VALUE cname = rb_class_name(CLASS_OF(self));
|
182
|
+
VALUE sym = mode_to_symbol(self_to_iterator(self));
|
183
|
+
|
184
|
+
return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sym);
|
185
|
+
}
|
186
|
+
|
164
187
|
void Init_ExtraliteIterator(void) {
|
165
188
|
VALUE mExtralite = rb_define_module("Extralite");
|
166
189
|
|
@@ -171,10 +194,15 @@ void Init_ExtraliteIterator(void) {
|
|
171
194
|
|
172
195
|
rb_define_method(cIterator, "initialize", Iterator_initialize, 2);
|
173
196
|
rb_define_method(cIterator, "each", Iterator_each, 0);
|
197
|
+
rb_define_method(cIterator, "inspect", Iterator_inspect, 0);
|
174
198
|
rb_define_method(cIterator, "next", Iterator_next, -1);
|
175
199
|
rb_define_method(cIterator, "to_a", Iterator_to_a, 0);
|
176
200
|
|
177
201
|
SYM_hash = ID2SYM(rb_intern("hash"));
|
178
202
|
SYM_ary = ID2SYM(rb_intern("ary"));
|
179
203
|
SYM_single_column = ID2SYM(rb_intern("single_column"));
|
204
|
+
|
205
|
+
rb_gc_register_mark_object(SYM_hash);
|
206
|
+
rb_gc_register_mark_object(SYM_ary);
|
207
|
+
rb_gc_register_mark_object(SYM_single_column);
|
180
208
|
}
|
data/ext/extralite/query.c
CHANGED
@@ -11,6 +11,9 @@
|
|
11
11
|
|
12
12
|
VALUE cQuery;
|
13
13
|
|
14
|
+
ID ID_inspect;
|
15
|
+
ID ID_slice;
|
16
|
+
|
14
17
|
static size_t Query_size(const void *ptr) {
|
15
18
|
return sizeof(Query_t);
|
16
19
|
}
|
@@ -42,7 +45,7 @@ static VALUE Query_allocate(VALUE klass) {
|
|
42
45
|
return TypedData_Wrap_Struct(klass, &Query_type, query);
|
43
46
|
}
|
44
47
|
|
45
|
-
static inline Query_t *
|
48
|
+
static inline Query_t *self_to_query(VALUE obj) {
|
46
49
|
Query_t *query;
|
47
50
|
TypedData_Get_Struct((obj), Query_t, &Query_type, (query));
|
48
51
|
return query;
|
@@ -58,14 +61,14 @@ static inline Query_t *value_to_query(VALUE obj) {
|
|
58
61
|
* @return [void]
|
59
62
|
*/
|
60
63
|
VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
|
61
|
-
Query_t *query =
|
64
|
+
Query_t *query = self_to_query(self);
|
62
65
|
|
63
66
|
sql = rb_funcall(sql, ID_strip, 0);
|
64
67
|
if (!RSTRING_LEN(sql))
|
65
68
|
rb_raise(cError, "Cannot prepare an empty SQL query");
|
66
69
|
|
67
70
|
query->db = db;
|
68
|
-
query->db_struct =
|
71
|
+
query->db_struct = self_to_database(db);
|
69
72
|
query->sqlite3_db = Database_sqlite3_db(db);
|
70
73
|
query->sql = sql;
|
71
74
|
query->stmt = NULL;
|
@@ -113,7 +116,7 @@ static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv)
|
|
113
116
|
* @return [Extralite::Query] self
|
114
117
|
*/
|
115
118
|
VALUE Query_reset(VALUE self) {
|
116
|
-
Query_t *query =
|
119
|
+
Query_t *query = self_to_query(self);
|
117
120
|
if (query->closed) rb_raise(cError, "Query is closed");
|
118
121
|
|
119
122
|
query_reset(query);
|
@@ -145,7 +148,7 @@ VALUE Query_reset(VALUE self) {
|
|
145
148
|
* @return [Extralite::Query] self
|
146
149
|
*/
|
147
150
|
VALUE Query_bind(int argc, VALUE *argv, VALUE self) {
|
148
|
-
Query_t *query =
|
151
|
+
Query_t *query = self_to_query(self);
|
149
152
|
if (query->closed) rb_raise(cError, "Query is closed");
|
150
153
|
|
151
154
|
query_reset_and_bind(query, argc, argv);
|
@@ -157,7 +160,7 @@ VALUE Query_bind(int argc, VALUE *argv, VALUE self) {
|
|
157
160
|
* @return [boolean] true if iteration has reached the end of the result set
|
158
161
|
*/
|
159
162
|
VALUE Query_eof_p(VALUE self) {
|
160
|
-
Query_t *query =
|
163
|
+
Query_t *query = self_to_query(self);
|
161
164
|
if (query->closed) rb_raise(cError, "Query is closed");
|
162
165
|
|
163
166
|
return query->eof ? Qtrue : Qfalse;
|
@@ -166,7 +169,7 @@ VALUE Query_eof_p(VALUE self) {
|
|
166
169
|
#define MAX_ROWS(max_rows) (max_rows == SINGLE_ROW ? 1 : max_rows)
|
167
170
|
|
168
171
|
static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(query_ctx *)) {
|
169
|
-
Query_t *query =
|
172
|
+
Query_t *query = self_to_query(self);
|
170
173
|
if (query->closed) rb_raise(cError, "Query is closed");
|
171
174
|
|
172
175
|
if (!query->stmt) query_reset(query);
|
@@ -265,7 +268,7 @@ VALUE Query_next_single_column(int argc, VALUE *argv, VALUE self) {
|
|
265
268
|
* @return [Array<Hash>] all rows
|
266
269
|
*/
|
267
270
|
VALUE Query_to_a_hash(VALUE self) {
|
268
|
-
Query_t *query =
|
271
|
+
Query_t *query = self_to_query(self);
|
269
272
|
query_reset(query);
|
270
273
|
return Query_perform_next(self, ALL_ROWS, safe_query_hash);
|
271
274
|
}
|
@@ -275,7 +278,7 @@ VALUE Query_to_a_hash(VALUE self) {
|
|
275
278
|
* @return [Array<Array>] all rows
|
276
279
|
*/
|
277
280
|
VALUE Query_to_a_ary(VALUE self) {
|
278
|
-
Query_t *query =
|
281
|
+
Query_t *query = self_to_query(self);
|
279
282
|
query_reset(query);
|
280
283
|
return Query_perform_next(self, ALL_ROWS, safe_query_ary);
|
281
284
|
}
|
@@ -286,7 +289,7 @@ VALUE Query_to_a_ary(VALUE self) {
|
|
286
289
|
* @return [Array<Object>] all rows
|
287
290
|
*/
|
288
291
|
VALUE Query_to_a_single_column(VALUE self) {
|
289
|
-
Query_t *query =
|
292
|
+
Query_t *query = self_to_query(self);
|
290
293
|
query_reset(query);
|
291
294
|
return Query_perform_next(self, ALL_ROWS, safe_query_single_column);
|
292
295
|
}
|
@@ -300,7 +303,7 @@ VALUE Query_to_a_single_column(VALUE self) {
|
|
300
303
|
VALUE Query_each_hash(VALUE self) {
|
301
304
|
if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_hash);
|
302
305
|
|
303
|
-
Query_t *query =
|
306
|
+
Query_t *query = self_to_query(self);
|
304
307
|
query_reset(query);
|
305
308
|
return Query_perform_next(self, ALL_ROWS, safe_query_hash);
|
306
309
|
}
|
@@ -314,7 +317,7 @@ VALUE Query_each_hash(VALUE self) {
|
|
314
317
|
VALUE Query_each_ary(VALUE self) {
|
315
318
|
if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_ary);
|
316
319
|
|
317
|
-
Query_t *query =
|
320
|
+
Query_t *query = self_to_query(self);
|
318
321
|
query_reset(query);
|
319
322
|
return Query_perform_next(self, ALL_ROWS, safe_query_ary);
|
320
323
|
}
|
@@ -329,11 +332,40 @@ VALUE Query_each_ary(VALUE self) {
|
|
329
332
|
VALUE Query_each_single_column(VALUE self) {
|
330
333
|
if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_single_column);
|
331
334
|
|
332
|
-
Query_t *query =
|
335
|
+
Query_t *query = self_to_query(self);
|
333
336
|
query_reset(query);
|
334
337
|
return Query_perform_next(self, ALL_ROWS, safe_query_single_column);
|
335
338
|
}
|
336
339
|
|
340
|
+
/* call-seq:
|
341
|
+
* query.execute(*parameters) -> changes
|
342
|
+
*
|
343
|
+
* Runs a query returning the total changes effected. This method should be used
|
344
|
+
* for data- or schema-manipulation queries.
|
345
|
+
*
|
346
|
+
* Query parameters to be bound to placeholders in the query can be specified as
|
347
|
+
* a list of values or as a hash mapping parameter names to values. When
|
348
|
+
* parameters are given as an array, the query should specify parameters using
|
349
|
+
* `?`:
|
350
|
+
*
|
351
|
+
* query = db.prepare('update foo set x = ? where y = ?')
|
352
|
+
* query.execute(42, 43)
|
353
|
+
*
|
354
|
+
* Named placeholders are specified using `:`. The placeholder values are
|
355
|
+
* specified using a hash, where keys are either strings are symbols. String
|
356
|
+
* keys can include or omit the `:` prefix. The following are equivalent:
|
357
|
+
*
|
358
|
+
* query = db.prepare('update foo set x = :bar')
|
359
|
+
* query.execute(bar: 42)
|
360
|
+
* query.execute('bar' => 42)
|
361
|
+
* query.execute(':bar' => 42)
|
362
|
+
*/
|
363
|
+
VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
|
364
|
+
Query_t *query = self_to_query(self);
|
365
|
+
query_reset_and_bind(query, argc, argv);
|
366
|
+
return Query_perform_next(self, ALL_ROWS, safe_query_changes);
|
367
|
+
}
|
368
|
+
|
337
369
|
/* Executes the query for each set of parameters in the given array. Parameters
|
338
370
|
* can be specified as either an array (for unnamed parameters) or a hash (for
|
339
371
|
* named parameters). Returns the number of changes effected. This method is
|
@@ -350,7 +382,7 @@ VALUE Query_each_single_column(VALUE self) {
|
|
350
382
|
* @return [Integer] number of changes effected
|
351
383
|
*/
|
352
384
|
VALUE Query_execute_multi(VALUE self, VALUE parameters) {
|
353
|
-
Query_t *query =
|
385
|
+
Query_t *query = self_to_query(self);
|
354
386
|
if (query->closed) rb_raise(cError, "Query is closed");
|
355
387
|
|
356
388
|
if (!query->stmt)
|
@@ -368,7 +400,7 @@ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
|
|
368
400
|
* @return [Extralite::Database] associated database
|
369
401
|
*/
|
370
402
|
VALUE Query_database(VALUE self) {
|
371
|
-
Query_t *query =
|
403
|
+
Query_t *query = self_to_query(self);
|
372
404
|
return query->db;
|
373
405
|
}
|
374
406
|
|
@@ -377,7 +409,7 @@ VALUE Query_database(VALUE self) {
|
|
377
409
|
* @return [String] SQL string
|
378
410
|
*/
|
379
411
|
VALUE Query_sql(VALUE self) {
|
380
|
-
Query_t *query =
|
412
|
+
Query_t *query = self_to_query(self);
|
381
413
|
return query->sql;
|
382
414
|
}
|
383
415
|
|
@@ -386,7 +418,7 @@ VALUE Query_sql(VALUE self) {
|
|
386
418
|
* @return [Array<Symbol>] column names
|
387
419
|
*/
|
388
420
|
VALUE Query_columns(VALUE self) {
|
389
|
-
Query_t *query =
|
421
|
+
Query_t *query = self_to_query(self);
|
390
422
|
query_reset(query);
|
391
423
|
return Query_perform_next(self, ALL_ROWS, safe_query_columns);
|
392
424
|
}
|
@@ -396,7 +428,7 @@ VALUE Query_columns(VALUE self) {
|
|
396
428
|
* @return [Extralite::Query] self
|
397
429
|
*/
|
398
430
|
VALUE Query_close(VALUE self) {
|
399
|
-
Query_t *query =
|
431
|
+
Query_t *query = self_to_query(self);
|
400
432
|
if (query->stmt) {
|
401
433
|
sqlite3_finalize(query->stmt);
|
402
434
|
query->stmt = NULL;
|
@@ -410,7 +442,7 @@ VALUE Query_close(VALUE self) {
|
|
410
442
|
* @return [boolean] true if query is closed
|
411
443
|
*/
|
412
444
|
VALUE Query_closed_p(VALUE self) {
|
413
|
-
Query_t *query =
|
445
|
+
Query_t *query = self_to_query(self);
|
414
446
|
return query->closed ? Qtrue : Qfalse;
|
415
447
|
}
|
416
448
|
|
@@ -431,7 +463,7 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
|
|
431
463
|
|
432
464
|
rb_scan_args(argc, argv, "11", &op, &reset);
|
433
465
|
|
434
|
-
Query_t *query =
|
466
|
+
Query_t *query = self_to_query(self);
|
435
467
|
if (query->closed) rb_raise(cError, "Query is closed");
|
436
468
|
|
437
469
|
if (!query->stmt)
|
@@ -441,6 +473,24 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
|
|
441
473
|
return INT2NUM(value);
|
442
474
|
}
|
443
475
|
|
476
|
+
/* Returns a short string representation of the query instance, including the
|
477
|
+
* SQL string.
|
478
|
+
*
|
479
|
+
* @return [String] string representation
|
480
|
+
*/
|
481
|
+
VALUE Query_inspect(VALUE self) {
|
482
|
+
VALUE cname = rb_class_name(CLASS_OF(self));
|
483
|
+
VALUE sql = self_to_query(self)->sql;
|
484
|
+
if (RSTRING_LEN(sql) > 48) {
|
485
|
+
sql = rb_funcall(sql, ID_slice, 2, INT2FIX(0), INT2FIX(45));
|
486
|
+
rb_str_cat2(sql, "...");
|
487
|
+
}
|
488
|
+
sql = rb_funcall(sql, ID_inspect, 0);
|
489
|
+
|
490
|
+
RB_GC_GUARD(sql);
|
491
|
+
return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sql);
|
492
|
+
}
|
493
|
+
|
444
494
|
void Init_ExtraliteQuery(void) {
|
445
495
|
VALUE mExtralite = rb_define_module("Extralite");
|
446
496
|
|
@@ -460,8 +510,10 @@ void Init_ExtraliteQuery(void) {
|
|
460
510
|
rb_define_method(cQuery, "each_single_column", Query_each_single_column, 0);
|
461
511
|
|
462
512
|
rb_define_method(cQuery, "eof?", Query_eof_p, 0);
|
513
|
+
rb_define_method(cQuery, "execute", Query_execute, -1);
|
463
514
|
rb_define_method(cQuery, "execute_multi", Query_execute_multi, 1);
|
464
515
|
rb_define_method(cQuery, "initialize", Query_initialize, 2);
|
516
|
+
rb_define_method(cQuery, "inspect", Query_inspect, 0);
|
465
517
|
|
466
518
|
rb_define_method(cQuery, "next", Query_next_hash, -1);
|
467
519
|
rb_define_method(cQuery, "next_ary", Query_next_ary, -1);
|
@@ -476,4 +528,7 @@ void Init_ExtraliteQuery(void) {
|
|
476
528
|
rb_define_method(cQuery, "to_a_ary", Query_to_a_ary, 0);
|
477
529
|
rb_define_method(cQuery, "to_a_hash", Query_to_a_hash, 0);
|
478
530
|
rb_define_method(cQuery, "to_a_single_column", Query_to_a_single_column, 0);
|
531
|
+
|
532
|
+
ID_inspect = rb_intern("inspect");
|
533
|
+
ID_slice = rb_intern("slice");
|
479
534
|
}
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -212,7 +212,11 @@ module Sequel
|
|
212
212
|
case type
|
213
213
|
when :select
|
214
214
|
query = conn.prepare(sql, args)
|
215
|
-
|
215
|
+
begin
|
216
|
+
log_connection_yield(sql, conn, log_args){block.call(query.each, query.columns)}
|
217
|
+
ensure
|
218
|
+
query.reset
|
219
|
+
end
|
216
220
|
when :insert
|
217
221
|
log_connection_yield(sql, conn, log_args){conn.query(sql, args)}
|
218
222
|
conn.last_insert_rowid
|
data/test/test_database.rb
CHANGED
@@ -206,6 +206,17 @@ end
|
|
206
206
|
assert_equal [{recursive_triggers: 1}], @db.pragma(:recursive_triggers)
|
207
207
|
end
|
208
208
|
|
209
|
+
def test_execute
|
210
|
+
changes = @db.execute('update t set x = 42')
|
211
|
+
assert_equal 2, changes
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_execute_with_params
|
215
|
+
changes = @db.execute('update t set x = ? where z = ?', 42, 6)
|
216
|
+
assert_equal 1, changes
|
217
|
+
assert_equal [[1, 2, 3], [42, 5, 6]], @db.query_ary('select * from t order by x')
|
218
|
+
end
|
219
|
+
|
209
220
|
def test_execute_multi
|
210
221
|
@db.query('create table foo (a, b, c)')
|
211
222
|
assert_equal [], @db.query('select * from foo')
|
@@ -361,6 +372,28 @@ end
|
|
361
372
|
query.next
|
362
373
|
@db.close
|
363
374
|
end
|
375
|
+
|
376
|
+
def test_read_only_database
|
377
|
+
db = Extralite::Database.new(':memory:')
|
378
|
+
db.query('create table foo (bar)')
|
379
|
+
assert_equal false, db.read_only?
|
380
|
+
|
381
|
+
db = Extralite::Database.new(':memory:', read_only: true)
|
382
|
+
assert_raises(Extralite::Error) { db.query('create table foo (bar)') }
|
383
|
+
assert_equal true, db.read_only?
|
384
|
+
end
|
385
|
+
|
386
|
+
def test_database_inspect
|
387
|
+
db = Extralite::Database.new(':memory:')
|
388
|
+
assert_match /^\#\<Extralite::Database:0x[0-9a-f]+ :memory:\>$/, db.inspect
|
389
|
+
end
|
390
|
+
|
391
|
+
def test_string_encoding
|
392
|
+
db = Extralite::Database.new(':memory:')
|
393
|
+
v = db.query_single_value("select 'foo'")
|
394
|
+
assert_equal 'foo', v
|
395
|
+
assert_equal 'UTF-8', v.encoding.name
|
396
|
+
end
|
364
397
|
end
|
365
398
|
|
366
399
|
class ScenarioTest < MiniTest::Test
|
data/test/test_iterator.rb
CHANGED
@@ -96,4 +96,22 @@ class IteratorTest < MiniTest::Test
|
|
96
96
|
mapped = query.each_single_column.map { |v| v * 10 }
|
97
97
|
assert_equal [30, 60, 90], mapped
|
98
98
|
end
|
99
|
+
|
100
|
+
def test_iterator_inspect
|
101
|
+
i = @query.each_ary
|
102
|
+
assert_match /^\#\<Extralite::Iterator:0x[0-9a-f]+ ary\>$/, i.inspect
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_return_from_block_issue_26
|
106
|
+
db = Extralite::Database.new('/tmp/locked.db')
|
107
|
+
|
108
|
+
λ = ->(sql) {
|
109
|
+
db.prepare(sql).each { |r| r.each { |_, v| return v } }
|
110
|
+
}
|
111
|
+
|
112
|
+
20.times do |i|
|
113
|
+
λ.('DROP TABLE IF EXISTS `test1`')
|
114
|
+
λ.('CREATE TABLE `test1` (`_id` integer NOT NULL PRIMARY KEY AUTOINCREMENT) STRICT')
|
115
|
+
end
|
116
|
+
end
|
99
117
|
end
|
data/test/test_query.rb
CHANGED
@@ -430,6 +430,18 @@ class QueryTest < MiniTest::Test
|
|
430
430
|
assert_raises(Extralite::Error) { p.next }
|
431
431
|
end
|
432
432
|
|
433
|
+
def test_query_execute
|
434
|
+
q = @db.prepare('update t set x = 42')
|
435
|
+
assert_equal 3, q.execute
|
436
|
+
assert_equal [[42, 2, 3], [42, 5, 6], [42, 8, 9]], @db.query_ary('select * from t order by z')
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_query_execute_with_params
|
440
|
+
q = @db.prepare('update t set x = ? where z = ?')
|
441
|
+
assert_equal 1, q.execute(42, 9)
|
442
|
+
assert_equal [[1, 2, 3], [4, 5, 6], [42, 8, 9]], @db.query_ary('select * from t order by z')
|
443
|
+
end
|
444
|
+
|
433
445
|
def test_query_execute_multi
|
434
446
|
@db.query('create table foo (a, b, c)')
|
435
447
|
assert_equal [], @db.query('select * from foo')
|
@@ -499,4 +511,9 @@ class QueryTest < MiniTest::Test
|
|
499
511
|
assert_equal [1, 4, 7], query.to_a_single_column
|
500
512
|
assert_equal true, query.eof?
|
501
513
|
end
|
514
|
+
|
515
|
+
def test_query_inspect
|
516
|
+
q = @db.prepare('select x from t')
|
517
|
+
assert_match /^\#\<Extralite::Query:0x[0-9a-f]+ #{q.sql.inspect}\>$/, q.inspect
|
518
|
+
end
|
502
519
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extralite-bundle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '2.
|
4
|
+
version: '2.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - '='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 5.51.0
|
83
|
-
description:
|
83
|
+
description:
|
84
84
|
email: sharon@noteflakes.com
|
85
85
|
executables: []
|
86
86
|
extensions:
|
@@ -138,7 +138,7 @@ metadata:
|
|
138
138
|
documentation_uri: https://www.rubydoc.info/gems/extralite
|
139
139
|
homepage_uri: https://github.com/digital-fabric/extralite
|
140
140
|
changelog_uri: https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md
|
141
|
-
post_install_message:
|
141
|
+
post_install_message:
|
142
142
|
rdoc_options:
|
143
143
|
- "--title"
|
144
144
|
- extralite
|
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
158
|
version: '0'
|
159
159
|
requirements: []
|
160
160
|
rubygems_version: 3.4.1
|
161
|
-
signing_key:
|
161
|
+
signing_key:
|
162
162
|
specification_version: 4
|
163
163
|
summary: Extra-lightweight SQLite3 wrapper for Ruby with bundled SQLite3
|
164
164
|
test_files: []
|