arel_toolkit 0.2.0 → 0.3.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG.md +56 -7
  5. data/Gemfile.lock +54 -1
  6. data/Guardfile +19 -12
  7. data/README.md +56 -2
  8. data/Rakefile +8 -0
  9. data/arel_toolkit.gemspec +6 -0
  10. data/bin/console +1 -0
  11. data/lib/arel/extensions/assignment.rb +22 -0
  12. data/lib/arel/extensions/at_time_zone.rb +30 -0
  13. data/lib/arel/extensions/contained_within_equals.rb +10 -0
  14. data/lib/arel/extensions/contains.rb +2 -2
  15. data/lib/arel/extensions/contains_equals.rb +10 -0
  16. data/lib/arel/extensions/delete_manager.rb +9 -0
  17. data/lib/arel/extensions/delete_statement.rb +7 -0
  18. data/lib/arel/extensions/distinct_from.rb +3 -16
  19. data/lib/arel/extensions/equality.rb +2 -4
  20. data/lib/arel/extensions/extract_from.rb +32 -0
  21. data/lib/arel/extensions/insert_manager.rb +5 -0
  22. data/lib/arel/extensions/insert_statement.rb +10 -3
  23. data/lib/arel/extensions/json_get_field.rb +10 -0
  24. data/lib/arel/extensions/json_get_object.rb +10 -0
  25. data/lib/arel/extensions/json_path_get_field.rb +10 -0
  26. data/lib/arel/extensions/json_path_get_object.rb +10 -0
  27. data/lib/arel/extensions/jsonb_all_key_exists.rb +10 -0
  28. data/lib/arel/extensions/jsonb_any_key_exists.rb +10 -0
  29. data/lib/arel/extensions/jsonb_key_exists.rb +10 -0
  30. data/lib/arel/extensions/named_argument.rb +29 -0
  31. data/lib/arel/extensions/not_distinct_from.rb +3 -16
  32. data/lib/arel/extensions/not_equal.rb +2 -4
  33. data/lib/arel/extensions/overlap.rb +1 -1
  34. data/lib/arel/extensions/overlaps.rb +40 -0
  35. data/lib/arel/extensions/overlay.rb +44 -0
  36. data/lib/arel/extensions/position.rb +32 -0
  37. data/lib/arel/extensions/select_manager.rb +9 -0
  38. data/lib/arel/extensions/substring.rb +38 -0
  39. data/lib/arel/extensions/transaction.rb +50 -0
  40. data/lib/arel/extensions/trim.rb +36 -0
  41. data/lib/arel/extensions/type_cast.rb +4 -0
  42. data/lib/arel/{sql_to_arel → extensions}/unbound_column_reference.rb +1 -1
  43. data/lib/arel/extensions/update_manager.rb +9 -0
  44. data/lib/arel/extensions/update_statement.rb +8 -0
  45. data/lib/arel/extensions/variable_set.rb +46 -0
  46. data/lib/arel/extensions/variable_show.rb +31 -0
  47. data/lib/arel/extensions.rb +26 -0
  48. data/lib/arel/middleware/chain.rb +97 -0
  49. data/lib/arel/middleware/postgresql_adapter.rb +26 -0
  50. data/lib/arel/middleware/railtie.rb +11 -0
  51. data/lib/arel/middleware.rb +23 -0
  52. data/lib/arel/sql_formatter.rb +59 -0
  53. data/lib/arel/sql_to_arel/error.rb +6 -0
  54. data/lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb +112 -0
  55. data/lib/arel/sql_to_arel/pg_query_visitor.rb +271 -52
  56. data/lib/arel/sql_to_arel/result.rb +17 -0
  57. data/lib/arel/sql_to_arel.rb +4 -3
  58. data/lib/arel_toolkit/version.rb +1 -1
  59. data/lib/arel_toolkit.rb +2 -0
  60. metadata +120 -4
  61. data/lib/arel/sql_to_arel/frame_options.rb +0 -110
