arel_toolkit 0.1.0 → 0.2.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 (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