extralite 2.15 → 3.0.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.
@@ -14,10 +14,10 @@
14
14
  // debug utility
15
15
  #define INSPECT(str, obj) { \
16
16
  printf(str); \
17
- VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); \
17
+ VALUE s = rb_funcall(obj, ID_inspect, 0); \
18
18
  printf(": %s\n", StringValueCStr(s)); \
19
19
  }
20
- #define CALLER() rb_funcall(rb_mKernel, rb_intern("caller"), 0)
20
+ #define CALLER() rb_funcall(rb_mKernel, rb_intern_const("caller"), 0)
21
21
  #define TRACE_CALLER() INSPECT("caller: ", CALLER())
22
22
 
23
23
  #define SAFE(f) (VALUE (*)(VALUE))(f)
@@ -29,6 +29,9 @@ extern VALUE cQuery;
29
29
  extern VALUE cIterator;
30
30
  extern VALUE cChangeset;
31
31
  extern VALUE cBlob;
32
+ extern VALUE cTransform;
33
+
34
+ extern VALUE mJSON;
32
35
 
33
36
  extern VALUE cError;
34
37
  extern VALUE cSQLError;
@@ -40,6 +43,7 @@ extern ID ID_call;
40
43
  extern ID ID_each;
41
44
  extern ID ID_keys;
42
45
  extern ID ID_new;
46
+ extern ID ID_parse;
43
47
  extern ID ID_strip;
44
48
  extern ID ID_to_s;
45
49
  extern ID ID_track;
