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.
@@ -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
+ }
data/gemspec.rb CHANGED
@@ -15,7 +15,7 @@ def common_spec(s)
15
15
  s.rdoc_options = ['--title', 'Extralite', '--main', 'README.md']
16
16
  s.extra_rdoc_files = ['README.md']
17
17
  s.require_paths = ['lib']
18
- s.required_ruby_version = '>= 3.2'
18
+ s.required_ruby_version = '>= 3.4'
19
19
 
20
20
  s.add_development_dependency 'rake-compiler', '1.3.1'
21
21
  s.add_development_dependency 'minitest'
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.15'
3
+ VERSION = '3.0.0'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -236,7 +236,7 @@ module Extralite
236
236
  # @return [Any] the given block's return value
237
237
  def transaction(mode = :immediate)
238
238
  abort = false
239
- execute "begin #{mode} transaction"
239
+ execute "begin #{mode} transaction"
240
240
  yield self
241
241
  rescue => e
242
242
  abort = true
@@ -296,6 +296,10 @@ module Extralite
296
296
  raise Rollback
297
297
  end
298
298
 
299
+ def quote(str)
300
+ str.gsub("'", "''")
301
+ end
302
+
299
303
  private
300
304
 
301
305
  def pragma_set(values)
@@ -311,4 +315,101 @@ module Extralite
311
315
  class Query
312
316
  alias_method :execute_multi, :batch_execute
313
317
  end
318
+
319
+ class Transform
320
+ alias_method :orig_initialize, :initialize
321
+
322
+ # call-seq:
323
+ # Extralite::Transform.new(literal_spec) -> transform
324
+ # Extralite::Transform.new { dsl_spec } -> transform
325
+ #
326
+ # Initializes a new transform with with the given spec. The spec may be
327
+ # expressed as a hash containing the columns and any nested entities, or as a
328
+ # block with the transform DSL.
329
+ #
330
+ # # literal transform spec
331
+ # transform = Extralite::Transform.new(
332
+ # columns: {
333
+ # id: { type: :integer, identity: true },
334
+ # content: { type: :text },
335
+ # author: {
336
+ # type: :relation,
337
+ # columns: {
338
+ # id: { type: :integer, identity: true },
339
+ # name: { type: :text },
340
+ # }
341
+ # }
342
+ # }
343
+ # )
344
+ #
345
+ # # DSL spec
346
+ # transform = Extralite::Transform.new do
347
+ # {
348
+ # id: integer.identity,
349
+ # content: text,
350
+ # author: {
351
+ # id: integer.identity,
352
+ # name: text
353
+ # }
354
+ # }
355
+ # end
356
+ #
357
+ # @param spec [Hash, nil] literal spec
358
+ # @param block [Proc, nil] DSL spec
359
+ # @return [void]
360
+ def initialize(spec = nil, &block)
361
+ return orig_initialize(spec) if spec
362
+ raise "No spec given" if !block
363
+
364
+ orig_initialize(dsl_to_spec(block))
365
+ end
366
+
367
+ private
368
+
369
+ class DSLContext
370
+ attr_reader :hash
371
+
372
+ def initialize(hash = {})
373
+ @hash = hash
374
+ end
375
+
376
+ def auto = mutate(type: nil)
377
+ def integer = mutate(type: :integer)
378
+ def float = mutate(type: :float)
379
+ def text = mutate(type: :text)
380
+ def bool = mutate(type: :bool)
381
+ def json = mutate(type: :json)
382
+ def identity = mutate(identity: true)
383
+
384
+ private
385
+ def mutate(opts)
386
+ DSLContext.new(@hash.merge(opts))
387
+ end
388
+ end
389
+
390
+ def dsl_to_spec(block)
391
+ intermediate = DSLContext.new.instance_eval(&block)
392
+ dsl_intermediate_to_spec(intermediate)
393
+ end
394
+
395
+ def dsl_intermediate_to_spec(intermediate)
396
+ columns = intermediate.each_with_object({}) do |(k, v), h|
397
+ h[k] = dsl_translate_intermediate_value(v)
398
+ end
399
+ { type: :relation, columns: }
400
+ end
401
+
402
+ def dsl_translate_intermediate_value(v)
403
+ case v
404
+ when DSLContext
405
+ v.hash
406
+ when Hash
407
+ dsl_intermediate_to_spec(v)
408
+ when Array
409
+ [dsl_intermediate_to_spec(v[0])]
410
+ else
411
+ raise Extralite::Error, 'Invalid transform spec'
412
+ end
413
+ end
414
+ end
314
415
  end