@@ -0,0 +1,26 @@
1
+ module Arel
2
+ module Middleware
3
+ module PostgreSQLAdapter
4
+ def initialize(*args)
5
+ Arel.middleware.none do
6
+ super(*args)
7
+ end
8
+ end
9
+
10
+ def execute(sql, name = nil)
11
+ sql = Arel::Middleware.current_chain.execute(sql)
12
+ super(sql, name)
13
+ end
14
+
15
+ def exec_no_cache(sql, name, binds)
16
+ sql = Arel::Middleware.current_chain.execute(sql, binds)
17
+ super(sql, name, binds)
18
+ end
19
+
20
+ def exec_cache(sql, name, binds)
21
+ sql = Arel::Middleware.current_chain.execute(sql, binds)
22
+ super(sql, name, binds)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Arel
2
+ module Middleware
3
+ class Railtie
4
+ def self.insert_postgresql
5
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(
6
+ Arel::Middleware::PostgreSQLAdapter,
7
+ )
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_record'
2
+ require_relative './middleware/railtie'
3
+ require_relative './middleware/chain'
4
+ require_relative './middleware/postgresql_adapter'
5
+
6
+ module Arel
7
+ module Middleware
8
+ class << self
9
+ def current_chain
10
+ Thread.current[:arel_toolkit_middleware_current_chain] ||=
11
+ Arel::Middleware::Chain.new
12
+ end
13
+
14
+ def current_chain=(new_chain)
15
+ Thread.current[:arel_toolkit_middleware_current_chain] = new_chain
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.middleware
21
+ Arel::Middleware.current_chain
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ module Arel
2
+ module Nodes
3
+ class Node
4
+ def to_formatted_sql(engine = Table.engine)
5
+ collector = Arel::Collectors::SQLString.new
6
+ Arel::SqlFormatter.new(engine.connection).accept self, collector
7
+ collector.value
8
+ end
9
+ end
10
+ end
11
+
12
+ class TreeManager
13
+ def to_formatted_sql(engine = Table.engine)
14
+ collector = Arel::Collectors::SQLString.new
15
+ Arel::SqlFormatter.new(engine.connection).accept @ast, collector
16
+ collector.value
17
+ end
18
+ end
19
+
20
+ class SqlFormatter < Arel::Visitors::PostgreSQL
21
+ def accept(object, collector)
22
+ super object, collector
23
+ collector << "\n"
24
+ end
25
+
26
+ private
27
+
28
+ # rubocop:disable Naming/MethodName
29
+ # rubocop:disable Naming/UncommunicativeMethodParamName
30
+ # rubocop:disable Metrics/AbcSize
31
+ def visit_Arel_Nodes_SelectCore(o, collector)
32
+ collector << "SELECT\n"
33
+
34
+ collector = maybe_visit o.top, collector
35
+
36
+ collector = maybe_visit o.set_quantifier, collector
37
+
38
+ collect_nodes_for(o.projections, collector, SPACE, ",\n")
39
+
40
+ if o.source && !o.source.empty?
41
+ collector << ' FROM '
42
+ collector = visit o.source, collector
43
+ end
44
+
45
+ collect_nodes_for o.wheres, collector, WHERE, AND
46
+ collect_nodes_for o.groups, collector, GROUP_BY
47
+ unless o.havings.empty?
48
+ collector << ' HAVING '
49
+ inject_join o.havings, collector, AND
50
+ end
51
+ collect_nodes_for o.windows, collector, WINDOW
52
+
53
+ collector
54
+ end
55
+ # rubocop:enable Metrics/AbcSize
56
+ # rubocop:enable Naming/MethodName
57
+ # rubocop:enable Naming/UncommunicativeMethodParamName
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module Arel
2
+ module SqlToArel
3
+ class Error < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,112 @@
1
+ module Arel
2
+ module SqlToArel
3
+ class PgQueryVisitor
4
+ class FrameOptions
5
+ class << self
6
+ def arel(frame_options, start_offset, end_offset)
7
+ frame_option_names = calculate_frame_option_names(frame_options)
8
+ return unless frame_option_names.include?('FRAMEOPTION_NONDEFAULT')
9
+
10
+ range_klass = if frame_option_names.include?('FRAMEOPTION_RANGE')
11
+ Arel::Nodes::Range
12
+ else
13
+ Arel::Nodes::Rows
14
+ end
15
+
16
+ start_node = calculate_frame_node(
17
+ 'FRAMEOPTION_START_',
18
+ frame_option_names,
19
+ start_offset,
20
+ )
21
+ end_node = calculate_frame_node(
22
+ 'FRAMEOPTION_END_',
23
+ frame_option_names,
24
+ end_offset,
25
+ )
26
+
27
+ if frame_option_names.include?('FRAMEOPTION_BETWEEN')
28
+ Arel::Nodes::Between.new(
29
+ range_klass.new,
30
+ Arel::Nodes::And.new([start_node, end_node]),
31
+ )
32
+ else
33
+ range_klass.new start_node
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # always NONDEFAULT
40
+ # RANGE or ROWS
41
+ # mandatory BETWEEN
42
+ # RANGE only unbounded
43
+ # ROWS all
44
+ # https://github.com/postgres/postgres/blob/REL_10_1/src/include/nodes/parsenodes.h
45
+ FRAMEOPTIONS = {
46
+ 'FRAMEOPTION_NONDEFAULT' => 0x00001,
47
+ 'FRAMEOPTION_RANGE' => 0x00002,
48
+ 'FRAMEOPTION_ROWS' => 0x00004,
49
+ 'FRAMEOPTION_BETWEEN' => 0x00008,
50
+ 'FRAMEOPTION_START_UNBOUNDED_PRECEDING' => 0x00010,
51
+ 'FRAMEOPTION_END_UNBOUNDED_PRECEDING' => 0x00020,
52
+ 'FRAMEOPTION_START_UNBOUNDED_FOLLOWING' => 0x00040,
53
+ 'FRAMEOPTION_END_UNBOUNDED_FOLLOWING' => 0x00080,
54
+ 'FRAMEOPTION_START_CURRENT_ROW' => 0x00100,
55
+ 'FRAMEOPTION_END_CURRENT_ROW' => 0x00200,
56
+ 'FRAMEOPTION_START_VALUE_PRECEDING' => 0x00400,
57
+ 'FRAMEOPTION_END_VALUE_PRECEDING' => 0x00800,
58
+ 'FRAMEOPTION_START_VALUE_FOLLOWING' => 0x01000,
59
+ 'FRAMEOPTION_END_VALUE_FOLLOWING' => 0x02000
60
+ }.freeze
61
+
62
+ def biggest_detractable_number(number, candidates)
63
+ high_to_low_candidates = candidates.sort { |a, b| b <=> a }
64
+ high_to_low_candidates.find do |candidate|
65
+ number - candidate >= 0
66
+ end
67
+ end
68
+
69
+ def calculate_frame_option_names(frame_options, names = [])
70
+ return names if frame_options.zero?
71
+
72
+ number = biggest_detractable_number(frame_options, FRAMEOPTIONS.values)
73
+ name = FRAMEOPTIONS.key(number)
74
+ calculate_frame_option_names(
75
+ frame_options - number, names + [name]
76
+ )
77
+ end
78
+
79
+ def calculate_frame_node(pattern, frame_option_names, offset)
80
+ node_name = frame_option_names.select { |n| n.start_with?(pattern) }
81
+ raise "Don't know how to handle multiple nodes" if node_name.length > 1
82
+
83
+ node_name = node_name.first.gsub(/FRAMEOPTION_(START|END)_/, '')
84
+ name_to_node(node_name, offset)
85
+ end
86
+
87
+ def name_to_node(node_name, offset)
88
+ case node_name
89
+ when 'UNBOUNDED_PRECEDING'
90
+ Arel::Nodes::Preceding.new
91
+
92
+ when 'UNBOUNDED_FOLLOWING'
93
+ Arel::Nodes::Following.new
94
+
95
+ when 'CURRENT_ROW'
96
+ Arel::Nodes::CurrentRow.new
97
+
98
+ when 'VALUE_PRECEDING'
99
+ Arel::Nodes::Preceding.new offset
100
+
101
+ when 'VALUE_FOLLOWING'
102
+ Arel::Nodes::Following.new offset
103
+
104
+ else
105
+ raise "Unknown start / end frame node `#{node_name}`"
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end