@@ -81,7 +85,7 @@ typedef struct {
81
85
  VALUE self;
82
86
  VALUE db;
83
87
  VALUE sql;
84
- VALUE transform_proc;
88
+ VALUE transform;
85
89
  VALUE bound_params;
86
90
  Database_t *db_struct;
87
91
  sqlite3 *sqlite3_db;
@@ -89,6 +93,7 @@ typedef struct {
89
93
  int eof;
90
94
  int closed;
91
95
  int should_reset;
96
+ int transform_object;
92
97
  enum query_mode query_mode;
93
98
  } Query_t;
94
99
 
@@ -103,6 +108,42 @@ typedef struct {
103
108
  } Changeset_t;
104
109
  #endif
105
110
 
111
+ // #define TRANSFORM_F_CONTAINER (1 << 0) // node is a container
112
+ #define TRANSFORM_F_ARRAY (1 << 0) // node is an array container
113
+ #define TRANSFORM_F_IDENTITY (1 << 1) // node is an identity column
114
+ #define TRANSFORM_F_NAME (1 << 2) // node has a name VALUE
115
+
116
+ enum transform_node_type {
117
+ TRANSFORM_T_AUTO,
118
+ TRANSFORM_T_INTEGER,
119
+ TRANSFORM_T_FLOAT,
120
+ TRANSFORM_T_TEXT,
121
+ TRANSFORM_T_BOOL,
122
+ TRANSFORM_T_JSON,
123
+ TRANSFORM_T_PROC,
124
+ TRANSFORM_T_RELATION
125
+ };
126
+
127
+ struct transform_node {
128
+ enum transform_node_type type;
129
+ unsigned short flags;
130
+ unsigned short idx; // column index
131
+
132
+ VALUE name;
133
+ VALUE conversion_proc;
134
+
135
+ unsigned short identity_idx; // identity column index
136
+ struct transform_node *identity_node;
137
+
138
+ struct transform_node *subnodes_head;
139
+ struct transform_node *subnodes_tail;
140
+ struct transform_node *next;
141
+ };
142
+
143
+ typedef struct {
144
+ struct transform_node *root;
145
+ } Transform_t;
146
+
106
147
  enum row_mode {
107
148
  ROW_YIELD,
108
149
  ROW_MULTI,
@@ -113,7 +154,7 @@ typedef struct {
113
154
  VALUE self;
114
155
  VALUE sql;
115
156
  VALUE params;
116
- VALUE transform_proc;
157
+ VALUE transform;
117
158
 
118
159
  Database_t *db;
119
160
  sqlite3 *sqlite3_db;
@@ -137,11 +178,11 @@ enum gvl_mode {
137
178
  #define SINGLE_ROW -2
138
179
  #define ROW_YIELD_OR_MODE(default) (rb_block_given_p() ? ROW_YIELD : default)
139
180
  #define ROW_MULTI_P(mode) (mode == ROW_MULTI)
140
- #define QUERY_CTX(self, sql, db, stmt, params, transform_proc, query_mode, row_mode, max_rows) { \
181
+ #define QUERY_CTX(self, sql, db, stmt, params, transform, query_mode, row_mode, max_rows) { \
141
182
  self, \
142
183
  sql, \
143
184
  params, \
144
- transform_proc, \
185
+ transform, \
145
186
  db, \
146
187
  db->sqlite3_db, \
147
188
  stmt, \
@@ -170,13 +211,16 @@ VALUE safe_query_array(query_ctx *ctx);
170
211
  VALUE safe_query_changes(query_ctx *ctx);
171
212
  VALUE safe_query_columns(query_ctx *ctx);
172
213
  VALUE safe_query_hash(query_ctx *ctx);
214
+ VALUE safe_query_transform(query_ctx *ctx);
173
215
  VALUE safe_query_single_row_hash(query_ctx *ctx);
174
216
  VALUE safe_query_single_row_splat(query_ctx *ctx);
175
217
  VALUE safe_query_single_row_array(query_ctx *ctx);
218
+ VALUE safe_query_single_row_transform(query_ctx *ctx);
176
219
 
177
220
  VALUE Query_each(VALUE self);
178
221
  VALUE Query_next(int argc, VALUE *argv, VALUE self);
179
222
  VALUE Query_to_a(VALUE self);
223
+ VALUE Query_transform_set(VALUE self, VALUE transform);
180
224
 
181
225
  void prepare_single_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
182
226
  void prepare_multi_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
@@ -192,4 +236,6 @@ Database_t *self_to_database(VALUE self);
192
236
 
193
237
  void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);
194
238
 
239
+ struct transform_node *get_transform_root(VALUE obj);
240
+
195
241
  #endif /* EXTRALITE_H */
@@ -6,6 +6,7 @@ void Init_ExtraliteIterator();
6
6
  #ifdef EXTRALITE_ENABLE_CHANGESET
7
7
  void Init_ExtraliteChangeset();
8
8
  #endif
9
+ void Init_ExtraliteTransform();
9
10
 
10
11
  void Init_extralite_ext(void) {
11
12
  rb_ext_ractor_safe(true);
@@ -16,4 +17,5 @@ void Init_extralite_ext(void) {
16
17
  #ifdef EXTRALITE_ENABLE_CHANGESET
17
18
  Init_ExtraliteChangeset();
18
19
  #endif
20
+ Init_ExtraliteTransform();
19
21
  }
@@ -28,7 +28,7 @@ static void Query_mark(void *ptr) {
28
28
  Query_t *query = ptr;
29
29
  rb_gc_mark_movable(query->db);
30
30
  rb_gc_mark_movable(query->sql);
31
- rb_gc_mark_movable(query->transform_proc);
31
+ rb_gc_mark_movable(query->transform);
32
32
  rb_gc_mark_movable(query->bound_params);
33
33
  }
34
34
 
@@ -36,7 +36,7 @@ static void Query_compact(void *ptr) {
36
36
  Query_t *query = ptr;
37
37
  query->db = rb_gc_location(query->db);
38
38
  query->sql = rb_gc_location(query->sql);
39
- query->transform_proc = rb_gc_location(query->transform_proc);
39
+ query->transform = rb_gc_location(query->transform);
40
40
  query->bound_params = rb_gc_location(query->bound_params);
41
41
  }
42
42
 
@@ -54,12 +54,11 @@ static const rb_data_type_t Query_type = {
54
54
 
55
55
  static VALUE Query_allocate(VALUE klass) {
56
56
  Query_t *query = ALLOC(Query_t);
57
+ memset(query, 0, sizeof(Query_t));
57
58
  query->db = Qnil;
58
59
  query->sql = Qnil;
59
- query->transform_proc = Qnil;
60
+ query->transform = Qnil;
60
61
  query->bound_params = Qnil;
61
- query->sqlite3_db = NULL;
62
- query->stmt = NULL;
63
62
  return TypedData_Wrap_Struct(klass, &Query_type, query);
64
63
  }
65
64
 
@@ -118,7 +117,7 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql, VALUE mode) {
118
117
  RB_OBJ_WRITE(self, &query->db, db);
119
118
  RB_OBJ_WRITE(self, &query->sql, sql);
120
119
  if (rb_block_given_p())
121
- RB_OBJ_WRITE(self, &query->transform_proc, rb_block_proc());
120
+ RB_OBJ_WRITE(self, &query->transform, rb_block_proc());
122
121
 
123
122
  query->self = self;
124
123
  query->db = db;
@@ -153,7 +152,7 @@ static inline void query_bind(Query_t *query, int argc, VALUE * argv) {
153
152
  // changing the binding. See note at bottom of
154
153
  // https://www.sqlite.org/c3ref/bind_blob.html
155
154
  sqlite3_reset(query->stmt);
156
-
155
+
157
156
  sqlite3_clear_bindings(query->stmt);
158
157
  if (argc > 0) {
159
158
  bind_all_parameters(query->stmt, argc, argv);
@@ -231,13 +230,18 @@ static inline VALUE Query_perform_next(VALUE self, int max_rows, safe_query_impl
231
230
  if (!query->stmt || query->should_reset) query_reset(query);
232
231
  if (query->eof) return rb_block_given_p() ? self : Qnil;
233
232
 
233
+ if (query->transform_object) {
234
+ max_rows = ALL_ROWS;
235
+ call = safe_query_transform;
236
+ }
237
+
234
238
  query_ctx ctx = QUERY_CTX(
235
239
  self,
236
240
  query->sql,
237
241
  query->db_struct,
238
242
  query->stmt,
239
243
  Qnil,
240
- query->transform_proc,
244
+ query->transform,
241
245
  query->query_mode,
242
246
  ROW_YIELD_OR_MODE(max_rows == SINGLE_ROW ? ROW_SINGLE : ROW_MULTI),
243
247
  MAX_ROWS(max_rows)
@@ -378,7 +382,7 @@ VALUE Query_execute_chevrons(VALUE self, VALUE params) {
378
382
  * parameters for running the query. If a callable is given, it is called
379
383
  * repeatedly and each of its return values is used as the parameters, until nil
380
384
  * is returned.
381
- *
385
+ *
382
386
  * Returns the number of changes effected. This method is designed for inserting
383
387
  * multiple records.
384
388
  *
@@ -388,7 +392,7 @@ VALUE Query_execute_chevrons(VALUE self, VALUE params) {
388
392
  * [4, 5, 6]
389
393
  * ]
390
394
  * query.batch_execute(records)
391
- *
395
+ *
392
396
  * source = [
393
397
  * [1, 2, 3],
394
398
  * [4, 5, 6]
@@ -432,7 +436,7 @@ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
432
436
  * invocation of the query, and the total number of changes is returned.
433
437
  * Otherwise, an array containing the resulting rows for each invocation is
434
438
  * returned.
435
- *
439
+ *
436
440
  * Rows are returned according to the query mode and transform.
437
441
  *
438
442
  * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
@@ -460,7 +464,7 @@ VALUE Query_batch_query(VALUE self, VALUE parameters) {
460
464
  query->db_struct,
461
465
  query->stmt,
462
466
  parameters,
463
- query->transform_proc,
467
+ query->transform,
464
468
  query->query_mode,
465
469
  ROW_YIELD_OR_MODE(ROW_MULTI),
466
470
  ALL_ROWS
@@ -514,7 +518,7 @@ VALUE Query_clone(VALUE self) {
514
518
  query->sql,
515
519
  query_mode_to_symbol(query->query_mode)
516
520
  };
517
- return rb_funcall_with_block(cQuery, ID_new, 3, args, query->transform_proc);
521
+ return rb_funcall_with_block(cQuery, ID_new, 3, args, query->transform);
518
522
  }
519
523
 
520
524
  /* Closes the query. Attempting to run a closed query will raise an error.
@@ -566,27 +570,6 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
566
570
  return INT2NUM(value);
567
571
  }
568
572
 
569
- /* Sets the transform block to the given block. If a transform block is set,
570
- * calls to #to_a, #next, #each and #batch_query will transform values fetched
571
- * from the database using the transform block before passing them to the
572
- * application code. To remove the transform block, call `#transform`
573
- * without a block. The transform for each row is done by passing the row hash
574
- * to the block.
575
- *
576
- * # fetch column c as an ORM object
577
- * q = db.prepare('select * from foo order by a').transform do |h|
578
- * MyModel.new(h)
579
- * end
580
- *
581
- * @return [Extralite::Query] query
582
- */
583
- VALUE Query_transform(VALUE self) {
584
- Query_t *query = self_to_query_verify(self);
585
-
586
- RB_OBJ_WRITE(self, &query->transform_proc, rb_block_given_p() ? rb_block_proc() : Qnil);
587
- return self;
588
- }
589
-
590
573
  /* Returns a short string representation of the query instance, including the
591
574
  * SQL string.
592
575
  *
@@ -616,7 +599,7 @@ VALUE Query_mode_get(VALUE self) {
616
599
 
617
600
  /* call-seq:
618
601
  * query.mode = mode
619
- *
602
+ *
620
603
  * Sets the query mode. This can be one of `:hash`, `:splat`, `:array`.
621
604
  *
622
605
  * @param mode [Symbol] query mode
@@ -628,6 +611,48 @@ VALUE Query_mode_set(VALUE self, VALUE mode) {
628
611
  return mode;
629
612
  }
630
613
 
614
+ /*
615
+ * Returns the transform set for the query.
616
+ *
617
+ * @return [Proc, Extralite::Transform, nil] transform
618
+ */
619
+ VALUE Query_transform_get(VALUE self) {
620
+ Query_t *query = self_to_query_verify(self);
621
+ return query->transform;
622
+ }
623
+
624
+ /*
625
+ * Sets the transform block to the given proc or transform object. If a
626
+ * transform is set, calls to #to_a, #next, #each and #batch_query will
627
+ * transform values fetched from the database using the transform before passing
628
+ * them to the application code. To remove the transform, call `#transform=`
629
+ * with a nil value.
630
+ *
631
+ * # fetch column c as an ORM object
632
+ * q = db.prepare('select * from foo')
633
+ * q.transform = ->(h) {
634
+ * MyModel.new(h)
635
+ * }
636
+ *
637
+ * # use a custom transform
638
+ * q = db.prepare('select id, name from foo')
639
+ * q.transform = Extralite::Transform.new(
640
+ * columns: {
641
+ * id: { type: integer, identity: true },
642
+ * name: { type: :text }
643
+ * }
644
+ * )
645
+ *
646
+ * @param transform [Extralite::Transform, Proc, nil] transform or nil
647
+ * @return [Extralite::Query] query
648
+ */
649
+ VALUE Query_transform_set(VALUE self, VALUE transform) {
650
+ Query_t *query = self_to_query(self);
651
+ query->transform = transform;
652
+ query->transform_object = rb_obj_is_instance_of(transform, cTransform);
653
+ return self;
654
+ }
655
+
631
656
  void Init_ExtraliteQuery(void) {
632
657
  VALUE mExtralite = rb_define_module("Extralite");
633
658
 
@@ -657,14 +682,15 @@ void Init_ExtraliteQuery(void) {
657
682
  rb_define_method(cQuery, "sql", Query_sql, 0);
658
683
  rb_define_method(cQuery, "status", Query_status, -1);
659
684
  rb_define_method(cQuery, "to_a", Query_to_a, 0);
660
- rb_define_method(cQuery, "transform", Query_transform, 0);
685
+ rb_define_method(cQuery, "transform", Query_transform_get, 0);
686
+ rb_define_method(cQuery, "transform=", Query_transform_set, 1);
661
687
 
662
- ID_inspect = rb_intern("inspect");
663
- ID_slice = rb_intern("slice");
688
+ ID_inspect = rb_intern_const("inspect");
689
+ ID_slice = rb_intern_const("slice");
664
690
 
665
- SYM_hash = ID2SYM(rb_intern("hash"));
666
- SYM_splat = ID2SYM(rb_intern("splat"));
667
- SYM_array = ID2SYM(rb_intern("array"));
691
+ SYM_hash = ID2SYM(rb_intern_const("hash"));
692
+ SYM_splat = ID2SYM(rb_intern_const("splat"));
693
+ SYM_array = ID2SYM(rb_intern_const("array"));
668
694
 
669
695
  rb_gc_register_mark_object(SYM_hash);
670
696
  rb_gc_register_mark_object(SYM_splat);