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
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'helper'
|
|
4
|
+
require 'date'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
class TransformOneToOneTest < Minitest::Test
|
|
8
|
+
def setup
|
|
9
|
+
@db = Extralite::Database.new(':memory:')
|
|
10
|
+
@db.pragma('foreign_keys' => 1)
|
|
11
|
+
@db.execute <<~SQL
|
|
12
|
+
create table posts (
|
|
13
|
+
id integer primary key,
|
|
14
|
+
title text,
|
|
15
|
+
content text,
|
|
16
|
+
author_id integer references authors(id)
|
|
17
|
+
on delete cascade
|
|
18
|
+
);
|
|
19
|
+
create table authors (
|
|
20
|
+
id integer primary key,
|
|
21
|
+
name text
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
insert into authors (name) values ('Foo');
|
|
25
|
+
insert into authors (name) values ('Bar');
|
|
26
|
+
insert into posts (title, content, author_id) values ('T1', 'C1', 1);
|
|
27
|
+
insert into posts (title, content, author_id) values ('T2', 'C2', 1);
|
|
28
|
+
insert into posts (title, content, author_id) values ('T3', 'C3', 2);
|
|
29
|
+
insert into posts (title, content, author_id) values ('T4', 'C4', 2);
|
|
30
|
+
SQL
|
|
31
|
+
|
|
32
|
+
@sql = <<~SQL
|
|
33
|
+
select
|
|
34
|
+
posts.id, posts.title, posts.content,
|
|
35
|
+
authors.id, authors.name
|
|
36
|
+
from posts
|
|
37
|
+
left outer join authors
|
|
38
|
+
on posts.author_id = authors.id
|
|
39
|
+
order by posts.id
|
|
40
|
+
SQL
|
|
41
|
+
|
|
42
|
+
@spec = {
|
|
43
|
+
columns: {
|
|
44
|
+
id: { identity: true },
|
|
45
|
+
title: {},
|
|
46
|
+
content: {},
|
|
47
|
+
author: {
|
|
48
|
+
type: :relation,
|
|
49
|
+
columns: {
|
|
50
|
+
id: { identity: true },
|
|
51
|
+
name: {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_transform_one_to_one_to_h
|
|
59
|
+
t = Extralite::Transform.new(@spec)
|
|
60
|
+
assert_equal @spec, t.to_h
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_transform_one_to_one_query
|
|
64
|
+
t = Extralite::Transform.new(@spec)
|
|
65
|
+
result = @db.query(t, @sql)
|
|
66
|
+
assert_kind_of Array, result
|
|
67
|
+
assert_equal 4, result.size
|
|
68
|
+
|
|
69
|
+
assert_equal 'T1', result[0][:title]
|
|
70
|
+
assert_equal 'C1', result[0][:content]
|
|
71
|
+
assert_equal({ id: 1, name: 'Foo' }, result[0][:author])
|
|
72
|
+
|
|
73
|
+
assert_equal 'T2', result[1][:title]
|
|
74
|
+
assert_equal 'C2', result[1][:content]
|
|
75
|
+
assert_equal({ id: 1, name: 'Foo' }, result[1][:author])
|
|
76
|
+
assert_equal result[0][:author].object_id, result[1][:author].object_id
|
|
77
|
+
|
|
78
|
+
assert_equal 'T3', result[2][:title]
|
|
79
|
+
assert_equal 'C3', result[2][:content]
|
|
80
|
+
assert_equal({ id: 2, name: 'Bar' }, result[2][:author])
|
|
81
|
+
|
|
82
|
+
assert_equal 'T4', result[3][:title]
|
|
83
|
+
assert_equal 'C4', result[3][:content]
|
|
84
|
+
assert_equal({ id: 2, name: 'Bar' }, result[3][:author])
|
|
85
|
+
assert_equal result[2][:author].object_id, result[3][:author].object_id
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_transform_one_to_one_query_with_block
|
|
89
|
+
t = Extralite::Transform.new(@spec)
|
|
90
|
+
result = []
|
|
91
|
+
ret = @db.query(t, @sql) { result << it }
|
|
92
|
+
assert_equal @db, ret
|
|
93
|
+
assert_equal 4, result.size
|
|
94
|
+
|
|
95
|
+
assert_equal 'T1', result[0][:title]
|
|
96
|
+
assert_equal 'C1', result[0][:content]
|
|
97
|
+
assert_equal({ id: 1, name: 'Foo' }, result[0][:author])
|
|
98
|
+
|
|
99
|
+
assert_equal 'T2', result[1][:title]
|
|
100
|
+
assert_equal 'C2', result[1][:content]
|
|
101
|
+
assert_equal({ id: 1, name: 'Foo' }, result[1][:author])
|
|
102
|
+
assert_equal result[0][:author].object_id, result[1][:author].object_id
|
|
103
|
+
|
|
104
|
+
assert_equal 'T3', result[2][:title]
|
|
105
|
+
assert_equal 'C3', result[2][:content]
|
|
106
|
+
assert_equal({ id: 2, name: 'Bar' }, result[2][:author])
|
|
107
|
+
|
|
108
|
+
assert_equal 'T4', result[3][:title]
|
|
109
|
+
assert_equal 'C4', result[3][:content]
|
|
110
|
+
assert_equal({ id: 2, name: 'Bar' }, result[3][:author])
|
|
111
|
+
assert_equal result[2][:author].object_id, result[3][:author].object_id
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class TransformOneToManyTest < Minitest::Test
|
|
116
|
+
def setup
|
|
117
|
+
@db = Extralite::Database.new(':memory:')
|
|
118
|
+
@db.pragma('foreign_keys' => 1)
|
|
119
|
+
@db.execute <<~SQL
|
|
120
|
+
create table posts (
|
|
121
|
+
id integer primary key,
|
|
122
|
+
title text,
|
|
123
|
+
content text
|
|
124
|
+
);
|
|
125
|
+
create table comments (
|
|
126
|
+
id integer primary key,
|
|
127
|
+
post_id integer references posts(id) on delete cascade,
|
|
128
|
+
content text
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
insert into posts (title, content) values ('T1', 'C1');
|
|
132
|
+
insert into posts (title, content) values ('T2', 'C2');
|
|
133
|
+
insert into comments (post_id, content) values (1, 'comment 1');
|
|
134
|
+
insert into comments (post_id, content) values (1, 'comment 2');
|
|
135
|
+
insert into comments (post_id, content) values (2, 'comment 3');
|
|
136
|
+
insert into comments (post_id, content) values (2, 'comment 4');
|
|
137
|
+
insert into comments (post_id, content) values (2, 'comment 5');
|
|
138
|
+
SQL
|
|
139
|
+
|
|
140
|
+
@sql = <<~SQL
|
|
141
|
+
select
|
|
142
|
+
posts.id, posts.title, posts.content,
|
|
143
|
+
comments.id, comments.content
|
|
144
|
+
from posts
|
|
145
|
+
left outer join comments
|
|
146
|
+
on comments.post_id = posts.id
|
|
147
|
+
order by posts.id, comments.id
|
|
148
|
+
SQL
|
|
149
|
+
|
|
150
|
+
@spec = {
|
|
151
|
+
columns: {
|
|
152
|
+
id: { identity: true},
|
|
153
|
+
title: {},
|
|
154
|
+
content: {},
|
|
155
|
+
comments: [{
|
|
156
|
+
type: :relation,
|
|
157
|
+
columns: {
|
|
158
|
+
id: { identity: true },
|
|
159
|
+
content: {}
|
|
160
|
+
}
|
|
161
|
+
}]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def test_transform_one_to_many_to_h
|
|
167
|
+
t = Extralite::Transform.new(@spec)
|
|
168
|
+
assert_equal @spec, t.to_h
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def test_transform_one_to_many_query
|
|
172
|
+
t = Extralite::Transform.new(@spec)
|
|
173
|
+
result = @db.query(t, @sql)
|
|
174
|
+
assert_kind_of Array, result
|
|
175
|
+
assert_equal 2, result.size
|
|
176
|
+
|
|
177
|
+
assert_equal 'T1', result[0][:title]
|
|
178
|
+
assert_equal 'C1', result[0][:content]
|
|
179
|
+
assert_equal [
|
|
180
|
+
{ id: 1, content: 'comment 1' },
|
|
181
|
+
{ id: 2, content: 'comment 2' }
|
|
182
|
+
], result[0][:comments]
|
|
183
|
+
|
|
184
|
+
assert_equal 'T2', result[1][:title]
|
|
185
|
+
assert_equal 'C2', result[1][:content]
|
|
186
|
+
assert_equal [
|
|
187
|
+
{ id: 3, content: 'comment 3' },
|
|
188
|
+
{ id: 4, content: 'comment 4' },
|
|
189
|
+
{ id: 5, content: 'comment 5' }
|
|
190
|
+
], result[1][:comments]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def test_transform_one_to_many_query_with_block
|
|
194
|
+
t = Extralite::Transform.new(@spec)
|
|
195
|
+
result = []
|
|
196
|
+
ret = @db.query(t, @sql) { result << it }
|
|
197
|
+
assert_equal @db, ret
|
|
198
|
+
assert_kind_of Array, result
|
|
199
|
+
assert_equal 2, result.size
|
|
200
|
+
|
|
201
|
+
assert_equal 'T1', result[0][:title]
|
|
202
|
+
assert_equal 'C1', result[0][:content]
|
|
203
|
+
assert_equal [
|
|
204
|
+
{ id: 1, content: 'comment 1' },
|
|
205
|
+
{ id: 2, content: 'comment 2' }
|
|
206
|
+
], result[0][:comments]
|
|
207
|
+
|
|
208
|
+
assert_equal 'T2', result[1][:title]
|
|
209
|
+
assert_equal 'C2', result[1][:content]
|
|
210
|
+
assert_equal [
|
|
211
|
+
{ id: 3, content: 'comment 3' },
|
|
212
|
+
{ id: 4, content: 'comment 4' },
|
|
213
|
+
{ id: 5, content: 'comment 5' }
|
|
214
|
+
], result[1][:comments]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
class TransformManyToManyTest < Minitest::Test
|
|
219
|
+
def setup
|
|
220
|
+
@db = Extralite::Database.new(':memory:')
|
|
221
|
+
@db.pragma('foreign_keys' => 1)
|
|
222
|
+
@db.execute <<~SQL
|
|
223
|
+
create table posts (
|
|
224
|
+
id integer primary key,
|
|
225
|
+
title text,
|
|
226
|
+
content text
|
|
227
|
+
);
|
|
228
|
+
create table tags (
|
|
229
|
+
id integer primary key,
|
|
230
|
+
name text
|
|
231
|
+
);
|
|
232
|
+
create table posts_tags (
|
|
233
|
+
post_id integer references posts(id) on delete cascade,
|
|
234
|
+
tag_id integer references tags(id) on delete cascade
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
insert into posts (title, content) values ('T1', 'C1');
|
|
238
|
+
insert into posts (title, content) values ('T2', 'C2');
|
|
239
|
+
insert into tags (name) values ('tag1');
|
|
240
|
+
insert into tags (name) values ('tag2');
|
|
241
|
+
insert into tags (name) values ('tag3');
|
|
242
|
+
|
|
243
|
+
insert into posts_tags(post_id, tag_id) values (1, 1);
|
|
244
|
+
insert into posts_tags(post_id, tag_id) values (1, 2);
|
|
245
|
+
insert into posts_tags(post_id, tag_id) values (2, 2);
|
|
246
|
+
insert into posts_tags(post_id, tag_id) values (2, 3);
|
|
247
|
+
SQL
|
|
248
|
+
|
|
249
|
+
@sql = <<~SQL
|
|
250
|
+
select
|
|
251
|
+
posts.id, posts.title, posts.content,
|
|
252
|
+
tags.id, tags.name
|
|
253
|
+
from posts
|
|
254
|
+
left outer join posts_tags on posts_tags.post_id = posts.id
|
|
255
|
+
left outer join tags on posts_tags.tag_id = tags.id
|
|
256
|
+
order by posts.id, tags.id
|
|
257
|
+
SQL
|
|
258
|
+
|
|
259
|
+
@spec = {
|
|
260
|
+
columns: {
|
|
261
|
+
id: { identity: true },
|
|
262
|
+
title: {},
|
|
263
|
+
content: {},
|
|
264
|
+
tags: [{
|
|
265
|
+
type: :relation,
|
|
266
|
+
columns: {
|
|
267
|
+
id: { identity: true },
|
|
268
|
+
name: {}
|
|
269
|
+
}
|
|
270
|
+
}]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def test_transform_many_to_many_to_h
|
|
276
|
+
t = Extralite::Transform.new(@spec)
|
|
277
|
+
assert_equal @spec, t.to_h
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def test_transform_many_to_many_query
|
|
281
|
+
t = Extralite::Transform.new(@spec)
|
|
282
|
+
result = @db.query(t, @sql)
|
|
283
|
+
assert_kind_of Array, result
|
|
284
|
+
assert_equal 2, result.size
|
|
285
|
+
|
|
286
|
+
assert_equal 'T1', result[0][:title]
|
|
287
|
+
assert_equal 'C1', result[0][:content]
|
|
288
|
+
assert_equal [
|
|
289
|
+
{ id: 1, name: 'tag1' },
|
|
290
|
+
{ id: 2, name: 'tag2' }
|
|
291
|
+
], result[0][:tags]
|
|
292
|
+
|
|
293
|
+
assert_equal 'T2', result[1][:title]
|
|
294
|
+
assert_equal 'C2', result[1][:content]
|
|
295
|
+
assert_equal [
|
|
296
|
+
{ id: 2, name: 'tag2' },
|
|
297
|
+
{ id: 3, name: 'tag3' },
|
|
298
|
+
], result[1][:tags]
|
|
299
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def test_transform_many_to_many_query_with_block
|
|
303
|
+
t = Extralite::Transform.new(@spec)
|
|
304
|
+
result = []
|
|
305
|
+
ret = @db.query(t, @sql) { result << it }
|
|
306
|
+
assert_equal @db, ret
|
|
307
|
+
assert_kind_of Array, result
|
|
308
|
+
assert_equal 2, result.size
|
|
309
|
+
|
|
310
|
+
assert_equal 'T1', result[0][:title]
|
|
311
|
+
assert_equal 'C1', result[0][:content]
|
|
312
|
+
assert_equal [
|
|
313
|
+
{ id: 1, name: 'tag1' },
|
|
314
|
+
{ id: 2, name: 'tag2' }
|
|
315
|
+
], result[0][:tags]
|
|
316
|
+
|
|
317
|
+
assert_equal 'T2', result[1][:title]
|
|
318
|
+
assert_equal 'C2', result[1][:content]
|
|
319
|
+
assert_equal [
|
|
320
|
+
{ id: 2, name: 'tag2' },
|
|
321
|
+
{ id: 3, name: 'tag3' },
|
|
322
|
+
], result[1][:tags]
|
|
323
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def test_transform_many_to_many_query_single
|
|
327
|
+
t = Extralite::Transform.new(@spec)
|
|
328
|
+
result = @db.query_single(t, @sql)
|
|
329
|
+
|
|
330
|
+
assert_equal 'T1', result[:title]
|
|
331
|
+
assert_equal 'C1', result[:content]
|
|
332
|
+
assert_equal [
|
|
333
|
+
{ id: 1, name: 'tag1' },
|
|
334
|
+
{ id: 2, name: 'tag2' }
|
|
335
|
+
], result[:tags]
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
class TransformErrorTest < Minitest::Test
|
|
340
|
+
def test_transform_bad_spec
|
|
341
|
+
spec = {
|
|
342
|
+
columnss: [
|
|
343
|
+
:id,
|
|
344
|
+
:title,
|
|
345
|
+
:content,
|
|
346
|
+
{
|
|
347
|
+
name: :author,
|
|
348
|
+
identity_idx: 3,
|
|
349
|
+
columns: [:id, :name]
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
assert_raises(Extralite::Error) { Extralite::Transform.new(spec) }
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
class TransformTypesTest < Minitest::Test
|
|
358
|
+
def setup
|
|
359
|
+
@db = Extralite::Database.new(':memory:')
|
|
360
|
+
@db.pragma('foreign_keys' => 1)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def test_transform_types_to_h
|
|
364
|
+
spec = {
|
|
365
|
+
columns: {
|
|
366
|
+
id: { type: :integer, identity: true },
|
|
367
|
+
title: { type: :text },
|
|
368
|
+
content: { type: :text },
|
|
369
|
+
tags: [{
|
|
370
|
+
type: :relation,
|
|
371
|
+
columns: {
|
|
372
|
+
id: { type: :integer, identity: true },
|
|
373
|
+
name: { type: :text }
|
|
374
|
+
}
|
|
375
|
+
}]
|
|
376
|
+
}
|
|
377
|
+
# { name: :x, type: :integer },
|
|
378
|
+
# { name: :y, type: :integer }
|
|
379
|
+
# ]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# spec = {
|
|
384
|
+
# identity_idx: 0,
|
|
385
|
+
# columns: {
|
|
386
|
+
|
|
387
|
+
# }
|
|
388
|
+
# { name: :x, type: :integer },
|
|
389
|
+
# { name: :y, type: :integer }
|
|
390
|
+
# ]
|
|
391
|
+
# }
|
|
392
|
+
t = Extralite::Transform.new(spec)
|
|
393
|
+
assert_equal spec, t.to_h
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def test_transform_types_coercion
|
|
397
|
+
t = Extralite::Transform.new(
|
|
398
|
+
columns: {
|
|
399
|
+
x: { type: :integer },
|
|
400
|
+
y: { type: :integer }
|
|
401
|
+
}
|
|
402
|
+
)
|
|
403
|
+
result = @db.query(t, <<~SQL)
|
|
404
|
+
select 'foo', '42'
|
|
405
|
+
SQL
|
|
406
|
+
assert_equal [
|
|
407
|
+
{ x: 0, y: 42 }
|
|
408
|
+
], result
|
|
409
|
+
|
|
410
|
+
t = Extralite::Transform.new(
|
|
411
|
+
columns: {
|
|
412
|
+
x: { type: :text },
|
|
413
|
+
y: { type: :integer }
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
result = @db.query(t, <<~SQL)
|
|
417
|
+
select 42, '42'
|
|
418
|
+
SQL
|
|
419
|
+
assert_equal [
|
|
420
|
+
{ x: '42', y: 42 }
|
|
421
|
+
], result
|
|
422
|
+
|
|
423
|
+
t = Extralite::Transform.new(
|
|
424
|
+
columns: {
|
|
425
|
+
x: { type: :text },
|
|
426
|
+
y: { type: :float }
|
|
427
|
+
}
|
|
428
|
+
)
|
|
429
|
+
result = @db.query(t, <<~SQL)
|
|
430
|
+
select 42, 42
|
|
431
|
+
SQL
|
|
432
|
+
assert_equal [
|
|
433
|
+
{ x: '42', y: 42.0 }
|
|
434
|
+
], result
|
|
435
|
+
|
|
436
|
+
t = Extralite::Transform.new(
|
|
437
|
+
columns: {
|
|
438
|
+
x: { type: :bool },
|
|
439
|
+
y: { type: :bool },
|
|
440
|
+
z: { type: -> (x) { x.to_i * 2 } }
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
result = @db.query(t, <<~SQL)
|
|
444
|
+
select 42, 'foo', 42
|
|
445
|
+
SQL
|
|
446
|
+
assert_equal [
|
|
447
|
+
{ x: true, y: false, z: 84 }
|
|
448
|
+
], result
|
|
449
|
+
|
|
450
|
+
# NULL handling
|
|
451
|
+
t = Extralite::Transform.new(
|
|
452
|
+
columns: {
|
|
453
|
+
a: { type: nil },
|
|
454
|
+
b: { type: :integer },
|
|
455
|
+
c: { type: :float },
|
|
456
|
+
d: { type: :text },
|
|
457
|
+
e: { type: :bool },
|
|
458
|
+
f: { type: :json },
|
|
459
|
+
g: { type: ->(x) { x ? x.to_i * 2 : :null } }
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
result = @db.query(t, <<~SQL)
|
|
463
|
+
select null, null, null, null, null, null, null
|
|
464
|
+
SQL
|
|
465
|
+
assert_equal [
|
|
466
|
+
{ a: nil, b: nil, c: nil, d: nil, e: nil, f: nil, g: :null }
|
|
467
|
+
], result
|
|
468
|
+
|
|
469
|
+
# JSON parsing
|
|
470
|
+
require 'json'
|
|
471
|
+
t = Extralite::Transform.new(
|
|
472
|
+
columns: {
|
|
473
|
+
a: {},
|
|
474
|
+
b: { type: :json }
|
|
475
|
+
}
|
|
476
|
+
)
|
|
477
|
+
result = @db.query(t, <<~SQL)
|
|
478
|
+
select 42, '[1, 2, 3]'
|
|
479
|
+
SQL
|
|
480
|
+
assert_equal [
|
|
481
|
+
{ a: 42, b: [1, 2, 3] }
|
|
482
|
+
], result
|
|
483
|
+
|
|
484
|
+
result = @db.query(t, <<~SQL)
|
|
485
|
+
select 42, '{ "a": [1, 2, 3], "blah": "hi"}'
|
|
486
|
+
SQL
|
|
487
|
+
assert_equal [
|
|
488
|
+
{ a: 42, b: { 'a' => [1, 2, 3], 'blah' => 'hi' } }
|
|
489
|
+
], result
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def test_transform_types_with_relations
|
|
493
|
+
@db.execute <<~SQL
|
|
494
|
+
create table posts (
|
|
495
|
+
id integer primary key,
|
|
496
|
+
title text,
|
|
497
|
+
content text
|
|
498
|
+
);
|
|
499
|
+
create table tags (
|
|
500
|
+
id integer primary key,
|
|
501
|
+
name text
|
|
502
|
+
);
|
|
503
|
+
create table posts_tags (
|
|
504
|
+
post_id integer references posts(id) on delete cascade,
|
|
505
|
+
tag_id integer references tags(id) on delete cascade
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
insert into posts (title, content) values ('T1', 'C1');
|
|
509
|
+
insert into posts (title, content) values ('T2', 'C2');
|
|
510
|
+
insert into tags (name) values ('tag1');
|
|
511
|
+
insert into tags (name) values ('tag2');
|
|
512
|
+
insert into tags (name) values ('tag3');
|
|
513
|
+
|
|
514
|
+
insert into posts_tags(post_id, tag_id) values (1, 1);
|
|
515
|
+
insert into posts_tags(post_id, tag_id) values (1, 2);
|
|
516
|
+
insert into posts_tags(post_id, tag_id) values (2, 2);
|
|
517
|
+
insert into posts_tags(post_id, tag_id) values (2, 3);
|
|
518
|
+
SQL
|
|
519
|
+
|
|
520
|
+
sql = <<~SQL
|
|
521
|
+
select
|
|
522
|
+
posts.id, posts.title, posts.content,
|
|
523
|
+
tags.id, tags.name
|
|
524
|
+
from posts
|
|
525
|
+
left outer join posts_tags on posts_tags.post_id = posts.id
|
|
526
|
+
left outer join tags on posts_tags.tag_id = tags.id
|
|
527
|
+
order by posts.id, tags.id
|
|
528
|
+
SQL
|
|
529
|
+
|
|
530
|
+
spec = {
|
|
531
|
+
columns: {
|
|
532
|
+
id: { type: :integer, identity: true },
|
|
533
|
+
title: { type: :text },
|
|
534
|
+
content: { type: :text },
|
|
535
|
+
tags: [{
|
|
536
|
+
type: :relation,
|
|
537
|
+
columns: {
|
|
538
|
+
id: { type: :integer, identity: true },
|
|
539
|
+
name: { type: :text }
|
|
540
|
+
}
|
|
541
|
+
}]
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
t = Extralite::Transform.new(spec)
|
|
546
|
+
result = @db.query(t, sql)
|
|
547
|
+
assert_kind_of Array, result
|
|
548
|
+
assert_equal 2, result.size
|
|
549
|
+
|
|
550
|
+
assert_equal 'T1', result[0][:title]
|
|
551
|
+
assert_equal 'C1', result[0][:content]
|
|
552
|
+
assert_equal [
|
|
553
|
+
{ id: 1, name: 'tag1' },
|
|
554
|
+
{ id: 2, name: 'tag2' }
|
|
555
|
+
], result[0][:tags]
|
|
556
|
+
|
|
557
|
+
assert_equal 'T2', result[1][:title]
|
|
558
|
+
assert_equal 'C2', result[1][:content]
|
|
559
|
+
assert_equal [
|
|
560
|
+
{ id: 2, name: 'tag2' },
|
|
561
|
+
{ id: 3, name: 'tag3' },
|
|
562
|
+
], result[1][:tags]
|
|
563
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
class TransformPreparedQueryTest < Minitest::Test
|
|
568
|
+
def setup
|
|
569
|
+
@db = Extralite::Database.new(':memory:')
|
|
570
|
+
@db.pragma('foreign_keys' => 1)
|
|
571
|
+
@db.execute <<~SQL
|
|
572
|
+
create table posts (
|
|
573
|
+
id integer primary key,
|
|
574
|
+
title text,
|
|
575
|
+
content text
|
|
576
|
+
);
|
|
577
|
+
create table tags (
|
|
578
|
+
id integer primary key,
|
|
579
|
+
name text
|
|
580
|
+
);
|
|
581
|
+
create table posts_tags (
|
|
582
|
+
post_id integer references posts(id) on delete cascade,
|
|
583
|
+
tag_id integer references tags(id) on delete cascade
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
insert into posts (title, content) values ('T1', 'C1');
|
|
587
|
+
insert into posts (title, content) values ('T2', 'C2');
|
|
588
|
+
insert into tags (name) values ('tag1');
|
|
589
|
+
insert into tags (name) values ('tag2');
|
|
590
|
+
insert into tags (name) values ('tag3');
|
|
591
|
+
|
|
592
|
+
insert into posts_tags(post_id, tag_id) values (1, 1);
|
|
593
|
+
insert into posts_tags(post_id, tag_id) values (1, 2);
|
|
594
|
+
insert into posts_tags(post_id, tag_id) values (2, 2);
|
|
595
|
+
insert into posts_tags(post_id, tag_id) values (2, 3);
|
|
596
|
+
SQL
|
|
597
|
+
|
|
598
|
+
@sql = <<~SQL
|
|
599
|
+
select
|
|
600
|
+
posts.id, posts.title, posts.content,
|
|
601
|
+
tags.id, tags.name
|
|
602
|
+
from posts
|
|
603
|
+
left outer join posts_tags on posts_tags.post_id = posts.id
|
|
604
|
+
left outer join tags on posts_tags.tag_id = tags.id
|
|
605
|
+
order by posts.id, tags.id
|
|
606
|
+
SQL
|
|
607
|
+
|
|
608
|
+
@spec = {
|
|
609
|
+
columns: {
|
|
610
|
+
id: { identity: true },
|
|
611
|
+
title: {},
|
|
612
|
+
content: {},
|
|
613
|
+
tags: [{
|
|
614
|
+
type: :relation,
|
|
615
|
+
columns: {
|
|
616
|
+
id: { identity: true },
|
|
617
|
+
name: {}
|
|
618
|
+
}
|
|
619
|
+
}]
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def test_transform_prepared_query
|
|
625
|
+
t = Extralite::Transform.new(@spec)
|
|
626
|
+
q = @db.prepare(t, @sql)
|
|
627
|
+
|
|
628
|
+
result = q.to_a
|
|
629
|
+
assert_kind_of Array, result
|
|
630
|
+
assert_equal 2, result.size
|
|
631
|
+
|
|
632
|
+
assert_equal 'T1', result[0][:title]
|
|
633
|
+
assert_equal 'C1', result[0][:content]
|
|
634
|
+
assert_equal [
|
|
635
|
+
{ id: 1, name: 'tag1' },
|
|
636
|
+
{ id: 2, name: 'tag2' }
|
|
637
|
+
], result[0][:tags]
|
|
638
|
+
|
|
639
|
+
assert_equal 'T2', result[1][:title]
|
|
640
|
+
assert_equal 'C2', result[1][:content]
|
|
641
|
+
assert_equal [
|
|
642
|
+
{ id: 2, name: 'tag2' },
|
|
643
|
+
{ id: 3, name: 'tag3' },
|
|
644
|
+
], result[1][:tags]
|
|
645
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def test_transform_prepared_query
|
|
649
|
+
t = Extralite::Transform.new(@spec)
|
|
650
|
+
q = @db.prepare(t, @sql)
|
|
651
|
+
|
|
652
|
+
result = q.to_a
|
|
653
|
+
assert_kind_of Array, result
|
|
654
|
+
assert_equal 2, result.size
|
|
655
|
+
|
|
656
|
+
assert_equal 'T1', result[0][:title]
|
|
657
|
+
assert_equal 'C1', result[0][:content]
|
|
658
|
+
assert_equal [
|
|
659
|
+
{ id: 1, name: 'tag1' },
|
|
660
|
+
{ id: 2, name: 'tag2' }
|
|
661
|
+
], result[0][:tags]
|
|
662
|
+
|
|
663
|
+
assert_equal 'T2', result[1][:title]
|
|
664
|
+
assert_equal 'C2', result[1][:content]
|
|
665
|
+
assert_equal [
|
|
666
|
+
{ id: 2, name: 'tag2' },
|
|
667
|
+
{ id: 3, name: 'tag3' },
|
|
668
|
+
], result[1][:tags]
|
|
669
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def test_transform_prepared_query_each
|
|
673
|
+
t = Extralite::Transform.new(@spec)
|
|
674
|
+
q = @db.prepare(t, @sql)
|
|
675
|
+
|
|
676
|
+
result = []
|
|
677
|
+
q.each { result << it }
|
|
678
|
+
assert_kind_of Array, result
|
|
679
|
+
assert_equal 2, result.size
|
|
680
|
+
|
|
681
|
+
assert_equal 'T1', result[0][:title]
|
|
682
|
+
assert_equal 'C1', result[0][:content]
|
|
683
|
+
assert_equal [
|
|
684
|
+
{ id: 1, name: 'tag1' },
|
|
685
|
+
{ id: 2, name: 'tag2' }
|
|
686
|
+
], result[0][:tags]
|
|
687
|
+
|
|
688
|
+
assert_equal 'T2', result[1][:title]
|
|
689
|
+
assert_equal 'C2', result[1][:content]
|
|
690
|
+
assert_equal [
|
|
691
|
+
{ id: 2, name: 'tag2' },
|
|
692
|
+
{ id: 3, name: 'tag3' },
|
|
693
|
+
], result[1][:tags]
|
|
694
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def test_transform_prepared_query_enumerable
|
|
698
|
+
t = Extralite::Transform.new(@spec)
|
|
699
|
+
q = @db.prepare(t, @sql)
|
|
700
|
+
|
|
701
|
+
result = []
|
|
702
|
+
e = q.each
|
|
703
|
+
e.each { result << it }
|
|
704
|
+
assert_kind_of Array, result
|
|
705
|
+
assert_equal 2, result.size
|
|
706
|
+
|
|
707
|
+
assert_equal 'T1', result[0][:title]
|
|
708
|
+
assert_equal 'C1', result[0][:content]
|
|
709
|
+
assert_equal [
|
|
710
|
+
{ id: 1, name: 'tag1' },
|
|
711
|
+
{ id: 2, name: 'tag2' }
|
|
712
|
+
], result[0][:tags]
|
|
713
|
+
|
|
714
|
+
assert_equal 'T2', result[1][:title]
|
|
715
|
+
assert_equal 'C2', result[1][:content]
|
|
716
|
+
assert_equal [
|
|
717
|
+
{ id: 2, name: 'tag2' },
|
|
718
|
+
{ id: 3, name: 'tag3' },
|
|
719
|
+
], result[1][:tags]
|
|
720
|
+
assert_equal result[0][:tags][1].object_id, result[1][:tags][0].object_id
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
class TransformDSLTest < Minitest::Test
|
|
725
|
+
def test_transform_dsl_many_to_many
|
|
726
|
+
t = Extralite::Transform.new do
|
|
727
|
+
{
|
|
728
|
+
id: integer.identity,
|
|
729
|
+
title: text,
|
|
730
|
+
content: text,
|
|
731
|
+
tags: [{
|
|
732
|
+
id: integer.identity,
|
|
733
|
+
name: text
|
|
734
|
+
}]
|
|
735
|
+
}
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
spec = {
|
|
739
|
+
columns: {
|
|
740
|
+
id: { type: :integer, identity: true },
|
|
741
|
+
title: { type: :text, },
|
|
742
|
+
content: { type: :text },
|
|
743
|
+
tags: [{
|
|
744
|
+
type: :relation,
|
|
745
|
+
columns: {
|
|
746
|
+
id: { type: :integer, identity: true },
|
|
747
|
+
name: { type: :text }
|
|
748
|
+
}
|
|
749
|
+
}]
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
assert_equal spec, t.to_h
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
def test_transform_dsl_many_to_many_auto_types
|
|
757
|
+
t = Extralite::Transform.new do
|
|
758
|
+
{
|
|
759
|
+
id: integer.identity,
|
|
760
|
+
title: auto,
|
|
761
|
+
content: auto,
|
|
762
|
+
tags: [{
|
|
763
|
+
id: identity,
|
|
764
|
+
name: text
|
|
765
|
+
}]
|
|
766
|
+
}
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
spec = {
|
|
770
|
+
columns: {
|
|
771
|
+
id: { type: :integer, identity: true },
|
|
772
|
+
title: {},
|
|
773
|
+
content: {},
|
|
774
|
+
tags: [{
|
|
775
|
+
type: :relation,
|
|
776
|
+
columns: {
|
|
777
|
+
id: { identity: true },
|
|
778
|
+
name: { type: :text }
|
|
779
|
+
}
|
|
780
|
+
}]
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
assert_equal spec, t.to_h
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def test_transform_dsl_one_to_one
|
|
788
|
+
t = Extralite::Transform.new do
|
|
789
|
+
{
|
|
790
|
+
id: integer.identity,
|
|
791
|
+
title: auto,
|
|
792
|
+
content: auto,
|
|
793
|
+
author: {
|
|
794
|
+
id: integer.identity,
|
|
795
|
+
name: text
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
spec = {
|
|
801
|
+
columns: {
|
|
802
|
+
id: { type: :integer, identity: true },
|
|
803
|
+
title: {},
|
|
804
|
+
content: {},
|
|
805
|
+
author: {
|
|
806
|
+
type: :relation,
|
|
807
|
+
columns: {
|
|
808
|
+
id: { type: :integer, identity: true },
|
|
809
|
+
name: { type: :text }
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
assert_equal spec, t.to_h
|
|
816
|
+
end
|
|
817
|
+
end
|