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.
- checksums.yaml +4 -4
- data/.github/workflows/test-bundle.yml +1 -1
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +10 -0
- data/README.md +106 -42
- data/TODO.md +2 -16
- data/examples/transform.rb +61 -0
- data/ext/extralite/changeset.c +11 -11
- data/ext/extralite/common.c +234 -22
- data/ext/extralite/database.c +157 -100
- data/ext/extralite/extralite.h +52 -6
- data/ext/extralite/extralite_ext.c +2 -0
- data/ext/extralite/query.c +67 -41
- data/ext/extralite/transform.c +420 -0
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +102 -1
- data/test/perf_array.rb +1 -1
- data/test/perf_hash.rb +1 -1
- data/test/perf_hash_prepared.rb +2 -2
- data/test/perf_splat.rb +1 -1
- data/test/perf_transform.rb +58 -0
- data/test/test_database.rb +37 -10
- data/test/test_query.rb +11 -15
- data/test/test_transform.rb +817 -0
- metadata +6 -2
data/ext/extralite/extralite.h
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
// debug utility
|
|
15
15
|
#define INSPECT(str, obj) { \
|
|
16
16
|
printf(str); \
|
|
17
|
-
VALUE s = rb_funcall(obj,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
}
|
data/ext/extralite/query.c
CHANGED
|
@@ -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->
|
|
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->
|
|
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->
|
|
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->
|
|
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->
|
|
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->
|
|
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->
|
|
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",
|
|
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 =
|
|
663
|
-
ID_slice =
|
|
688
|
+
ID_inspect = rb_intern_const("inspect");
|
|
689
|
+
ID_slice = rb_intern_const("slice");
|
|
664
690
|
|
|
665
|
-
SYM_hash
|
|
666
|
-
SYM_splat
|
|
667
|
-
SYM_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);
|