eno 0.4 → 0.5

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,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './ext'
4
+
5
+ class ExpressionTest < T
6
+ def test_aliases
7
+ assert_sql('select 1 as c') { select _l(1).as c }
8
+ assert_sql('select a as b') { select a.as b }
9
+ assert_sql('select a as b, c as d') { select (a.as b), (c.as d) }
10
+ end
11
+
12
+ def test_aliased_function_expressions
13
+ assert_sql('select pg_sleep(1) as s, pg_sleep(2) as s2') {
14
+ select pg_sleep(1).as(s), pg_sleep(2).as(s2)
15
+ }
16
+ end
17
+
18
+ def test_that_aliases_can_be_expressed_with_symbols
19
+ assert_sql('select pg_sleep(1) as s, pg_sleep(2) as s2') {
20
+ select pg_sleep(1).as(:s), pg_sleep(2).as(:s2)
21
+ }
22
+ end
23
+
24
+ def test_qualified_names
25
+ assert_sql('select a.b, c.d as e') {
26
+ select a.b, c.d.as(e)
27
+ }
28
+
29
+ assert_sql('select a.b.c.d') {
30
+ select a.b.c.d
31
+ }
32
+ end
33
+ end
34
+
35
+ class OpTest < T
36
+ def test_comparison_operators
37
+ assert_sql('select (a = b)') { select a == b }
38
+ assert_sql('select (a = b)') { select !(a != b) }
39
+ assert_sql('select (a = (b + c))') { select a == (b + c) }
40
+
41
+ assert_sql('select (a <> b)') { select (a != b) }
42
+ assert_sql('select (a <> b)') { select !(a == b) }
43
+
44
+ assert_sql('select (a > b), (c < d)') { select (a > b), (c < d) }
45
+ assert_sql('select (a >= b), (c <= d)') { select !(a < b), !(c > d) }
46
+
47
+ assert_sql('select (a >= b), (c <= d)') { select (a >= b), (c <= d) }
48
+ assert_sql('select (a > b), (c < d)') { select !(a <= b), !(c >= d) }
49
+ end
50
+
51
+ def test_math_operators
52
+ assert_sql('select (a + b), (c - d)') { select a + b, c - d }
53
+ assert_sql('select (a * b), (c / d), (e % f)') { select a * b, c / d, e % f }
54
+
55
+ assert_sql('select (a + (b * c))') { select a + b * c }
56
+ end
57
+
58
+ def test_logical_operators
59
+ assert_sql('select (a and b), (c or d)') { select a & b, c | d }
60
+ assert_sql('select (a and (not b))') { select a & !b }
61
+ assert_sql('select (not (a or b))') { select !(a | b) }
62
+ end
63
+
64
+ def test_cast_shorthand_operator
65
+ assert_sql('select a::integer') { select a^integer }
66
+ end
67
+ end
68
+
69
+ class CastTest < T
70
+ def test_that_cast_is_correctly_formatted
71
+ assert_sql('select cast (a as b)') { select a.cast(b) }
72
+ assert_sql('select cast (123 as float)') { select _l(123).cast(float) }
73
+ assert_sql("select cast ('123' as integer)") { select _l('123').cast(integer) }
74
+ end
75
+
76
+ def test_that_cast_shorthand_is_correctly_formatted
77
+ assert_sql('select a::b') { select a^b }
78
+ assert_sql('select 123::float') { select _l(123)^float }
79
+ assert_sql("select '2019-01-01 00:00+00'::timestamptz") {
80
+ select _l('2019-01-01 00:00+00')^timestamptz
81
+ }
82
+ end
83
+
84
+ def test_that_cast_works_wih_symbols
85
+ assert_sql('select cast (a as b)') { select a.cast(:b) }
86
+ end
87
+ end
88
+
89
+ class InTest < T
90
+ def test_that_in_is_correctly_formatted
91
+ assert_sql('select * where a in (1, 2, 3)') { where a.in 1, 2, 3 }
92
+ assert_sql('select * where a not in (1, 2, 3)') { where !a.in(1, 2, 3) }
93
+ end
94
+
95
+ def test_that_not_in_is_correcly_formatted
96
+ assert_sql('select * where a not in (1, 2, 3)') { where a.not_in 1, 2, 3 }
97
+ end
98
+ end
99
+
100
+ class LiteralTest < T
101
+ def test_that_numbers_are_correctly_quoted
102
+ assert_sql('select 123') { select 123 }
103
+ assert_sql('select 123') { select _l(123) }
104
+ assert_sql('select (2 + 2)') { select _l(2) + _l(2) }
105
+ end
106
+
107
+ def test_that_strings_are_correctly_quoted
108
+ assert_sql("select 'abc'") { select 'abc' }
109
+ end
110
+
111
+ def test_that_null_literal_is_correctly_quoted
112
+ assert_sql('select null') { select null }
113
+ end
114
+ end
115
+
116
+ class ConditionalTest < T
117
+ def test_that_cond_expression_is_correctly_formatted
118
+ assert_sql('select case when (a < b) then c else d end') {
119
+ select cond(
120
+ (a < b) => c,
121
+ default => d
122
+ )
123
+ }
124
+ end
125
+
126
+ def test_that_cond_expression_can_be_nested
127
+ assert_sql("select case when quality not in (1, 4, 5) then null when (datatype = 3) then case when unformatted_value::boolean then 1 else 0 end when (unformatted_value ~ '^[+-]?([0-9]*[.])?[0-9]+$') then unformatted_value::float else null end as value_float") {
128
+ select cond(
129
+ !quality.in(1, 4, 5) => null,
130
+ datatype == 3 => cond(
131
+ unformatted_value^boolean => 1,
132
+ default => 0
133
+ ),
134
+ unformatted_value =~ '^[+-]?([0-9]*[.])?[0-9]+$' => unformatted_value^float,
135
+ default => null
136
+ ).as value_float
137
+ }
138
+ end
139
+ end
140
+
141
+ class AliasTest < T
142
+ def test_that_alias_is_escaped_as_identifier
143
+ end
144
+ end
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './ext'
4
+
5
+ class ContextTest < T
6
+ def test_that_context_passed_can_be_used_in_query
7
+ query = Q(tbl: :nodes, field: :sample_rate, value: 42) {
8
+ select a, b
9
+ from tbl
10
+ where field < value
11
+ }
12
+ assert_equal(
13
+ 'select a, b from nodes where (sample_rate < 42)',
14
+ query.to_sql
15
+ )
16
+ end
17
+
18
+ def test_that_context_can_be_passed_in_to_sql_method
19
+ query = Q {
20
+ select a, b
21
+ from tbl
22
+ where field < value
23
+ }
24
+ assert_equal(
25
+ 'select a, b from nodes where (sample_rate < 43)',
26
+ query.to_sql(tbl: :nodes, field: :sample_rate, value: 43)
27
+ )
28
+ end
29
+
30
+ def test_that_to_sql_overrides_initial_context
31
+ query = Q(tbl: :nodes, field: :deadband) {
32
+ select a, b
33
+ from tbl
34
+ where field < value
35
+ }
36
+ assert_equal(
37
+ 'select a, b from nodes where (sample_rate < 42)',
38
+ query.to_sql(field: :sample_rate, value: 42)
39
+ )
40
+
41
+ assert_equal(
42
+ 'select a, b from nodes where (deadband < 42)',
43
+ query.to_sql(value: 42)
44
+ )
45
+ end
46
+
47
+ def test_that_context_is_accessible_for_sub_query
48
+ q1 = Q { select a }
49
+ q2 = Q { select b; from q1.as t1 }
50
+ assert_equal('select 3 from (select 2) t1', q2.to_sql(a: 2, b: 3))
51
+ assert_equal('select 3 from (select 2) tbl', q2.to_sql(a: 2, b: 3, t1: :tbl))
52
+ end
53
+
54
+ def test_that_to_sql_context_overrides_initialized_context
55
+ q1 = Q(t1: :tbl1) { select a from t1 }
56
+ q2 = Q(t2: :tbl2) { select b; from q1.as t2 }
57
+ assert_equal('select 3 from (select 2 from tbl1) tbl2', q2.to_sql(a: 2, b: 3))
58
+ end
59
+
60
+ def test_that_context_method_gives_context
61
+ q = Q { from users; where name == context[:user_name] }
62
+ assert_equal("select * from users where (name = 'foo')", q.to_sql(user_name: 'foo'))
63
+ end
64
+ end
65
+
66
+ class MutationTest < T
67
+ def test_that_query_can_further_refined_with_where_clause
68
+ q = Q {
69
+ select a, b
70
+ }
71
+ assert_equal('select a, b', q.to_sql)
72
+
73
+ q2 = q.where { c < d}
74
+ assert(q != q2)
75
+ assert_equal('select a, b', q.to_sql)
76
+ assert_equal('select a, b where (c < d)', q2.to_sql)
77
+
78
+ q = Q {
79
+ where _l(2) + _l(2) == _l(5)
80
+ }
81
+ assert_equal('select * where ((2 + 2) = 5)', q.to_sql)
82
+
83
+ q2 = q.where { _l('up') == _l('down') }
84
+ assert_equal("select * where ((2 + 2) = 5) and ('up' = 'down')", q2.to_sql)
85
+ end
86
+
87
+ def test_that_mutated_query_can_change_arbitrary_clauses
88
+ q = Q { select a; from b }
89
+ assert_equal('select a from b', q.to_sql)
90
+
91
+ q2 = q.mutate { from c }
92
+ assert_equal('select a from b', q.to_sql)
93
+ assert_equal('select a from c', q2.to_sql)
94
+ end
95
+ end
96
+
97
+ class ConvenienceVariablesTest < T
98
+ def test_that_convenience_variables_do_not_change_query
99
+ assert_sql('select unformatted_value::boolean, unformatted_value::float') {
100
+ uv = unformatted_value
101
+ select uv^boolean, uv^float
102
+ }
103
+ end
104
+ end
105
+
106
+ class CustomFunctionTest < T
107
+ class Eno::SQL
108
+ FLOAT_RE = '^[+-]?([0-9]*[.])?[0-9]+$'.freeze
109
+
110
+ def cast_value
111
+ uv = unformatted_value
112
+ cond(
113
+ quality.not_in(1, 4, 5) => null,
114
+ (datatype == 3) => cond(uv^boolean => 1, default => 0),
115
+ (uv =~ FLOAT_RE) => uv^float,
116
+ default => null
117
+ )
118
+ end
119
+ end
120
+
121
+ def test_that_custom_function_can_be_used_normally
122
+ assert_sql("select case when quality not in (1, 4, 5) then null when (datatype = 3) then case when unformatted_value::boolean then 1 else 0 end when (unformatted_value ~ '^[+-]?([0-9]*[.])?[0-9]+$') then unformatted_value::float else null end as value_float") {
123
+ select cast_value.as value_float
124
+ }
125
+ end
126
+ end
127
+
128
+ class CombinationTest < T
129
+ def test_union
130
+ query = Q { select a }.union { select b}
131
+ assert_equal("(select a) union (select b)", query.to_sql)
132
+
133
+ q1 = Q { select a }
134
+ q2 = Q { select b }
135
+ assert_equal("(select a) union (select b)", q1.union(q2).to_sql)
136
+
137
+ q3 = q1.union(q2).union { select c }
138
+ assert_equal("((select a) union (select b)) union (select c)", q3.to_sql)
139
+
140
+ q4 = q1.union(Q { select b}, Q { select c })
141
+ assert_equal("(select a) union (select b) union (select c)", q4.to_sql)
142
+
143
+ assert_sql("(select a) union (select b)") {
144
+ union q1, q2
145
+ }
146
+ end
147
+
148
+ def test_union_all
149
+ q1 = Q { select a }.union(all: true) { select b }
150
+ assert_equal("(select a) union all (select b)", q1.to_sql)
151
+
152
+ q2 = Q { select a }.union_all(Q { select b}, Q { select c })
153
+ assert_equal("(select a) union all (select b) union all (select c)", q2.to_sql)
154
+ end
155
+
156
+ def test_union_shorthand
157
+ q1 = Q { select a }
158
+ q2 = Q { select b }
159
+ q3 = Q { select c }
160
+ assert_equal(
161
+ '((select a) union (select b)) union (select c)',
162
+ (q1 | q2 | q3).to_sql
163
+ )
164
+ end
165
+
166
+ def test_intersect
167
+ query = Q { select a }.intersect { select b}
168
+ assert_equal("(select a) intersect (select b)", query.to_sql)
169
+
170
+ q1 = Q { select a }
171
+ q2 = Q { select b }
172
+ assert_equal("(select a) intersect (select b)", q1.intersect(q2).to_sql)
173
+
174
+ q3 = q1.intersect(q2).intersect { select c }
175
+ assert_equal("((select a) intersect (select b)) intersect (select c)", q3.to_sql)
176
+
177
+ q4 = q1.intersect(Q { select b}, Q { select c })
178
+ assert_equal("(select a) intersect (select b) intersect (select c)", q4.to_sql)
179
+
180
+ assert_sql("(select a) intersect (select b)") {
181
+ intersect q1, q2
182
+ }
183
+ end
184
+
185
+ def test_intersect_all
186
+ q1 = Q { select a }.intersect(all: true) { select b }
187
+ assert_equal("(select a) intersect all (select b)", q1.to_sql)
188
+
189
+ q2 = Q { select a }.intersect_all(Q { select b}, Q { select c })
190
+ assert_equal("(select a) intersect all (select b) intersect all (select c)", q2.to_sql)
191
+ end
192
+
193
+ def test_intersect_shorthand
194
+ q1 = Q { select a }
195
+ q2 = Q { select b }
196
+ q3 = Q { select c }
197
+ assert_equal(
198
+ '((select a) intersect (select b)) intersect (select c)',
199
+ (q1 & q2 & q3).to_sql
200
+ )
201
+ end
202
+
203
+ def test_except
204
+ query = Q { select a }.except { select b}
205
+ assert_equal("(select a) except (select b)", query.to_sql)
206
+
207
+ q1 = Q { select a }
208
+ q2 = Q { select b }
209
+ assert_equal("(select a) except (select b)", q1.except(q2).to_sql)
210
+
211
+ q3 = q1.except(q2).except { select c }
212
+ assert_equal("((select a) except (select b)) except (select c)", q3.to_sql)
213
+
214
+ q4 = q1.except(Q { select b}, Q { select c })
215
+ assert_equal("(select a) except (select b) except (select c)", q4.to_sql)
216
+
217
+ assert_sql("(select a) except (select b)") {
218
+ except q1, q2
219
+ }
220
+ end
221
+
222
+ def test_except_all
223
+ q1 = Q { select a }.except(all: true) { select b }
224
+ assert_equal("(select a) except all (select b)", q1.to_sql)
225
+
226
+ q2 = Q { select a }.except_all(Q { select b}, Q { select c })
227
+ assert_equal("(select a) except all (select b) except all (select c)", q2.to_sql)
228
+ end
229
+
230
+ def test_except_shorthand
231
+ q1 = Q { select a }
232
+ q2 = Q { select b }
233
+ q3 = Q { select c }
234
+ assert_equal(
235
+ '((select a) except (select b)) except (select c)',
236
+ (q1 ^ q2 ^ q3).to_sql
237
+ )
238
+ end
239
+
240
+ def test_combination
241
+ q1 = Q { select a }
242
+ q2 = Q { select b }
243
+ q3 = Q { select c }
244
+ assert_equal(
245
+ '((select a) union (select b)) intersect (select c)',
246
+ ((q1 | q2) & q3).to_sql
247
+ )
248
+
249
+ assert_equal(
250
+ '((select 1) union (select 2)) intersect (select 1)',
251
+ ((q1 | q2) & q3).to_sql(a: 1, b: 2, c: 1)
252
+ )
253
+ end
254
+ end
255
+
256
+ class UseCasesTest < T
257
+ class Eno::SQL
258
+ def extract_epoch_from(sym)
259
+ ExtractEpoch.new(sym)
260
+ end
261
+ end
262
+
263
+ class ExtractEpoch < Eno::Expression
264
+ def to_sql(sql)
265
+ "extract (epoch from #{sql.quote(@members[0])})::integer"
266
+ end
267
+ end
268
+
269
+ def test_1
270
+ assert_sql("select extract (epoch from stamp)::integer as stamp, quality, value, unformatted_value, datatype from states where ((path = '/r1') and (stamp >= '2019-01-01 00:00:00+00') and (stamp < '2019-01-02 00:00:00+00')) order by stamp desc limit 1") {
271
+ select extract_epoch_from(stamp).as(stamp), quality, value, unformatted_value, datatype
272
+ from states
273
+ where (path == '/r1') & (stamp >= '2019-01-01 00:00:00+00') & (stamp < '2019-01-02 00:00:00+00')
274
+ order_by stamp.desc
275
+ limit 1
276
+ }
277
+ end
278
+
279
+ def test_2
280
+ # http://www.postgresqltutorial.com/postgresql-window-function/
281
+ assert_sql('select product_name, price, group_name, avg(price) over (partition by group_name) from products inner join product_groups using (group_id)') {
282
+ select product_name,
283
+ price,
284
+ group_name,
285
+ avg(price).over { partition_by group_name }
286
+ from products.inner_join(product_groups, using: group_id)
287
+ }
288
+ end
289
+
290
+ def test_3
291
+ # http://www.postgresqltutorial.com/postgresql-window-function/
292
+ assert_sql('select product_name, group_name, price, row_number() over (partition by group_name order by price) from products inner join product_groups using (group_id)') {
293
+ select product_name,
294
+ group_name,
295
+ price,
296
+ row_number(_).over {
297
+ partition_by group_name
298
+ order_by price
299
+ }
300
+ from products.inner_join(product_groups, using: group_id)
301
+ }
302
+ end
303
+
304
+ def test_4
305
+ # http://www.postgresqltutorial.com/postgresql-window-function/
306
+ assert_sql('select product_name, group_name, price, lag(price, 1) over (partition by group_name order by price) as prev_price, (price - lag(price, 1) over (partition by group_name order by price)) as cur_prev_diff from products inner join product_groups using (group_id)') {
307
+ select product_name,
308
+ group_name,
309
+ price,
310
+ lag(price, 1).over {
311
+ partition_by group_name
312
+ order_by price
313
+ }.as(prev_price),
314
+ (price - lag(price, 1).over {
315
+ partition_by group_name
316
+ order_by price
317
+ }).as(cur_prev_diff)
318
+ from products.inner_join product_groups, using: group_id
319
+ }
320
+ end
321
+ end