extralite-bundle 2.14 → 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.
@@ -0,0 +1,420 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <string.h>
4
+ #include "ruby.h"
5
+ #include "extralite.h"
6
+
7
+ /*
8
+ * Document-class: Extralite::Transform
9
+ *
10
+ * This class represents a specification for retrieving structured data as the
11
+ * result of a query. When querying data from multiple tables using joins,
12
+ * transforms allow you to retrieve the results as an object graph containing
13
+ * the different entities.
14
+ *
15
+ * For example, a posts table and a tags table may be joined to represent a
16
+ * many-to-many relationship:
17
+ *
18
+ * select
19
+ * posts.id, posts.content,
20
+ * tags.id, tags.name
21
+ * from posts
22
+ * left outer join posts_tags on posts_tags.post_id = posts.id
23
+ * left outer join tags on posts_tags.tag_id = tags.id
24
+ *
25
+ * Normally, the resulting data will be represented as an array of hashes, of
26
+ * the form:
27
+ *
28
+ * [
29
+ * { posts_id: 1, posts_content: "foo", tags_id: 1, tags_name: "blah" },
30
+ * { posts_id: 2, posts_content: "bar", tags_id: 1, tags_name: "blah" },
31
+ * { posts_id: 2, posts_content: "bar", tags_id: 2, tags_name: "bleh" },
32
+ * { posts_id: 3, posts_content: "baz", tags_id: 2, tags_name: "blah" },
33
+ * ...
34
+ * ]
35
+ *
36
+ * Those results, while containing all the information that we requested, also
37
+ * contain a lot of duplication: the same post entity may appear in multiple
38
+ * rows, and the same tag entity may be repeated for different posts.
39
+ *
40
+ * With a properly configured transform, we can convert those flat rows with
41
+ * duplicate data into an object graph that represents the different entities
42
+ * (posts and tags), such that each post entity will also include the
43
+ * corresponding tags. Furthermore, we can eliminate duplication by using
44
+ * identity maps to create each entity only once, and reuse it if it repeats
45
+ * (such as in the case of tags):
46
+ *
47
+ * [
48
+ * { id: 1, content: "foo", tags: [{id: 1, name: "blah"}] },
49
+ * { id: 2, content: "bar", tags: [{id: 1, name: "blah"}, {id: 2, name: "bleh"}] },
50
+ * { id: 3, content: "baz", tags: [{id: 2, name: "bleh"}] }
51
+ * ]
52
+ *
53
+ * The transform is expressed using the transform DSL:
54
+ *
55
+ * transform = Extralite::Transform.new do
56
+ * {
57
+ * id: integer.identity,
58
+ * content: text,
59
+ * tags: [{
60
+ * id: integer.identity,
61
+ * name: text
62
+ * }]
63
+ * }
64
+ * end
65
+ *
66
+ * To use the transform we can feed it into Database#query:
67
+ *
68
+ * db.query(transform, sql) #=> [...]
69
+ *
70
+ * A transform may also be used with a prepared query:
71
+ *
72
+ * q = db.prepare(transform, sql)
73
+ * q.to_a #=> [...]
74
+ *
75
+ * Transforms can also be used for type coercion. If the type is 'auto' or not
76
+ * specified, the returned value will reflect the native type of the value in
77
+ * the database. The following types are supported:
78
+ *
79
+ * - auto: native database type
80
+ * - integer: 64-bit integer
81
+ * - float: floating point number
82
+ * - text: text/string value
83
+ * - bool: a boolean (after coercion to integer)
84
+ * - json: parsed JSON representation
85
+ * - proc: a custom proc for converting a value
86
+ *
87
+ * To use the proc type, specify the proc as the type, e.g.:
88
+ *
89
+ * Extralite::Transform.new do
90
+ * {
91
+ * stamp: ->(s) { Time.at(s) },
92
+ * value: float
93
+ * }
94
+ * end
95
+ */
96
+
97
+ VALUE cTransform;
98
+ VALUE mJSON;
99
+
100
+ VALUE SYM_bool;
101
+ VALUE SYM_columns;
102
+ VALUE SYM_float;
103
+ VALUE SYM_identity;
104
+ VALUE SYM_integer;
105
+ VALUE SYM_json;
106
+ VALUE SYM_name;
107
+ VALUE SYM_relation;
108
+ VALUE SYM_text;
109
+ VALUE SYM_type;
110
+
111
+ static size_t Transform_size(const void *ptr) {
112
+ return sizeof(Transform_t);
113
+ }
114
+
115
+ static inline void transform_node_mark(struct transform_node *node) {
116
+ rb_gc_mark_movable(node->name);
117
+ if (node->type == TRANSFORM_T_PROC)
118
+ rb_gc_mark_movable(node->conversion_proc);
119
+
120
+ struct transform_node *cur = node->subnodes_head;
121
+ while (cur) {
122
+ transform_node_mark(cur);
123
+ cur = cur->next;
124
+ }
125
+
126
+ if (node->next) transform_node_mark(node->next);
127
+ }
128
+
129
+ static void Transform_mark(void *ptr) {
130
+ Transform_t *t = ptr;
131
+ if (t->root) transform_node_mark(t->root);
132
+ }
133
+
134
+ static inline void transform_node_compact(struct transform_node *node) {
135
+ if (node->flags & TRANSFORM_F_NAME)
136
+ node->name = rb_gc_location(node->name);
137
+
138
+ if (node->type == TRANSFORM_T_PROC)
139
+ node->conversion_proc = rb_gc_location(node->conversion_proc);
140
+
141
+ struct transform_node *cur = node->subnodes_head;
142
+ while (cur) {
143
+ transform_node_compact(cur);
144
+ cur = cur->next;
145
+ }
146
+
147
+ if (node->next) transform_node_compact(node->next);
148
+ }
149
+
150
+ static void Transform_compact(void *ptr) {
151
+ Transform_t *t = ptr;
152
+ if (t->root) transform_node_compact(t->root);
153
+ }
154
+
155
+ static inline void transform_node_free(struct transform_node *node) {
156
+ if (node->subnodes_head)
157
+ transform_node_free(node->subnodes_head);
158
+ if (node->next)
159
+ transform_node_free(node->next);
160
+
161
+ free(node);
162
+ }
163
+
164
+ static void Transform_free(void *ptr) {
165
+ Transform_t *t = ptr;
166
+ if (t->root) transform_node_free(t->root);
167
+ free(ptr);
168
+ }
169
+
170
+ static const rb_data_type_t Transform_type = {
171
+ "Transform",
172
+ {Transform_mark, Transform_free, Transform_size, Transform_compact},
173
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
174
+ };
175
+
176
+ static VALUE Transform_allocate(VALUE klass) {
177
+ Transform_t *t = ALLOC(Transform_t);
178
+ t->root = NULL;
179
+ return TypedData_Wrap_Struct(klass, &Transform_type, t);
180
+ }
181
+
182
+ static inline Transform_t *self_to_transform(VALUE self) {
183
+ Transform_t *t;
184
+ TypedData_Get_Struct(self, Transform_t, &Transform_type, t);
185
+ return t;
186
+ }
187
+
188
+ struct transform_node *get_transform_root(VALUE obj) {
189
+ return self_to_transform(obj)->root;
190
+ }
191
+
192
+ inline struct transform_node *allocate_transform_node() {
193
+ struct transform_node *node = malloc(sizeof(struct transform_node));
194
+ memset(node, 0, sizeof(struct transform_node));
195
+ node->name = Qnil;
196
+ node->conversion_proc = Qnil;
197
+ return node;
198
+ }
199
+
200
+ struct transform_node *compile_transform_relation(VALUE spec, int *col_counter);
201
+
202
+ enum transform_node_type get_node_type(VALUE col_hash, VALUE *proc) {
203
+ VALUE type = rb_hash_aref(col_hash, SYM_type);
204
+ if (NIL_P(type)) return TRANSFORM_T_AUTO;
205
+ if (type == SYM_integer) return TRANSFORM_T_INTEGER;
206
+ if (type == SYM_float) return TRANSFORM_T_FLOAT;
207
+ if (type == SYM_text) return TRANSFORM_T_TEXT;
208
+ if (type == SYM_bool) return TRANSFORM_T_BOOL;
209
+ if (type == SYM_json) return TRANSFORM_T_JSON;
210
+ if (type == SYM_relation) return TRANSFORM_T_RELATION;
211
+
212
+ if (TYPE(type) == T_DATA) {
213
+ *proc = type;
214
+ return TRANSFORM_T_PROC;
215
+ }
216
+
217
+ rb_raise(cError, "Invalid column type");
218
+ }
219
+
220
+ struct transform_node *compile_transform_column(VALUE col, int *col_counter) {
221
+ // In the PORO spec, a multi-row (array) relation is expressed as:
222
+ //
223
+ // spec = {
224
+ // columns: {
225
+ // **,
226
+ // tags: [{
227
+ // type: :relation,
228
+ // columns: {**}
229
+ // }]
230
+ // }
231
+ // }
232
+ //
233
+ // (Note the literal array surrounding the tags entry.) So we check for it
234
+ // here, it's also checked in compile_transform_relation.
235
+ if (TYPE(col) == T_ARRAY) goto relation_node;
236
+ if (TYPE(col) != T_HASH)
237
+ rb_raise(cError, "Each column must be a hash");
238
+
239
+ VALUE proc = Qnil;
240
+ enum transform_node_type node_type = get_node_type(col, &proc);
241
+ if (node_type == TRANSFORM_T_RELATION) goto relation_node;
242
+
243
+ struct transform_node *node = allocate_transform_node();
244
+ node->type = node_type;
245
+ node->idx = *col_counter;
246
+
247
+ VALUE identity = rb_hash_aref(col, SYM_identity);
248
+ if (RTEST(identity)) node->flags |= TRANSFORM_F_IDENTITY;
249
+
250
+ if (node_type == TRANSFORM_T_PROC) node->conversion_proc = proc;
251
+
252
+ (*col_counter)++;
253
+ RB_GC_GUARD(proc);
254
+ return node;
255
+
256
+ relation_node:
257
+ return compile_transform_relation(col, col_counter);
258
+ }
259
+
260
+ struct column_iterator_ctx {
261
+ VALUE spec;
262
+ int *col_counter;
263
+ struct transform_node *node;
264
+ };
265
+
266
+ int column_iterator(VALUE name, VALUE col, VALUE arg) {
267
+ struct column_iterator_ctx *ctx = (struct column_iterator_ctx *)arg;
268
+ struct transform_node *node = ctx->node;
269
+
270
+ int col_idx = *(ctx->col_counter);
271
+ struct transform_node *col_node = compile_transform_column(col, ctx->col_counter);
272
+ col_node->flags |= TRANSFORM_F_NAME;
273
+ col_node->name = name;
274
+
275
+ if (col_node->flags & TRANSFORM_F_IDENTITY) {
276
+ node->identity_node = col_node;
277
+ node->identity_idx = col_idx;
278
+ }
279
+
280
+ if (node->subnodes_tail) {
281
+ node->subnodes_tail->next = col_node;
282
+ node->subnodes_tail = col_node;
283
+ }
284
+ else {
285
+ node->subnodes_head = node->subnodes_tail = col_node;
286
+ }
287
+
288
+ return ST_CONTINUE;
289
+ }
290
+
291
+ struct transform_node *compile_transform_relation(VALUE spec, int *col_counter) {
292
+ struct transform_node *node = allocate_transform_node();
293
+ node->type = TRANSFORM_T_RELATION;
294
+
295
+ if (TYPE(spec) == T_ARRAY) {
296
+ node->flags |= TRANSFORM_F_ARRAY;
297
+ spec = rb_ary_entry(spec, 0);
298
+ }
299
+
300
+ VALUE val = rb_hash_aref(spec, SYM_columns);
301
+ if (TYPE(val) != T_HASH)
302
+ rb_raise(cError, "columns member must be a hash");
303
+
304
+ struct column_iterator_ctx ctx = { spec, col_counter, node };
305
+ rb_hash_foreach(val, column_iterator, (VALUE)&ctx);
306
+ return node;
307
+ }
308
+
309
+ VALUE Transform_initialize(VALUE self, VALUE spec) {
310
+ Transform_t *t = self_to_transform(self);
311
+ int col_counter = 0;
312
+ t->root = compile_transform_relation(spec, &col_counter);
313
+ return self;
314
+ }
315
+
316
+ VALUE node_type_to_value(enum transform_node_type type) {
317
+ switch (type) {
318
+ case TRANSFORM_T_AUTO: return Qnil;
319
+ case TRANSFORM_T_INTEGER: return SYM_integer;
320
+ case TRANSFORM_T_FLOAT: return SYM_float;
321
+ case TRANSFORM_T_TEXT: return SYM_text;
322
+ case TRANSFORM_T_BOOL: return SYM_bool;
323
+ case TRANSFORM_T_JSON: return SYM_json;
324
+ default:
325
+ rb_raise(cError, "Invalid node type in node_type_to_value");
326
+ }
327
+ }
328
+
329
+ VALUE transform_node_to_obj(struct transform_node *node) {
330
+ // printf("transform_node_to_obj type: %d flags: %02x\n", node->type, node->flags);
331
+ // if (node->flags & TRANSFORM_F_NAME) INSPECT(" name", node->name);
332
+ VALUE hash = rb_hash_new();
333
+ if (node->type == TRANSFORM_T_RELATION) {
334
+ VALUE cols = rb_hash_new();
335
+
336
+ if (node->flags & TRANSFORM_F_NAME)
337
+ rb_hash_aset(hash, SYM_type, SYM_relation);
338
+ rb_hash_aset(hash, SYM_columns, cols);
339
+
340
+ struct transform_node *cur = node->subnodes_head;
341
+ while (cur) {
342
+ struct transform_node *next = cur->next;
343
+ VALUE val = transform_node_to_obj(cur);
344
+ rb_hash_aset(cols, cur->name, val);
345
+ RB_GC_GUARD(val);
346
+
347
+ cur = next;
348
+ }
349
+
350
+ if (node->flags & TRANSFORM_F_ARRAY) {
351
+ VALUE array = rb_ary_new();
352
+ rb_ary_push(array, hash);
353
+ return array;
354
+ RB_GC_GUARD(array);
355
+ }
356
+ else
357
+ return hash;
358
+ RB_GC_GUARD(cols);
359
+ }
360
+ else {
361
+ switch (node->type) {
362
+ case TRANSFORM_T_AUTO:
363
+ break;
364
+ case TRANSFORM_T_PROC:
365
+ rb_hash_aset(hash, SYM_type, node->conversion_proc);
366
+ break;
367
+ default:
368
+ VALUE v = node_type_to_value(node->type);
369
+ RB_GC_GUARD(v);
370
+ rb_hash_aset(hash, SYM_type, node_type_to_value(node->type));
371
+ }
372
+ if (node->flags & TRANSFORM_F_IDENTITY)
373
+ rb_hash_aset(hash, SYM_identity, Qtrue);
374
+ return hash;
375
+ }
376
+ RB_GC_GUARD(hash);
377
+ }
378
+
379
+ /* Returns the transform spec in literal form.
380
+ *
381
+ * @return [Hash] literal transform spec
382
+ */
383
+ VALUE Transform_to_h(VALUE self) {
384
+ Transform_t *t = self_to_transform(self);
385
+ return transform_node_to_obj(t->root);
386
+ }
387
+
388
+ void Init_ExtraliteTransform(void) {
389
+ VALUE mExtralite = rb_define_module("Extralite");
390
+
391
+ cTransform = rb_define_class_under(mExtralite, "Transform", rb_cObject);
392
+ rb_define_alloc_func(cTransform, Transform_allocate);
393
+
394
+ rb_define_method(cTransform, "initialize", Transform_initialize, 1);
395
+ rb_define_method(cTransform, "to_h", Transform_to_h, 0);
396
+
397
+ SYM_bool = ID2SYM(rb_intern_const("bool"));
398
+ SYM_columns = ID2SYM(rb_intern_const("columns"));
399
+ SYM_float = ID2SYM(rb_intern_const("float"));
400
+ SYM_identity = ID2SYM(rb_intern_const("identity"));
401
+ SYM_integer = ID2SYM(rb_intern_const("integer"));
402
+ SYM_json = ID2SYM(rb_intern_const("json"));
403
+ SYM_name = ID2SYM(rb_intern_const("name"));
404
+ SYM_relation = ID2SYM(rb_intern_const("relation"));
405
+ SYM_text = ID2SYM(rb_intern_const("text"));
406
+ SYM_type = ID2SYM(rb_intern_const("type"));
407
+
408
+ rb_gc_register_mark_object(SYM_bool);
409
+ rb_gc_register_mark_object(SYM_columns);
410
+ rb_gc_register_mark_object(SYM_float);
411
+ rb_gc_register_mark_object(SYM_identity);
412
+ rb_gc_register_mark_object(SYM_integer);
413
+ rb_gc_register_mark_object(SYM_json);
414
+ rb_gc_register_mark_object(SYM_name);
415
+ rb_gc_register_mark_object(SYM_relation);
416
+ rb_gc_register_mark_object(SYM_text);
417
+ rb_gc_register_mark_object(SYM_type);
418
+
419
+ mJSON = Qnil;
420
+ }