alf 0.14.0 → 0.15.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.
Files changed (67) hide show
  1. data/Gemfile +6 -3
  2. data/Gemfile.lock +65 -19
  3. data/README.md +77 -183
  4. data/Rakefile +25 -0
  5. data/alf.gemspec +5 -3
  6. data/alf.noespec +8 -6
  7. data/lib/alf/loader.rb +1 -0
  8. data/lib/alf/version.rb +1 -1
  9. data/spec/facade/test_query.rb +12 -0
  10. data/spec/key-inference/queries.yml +10 -0
  11. data/spec/key-inference/test_all.rb +21 -0
  12. data/{test → spec}/migrations/test_folder_migration.rb +0 -0
  13. data/{test → spec}/migrations/test_sequel_migration.rb +1 -1
  14. data/spec/operators/ungroup/grouped.json +3 -0
  15. data/spec/operators/ungroup/test_on_json_content.rb +11 -0
  16. data/spec/operators/unwrap/test_on_json_content.rb +11 -0
  17. data/spec/operators/unwrap/wrapped.json +3 -0
  18. data/spec/optimizer/project/extend.yml +20 -0
  19. data/spec/optimizer/project/intersect.yml +10 -0
  20. data/spec/optimizer/project/join.yml +20 -0
  21. data/spec/optimizer/project/matching.yml +21 -0
  22. data/spec/optimizer/project/minus.yml +9 -0
  23. data/spec/optimizer/project/not_matching.yml +21 -0
  24. data/spec/optimizer/project/project.yml +88 -0
  25. data/spec/optimizer/project/rename.yml +30 -0
  26. data/spec/optimizer/project/sort.yml +45 -0
  27. data/spec/optimizer/project/union.yml +8 -0
  28. data/spec/optimizer/restrict/clip.yml +4 -0
  29. data/spec/optimizer/restrict/compact.yml +4 -0
  30. data/spec/optimizer/restrict/generator.yml +4 -0
  31. data/spec/optimizer/restrict/intersect.yml +4 -0
  32. data/spec/optimizer/restrict/leaf_operand.yml +4 -0
  33. data/spec/optimizer/restrict/minus.yml +4 -0
  34. data/spec/optimizer/restrict/page.yml +12 -0
  35. data/spec/optimizer/restrict/project.yml +4 -0
  36. data/spec/optimizer/restrict/sort.yml +4 -0
  37. data/spec/optimizer/restrict/union.yml +4 -0
  38. data/spec/optimizer/test_all.rb +34 -0
  39. data/spec/sql/helpers.rb +25 -0
  40. data/spec/sql/queries/01-leaf-operand.yml +5 -0
  41. data/spec/sql/queries/02-clip.yml +12 -0
  42. data/spec/sql/queries/03-sort.yml +58 -0
  43. data/spec/sql/queries/04-frame.yml +57 -0
  44. data/spec/sql/queries/05-intersect.yml +23 -0
  45. data/spec/sql/queries/06-join.yml +207 -0
  46. data/spec/sql/queries/07-matching.yml +76 -0
  47. data/spec/sql/queries/08-minus.yml +23 -0
  48. data/spec/sql/queries/09-not-matching.yml +57 -0
  49. data/spec/sql/queries/10-page.yml +31 -0
  50. data/spec/sql/queries/11-project.yml +48 -0
  51. data/spec/sql/queries/12-rename.yml +24 -0
  52. data/spec/sql/queries/13-restrict.yml +114 -0
  53. data/spec/sql/queries/15-union.yml +90 -0
  54. data/spec/sql/queries/16-wrap.yml +3 -0
  55. data/spec/sql/queries/91-reuse.yml +28 -0
  56. data/spec/sql/test_sequel_compiler.rb +41 -0
  57. data/spec/sql/test_sql_compiler.rb +52 -0
  58. data/{test → spec}/test_alf.rb +0 -0
  59. data/spec/test_helpers.rb +54 -0
  60. data/tasks/doc.rake +10 -0
  61. data/tasks/fixtures.rake +52 -0
  62. data/tasks/mod.rake +50 -0
  63. data/tasks/release.rake +34 -0
  64. data/tasks/test.rake +2 -2
  65. metadata +150 -19
  66. data/test/seeding/test_seeding.rb +0 -49
  67. data/test/test_helpers.rb +0 -24
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(project(suppliers, [:sid, :name]), city: 'Paris')
3
+ optimized: |-
4
+ project(restrict(suppliers, city: 'Paris'), [:sid, :name])
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(sort(suppliers, [:sid, :asc]), city: 'Paris')
3
+ optimized: |-
4
+ sort(restrict(suppliers, city: 'Paris'), [:sid, :asc])
@@ -0,0 +1,4 @@
1
+ - alf: |-
2
+ restrict(union(suppliers, suppliers), city: 'Paris')
3
+ optimized: |-
4
+ union(restrict(suppliers, city: 'Paris'), restrict(suppliers, city: 'Paris'))
@@ -0,0 +1,34 @@
1
+ require 'test_helpers'
2
+
3
+ Path.dir.glob('*').select{|f| f.directory? }.each do |folder|
4
+ optimizer = Alf::Optimizer.const_get(folder.basename.to_s.capitalize)
5
+
6
+ describe optimizer do
7
+
8
+ folder.glob('*.yml').each do |file|
9
+ context "On #{file.basename}" do
10
+
11
+ file.load.each do |query|
12
+ alf, optimized = query['alf'], query['optimized']
13
+
14
+ context "on #{alf}" do
15
+ let(:expr) { conn.parse(alf) }
16
+ let(:expected){ conn.parse(optimized) }
17
+
18
+ subject{ optimizer.new.call(expr) }
19
+
20
+ it 'should be optimized as expected' do
21
+ subject.should eq(expected)
22
+ end
23
+
24
+ it 'should have same heading as the initial expression' do
25
+ subject.heading.should eq(expr.heading)
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helpers'
2
+
3
+ module SqlTestHelpers
4
+
5
+ def each_query_file(&bl)
6
+ (Path.dir/'queries').glob("*.yml", &bl)
7
+ end
8
+
9
+ def each_query(&bl)
10
+ each_query_file do |file|
11
+ queries = file.load
12
+ queries.each(&bl)
13
+ end
14
+ end
15
+
16
+ def strip(x)
17
+ x.strip.gsub(/\s+/, " ").gsub(/\(\s+/, "(").gsub(/\s+\)/, ")")
18
+ end
19
+
20
+ end
21
+
22
+ RSpec.configure do |c|
23
+ c.include(SqlTestHelpers)
24
+ c.extend(SqlTestHelpers)
25
+ end
@@ -0,0 +1,5 @@
1
+ - alf: |-
2
+ suppliers
3
+ sql: |-
4
+ SELECT t1.sid, t1.name, t1.status, t1.city
5
+ FROM suppliers AS t1
@@ -0,0 +1,12 @@
1
+ # normal clip
2
+ - alf: |-
3
+ clip(suppliers, [:sid, :status])
4
+ sql: |-
5
+ SELECT t1.sid, t1.status
6
+ FROM suppliers AS t1
7
+ # clip with no attribute at all
8
+ - alf: |-
9
+ clip(suppliers, [])
10
+ sql: |-
11
+ SELECT TRUE AS is_table_dee
12
+ WHERE EXISTS(SELECT * FROM suppliers AS t1)
@@ -0,0 +1,58 @@
1
+ - alf: |-
2
+ sort(suppliers, [[:sid, :asc]])
3
+ sql: |-
4
+ SELECT t1.sid, t1.name, t1.status, t1.city
5
+ FROM suppliers AS t1
6
+ ORDER BY t1.sid ASC
7
+ #
8
+ - alf: |-
9
+ sort(suppliers, [[:sid, :desc]])
10
+ sql: |-
11
+ SELECT t1.sid, t1.name, t1.status, t1.city
12
+ FROM suppliers AS t1
13
+ ORDER BY t1.sid DESC
14
+ #
15
+ - alf: |-
16
+ sort(suppliers, [[:city, :desc], [:sid, :asc]])
17
+ sql: |-
18
+ SELECT t1.sid, t1.name, t1.status, t1.city
19
+ FROM suppliers AS t1
20
+ ORDER BY t1.city DESC, t1.sid ASC
21
+ #
22
+ - alf: |-
23
+ sort(sort(suppliers, [[:sid, :asc], [:city, :desc]]), [:sid, :asc])
24
+ sql: |-
25
+ SELECT t1.sid, t1.name, t1.status, t1.city
26
+ FROM suppliers AS t1
27
+ ORDER BY t1.sid ASC, t1.city DESC
28
+ #
29
+ - alf: |-
30
+ sort(sort(suppliers, [:sid, :asc]), [:sid, :desc])
31
+ sql: |-
32
+ WITH t2 AS (
33
+ SELECT t1.sid,
34
+ t1.name,
35
+ t1.status,
36
+ t1.city
37
+ FROM suppliers AS t1
38
+ ORDER BY t1.sid ASC
39
+ )
40
+ SELECT t2.sid, t2.name, t2.status, t2.city
41
+ FROM t2 AS t2
42
+ ORDER BY t2.sid DESC
43
+ #
44
+ - alf: |-
45
+ sort(union(suppliers_in_paris, suppliers_in_london), [[:sid, :asc]])
46
+ sql: |-
47
+ WITH t3 AS (
48
+ (SELECT t1.sid, t1.name, t1.status, t1.city
49
+ FROM suppliers AS t1
50
+ WHERE t1.city = 'Paris')
51
+ UNION
52
+ (SELECT t2.sid, t2.name, t2.status, t2.city
53
+ FROM suppliers AS t2
54
+ WHERE t2.city = 'London')
55
+ )
56
+ SELECT t3.sid, t3.name, t3.status, t3.city
57
+ FROM t3 AS t3
58
+ ORDER BY t3.sid ASC
@@ -0,0 +1,57 @@
1
+ # BASE case
2
+ - alf: |-
3
+ frame(suppliers, [[:sid, :asc]], 2, 4)
4
+ sql: |-
5
+ SELECT t1.sid, t1.name, t1.status, t1.city
6
+ FROM suppliers AS t1
7
+ ORDER BY t1.sid ASC
8
+ LIMIT 4 OFFSET 2
9
+ # KEY INFERENCE for total ordering
10
+ - alf: |-
11
+ frame(suppliers, [[:city, :desc]], 2, 4)
12
+ sql: |-
13
+ SELECT t1.sid, t1.name, t1.status, t1.city
14
+ FROM suppliers AS t1
15
+ ORDER BY t1.city DESC, t1.sid ASC
16
+ LIMIT 4 OFFSET 2
17
+ # ORDER BY already ok
18
+ - alf: |-
19
+ frame(sort(suppliers, [[:city, :desc], [:sid, :asc]]), [[:city, :desc]], 2, 4)
20
+ sql: |-
21
+ SELECT t1.sid, t1.name, t1.status, t1.city
22
+ FROM suppliers AS t1
23
+ ORDER BY t1.city DESC, t1.sid ASC
24
+ LIMIT 4 OFFSET 2
25
+ # ORDER BY existing but incompatible
26
+ - alf: |-
27
+ frame(sort(suppliers, [[:sid, :asc], [:city, :desc]]), [[:city, :desc]], 2, 4)
28
+ sql: |-
29
+ WITH t2 AS (
30
+ SELECT t1.sid, t1.name, t1.status, t1.city
31
+ FROM suppliers AS t1
32
+ ORDER BY t1.sid ASC, t1.city DESC
33
+ )
34
+ SELECT t2.sid, t2.name, t2.status, t2.city
35
+ FROM t2 AS t2
36
+ ORDER BY t2.city DESC, t2.sid ASC
37
+ LIMIT 4 OFFSET 2
38
+ # UNION and other nadics
39
+ - alf: |-
40
+ frame(union(suppliers_in_paris, suppliers_in_london), [[:sid, :asc]], 2, 4)
41
+ sql: |-
42
+ WITH t3 AS (
43
+ (SELECT t1.sid, t1.name, t1.status, t1.city
44
+ FROM suppliers AS t1
45
+ WHERE t1.city = 'Paris')
46
+ UNION
47
+ (SELECT t2.sid, t2.name, t2.status, t2.city
48
+ FROM suppliers AS t2
49
+ WHERE t2.city = 'London')
50
+ )
51
+ SELECT t3.sid, t3.name, t3.status, t3.city
52
+ FROM t3 AS t3
53
+ ORDER BY t3.sid ASC, t3.name ASC, t3.status ASC, t3.city ASC
54
+ LIMIT 4 OFFSET 2
55
+ comment: |-
56
+ The big ordering comes from the fact that key inference is not smart
57
+ enough to detect that sid is still a key in the resulting union.
@@ -0,0 +1,23 @@
1
+ - alf: |-
2
+ intersect(suppliers_in_paris, suppliers_in_london)
3
+ sql: |-
4
+ (SELECT t1.sid, t1.name, t1.status, t1.city
5
+ FROM suppliers AS t1
6
+ WHERE t1.city = 'Paris')
7
+ INTERSECT
8
+ (SELECT t2.sid, t2.name, t2.status, t2.city
9
+ FROM suppliers AS t2
10
+ WHERE t2.city = 'London')
11
+ - alf: |-
12
+ intersect(intersect(suppliers_in_paris, suppliers_in_london), suppliers)
13
+ sql: |-
14
+ ((SELECT t1.sid, t1.name, t1.status, t1.city
15
+ FROM suppliers AS t1
16
+ WHERE t1.city = 'Paris')
17
+ INTERSECT
18
+ (SELECT t2.sid, t2.name, t2.status, t2.city
19
+ FROM suppliers AS t2
20
+ WHERE t2.city = 'London'))
21
+ INTERSECT
22
+ (SELECT t3.sid, t3.name, t3.status, t3.city
23
+ FROM suppliers AS t3)
@@ -0,0 +1,207 @@
1
+ # cross join
2
+ - alf: |-
3
+ join(project(suppliers, [:sid]), project(parts, [:pid]))
4
+ sql:
5
+ SELECT t1.sid, t2.pid
6
+ FROM suppliers AS t1, parts AS t2
7
+ # inner join
8
+ - alf: |-
9
+ join(suppliers, supplies)
10
+ sql:
11
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
12
+ FROM suppliers AS t1
13
+ JOIN supplies AS t2 ON t1.sid = t2.sid
14
+ # inner join with same operand
15
+ - alf: |-
16
+ join(suppliers, suppliers)
17
+ sql: |-
18
+ SELECT t1.sid, t1.name, t1.status, t1.city
19
+ FROM suppliers AS t1
20
+ JOIN suppliers AS t2 ON t1.sid = t2.sid
21
+ AND t1.name = t2.name
22
+ AND t1.status = t2.status
23
+ AND t1.city = t2.city
24
+ # inner join requiring keeping a common distinct
25
+ - alf: |-
26
+ join(project(suppliers, [:city]), project(parts, [:city]))
27
+ sql:
28
+ SELECT DISTINCT t1.city
29
+ FROM suppliers AS t1
30
+ JOIN parts AS t2 ON t1.city = t2.city
31
+ # inner join requiring keeping the left distinct
32
+ - alf: |-
33
+ join(project(suppliers, [:city]), parts)
34
+ sql:
35
+ SELECT DISTINCT t1.city, t2.pid, t2.name, t2.color, t2.weight
36
+ FROM suppliers AS t1
37
+ JOIN parts AS t2 ON t1.city = t2.city
38
+ # inner join requiring keeping the right distinct
39
+ - alf: |-
40
+ join(suppliers, project(parts, [:city]))
41
+ sql:
42
+ SELECT DISTINCT t1.sid, t1.name, t1.status, t1.city
43
+ FROM suppliers AS t1
44
+ JOIN parts AS t2 ON t1.city = t2.city
45
+ # inner join requiring renamings
46
+ - alf: |-
47
+ join(rename(suppliers, sid: :id), rename(supplies, sid: :id))
48
+ sql:
49
+ SELECT t1.sid AS id, t1.name, t1.status, t1.city, t2.pid, t2.qty
50
+ FROM suppliers AS t1
51
+ JOIN supplies AS t2 ON t1.sid = t2.sid
52
+ # triple join
53
+ - alf: |-
54
+ join(join(suppliers, supplies), project(parts, [:pid, :color]))
55
+ sql:
56
+ WITH t4 AS (
57
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
58
+ FROM suppliers AS t1
59
+ JOIN supplies AS t2 ON t1.sid = t2.sid
60
+ )
61
+ SELECT t4.sid, t4.name, t4.status, t4.city, t4.pid, t4.qty, t3.color
62
+ FROM t4 AS t4
63
+ JOIN parts AS t3 ON t4.pid = t3.pid
64
+ # join of two joins
65
+ - alf: |-
66
+ join(join(suppliers, cities), join(project(parts, [:pid, :color]), supplies))
67
+ sql:
68
+ WITH
69
+ t5 AS (
70
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.country
71
+ FROM suppliers AS t1
72
+ JOIN cities AS t2 ON t1.city = t2.city
73
+ ),
74
+ t6 AS (
75
+ SELECT t3.pid, t3.color, t4.sid, t4.qty
76
+ FROM parts AS t3
77
+ JOIN supplies AS t4 ON t3.pid = t4.pid
78
+ )
79
+ SELECT t5.sid, t5.name, t5.status, t5.city, t5.country, t6.pid, t6.color, t6.qty
80
+ FROM t5 AS t5
81
+ JOIN t6 AS t6 ON t5.sid = t6.sid
82
+ # join with a where clause at left
83
+ - alf: |-
84
+ join(restrict(suppliers, sid: 'S2'), supplies)
85
+ sql:
86
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
87
+ FROM suppliers AS t1
88
+ JOIN supplies AS t2 ON t1.sid = t2.sid
89
+ WHERE t1.sid = 'S2'
90
+ # join with a where clause at right
91
+ - alf: |-
92
+ join(suppliers, restrict(supplies, sid: 'S2'))
93
+ sql:
94
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
95
+ FROM suppliers AS t1
96
+ JOIN supplies AS t2 ON t1.sid = t2.sid
97
+ WHERE t2.sid = 'S2'
98
+ # join with where clauses both sides
99
+ - alf: |-
100
+ join(restrict(suppliers, sid: 'S2'), restrict(supplies, qty: 300))
101
+ sql:
102
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
103
+ FROM suppliers AS t1
104
+ JOIN supplies AS t2 ON t1.sid = t2.sid
105
+ WHERE t1.sid = 'S2'
106
+ AND t2.qty = 300
107
+ # join with an order-by clause at left
108
+ - alf: |-
109
+ join(sort(suppliers, [:sid, :asc]), supplies)
110
+ sql:
111
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
112
+ FROM suppliers AS t1
113
+ JOIN supplies AS t2 ON t1.sid = t2.sid
114
+ ORDER BY t1.sid ASC
115
+ # join with an order-by clause at right
116
+ - alf: |-
117
+ join(suppliers, sort(supplies, [:qty, :asc]))
118
+ sql:
119
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
120
+ FROM suppliers AS t1
121
+ JOIN supplies AS t2 ON t1.sid = t2.sid
122
+ ORDER BY t2.qty ASC
123
+ # join with an order-by clause on both sides
124
+ - alf: |-
125
+ join(sort(suppliers, [:sid, :asc]), sort(supplies, [:qty, :asc]))
126
+ sql:
127
+ SELECT t1.sid, t1.name, t1.status, t1.city, t2.pid, t2.qty
128
+ FROM suppliers AS t1
129
+ JOIN supplies AS t2 ON t1.sid = t2.sid
130
+ ORDER BY t1.sid ASC, t2.qty ASC
131
+ # join with a limit/offset clause at left
132
+ - alf: |-
133
+ join(frame(suppliers, [:sid, :asc], 5, 10), supplies)
134
+ sql:
135
+ WITH t3 AS (
136
+ SELECT t1.sid, t1.name, t1.status, t1.city
137
+ FROM suppliers AS t1
138
+ ORDER BY t1.sid ASC
139
+ LIMIT 10 OFFSET 5
140
+ )
141
+ SELECT t3.sid, t3.name, t3.status, t3.city, t2.pid, t2.qty
142
+ FROM t3 AS t3
143
+ JOIN supplies AS t2 ON t3.sid = t2.sid
144
+ # join with a limit/offset clause at right
145
+ - alf: |-
146
+ join(suppliers, frame(supplies, [[:sid, :asc], [:pid, :asc]], 5, 10))
147
+ sql:
148
+ WITH t3 AS (
149
+ SELECT t2.sid, t2.pid, t2.qty
150
+ FROM supplies AS t2
151
+ ORDER BY t2.sid ASC, t2.pid ASC
152
+ LIMIT 10 OFFSET 5
153
+ )
154
+ SELECT t1.sid, t1.name, t1.status, t1.city, t3.pid, t3.qty
155
+ FROM suppliers AS t1
156
+ JOIN t3 AS t3 ON t1.sid = t3.sid
157
+ # join with a limit/offset clause on both sides
158
+ - alf: |-
159
+ join(frame(suppliers, [:sid, :asc], 5, 10),
160
+ frame(supplies, [[:sid, :asc], [:pid, :asc]], 5, 10))
161
+ sql:
162
+ WITH t3 AS (SELECT t1.sid, t1.name, t1.status, t1.city
163
+ FROM suppliers AS t1
164
+ ORDER BY t1.sid ASC
165
+ LIMIT 10 OFFSET 5),
166
+ t4 AS (SELECT t2.sid, t2.pid, t2.qty
167
+ FROM supplies AS t2
168
+ ORDER BY t2.sid ASC, t2.pid ASC
169
+ LIMIT 10 OFFSET 5)
170
+ SELECT t3.sid, t3.name, t3.status, t3.city, t4.pid, t4.qty
171
+ FROM t3 AS t3
172
+ JOIN t4 AS t4 ON t3.sid = t4.sid
173
+ # join on a union at left
174
+ - alf: |-
175
+ join(union(suppliers_in_london, suppliers_in_paris), supplies)
176
+ sql:
177
+ WITH t4 AS (
178
+ (SELECT t1.sid, t1.name, t1.status, t1.city
179
+ FROM suppliers AS t1
180
+ WHERE t1.city = 'London')
181
+ UNION
182
+ (SELECT t2.sid, t2.name, t2.status, t2.city
183
+ FROM suppliers AS t2
184
+ WHERE t2.city = 'Paris')
185
+ )
186
+ SELECT t4.sid, t4.name, t4.status, t4.city, t3.pid, t3.qty
187
+ FROM t4 AS t4
188
+ JOIN supplies AS t3 ON t4.sid = t3.sid
189
+ # join on a union at right
190
+ - alf: |-
191
+ join(supplies, union(suppliers_in_london, suppliers_in_paris))
192
+ sql:
193
+ WITH t4 AS (
194
+ (SELECT t2.sid, t2.name, t2.status, t2.city
195
+ FROM suppliers AS t2
196
+ WHERE t2.city = 'London')
197
+ UNION
198
+ (SELECT t3.sid, t3.name, t3.status, t3.city
199
+ FROM suppliers AS t3
200
+ WHERE t3.city = 'Paris')
201
+ )
202
+ SELECT t1.sid, t1.pid, t1.qty, t4.name, t4.status, t4.city
203
+ FROM supplies AS t1
204
+ JOIN t4 AS t4 ON t1.sid = t4.sid
205
+ # joining from Relation::DEE
206
+ - alf: |-
207
+ join(Relation::DEE, suppliers)