arel_toolkit 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +29 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +34 -0
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +16 -2
  7. data/CHANGELOG.md +10 -0
  8. data/Gemfile +1 -1
  9. data/Gemfile.lock +94 -1
  10. data/Guardfile +42 -0
  11. data/README.md +23 -4
  12. data/Rakefile +3 -3
  13. data/arel_toolkit.gemspec +32 -15
  14. data/bin/console +4 -7
  15. data/lib/arel/extensions.rb +71 -0
  16. data/lib/arel/extensions/absolute.rb +10 -0
  17. data/lib/arel/extensions/all.rb +23 -0
  18. data/lib/arel/extensions/any.rb +23 -0
  19. data/lib/arel/extensions/array.rb +29 -0
  20. data/lib/arel/extensions/array_subselect.rb +23 -0
  21. data/lib/arel/extensions/between_symmetric.rb +23 -0
  22. data/lib/arel/extensions/bit_string.rb +27 -0
  23. data/lib/arel/extensions/bitwise_xor.rb +10 -0
  24. data/lib/arel/extensions/coalesce.rb +9 -0
  25. data/lib/arel/extensions/conflict.rb +47 -0
  26. data/lib/arel/extensions/contained_by.rb +10 -0
  27. data/lib/arel/extensions/contains.rb +10 -0
  28. data/lib/arel/extensions/cross_join.rb +21 -0
  29. data/lib/arel/extensions/cube_root.rb +10 -0
  30. data/lib/arel/extensions/current_catalog.rb +20 -0
  31. data/lib/arel/extensions/current_date.rb +20 -0
  32. data/lib/arel/extensions/current_of_expression.rb +29 -0
  33. data/lib/arel/extensions/current_role.rb +20 -0
  34. data/lib/arel/extensions/current_schema.rb +20 -0
  35. data/lib/arel/extensions/current_time.rb +22 -0
  36. data/lib/arel/extensions/current_timestamp.rb +22 -0
  37. data/lib/arel/extensions/current_user.rb +20 -0
  38. data/lib/arel/extensions/default_values.rb +21 -0
  39. data/lib/arel/extensions/delete_statement.rb +49 -0
  40. data/lib/arel/extensions/distinct_from.rb +22 -0
  41. data/lib/arel/extensions/equality.rb +30 -0
  42. data/lib/arel/extensions/except_all.rb +21 -0
  43. data/lib/arel/extensions/exponentiation.rb +10 -0
  44. data/lib/arel/extensions/factorial.rb +33 -0
  45. data/lib/arel/extensions/function.rb +68 -0
  46. data/lib/arel/extensions/generate_series.rb +9 -0
  47. data/lib/arel/extensions/greatest.rb +9 -0
  48. data/lib/arel/extensions/indirection.rb +32 -0
  49. data/lib/arel/extensions/infer.rb +35 -0
  50. data/lib/arel/extensions/insert_statement.rb +69 -0
  51. data/lib/arel/extensions/intersect_all.rb +21 -0
  52. data/lib/arel/extensions/lateral.rb +34 -0
  53. data/lib/arel/extensions/least.rb +9 -0
  54. data/lib/arel/extensions/local_time.rb +22 -0
  55. data/lib/arel/extensions/local_timestamp.rb +22 -0
  56. data/lib/arel/extensions/modulo.rb +10 -0
  57. data/lib/arel/extensions/named_function.rb +15 -0
  58. data/lib/arel/extensions/natural_join.rb +21 -0
  59. data/lib/arel/extensions/not_between.rb +22 -0
  60. data/lib/arel/extensions/not_between_symmetric.rb +23 -0
  61. data/lib/arel/extensions/not_distinct_from.rb +22 -0
  62. data/lib/arel/extensions/not_equal.rb +30 -0
  63. data/lib/arel/extensions/not_similar.rb +29 -0
  64. data/lib/arel/extensions/null_if.rb +24 -0
  65. data/lib/arel/extensions/ordering.rb +47 -0
  66. data/lib/arel/extensions/overlap.rb +10 -0
  67. data/lib/arel/extensions/range_function.rb +23 -0
  68. data/lib/arel/extensions/rank.rb +9 -0
  69. data/lib/arel/extensions/row.rb +30 -0
  70. data/lib/arel/extensions/select_statement.rb +26 -0
  71. data/lib/arel/extensions/session_user.rb +20 -0
  72. data/lib/arel/extensions/set_to_default.rb +21 -0
  73. data/lib/arel/extensions/similar.rb +32 -0
  74. data/lib/arel/extensions/square_root.rb +10 -0
  75. data/lib/arel/extensions/table.rb +49 -0
  76. data/lib/arel/extensions/time_with_precision.rb +13 -0
  77. data/lib/arel/extensions/type_cast.rb +30 -0
  78. data/lib/arel/extensions/unknown.rb +20 -0
  79. data/lib/arel/extensions/update_statement.rb +63 -0
  80. data/lib/arel/extensions/user.rb +20 -0
  81. data/lib/arel/extensions/with_ordinality.rb +22 -0
  82. data/lib/arel/sql_to_arel.rb +8 -0
  83. data/lib/arel/sql_to_arel/frame_options.rb +110 -0
  84. data/lib/arel/sql_to_arel/pg_query_visitor.rb +1005 -0
  85. data/lib/arel/sql_to_arel/unbound_column_reference.rb +5 -0
  86. data/lib/arel_toolkit.rb +4 -3
  87. data/lib/arel_toolkit/version.rb +1 -1
  88. metadata +250 -4
