extralite 2.0 → 2.1

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: d399062a5652d11c0e969dfb74f6c998015980e8831e221d2aece7ddb6cfc83d
4
- data.tar.gz: '0759cd16b15124934c52d5e2a3f36620b165f5a205aaea707c3d957503038173'
3
+ metadata.gz: bd8cf56eff701ddf57586d6f7510d5959cdf81ff15e6a64c6ed8ff698f93b43b
4
+ data.tar.gz: 42000f8c83849577fd1672996969961357c5bbadb82d89d2168c74be7acfa303
5
5
  SHA512:
6
- metadata.gz: 62ad641bd7661d326c3ba83eff0e7ecf5befdd12d7267608e748d6a7cf586d96e9f870cd179989cabb230b822e9d245ff9d1ed8877632ca33c7577858a639ded
7
- data.tar.gz: 2984632a7bb790b354713396ac00856f0646c79aa22bb3a9bcd26995a127eb8cb86a5ca64efcbd91b1fcc8dd5a41f95e0bacefed7b5bacd477e9b689d79186a6
6
+ metadata.gz: 1159c965d8ce9bfade2c81de97b6ef311dcfdb84ce65eb85238aa6a23b29b786d31de2bb8f30f87a24a800b88d01475a7cb400b97245401d587bef365b140087
7
+ data.tar.gz: 74f975529a9efb7291524eaa4c285fadc6976377a607cd28e72cbd3010cbc9fd061264aabb9a5df1ce864f097ca330c9acd9dded62b8de7cc63c1217be88412a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 2.1 2023-07-11
2
+
3
+ - Implement `Database#execute`, `Query#execute` for data-manipulation queries
4
+ - Add option for opening databases for read only access
5
+
1
6
  # 2.0 2023-07-08
2
7
 
3
8
  - Fix Sequel migrations (#8)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (2.0)
4
+ extralite (2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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
- - Add `#inspect` method for `Database`, `Query`, `Iterator` classes
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.
@@ -396,3 +396,8 @@ VALUE safe_execute_multi(query_ctx *ctx) {
396
396
  VALUE safe_query_columns(query_ctx *ctx) {
397
397
  return get_column_names(ctx->stmt, sqlite3_column_count(ctx->stmt));
398
398
  }
399
+
400
+ VALUE safe_query_changes(query_ctx *ctx) {
401
+ while (stmt_iterate(ctx));
402
+ return INT2FIX(sqlite3_changes(ctx->sqlite3_db));
403
+ }
@@ -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
- #define GetDatabase(obj, database) \
40
- TypedData_Get_Struct((obj), Database_t, &Database_type, (database))
41
-
42
- // make sure the database is open
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 *Database_struct(VALUE self) {
51
- Database_t *db;
52
- GetDatabase(self, db);
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
- Database_t *db;
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
- /* call-seq:
73
- * db.initialize(path)
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
- * Initializes a new SQLite database with the given path.
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
- VALUE Database_initialize(VALUE self, VALUE path) {
79
- int rc;
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 = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
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,15 @@ 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);
759
810
  }
@@ -94,6 +94,7 @@ typedef struct {
94
94
 
95
95
  VALUE safe_execute_multi(query_ctx *ctx);
96
96
  VALUE safe_query_ary(query_ctx *ctx);
97
+ VALUE safe_query_changes(query_ctx *ctx);
97
98
  VALUE safe_query_columns(query_ctx *ctx);
98
99
  VALUE safe_query_hash(query_ctx *ctx);
99
100
  VALUE safe_query_single_column(query_ctx *ctx);
@@ -120,6 +121,6 @@ int stmt_iterate(query_ctx *ctx);
120
121
  VALUE cleanup_stmt(query_ctx *ctx);
121
122
 
122
123
  sqlite3 *Database_sqlite3_db(VALUE self);
123
- Database_t *Database_struct(VALUE self);
124
+ Database_t *self_to_database(VALUE self);
124
125
 
125
126
  #endif /* EXTRALITE_H */
@@ -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 *value_to_iterator(VALUE obj) {
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 = value_to_iterator(self);
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 = value_to_iterator(self);
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 = value_to_iterator(self);
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 = value_to_iterator(self);
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
  }
@@ -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 *value_to_query(VALUE obj) {
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 = value_to_query(self);
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 = Database_struct(db);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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 = value_to_query(self);
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
  }
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.0'
3
+ VERSION = '2.1'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -27,8 +27,6 @@ module Extralite
27
27
 
28
28
  # An SQLite database
29
29
  class Database
30
- alias_method :execute, :query
31
-
32
30
  # @!visibility private
33
31
  TABLES_SQL = <<~SQL
34
32
  SELECT name FROM sqlite_master
@@ -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,21 @@ 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
364
390
  end
365
391
 
366
392
  class ScenarioTest < MiniTest::Test
@@ -96,4 +96,9 @@ 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
99
104
  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
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.0'
4
+ version: '2.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-08 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler