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,555 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :Expression,
4
+
5
+ :Alias,
6
+ :Case,
7
+ :Cast,
8
+ :CastShorthand,
9
+ :Desc,
10
+ :FunctionCall,
11
+ :Identifier,
12
+ :In,
13
+ :IsNotNull,
14
+ :IsNull,
15
+ :Join,
16
+ :Literal,
17
+ :Operator,
18
+ :Over,
19
+ :Not,
20
+ :NotIn,
21
+ :WindowExpression,
22
+
23
+ :Combination,
24
+ :From,
25
+ :Limit,
26
+ :OrderBy,
27
+ :Select,
28
+ :Where,
29
+ :Window,
30
+ :With
31
+
32
+ Query = import('./query')
33
+
34
+ class Expression
35
+ attr_reader :members, :props
36
+
37
+ def initialize(*members, **props)
38
+ @members = members
39
+ @props = props
40
+ end
41
+
42
+ def as(sym = nil, &block)
43
+ if sym
44
+ Alias.new(self, sym)
45
+ else
46
+ Alias.new(self, Query::Query.new(&block))
47
+ end
48
+ end
49
+
50
+ def desc
51
+ Desc.new(self)
52
+ end
53
+
54
+ def over(sym = nil, &block)
55
+ Over.new(self, sym || WindowExpression.new(&block))
56
+ end
57
+
58
+ S_EQ = '='
59
+ S_TILDE = '~'
60
+ S_NEQ = '<>'
61
+ S_LT = '<'
62
+ S_GT = '>'
63
+ S_LTE = '<='
64
+ S_GTE = '>='
65
+ S_AND = 'and'
66
+ S_OR = 'or'
67
+ S_PLUS = '+'
68
+ S_MINUS = '-'
69
+ S_MUL = '*'
70
+ S_DIV = '/'
71
+ S_MOD = '%'
72
+
73
+ def ==(expr2)
74
+ Operator.new(S_EQ, self, expr2)
75
+ end
76
+
77
+ def =~(expr2)
78
+ Operator.new(S_TILDE, self, expr2)
79
+ end
80
+
81
+ def !=(expr2)
82
+ Operator.new(S_NEQ, self, expr2)
83
+ end
84
+
85
+ def <(expr2)
86
+ Operator.new(S_LT, self, expr2)
87
+ end
88
+
89
+ def >(expr2)
90
+ Operator.new(S_GT, self, expr2)
91
+ end
92
+
93
+ def <=(expr2)
94
+ Operator.new(S_LTE, self, expr2)
95
+ end
96
+
97
+ def >=(expr2)
98
+ Operator.new(S_GTE, self, expr2)
99
+ end
100
+
101
+ def &(expr2)
102
+ Operator.new(S_AND, self, expr2)
103
+ end
104
+
105
+ def |(expr2)
106
+ Operator.new(S_OR, self, expr2)
107
+ end
108
+
109
+ def +(expr2)
110
+ Operator.new(S_PLUS, self, expr2)
111
+ end
112
+
113
+ def -(expr2)
114
+ Operator.new(S_MINUS, self, expr2)
115
+ end
116
+
117
+ def *(expr2)
118
+ Operator.new(S_MUL, self, expr2)
119
+ end
120
+
121
+ def /(expr2)
122
+ Operator.new(S_DIV, self, expr2)
123
+ end
124
+
125
+ def %(expr2)
126
+ Operator.new(S_MOD, self, expr2)
127
+ end
128
+
129
+ def ^(expr2)
130
+ CastShorthand.new(self, expr2)
131
+ end
132
+
133
+ # not
134
+ def !@
135
+ Not.new(self)
136
+ end
137
+
138
+ def null?
139
+ IsNull.new(self)
140
+ end
141
+
142
+ def not_null?
143
+ IsNotNull.new(self)
144
+ end
145
+
146
+ def join(sym, **props)
147
+ Join.new(self, sym, **props)
148
+ end
149
+
150
+ def inner_join(sym, **props)
151
+ join(sym, props.merge(type: :inner))
152
+ end
153
+
154
+ def cast(sym)
155
+ Cast.new(self, sym)
156
+ end
157
+
158
+ def in(*args)
159
+ In.new(self, *args)
160
+ end
161
+
162
+ def not_in(*args)
163
+ NotIn.new(self, *args)
164
+ end
165
+ end
166
+
167
+ ############################################################
168
+
169
+ S_COMMA = ', '
170
+
171
+ class Alias < Expression
172
+ S_AS = '%s as %s'
173
+ def to_sql(sql)
174
+ S_AS % [sql.quote(@members[0]), sql.quote(@members[1])]
175
+ end
176
+ end
177
+
178
+ class Case < Expression
179
+ def initialize(conditions)
180
+ @props = conditions
181
+ end
182
+
183
+ S_WHEN = 'when %s then %s'
184
+ S_ELSE = 'else %s'
185
+ S_CASE = 'case %s end'
186
+ S_SPACE = ' '
187
+
188
+ def to_sql(sql)
189
+ conditions = @props.inject([]) { |a, (k, v)|
190
+ if k.is_a?(Symbol) && k == :default
191
+ a
192
+ else
193
+ a << (S_WHEN % [sql.quote(k), sql.quote(v)])
194
+ end
195
+ }
196
+ if default = @props[:default]
197
+ conditions << (S_ELSE % sql.quote(default))
198
+ end
199
+
200
+ S_CASE % conditions.join(S_SPACE)
201
+ end
202
+ end
203
+
204
+ class Cast < Expression
205
+ S_CAST = 'cast (%s as %s)'
206
+
207
+ def to_sql(sql)
208
+ S_CAST % [sql.quote(@members[0]), sql.quote(@members[1])]
209
+ end
210
+ end
211
+
212
+ class CastShorthand < Expression
213
+ S_CAST = '%s::%s'
214
+
215
+ def to_sql(sql)
216
+ S_CAST % [sql.quote(@members[0]), sql.quote(@members[1])]
217
+ end
218
+ end
219
+
220
+ class Desc < Expression
221
+ S_DESC = '%s desc'
222
+
223
+ def to_sql(sql)
224
+ S_DESC % sql.quote(@members[0])
225
+ end
226
+ end
227
+
228
+ class FunctionCall < Expression
229
+ S_FUN_NO_ARGS = '%s()'
230
+ S_FUN = '%s(%s)'
231
+
232
+ def to_sql(sql)
233
+ fun = @members[0]
234
+ if @members.size == 2 && Identifier === @members.last && @members.last._empty_placeholder?
235
+ S_FUN_NO_ARGS % fun
236
+ else
237
+ S_FUN % [
238
+ fun,
239
+ @members[1..-1].map { |a| sql.quote(a) }.join(S_COMMA)
240
+ ]
241
+ end
242
+ end
243
+ end
244
+
245
+ class Identifier < Expression
246
+ def to_sql(sql)
247
+ sql.quote(@members[0].to_sym)
248
+ end
249
+
250
+ def method_missing(sym)
251
+ super if sym == :to_hash
252
+ Identifier.new("#{@members[0]}.#{sym}")
253
+ end
254
+
255
+ def _empty_placeholder?
256
+ m = @members[0]
257
+ Symbol === m && m == :_
258
+ end
259
+ end
260
+
261
+ class In < Expression
262
+ S_IN = '%s in (%s)'
263
+
264
+ def to_sql(sql)
265
+ S_IN % [
266
+ sql.quote(@members[0]),
267
+ @members[1..-1].map { |m| sql.quote(m) }.join(S_COMMA)
268
+ ]
269
+ end
270
+
271
+ def !@
272
+ NotIn.new(*@members)
273
+ end
274
+ end
275
+
276
+ class IsNotNull < Expression
277
+ S_NOT_NULL = '(%s is not null)'
278
+ def to_sql(sql)
279
+ S_NOT_NULL % sql.quote(@members[0])
280
+ end
281
+ end
282
+
283
+ class IsNull < Expression
284
+ S_NULL = '(%s is null)'
285
+
286
+ def to_sql(sql)
287
+ S_NULL % sql.quote(@members[0])
288
+ end
289
+
290
+ def !@
291
+ IsNotNull.new(@members[0])
292
+ end
293
+ end
294
+
295
+ class Join < Expression
296
+ H_JOIN_TYPES = {
297
+ nil: 'join',
298
+ inner: 'inner join',
299
+ outer: 'outer join'
300
+ }
301
+
302
+ S_JOIN = '%s %s %s %s'
303
+ S_ON = 'on %s'
304
+ S_USING = 'using (%s)'
305
+
306
+ def to_sql(sql)
307
+ (S_JOIN % [
308
+ sql.quote(@members[0]),
309
+ H_JOIN_TYPES[@props[:type]],
310
+ sql.quote(@members[1]),
311
+ condition_sql(sql)
312
+ ]).strip
313
+ end
314
+
315
+ def condition_sql(sql)
316
+ if @props[:on]
317
+ S_ON % sql.quote(@props[:on])
318
+ elsif using_fields = @props[:using]
319
+ fields = using_fields.is_a?(Array) ? using_fields : [using_fields]
320
+ S_USING % fields.map { |f| sql.quote(f) }.join(S_COMMA)
321
+ else
322
+ nil
323
+ end
324
+ end
325
+ end
326
+
327
+ class Literal < Expression
328
+ def to_sql(sql)
329
+ sql.quote(@members[0])
330
+ end
331
+ end
332
+
333
+ class Operator < Expression
334
+ attr_reader :op
335
+
336
+ def initialize(op, *members, **props)
337
+ if Operator === members[0] && op == members[0].op
338
+ members = members[0].members + members[1..-1]
339
+ end
340
+ if Operator === members.last && op == members.last.op
341
+ members = members[0..-2] + members.last.members
342
+ end
343
+
344
+ super(*members, **props)
345
+ @op = op
346
+ end
347
+
348
+ S_OP = ' %s '
349
+ S_OP_EXPR = '(%s)'
350
+
351
+ def to_sql(sql)
352
+ op_s = S_OP % @op
353
+ S_OP_EXPR % @members.map { |m| sql.quote(m) }.join(op_s)
354
+ end
355
+
356
+ INVERSE_OP = {
357
+ '=' => '<>',
358
+ '<>' => '=',
359
+ '<' => '>=',
360
+ '>' => '<=',
361
+ '<=' => '>',
362
+ '>=' => '<'
363
+ }
364
+
365
+ def !@
366
+ inverse = INVERSE_OP[@op]
367
+ inverse ? Operator.new(inverse, *@members) : super
368
+ end
369
+ end
370
+
371
+ class Over < Expression
372
+ S_OVER = '%s over %s'
373
+
374
+ def to_sql(sql)
375
+ S_OVER % [sql.quote(@members[0]), sql.quote(@members[1])]
376
+ end
377
+ end
378
+
379
+ class Not < Expression
380
+ S_NOT = '(not %s)'
381
+
382
+ def to_sql(sql)
383
+ S_NOT % sql.quote(@members[0])
384
+ end
385
+ end
386
+
387
+ class NotIn < Expression
388
+ S_NOT_IN = '%s not in (%s)'
389
+
390
+ def to_sql(sql)
391
+ S_NOT_IN % [
392
+ sql.quote(@members[0]),
393
+ @members[1..-1].map { |m| sql.quote(m) }.join(S_COMMA)
394
+ ]
395
+ end
396
+ end
397
+
398
+ class WindowExpression < Expression
399
+ def initialize(&block)
400
+ instance_eval(&block)
401
+ end
402
+
403
+ def partition_by(*args)
404
+ @partition_by = args
405
+ end
406
+
407
+ def order_by(*args)
408
+ @order_by = args
409
+ end
410
+
411
+ S_UNBOUNDED = 'between unbounded preceding and unbounded following'
412
+ S_WINDOW = '(%s)'
413
+ S_PARTITION_BY = 'partition by %s '
414
+ S_ORDER_BY = 'order by %s '
415
+ S_RANGE = 'range %s '
416
+
417
+ def range_unbounded
418
+ @range = S_UNBOUNDED
419
+ end
420
+
421
+ def to_sql(sql)
422
+ S_WINDOW % [
423
+ _partition_by_clause(sql),
424
+ _order_by_clause(sql),
425
+ _range_clause(sql)
426
+ ].join.strip
427
+ end
428
+
429
+ def _partition_by_clause(sql)
430
+ return nil unless @partition_by
431
+ S_PARTITION_BY % @partition_by.map { |e| sql.quote(e) }.join(S_COMMA)
432
+ end
433
+
434
+ def _order_by_clause(sql)
435
+ return nil unless @order_by
436
+ S_ORDER_BY % @order_by.map { |e| sql.quote(e) }.join(S_COMMA)
437
+ end
438
+
439
+ def _range_clause(sql)
440
+ return nil unless @range
441
+ S_RANGE % @range
442
+ end
443
+
444
+ def method_missing(sym)
445
+ super if sym == :to_hash
446
+ Identifier.new(sym)
447
+ end
448
+ end
449
+
450
+ ############################################################
451
+
452
+ class Combination < Expression
453
+ S_COMBINATION = ' %s '
454
+ S_COMBINATION_ALL = ' %s all '
455
+
456
+ def to_sql(sql)
457
+ union = (@props[:all] ? S_COMBINATION_ALL : S_COMBINATION) % @props[:kind]
458
+ @members.map { |m| sql.quote(m) }.join(union)
459
+ end
460
+ end
461
+
462
+ class From < Expression
463
+ S_FROM = 'from %s'
464
+ S_T1 = '%s t1'
465
+ S_ALIAS = '%s %s'
466
+
467
+ def to_sql(sql)
468
+ S_FROM % @members.map { |m| member_sql(m, sql) }.join(S_COMMA)
469
+ end
470
+
471
+ def member_sql(member, sql)
472
+ if Query::Query === member
473
+ S_T1 % sql.quote(member)
474
+ elsif Alias === member && Query::Query === member.members[0]
475
+ S_ALIAS % [sql.quote(member.members[0]), sql.quote(member.members[1])]
476
+ else
477
+ sql.quote(member)
478
+ end
479
+ end
480
+ end
481
+
482
+ class Limit < Expression
483
+ S_LIMIT = 'limit %d'
484
+
485
+ def to_sql(sql)
486
+ S_LIMIT % @members[0]
487
+ end
488
+ end
489
+
490
+ class OrderBy < Expression
491
+ S_ORDER_BY = 'order by %s'
492
+
493
+ def to_sql(sql)
494
+ S_ORDER_BY % @members.map { |e| sql.quote(e) }.join(S_COMMA)
495
+ end
496
+ end
497
+
498
+ class Select < Expression
499
+ S_SELECT = 'select %s%s'
500
+ S_DISTINCT = 'distinct '
501
+ S_DISTINCT_ON = 'distinct on (%s) '
502
+ S_DISTINCT_ON_SINGLE = 'distinct on %s '
503
+
504
+ def to_sql(sql)
505
+ S_SELECT % [
506
+ distinct_clause(sql), @members.map { |e| sql.quote(e) }.join(S_COMMA)
507
+ ]
508
+ end
509
+
510
+ def distinct_clause(sql)
511
+ case (on = @props[:distinct])
512
+ when nil
513
+ nil
514
+ when true
515
+ S_DISTINCT
516
+ when Array
517
+ S_DISTINCT_ON % on.map { |e| sql.quote(e) }.join(S_COMMA)
518
+ else
519
+ S_DISTINCT_ON_SINGLE % sql.quote(on)
520
+ end
521
+ end
522
+ end
523
+
524
+ class Where < Expression
525
+ S_WHERE = 'where %s'
526
+ S_AND = ' and '
527
+
528
+ def to_sql(sql)
529
+ S_WHERE % @members.map { |e| sql.quote(e) }.join(S_AND)
530
+ end
531
+ end
532
+
533
+ class Window < Expression
534
+ def initialize(sym, &block)
535
+ super(sym)
536
+ @block = block
537
+ end
538
+
539
+ S_WINDOW = 'window %s as %s'
540
+
541
+ def to_sql(sql)
542
+ S_WINDOW % [
543
+ sql.quote(@members.first),
544
+ WindowExpression.new(&@block).to_sql(sql)
545
+ ]
546
+ end
547
+ end
548
+
549
+ class With < Expression
550
+ S_WITH = 'with %s'
551
+
552
+ def to_sql(sql)
553
+ S_WITH % @members.map { |e| sql.quote(e) }.join(S_COMMA)
554
+ end
555
+ end