@@ -0,0 +1,1005 @@
1
+ # rubocop:disable Metrics/PerceivedComplexity
2
+ # rubocop:disable Naming/MethodName
3
+ # rubocop:disable Metrics/CyclomaticComplexity
4
+ # rubocop:disable Metrics/AbcSize
5
+ # rubocop:disable Naming/UncommunicativeMethodParamName
6
+ # rubocop:disable Metrics/ParameterLists
7
+
8
+ require 'pg_query'
9
+ require_relative './frame_options'
10
+
11
+ module Arel
12
+ module SqlToArel
13
+ class PgQueryVisitor
14
+ PG_CATALOG = 'pg_catalog'.freeze
15
+ MIN_MAX_EXPR = 'MinMaxExpr'.freeze
16
+
17
+ attr_reader :object
18
+
19
+ def accept(sql)
20
+ tree = PgQuery.parse(sql).tree
21
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/33' if tree.length > 1
22
+
23
+ @object = tree.first
24
+ visit object, :top
25
+ end
26
+
27
+ private
28
+
29
+ def visit_A_ArrayExpr(elements:)
30
+ Arel::Nodes::Array.new visit(elements)
31
+ end
32
+
33
+ def visit_A_Const(val:)
34
+ visit(val, :const)
35
+ end
36
+
37
+ def visit_A_Expr(kind:, lexpr: nil, rexpr: nil, name:)
38
+ case kind
39
+ when PgQuery::AEXPR_OP
40
+ left = visit(lexpr) if lexpr
41
+ right = visit(rexpr) if rexpr
42
+ operator = visit(name[0], :operator)
43
+ generate_operator(left, right, operator)
44
+
45
+ when PgQuery::AEXPR_OP_ANY
46
+ left = visit(lexpr)
47
+ right = visit(rexpr)
48
+ right = Arel::Nodes::Any.new right
49
+ operator = visit(name[0], :operator)
50
+ generate_operator(left, right, operator)
51
+
52
+ when PgQuery::AEXPR_OP_ALL
53
+ left = visit(lexpr)
54
+ right = visit(rexpr)
55
+ right = Arel::Nodes::All.new right
56
+ operator = visit(name[0], :operator)
57
+ generate_operator(left, right, operator)
58
+
59
+ when PgQuery::AEXPR_DISTINCT
60
+ left = visit(lexpr)
61
+ right = visit(rexpr)
62
+ Arel::Nodes::DistinctFrom.new(left, right)
63
+
64
+ when PgQuery::AEXPR_NOT_DISTINCT
65
+ left = visit(lexpr)
66
+ right = visit(rexpr)
67
+ Arel::Nodes::NotDistinctFrom.new(left, right)
68
+
69
+ when PgQuery::AEXPR_NULLIF
70
+ left = visit(lexpr)
71
+ right = visit(rexpr)
72
+ Arel::Nodes::NullIf.new(left, right)
73
+
74
+ when PgQuery::AEXPR_OF
75
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/34'
76
+
77
+ when PgQuery::AEXPR_IN
78
+ left = visit(lexpr)
79
+ right = visit(rexpr)
80
+ operator = visit(name[0], :operator)
81
+
82
+ if operator == '<>'
83
+ Arel::Nodes::NotIn.new(left, right)
84
+ else
85
+ Arel::Nodes::In.new(left, right)
86
+ end
87
+
88
+ when PgQuery::AEXPR_LIKE
89
+ left = visit(lexpr) if lexpr
90
+ right = visit(rexpr)
91
+ escape = nil
92
+
93
+ if right.is_a?(Array)
94
+ raise "Don't know how to handle length `#{right.length}`" if right.length != 2
95
+
96
+ right, escape = right
97
+ end
98
+
99
+ operator = visit(name[0], :operator)
100
+
101
+ if operator == '~~'
102
+ Arel::Nodes::Matches.new(left, right, escape, true)
103
+ else
104
+ Arel::Nodes::DoesNotMatch.new(left, right, escape, true)
105
+ end
106
+
107
+ when PgQuery::AEXPR_ILIKE
108
+ left = visit(lexpr) if lexpr
109
+ right = visit(rexpr)
110
+ escape = nil
111
+
112
+ if right.is_a?(Array)
113
+ raise "Don't know how to handle length `#{right.length}`" if right.length != 2
114
+
115
+ right, escape = right
116
+ end
117
+
118
+ operator = visit(name[0], :operator)
119
+
120
+ if operator == '~~*'
121
+ Arel::Nodes::Matches.new(left, right, escape, false)
122
+ else
123
+ Arel::Nodes::DoesNotMatch.new(left, right, escape, false)
124
+ end
125
+
126
+ when PgQuery::AEXPR_SIMILAR
127
+ left = visit(lexpr) if lexpr
128
+ right = visit(rexpr)
129
+ escape = nil
130
+
131
+ if right.is_a?(Array)
132
+ raise "Don't know how to handle length `#{right.length}`" if right.length != 2
133
+
134
+ right, escape = right
135
+ end
136
+
137
+ escape = nil if escape == 'NULL'
138
+ operator = visit(name[0], :operator)
139
+
140
+ if operator == '~'
141
+ Arel::Nodes::Similar.new(left, right, escape)
142
+ else
143
+ Arel::Nodes::NotSimilar.new(left, right, escape)
144
+ end
145
+
146
+ when PgQuery::AEXPR_BETWEEN
147
+ left = visit(lexpr) if lexpr
148
+ right = visit(rexpr)
149
+ Arel::Nodes::Between.new left, Arel::Nodes::And.new(right)
150
+
151
+ when PgQuery::AEXPR_NOT_BETWEEN
152
+ left = visit(lexpr) if lexpr
153
+ right = visit(rexpr)
154
+ Arel::Nodes::NotBetween.new left, Arel::Nodes::And.new(right)
155
+
156
+ when PgQuery::AEXPR_BETWEEN_SYM
157
+ left = visit(lexpr) if lexpr
158
+ right = visit(rexpr)
159
+ Arel::Nodes::BetweenSymmetric.new left, Arel::Nodes::And.new(right)
160
+
161
+ when PgQuery::AEXPR_NOT_BETWEEN_SYM
162
+ left = visit(lexpr) if lexpr
163
+ right = visit(rexpr)
164
+ Arel::Nodes::NotBetweenSymmetric.new left, Arel::Nodes::And.new(right)
165
+
166
+ when PgQuery::AEXPR_PAREN
167
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/35'
168
+
169
+ else
170
+ raise "Unknown Expr type `#{kind}`"
171
+ end
172
+ end
173
+
174
+ def visit_A_Indices(context, uidx:)
175
+ visit uidx, context
176
+ end
177
+
178
+ def visit_A_Indirection(arg:, indirection:)
179
+ Arel::Nodes::Indirection.new(visit(arg, :operator), visit(indirection, :operator))
180
+ end
181
+
182
+ def visit_A_Star
183
+ Arel.star
184
+ end
185
+
186
+ def visit_Alias(aliasname:)
187
+ aliasname
188
+ end
189
+
190
+ def visit_BitString(str:)
191
+ Arel::Nodes::BitString.new(str)
192
+ end
193
+
194
+ def visit_BoolExpr(context = false, args:, boolop:)
195
+ args = visit(args, context || true)
196
+
197
+ result = case boolop
198
+ when PgQuery::BOOL_EXPR_AND
199
+ Arel::Nodes::And.new(args)
200
+
201
+ when PgQuery::BOOL_EXPR_OR
202
+ generate_boolean_expression(args, Arel::Nodes::Or)
203
+
204
+ when PgQuery::BOOL_EXPR_NOT
205
+ Arel::Nodes::Not.new(args)
206
+
207
+ else
208
+ raise "? Boolop -> #{boolop}"
209
+ end
210
+
211
+ if context
212
+ Arel::Nodes::Grouping.new(result)
213
+ else
214
+ result
215
+ end
216
+ end
217
+
218
+ def visit_BooleanTest(arg:, booltesttype:)
219
+ arg = visit(arg)
220
+
221
+ case booltesttype
222
+ when PgQuery::BOOLEAN_TEST_TRUE
223
+ Arel::Nodes::Equality.new(arg, Arel::Nodes::True.new)
224
+
225
+ when PgQuery::BOOLEAN_TEST_NOT_TRUE
226
+ Arel::Nodes::NotEqual.new(arg, Arel::Nodes::True.new)
227
+
228
+ when PgQuery::BOOLEAN_TEST_FALSE
229
+ Arel::Nodes::Equality.new(arg, Arel::Nodes::False.new)
230
+
231
+ when PgQuery::BOOLEAN_TEST_NOT_FALSE
232
+ Arel::Nodes::NotEqual.new(arg, Arel::Nodes::False.new)
233
+
234
+ when PgQuery::BOOLEAN_TEST_UNKNOWN
235
+ Arel::Nodes::Equality.new(arg, Arel::Nodes::Unknown.new)
236
+
237
+ when PgQuery::BOOLEAN_TEST_NOT_UNKNOWN
238
+ Arel::Nodes::NotEqual.new(arg, Arel::Nodes::Unknown.new)
239
+
240
+ else
241
+ raise '?'
242
+ end
243
+ end
244
+
245
+ def visit_CaseExpr(arg: nil, args:, defresult: nil)
246
+ Arel::Nodes::Case.new.tap do |kees|
247
+ kees.case = visit(arg) if arg
248
+
249
+ kees.conditions = visit args
250
+
251
+ if defresult
252
+ default_result = visit(defresult, :sql)
253
+
254
+ kees.default = Arel::Nodes::Else.new default_result
255
+ end
256
+ end
257
+ end
258
+
259
+ def visit_CaseWhen(expr:, result:)
260
+ expr = visit(expr)
261
+ result = visit(result)
262
+
263
+ Arel::Nodes::When.new(expr, result)
264
+ end
265
+
266
+ def visit_CoalesceExpr(args:)
267
+ args = visit(args)
268
+
269
+ Arel::Nodes::Coalesce.new args
270
+ end
271
+
272
+ def visit_ColumnRef(context = nil, fields:)
273
+ UnboundColumnReference.new visit(fields, context).join('.')
274
+ end
275
+
276
+ def visit_CommonTableExpr(ctename:, ctequery:)
277
+ cte_table = Arel::Table.new(ctename)
278
+ cte_definition = visit(ctequery)
279
+ Arel::Nodes::As.new(cte_table, Arel::Nodes::Grouping.new(cte_definition))
280
+ end
281
+
282
+ def visit_CurrentOfExpr(cursor_name:)
283
+ Arel::Nodes::CurrentOfExpression.new(cursor_name)
284
+ end
285
+
286
+ def visit_DeleteStmt(
287
+ relation:,
288
+ using_clause: nil,
289
+ where_clause: nil,
290
+ returning_list: [],
291
+ with_clause: nil
292
+ )
293
+ relation = visit(relation)
294
+
295
+ delete_manager = Arel::DeleteManager.new
296
+ delete_statement = delete_manager.ast
297
+ delete_statement.relation = relation
298
+ delete_statement.using = visit(using_clause) if using_clause
299
+ delete_statement.wheres = where_clause ? [visit(where_clause)] : []
300
+ delete_statement.with = visit(with_clause) if with_clause
301
+ delete_statement.returning = visit(returning_list, :select)
302
+ delete_manager
303
+ end
304
+
305
+ def visit_Float(str:)
306
+ Arel::Nodes::SqlLiteral.new str
307
+ end
308
+
309
+ # https://github.com/postgres/postgres/blob/REL_10_1/src/include/nodes/parsenodes.h
310
+ def visit_FuncCall(
311
+ funcname:,
312
+ args: nil,
313
+ agg_order: nil,
314
+ agg_filter: nil,
315
+ agg_within_group: nil,
316
+ agg_star: nil,
317
+ agg_distinct: nil,
318
+ func_variadic: nil,
319
+ over: nil
320
+ )
321
+ args = if args
322
+ visit args
323
+ elsif agg_star
324
+ [Arel.star]
325
+ else
326
+ []
327
+ end
328
+
329
+ function_names = visit(funcname, :operator)
330
+
331
+ func = case function_names
332
+ when ['sum']
333
+ Arel::Nodes::Sum.new args
334
+
335
+ when ['rank']
336
+ Arel::Nodes::Rank.new args
337
+
338
+ when ['count']
339
+ Arel::Nodes::Count.new args
340
+
341
+ when ['generate_series']
342
+ Arel::Nodes::GenerateSeries.new args
343
+
344
+ when ['max']
345
+ Arel::Nodes::Max.new args
346
+
347
+ when ['min']
348
+ Arel::Nodes::Min.new args
349
+
350
+ when ['avg']
351
+ Arel::Nodes::Avg.new args
352
+
353
+ when [PG_CATALOG, 'like_escape']
354
+ args
355
+
356
+ when [PG_CATALOG, 'similar_escape']
357
+ args
358
+
359
+ else
360
+ raise "Don't know how to handle `#{function_names}`" if function_names.length > 1
361
+
362
+ Arel::Nodes::NamedFunction.new(function_names.first, args)
363
+ end
364
+
365
+ func.distinct = (agg_distinct.nil? ? false : true) unless func.is_a?(::Array)
366
+ func.orders = (agg_order ? visit(agg_order) : []) unless func.is_a?(::Array)
367
+ func.filter = (agg_filter ? visit(agg_filter) : nil) unless func.is_a?(::Array)
368
+ func.within_group = agg_within_group unless func.is_a?(::Array)
369
+ func.variardic = func_variadic unless func.is_a?(::Array)
370
+
371
+ if over
372
+ Arel::Nodes::Over.new(func, visit(over))
373
+ else
374
+ func
375
+ end
376
+ end
377
+
378
+ def visit_InferClause(conname: nil, index_elems: nil)
379
+ infer = Arel::Nodes::Infer.new
380
+ infer.name = Arel.sql(conname) if conname
381
+ infer.indexes = visit(index_elems) if index_elems
382
+ infer
383
+ end
384
+
385
+ def visit_IndexElem(name:, ordering:, nulls_ordering:)
386
+ raise "Unknown ordering `#{ordering}`" unless ordering.zero?
387
+ raise "Unknown nulls ordering `#{ordering}`" unless nulls_ordering.zero?
388
+
389
+ Arel.sql(name)
390
+ end
391
+
392
+ def visit_InsertStmt(
393
+ relation:,
394
+ cols: [],
395
+ select_stmt: nil,
396
+ on_conflict_clause: nil,
397
+ with_clause: nil,
398
+ returning_list: [],
399
+ override:
400
+ )
401
+ relation = visit(relation)
402
+ cols = visit(cols, :insert).map do |col|
403
+ Arel::Attribute.new(relation, col)
404
+ end
405
+ select_stmt = visit(select_stmt) if select_stmt
406
+
407
+ insert_manager = Arel::InsertManager.new
408
+ insert_statement = insert_manager.ast
409
+ insert_statement.relation = relation
410
+ insert_statement.columns = cols
411
+ insert_statement.override = override
412
+ insert_statement.with = visit(with_clause) if with_clause
413
+
414
+ if select_stmt
415
+ insert_statement.values = select_stmt.values_lists if select_stmt
416
+ else
417
+ insert_statement.values = Arel::Nodes::DefaultValues.new
418
+ end
419
+
420
+ insert_statement.returning = visit(returning_list, :select)
421
+ insert_statement.on_conflict = visit(on_conflict_clause) if on_conflict_clause
422
+ insert_manager
423
+ end
424
+
425
+ def visit_Integer(ival:)
426
+ ival
427
+ end
428
+
429
+ def visit_JoinExpr(jointype:, is_natural: nil, larg:, rarg:, quals: nil)
430
+ join_class = case jointype
431
+ when 0
432
+ if is_natural
433
+ Arel::Nodes::NaturalJoin
434
+ elsif quals.nil?
435
+ Arel::Nodes::CrossJoin
436
+ else
437
+ Arel::Nodes::InnerJoin
438
+ end
439
+ when 1
440
+ Arel::Nodes::OuterJoin
441
+ when 2
442
+ Arel::Nodes::FullOuterJoin
443
+ when 3
444
+ Arel::Nodes::RightOuterJoin
445
+ end
446
+
447
+ larg = visit(larg)
448
+ rarg = visit(rarg)
449
+
450
+ quals = Arel::Nodes::On.new visit(quals) if quals
451
+
452
+ join = join_class.new(rarg, quals)
453
+
454
+ if larg.is_a?(Array)
455
+ larg.concat([join])
456
+ else
457
+ [larg, join]
458
+ end
459
+ end
460
+
461
+ def visit_LockingClause(strength:, wait_policy:)
462
+ strength_clause = {
463
+ 1 => 'FOR KEY SHARE',
464
+ 2 => 'FOR SHARE',
465
+ 3 => 'FOR NO KEY UPDATE',
466
+ 4 => 'FOR UPDATE'
467
+ }.fetch(strength)
468
+ wait_policy_clause = {
469
+ 0 => '',
470
+ 1 => ' SKIP LOCKED',
471
+ 2 => ' NOWAIT'
472
+ }.fetch(wait_policy)
473
+
474
+ Arel::Nodes::Lock.new Arel.sql("#{strength_clause}#{wait_policy_clause}")
475
+ end
476
+
477
+ def visit_MinMaxExpr(op:, args:)
478
+ case op
479
+ when 0
480
+ Arel::Nodes::Greatest.new visit(args)
481
+ when 1
482
+ Arel::Nodes::Least.new visit(args)
483
+ else
484
+ raise "Unknown Op -> #{op}"
485
+ end
486
+ end
487
+
488
+ def visit_Null(**_)
489
+ Arel.sql 'NULL'
490
+ end
491
+
492
+ def visit_NullTest(arg:, nulltesttype:)
493
+ arg = visit(arg)
494
+
495
+ case nulltesttype
496
+ when PgQuery::CONSTR_TYPE_NULL
497
+ Arel::Nodes::Equality.new(arg, nil)
498
+ when PgQuery::CONSTR_TYPE_NOTNULL
499
+ Arel::Nodes::NotEqual.new(arg, nil)
500
+ end
501
+ end
502
+
503
+ def visit_OnConflictClause(action:, infer: nil, target_list: nil, where_clause: nil)
504
+ conflict = Arel::Nodes::Conflict.new
505
+ conflict.action = action
506
+ conflict.infer = visit(infer) if infer
507
+ conflict.values = target_list ? visit(target_list, :update) : []
508
+ conflict.wheres = where_clause ? [visit(where_clause)] : []
509
+ conflict
510
+ end
511
+
512
+ def visit_ParamRef(_args)
513
+ Arel::Nodes::BindParam.new(nil)
514
+ end
515
+
516
+ def visit_RangeFunction(is_rowsfrom:, functions:, lateral: false, ordinality: false)
517
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/36' unless is_rowsfrom == true
518
+
519
+ functions = functions.map do |function_array|
520
+ function, empty_value = function_array
521
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/37' unless empty_value.nil?
522
+
523
+ visit(function)
524
+ end
525
+
526
+ node = Arel::Nodes::RangeFunction.new functions
527
+ node = lateral ? Arel::Nodes::Lateral.new(node) : node
528
+ ordinality ? Arel::Nodes::WithOrdinality.new(node) : node
529
+ end
530
+
531
+ def visit_RangeSubselect(aliaz:, subquery:, lateral: false)
532
+ aliaz = visit(aliaz)
533
+ subquery = visit(subquery)
534
+ node = Arel::Nodes::TableAlias.new(Arel::Nodes::Grouping.new(subquery), aliaz)
535
+ lateral ? Arel::Nodes::Lateral.new(node) : node
536
+ end
537
+
538
+ def visit_RangeVar(aliaz: nil, relname:, inh: false, relpersistence:, schemaname: nil)
539
+ Arel::Table.new(
540
+ relname,
541
+ as: (visit(aliaz) if aliaz),
542
+ only: !inh,
543
+ relpersistence: relpersistence,
544
+ schema_name: schemaname,
545
+ )
546
+ end
547
+
548
+ def visit_RawStmt(context, stmt:)
549
+ visit(stmt, context)
550
+ end
551
+
552
+ def visit_ResTarget(context, val: nil, name: nil)
553
+ case context
554
+ when :select
555
+ val = visit(val)
556
+
557
+ if name
558
+ Arel::Nodes::As.new(val, Arel.sql(name))
559
+ else
560
+ val
561
+ end
562
+ when :insert
563
+ name
564
+ when :update
565
+ Arel::Nodes::Equality.new(
566
+ Arel.sql(visit_String(str: name)),
567
+ visit(val),
568
+ )
569
+ else
570
+ raise "Unknown context `#{context}`"
571
+ end
572
+ end
573
+
574
+ def visit_RowExpr(args:, row_format:)
575
+ Arel::Nodes::Row.new(visit(args), row_format)
576
+ end
577
+
578
+ def visit_SelectStmt(
579
+ context = nil,
580
+ from_clause: nil,
581
+ limit_count: nil,
582
+ target_list: nil,
583
+ sort_clause: nil,
584
+ where_clause: nil,
585
+ limit_offset: nil,
586
+ distinct_clause: nil,
587
+ group_clause: nil,
588
+ having_clause: nil,
589
+ with_clause: nil,
590
+ locking_clause: nil,
591
+ op:,
592
+ window_clause: nil,
593
+ values_lists: nil,
594
+ all: nil,
595
+ larg: nil,
596
+ rarg: nil
597
+ )
598
+ select_manager = Arel::SelectManager.new
599
+ select_core = select_manager.ast.cores.last
600
+ select_statement = select_manager.ast
601
+
602
+ froms, join_sources = generate_sources(from_clause)
603
+ select_core.from = froms if froms
604
+ select_core.source.right = join_sources
605
+
606
+ select_core.projections = visit(target_list, :select) if target_list
607
+ select_core.wheres = [visit(where_clause)] if where_clause
608
+ select_core.groups = visit(group_clause) if group_clause
609
+ select_core.havings = [visit(having_clause)] if having_clause
610
+ select_core.windows = visit(window_clause) if window_clause
611
+
612
+ if distinct_clause == [nil]
613
+ select_core.set_quantifier = Arel::Nodes::Distinct.new
614
+ elsif distinct_clause.is_a?(Array)
615
+ select_core.set_quantifier = Arel::Nodes::DistinctOn.new(visit(distinct_clause))
616
+ elsif distinct_clause.nil?
617
+ select_core.set_quantifier = nil
618
+ else
619
+ raise "Unknown distinct clause `#{distinct_clause}`"
620
+ end
621
+
622
+ select_statement.limit = ::Arel::Nodes::Limit.new visit(limit_count) if limit_count
623
+ select_statement.offset = ::Arel::Nodes::Offset.new visit(limit_offset) if limit_offset
624
+ select_statement.orders = visit(sort_clause.to_a)
625
+ select_statement.with = visit(with_clause) if with_clause
626
+ select_statement.lock = visit(locking_clause) if locking_clause
627
+ if values_lists
628
+ values_lists = visit(values_lists).map do |values_list|
629
+ values_list.map do |value|
630
+ case value
631
+ when String
632
+ value
633
+ when Integer
634
+ Arel.sql(value.to_s)
635
+ when Arel::Nodes::TypeCast
636
+ Arel.sql(value.to_sql)
637
+ when Arel::Nodes::BindParam
638
+ value
639
+ else
640
+ raise "Unknown value `#{value}`"
641
+ end
642
+ end
643
+ end
644
+ select_statement.values_lists = Arel::Nodes::ValuesList.new(values_lists)
645
+ end
646
+
647
+ union = case op
648
+ when 0
649
+ nil
650
+ when 1
651
+ if all
652
+ Arel::Nodes::UnionAll.new(visit(larg), visit(rarg))
653
+ else
654
+ Arel::Nodes::Union.new(visit(larg), visit(rarg))
655
+ end
656
+ when 2
657
+ if all
658
+ Arel::Nodes::IntersectAll.new(visit(larg), visit(rarg))
659
+ else
660
+ Arel::Nodes::Intersect.new(visit(larg), visit(rarg))
661
+ end
662
+ when 3
663
+ if all
664
+ Arel::Nodes::ExceptAll.new(visit(larg), visit(rarg))
665
+ else
666
+ Arel::Nodes::Except.new(visit(larg), visit(rarg))
667
+ end
668
+ else
669
+ # https://www.postgresql.org/docs/10/queries-union.html
670
+ raise "Unknown combining queries op `#{op}`"
671
+ end
672
+
673
+ unless union.nil?
674
+ select_statement.cores = []
675
+ select_statement.union = union
676
+ end
677
+
678
+ if context == :top
679
+ select_manager
680
+ else
681
+ select_statement
682
+ end
683
+ end
684
+
685
+ def visit_SetToDefault(_args)
686
+ Arel::Nodes::SetToDefault.new
687
+ end
688
+
689
+ def visit_SortBy(node:, sortby_dir:, sortby_nulls:)
690
+ result = visit(node)
691
+ case sortby_dir
692
+ when 1
693
+ Arel::Nodes::Ascending.new(result, sortby_nulls)
694
+ when 2
695
+ Arel::Nodes::Descending.new(result, sortby_nulls)
696
+ else
697
+ result
698
+ end
699
+ end
700
+
701
+ def visit_SQLValueFunction(op:, typmod:)
702
+ [
703
+ -> { Arel::Nodes::CurrentDate.new },
704
+ -> { Arel::Nodes::CurrentTime.new },
705
+ -> { Arel::Nodes::CurrentTime.new(precision: typmod) },
706
+ -> { Arel::Nodes::CurrentTimestamp.new },
707
+ -> { Arel::Nodes::CurrentTimestamp.new(precision: typmod) },
708
+ -> { Arel::Nodes::LocalTime.new },
709
+ -> { Arel::Nodes::LocalTime.new(precision: typmod) },
710
+ -> { Arel::Nodes::LocalTimestamp.new },
711
+ -> { Arel::Nodes::LocalTimestamp.new(precision: typmod) },
712
+ -> { Arel::Nodes::CurrentRole.new },
713
+ -> { Arel::Nodes::CurrentUser.new },
714
+ -> { Arel::Nodes::User.new },
715
+ -> { Arel::Nodes::SessionUser.new },
716
+ -> { Arel::Nodes::CurrentCatalog.new },
717
+ -> { Arel::Nodes::CurrentSchema.new },
718
+ ][op].call
719
+ end
720
+
721
+ def visit_String(context = nil, str:)
722
+ case context
723
+ when :operator
724
+ str
725
+ when :const
726
+ Arel.sql "'#{str}'"
727
+ else
728
+ "\"#{str}\""
729
+ end
730
+ end
731
+
732
+ def visit_SubLink(subselect:, sub_link_type:, testexpr: nil, oper_name: nil)
733
+ subselect = visit(subselect)
734
+ testexpr = visit(testexpr) if testexpr
735
+ operator = if oper_name
736
+ operator = visit(oper_name, :operator)
737
+ if operator.length > 1
738
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/39'
739
+ end
740
+
741
+ operator.first
742
+ end
743
+
744
+ generate_sublink(sub_link_type, subselect, testexpr, operator)
745
+ end
746
+
747
+ def visit_TypeCast(arg:, type_name:)
748
+ arg = visit(arg)
749
+ type_name = visit(type_name)
750
+
751
+ Arel::Nodes::TypeCast.new(arg, type_name)
752
+ end
753
+
754
+ def visit_TypeName(names:, typemod:)
755
+ names = names.map do |name|
756
+ visit(name, :operator)
757
+ end
758
+
759
+ names = names.reject { |name| name == PG_CATALOG }
760
+
761
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/40' if typemod != -1
762
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/41' if names.length > 1
763
+
764
+ names.first
765
+ end
766
+
767
+ def visit_UpdateStmt(
768
+ relation:,
769
+ target_list:,
770
+ where_clause: nil,
771
+ from_clause: [],
772
+ returning_list: [],
773
+ with_clause: nil
774
+ )
775
+ relation = visit(relation)
776
+ target_list = visit(target_list, :update)
777
+
778
+ update_manager = Arel::UpdateManager.new
779
+ update_statement = update_manager.ast
780
+ update_statement.relation = relation
781
+ update_statement.froms = visit(from_clause)
782
+ update_statement.values = target_list
783
+ update_statement.wheres = where_clause ? [visit(where_clause)] : []
784
+ update_statement.with = visit(with_clause) if with_clause
785
+ update_statement.returning = visit(returning_list, :select)
786
+ update_manager
787
+ end
788
+
789
+ def visit_WindowDef(
790
+ partition_clause: [],
791
+ order_clause: [],
792
+ frame_options:,
793
+ name: nil,
794
+ start_offset: nil,
795
+ end_offset: nil
796
+ )
797
+ instance = name.nil? ? Arel::Nodes::Window.new : Arel::Nodes::NamedWindow.new(name)
798
+
799
+ instance.tap do |window|
800
+ window.orders = visit order_clause
801
+ window.partitions = visit partition_clause
802
+
803
+ if frame_options
804
+ window.framing = FrameOptions.arel(
805
+ frame_options,
806
+ (visit(start_offset) if start_offset),
807
+ (visit(end_offset) if end_offset),
808
+ )
809
+ end
810
+ end
811
+ end
812
+
813
+ def visit_WithClause(ctes:, recursive: false)
814
+ if recursive
815
+ Arel::Nodes::WithRecursive.new visit(ctes)
816
+ else
817
+ Arel::Nodes::With.new visit(ctes)
818
+ end
819
+ end
820
+
821
+ def generate_operator(left, right, operator)
822
+ case operator
823
+
824
+ # https://www.postgresql.org/docs/10/functions-math.html
825
+ when '+'
826
+ Arel::Nodes::Addition.new(left, right)
827
+ when '-'
828
+ Arel::Nodes::Subtraction.new(left, right)
829
+ when '*'
830
+ Arel::Nodes::Multiplication.new(left, right)
831
+ when '/'
832
+ Arel::Nodes::Division.new(left, right)
833
+ when '%'
834
+ Arel::Nodes::Modulo.new(left, right)
835
+ when '^'
836
+ Arel::Nodes::Exponentiation.new(left, right)
837
+ when '|/'
838
+ Arel::Nodes::SquareRoot.new(right)
839
+ when '||/'
840
+ Arel::Nodes::CubeRoot.new(right)
841
+ when '!'
842
+ Arel::Nodes::Factorial.new(left || right, false)
843
+ when '!!'
844
+ Arel::Nodes::Factorial.new(right, true)
845
+ when '@'
846
+ Arel::Nodes::Absolute.new(right)
847
+ when '&'
848
+ Arel::Nodes::BitwiseAnd.new(left, right)
849
+ when '|'
850
+ Arel::Nodes::BitwiseOr.new(left, right)
851
+ when '#'
852
+ Arel::Nodes::BitwiseXor.new(left, right)
853
+ when '~'
854
+ Arel::Nodes::BitwiseNot.new(right)
855
+ when '<<'
856
+ Arel::Nodes::BitwiseShiftLeft.new(left, right)
857
+ when '>>'
858
+ Arel::Nodes::BitwiseShiftRight.new(left, right)
859
+
860
+ # https://www.postgresql.org/docs/9.0/functions-comparison.html
861
+ when '<'
862
+ Arel::Nodes::LessThan.new(left, right)
863
+ when '>'
864
+ Arel::Nodes::GreaterThan.new(left, right)
865
+ when '<='
866
+ Arel::Nodes::LessThanOrEqual.new(left, right)
867
+ when '>='
868
+ Arel::Nodes::GreaterThanOrEqual.new(left, right)
869
+ when '='
870
+ Arel::Nodes::Equality.new(left, right)
871
+ when '<>'
872
+ Arel::Nodes::NotEqual.new(left, right)
873
+
874
+ # https://www.postgresql.org/docs/9.1/functions-array.html
875
+ when '@>'
876
+ Arel::Nodes::Contains.new(left, right)
877
+ when '<@'
878
+ Arel::Nodes::ContainedBy.new(left, right)
879
+ when '&&'
880
+ Arel::Nodes::Overlap.new(left, right)
881
+ when '||'
882
+ Arel::Nodes::Concat.new(left, right)
883
+
884
+ else
885
+ raise "Unknown operator `#{operator}`"
886
+ end
887
+ end
888
+
889
+ def visit(attribute, context = nil)
890
+ return attribute.map { |attr| visit(attr, context) } if attribute.is_a? Array
891
+
892
+ klass, attributes = klass_and_attributes(attribute)
893
+ dispatch_method = "visit_#{klass}"
894
+ method = method(dispatch_method)
895
+
896
+ arg_has_context = (method.parameters.include?(%i[opt context]) ||
897
+ method.parameters.include?(%i[req context])) && context
898
+
899
+ args = arg_has_context ? [context] : nil
900
+
901
+ if attributes.empty?
902
+ send dispatch_method, *args
903
+ else
904
+ kwargs = attributes.transform_keys do |key|
905
+ key
906
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
907
+ .downcase
908
+ .to_sym
909
+ end
910
+
911
+ kwargs.delete(:location)
912
+
913
+ if (aliaz = kwargs.delete(:alias))
914
+ kwargs[:aliaz] = aliaz
915
+ end
916
+
917
+ send dispatch_method, *args, **kwargs
918
+ end
919
+ end
920
+
921
+ def klass_and_attributes(object)
922
+ [object.keys.first, object.values.first]
923
+ end
924
+
925
+ def generate_boolean_expression(args, boolean_class)
926
+ chain = boolean_class.new(nil, nil)
927
+
928
+ args.each_with_index.reduce(chain) do |c, (arg, index)|
929
+ if args.length - 1 == index
930
+ c.right = arg
931
+ c
932
+ else
933
+ new_chain = boolean_class.new(arg, nil)
934
+ c.right = new_chain
935
+ new_chain
936
+ end
937
+ end
938
+
939
+ chain.right
940
+ end
941
+
942
+ def generate_sources(clause)
943
+ froms = []
944
+ join_sources = []
945
+
946
+ if (from_clauses = clause)
947
+ results = visit(from_clauses).flatten
948
+
949
+ results.each do |result|
950
+ case result
951
+ when Arel::Table
952
+ froms << result
953
+ else
954
+ join_sources << result
955
+ end
956
+ end
957
+ end
958
+
959
+ if froms.empty?
960
+ [nil, join_sources]
961
+ else
962
+ [froms, join_sources]
963
+ end
964
+ end
965
+
966
+ def generate_sublink(sub_link_type, subselect, testexpr, operator)
967
+ case sub_link_type
968
+ when PgQuery::SUBLINK_TYPE_EXISTS
969
+ Arel::Nodes::Exists.new subselect
970
+
971
+ when PgQuery::SUBLINK_TYPE_ALL
972
+ generate_operator(testexpr, Arel::Nodes::All.new(subselect), operator)
973
+
974
+ when PgQuery::SUBLINK_TYPE_ANY
975
+ generate_operator(testexpr, Arel::Nodes::Any.new(subselect), operator)
976
+
977
+ when PgQuery::SUBLINK_TYPE_ROWCOMPARE
978
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/42'
979
+
980
+ when PgQuery::SUBLINK_TYPE_EXPR
981
+ Arel::Nodes::Grouping.new(subselect)
982
+
983
+ when PgQuery::SUBLINK_TYPE_MULTIEXPR
984
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/43'
985
+
986
+ when PgQuery::SUBLINK_TYPE_ARRAY
987
+ Arel::Nodes::ArraySubselect.new(subselect)
988
+
989
+ when PgQuery::SUBLINK_TYPE_CTE
990
+ raise 'https://github.com/mvgijssel/arel_toolkit/issues/44'
991
+
992
+ else
993
+ raise "Unknown sublinktype: #{type}"
994
+ end
995
+ end
996
+ end
997
+ end
998
+ end
999
+
1000
+ # rubocop:enable Metrics/PerceivedComplexity
1001
+ # rubocop:enable Naming/MethodName
1002
+ # rubocop:enable Metrics/CyclomaticComplexity
1003
+ # rubocop:enable Metrics/AbcSize
1004
+ # rubocop:enable Naming/UncommunicativeMethodParamName
1005
+ # rubocop:enable Metrics/ParameterLists