data/test/perf_array.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- gem 'sqlite3', '2.6.0'
7
+ gem 'sqlite3', '2.9.5'
8
8
  gem 'extralite', path: '..'
9
9
  gem 'benchmark-ips'
10
10
  end
data/test/perf_hash.rb CHANGED
@@ -7,7 +7,7 @@ require 'bundler/inline'
7
7
  gemfile do
8
8
  source 'https://rubygems.org'
9
9
  gem 'extralite', path: '..'
10
- gem 'sqlite3', '2.6.0'
10
+ gem 'sqlite3', '2.9.5'
11
11
  gem 'benchmark-ips'
12
12
  end
13
13
 
@@ -5,7 +5,7 @@ require 'bundler/inline'
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
7
  gem 'extralite', path: '..'
8
- gem 'sqlite3', '2.6.0'
8
+ gem 'sqlite3', '2.9.5'
9
9
  gem 'benchmark-ips'
10
10
  end
11
11
 
@@ -18,7 +18,7 @@ puts "DB_PATH = #{DB_PATH.inspect}"
18
18
  def prepare_database(count)
19
19
  $sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
20
20
  $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
21
-
21
+
22
22
  $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
23
23
  $extralite_db.query('delete from foo')
24
24
  $extralite_db.query('begin')
data/test/perf_splat.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- gem 'sqlite3', '2.6.0'
7
+ gem 'sqlite3', '2.9.5'
8
8
  gem 'extralite', path: '..'
9
9
  gem 'benchmark-ips'
10
10
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Run on Ruby 3.3 with YJIT enabled
4
+
5
+ require 'bundler/inline'
6
+
7
+ gemfile do
8
+ source 'https://rubygems.org'
9
+ gem 'extralite', path: '..'
10
+ gem 'benchmark-ips'
11
+ end
12
+
13
+ require 'benchmark/ips'
14
+ require 'fileutils'
15
+
16
+ DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
17
+ puts "DB_PATH = #{DB_PATH.inspect}"
18
+
19
+ $extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
20
+
21
+ def prepare_database(count)
22
+ $extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
23
+ $extralite_db.query('delete from foo')
24
+ $extralite_db.query('begin')
25
+ count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
26
+ $extralite_db.query('commit')
27
+ end
28
+
29
+ def run_normal(count)
30
+ results = $extralite_db.query('select * from foo')
31
+ raise unless results.size == count
32
+ end
33
+
34
+ @transform = Extralite::Transform.new {
35
+ { a: integer, b: text }
36
+ }
37
+
38
+ def run_transform(count)
39
+ results = $extralite_db.query(@transform, 'select * from foo')
40
+ raise unless results.size == count
41
+ end
42
+
43
+ [10, 1000, 100000].each do |c|
44
+ puts "Record count: #{c}"
45
+ prepare_database(c)
46
+
47
+ bm = Benchmark.ips do |x|
48
+ x.config(:time => 5, :warmup => 2)
49
+
50
+ x.report("normal") { run_normal(c) }
51
+ x.report("transform") { run_transform(c) }
52
+
53
+ x.compare!
54
+ end
55
+ puts;
56
+ bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
57
+ puts;
58
+ end