arel_toolkit 0.2.0 → 0.4.3

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +3 -0
  3. data/.github/workflows/develop.yml +90 -0
  4. data/.github/workflows/master.yml +67 -0
  5. data/.gitignore +8 -0
  6. data/.rubocop.yml +13 -2
  7. data/Appraisals +13 -0
  8. data/CHANGELOG.md +132 -6
  9. data/Gemfile +5 -0
  10. data/Gemfile.lock +92 -12
  11. data/Guardfile +23 -12
  12. data/README.md +104 -6
  13. data/Rakefile +18 -0
  14. data/arel_toolkit.gemspec +19 -4
  15. data/benchmark.rb +54 -0
  16. data/bin/console +1 -0
  17. data/ext/pg_result_init/extconf.rb +52 -0
  18. data/ext/pg_result_init/pg_result_init.c +138 -0
  19. data/ext/pg_result_init/pg_result_init.h +6 -0
  20. data/gemfiles/active_record_6.gemfile +7 -0
  21. data/gemfiles/active_record_6.gemfile.lock +210 -0
  22. data/gemfiles/arel_gems.gemfile +10 -0
  23. data/gemfiles/arel_gems.gemfile.lock +284 -0
  24. data/gemfiles/default.gemfile +5 -0
  25. data/gemfiles/default.gemfile.lock +208 -0
  26. data/lib/arel/enhance.rb +17 -0
  27. data/lib/arel/enhance/context_enhancer/arel_table.rb +92 -0
  28. data/lib/arel/enhance/node.rb +232 -0
  29. data/lib/arel/enhance/path.rb +38 -0
  30. data/lib/arel/enhance/path_node.rb +26 -0
  31. data/lib/arel/enhance/query.rb +38 -0
  32. data/lib/arel/enhance/query_methods.rb +23 -0
  33. data/lib/arel/enhance/visitor.rb +97 -0
  34. data/lib/arel/extensions.rb +55 -3
  35. data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
  36. data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
  37. data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
  38. data/lib/arel/extensions/active_record_type_caster_map.rb +7 -0
  39. data/lib/arel/extensions/array.rb +2 -9
  40. data/lib/arel/extensions/assignment.rb +22 -0
  41. data/lib/arel/extensions/at_time_zone.rb +37 -0
  42. data/lib/arel/extensions/attributes_attribute.rb +47 -0
  43. data/lib/arel/extensions/binary.rb +7 -0
  44. data/lib/arel/extensions/bind_param.rb +15 -0
  45. data/lib/arel/extensions/bit_string.rb +2 -9
  46. data/lib/arel/extensions/case.rb +17 -0
  47. data/lib/arel/extensions/coalesce.rb +17 -3
  48. data/lib/arel/extensions/conflict.rb +9 -0
  49. data/lib/arel/extensions/contained_within_equals.rb +10 -0
  50. data/lib/arel/extensions/contains.rb +27 -5
  51. data/lib/arel/extensions/contains_equals.rb +10 -0
  52. data/lib/arel/extensions/current_catalog.rb +4 -0
  53. data/lib/arel/extensions/current_date.rb +4 -0
  54. data/lib/arel/extensions/current_of_expression.rb +2 -9
  55. data/lib/arel/extensions/current_role.rb +4 -0
  56. data/lib/arel/extensions/current_row.rb +7 -0
  57. data/lib/arel/extensions/current_schema.rb +4 -0
  58. data/lib/arel/extensions/current_user.rb +4 -0
  59. data/lib/arel/extensions/dealocate.rb +31 -0
  60. data/lib/arel/extensions/default_values.rb +4 -0
  61. data/lib/arel/extensions/delete_manager.rb +25 -0
  62. data/lib/arel/extensions/delete_statement.rb +32 -8
  63. data/lib/arel/extensions/distinct_from.rb +3 -16
  64. data/lib/arel/extensions/dot.rb +11 -0
  65. data/lib/arel/extensions/equality.rb +2 -4
  66. data/lib/arel/extensions/exists.rb +59 -0
  67. data/lib/arel/extensions/extract_from.rb +25 -0
  68. data/lib/arel/extensions/factorial.rb +10 -2
  69. data/lib/arel/extensions/false.rb +7 -0
  70. data/lib/arel/extensions/function.rb +44 -14
  71. data/lib/arel/extensions/greatest.rb +17 -3
  72. data/lib/arel/extensions/indirection.rb +3 -12
  73. data/lib/arel/extensions/infer.rb +7 -7
  74. data/lib/arel/extensions/infix_operation.rb +17 -0
  75. data/lib/arel/extensions/insert_manager.rb +21 -0
  76. data/lib/arel/extensions/insert_statement.rb +35 -9
  77. data/lib/arel/extensions/into.rb +21 -0
  78. data/lib/arel/extensions/json_get_field.rb +10 -0
  79. data/lib/arel/extensions/json_get_object.rb +10 -0
  80. data/lib/arel/extensions/json_path_get_field.rb +10 -0
  81. data/lib/arel/extensions/json_path_get_object.rb +10 -0
  82. data/lib/arel/extensions/jsonb_all_key_exists.rb +10 -0
  83. data/lib/arel/extensions/jsonb_any_key_exists.rb +10 -0
  84. data/lib/arel/extensions/jsonb_key_exists.rb +10 -0
  85. data/lib/arel/extensions/least.rb +17 -3
  86. data/lib/arel/extensions/named_argument.rb +24 -0
  87. data/lib/arel/extensions/named_function.rb +7 -0
  88. data/lib/arel/extensions/node.rb +10 -0
  89. data/lib/arel/extensions/not_distinct_from.rb +3 -16
  90. data/lib/arel/extensions/not_equal.rb +2 -4
  91. data/lib/arel/extensions/ordering.rb +21 -6
  92. data/lib/arel/extensions/overlap.rb +1 -1
  93. data/lib/arel/extensions/overlaps.rb +49 -0
  94. data/lib/arel/extensions/overlay.rb +53 -0
  95. data/lib/arel/extensions/position.rb +27 -0
  96. data/lib/arel/extensions/prepare.rb +39 -0
  97. data/lib/arel/extensions/range_function.rb +10 -2
  98. data/lib/arel/extensions/row.rb +3 -8
  99. data/lib/arel/extensions/select_core.rb +73 -0
  100. data/lib/arel/extensions/select_manager.rb +25 -0
  101. data/lib/arel/extensions/select_statement.rb +31 -9
  102. data/lib/arel/extensions/session_user.rb +4 -0
  103. data/lib/arel/extensions/set_to_default.rb +4 -0
  104. data/lib/arel/extensions/substring.rb +46 -0
  105. data/lib/arel/extensions/table.rb +43 -10
  106. data/lib/arel/extensions/time_with_precision.rb +6 -0
  107. data/lib/arel/extensions/to_sql.rb +27 -0
  108. data/lib/arel/extensions/top.rb +8 -0
  109. data/lib/arel/extensions/transaction.rb +45 -0
  110. data/lib/arel/extensions/tree_manager.rb +15 -0
  111. data/lib/arel/extensions/trim.rb +44 -0
  112. data/lib/arel/extensions/true.rb +7 -0
  113. data/lib/arel/extensions/type_cast.rb +11 -0
  114. data/lib/arel/extensions/unary.rb +7 -0
  115. data/lib/arel/extensions/unary_operation.rb +16 -0
  116. data/lib/arel/extensions/unknown.rb +4 -0
  117. data/lib/arel/extensions/update_manager.rb +25 -0
  118. data/lib/arel/extensions/update_statement.rb +31 -6
  119. data/lib/arel/extensions/user.rb +4 -0
  120. data/lib/arel/extensions/values_list.rb +15 -0
  121. data/lib/arel/extensions/variable_set.rb +55 -0
  122. data/lib/arel/extensions/variable_show.rb +26 -0
  123. data/lib/arel/middleware.rb +27 -0
  124. data/lib/arel/middleware/active_record_extension.rb +13 -0
  125. data/lib/arel/middleware/cache_accessor.rb +35 -0
  126. data/lib/arel/middleware/chain.rb +172 -0
  127. data/lib/arel/middleware/database_executor.rb +77 -0
  128. data/lib/arel/middleware/no_op_cache.rb +9 -0
  129. data/lib/arel/middleware/postgresql_adapter.rb +62 -0
  130. data/lib/arel/middleware/railtie.rb +25 -0
  131. data/lib/arel/middleware/result.rb +170 -0
  132. data/lib/arel/middleware/to_sql_executor.rb +15 -0
  133. data/lib/arel/middleware/to_sql_middleware.rb +33 -0
  134. data/lib/arel/sql_to_arel.rb +8 -4
  135. data/lib/arel/sql_to_arel/error.rb +6 -0
  136. data/lib/arel/sql_to_arel/pg_query_visitor.rb +324 -76
  137. data/lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb +112 -0
  138. data/lib/arel/sql_to_arel/result.rb +30 -0
  139. data/lib/arel/transformer.rb +8 -0
  140. data/lib/arel/transformer/prefix_schema_name.rb +183 -0
  141. data/lib/arel/transformer/remove_active_record_info.rb +40 -0
  142. data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
  143. data/lib/arel_toolkit.rb +16 -1
  144. data/lib/arel_toolkit/version.rb +1 -1
  145. metadata +278 -25
  146. data/.travis.yml +0 -21
  147. data/lib/arel/extensions/generate_series.rb +0 -9
  148. data/lib/arel/extensions/rank.rb +0 -9
  149. data/lib/arel/sql_to_arel/frame_options.rb +0 -110
  150. data/lib/arel/sql_to_arel/unbound_column_reference.rb +0 -5
@@ -0,0 +1,15 @@
1
+ module Arel
2
+ class TreeManager
3
+ # Iterate through AST, nodes will be yielded depth-first
4
+ def each(&block)
5
+ return enum_for(:each) unless block_given?
6
+
7
+ ::Arel::Visitors::DepthFirst.new(block).accept ast
8
+ end
9
+
10
+ def to_sql_and_binds(engine = Arel::Table.engine)
11
+ collector = engine.connection.send(:collector)
12
+ engine.connection.visitor.accept(@ast, collector).value
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # rubocop:disable Naming/MethodName
2
+ # rubocop:disable Naming/UncommunicativeMethodParamName
3
+
4
+ module Arel
5
+ module Nodes
6
+ # https://www.postgresql.org/docs/10/functions-string.html
7
+ class Trim < Arel::Nodes::Node
8
+ attr_reader :type
9
+ attr_reader :substring
10
+ attr_reader :string
11
+
12
+ def initialize(type, substring, string)
13
+ @type = type
14
+ @substring = substring
15
+ @string = string
16
+ end
17
+ end
18
+ end
19
+
20
+ module Visitors
21
+ class ToSql
22
+ def visit_Arel_Nodes_Trim(o, collector)
23
+ collector << "trim(#{o.type} "
24
+ if o.substring
25
+ visit o.substring, collector
26
+ collector << ' from '
27
+ end
28
+ visit o.string, collector
29
+ collector << ')'
30
+ end
31
+ end
32
+
33
+ class Dot
34
+ def visit_Arel_Nodes_Trim(o)
35
+ visit_edge o, 'type'
36
+ visit_edge o, 'substring'
37
+ visit_edge o, 'string'
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # rubocop:enable Naming/MethodName
44
+ # rubocop:enable Naming/UncommunicativeMethodParamName
@@ -0,0 +1,7 @@
1
+ module Arel
2
+ module Visitors
3
+ class Dot
4
+ alias visit_Arel_Nodes_True terminal
5
+ end
6
+ end
7
+ end
@@ -12,6 +12,10 @@ module Arel
12
12
  @arg = arg
13
13
  @type_name = type_name
14
14
  end
15
+
16
+ def ==(other)
17
+ arg == other.arg && type_name == other.type_name
18
+ end
15
19
  end
16
20
  end
17
21
 
@@ -23,6 +27,13 @@ module Arel
23
27
  collector << o.type_name
24
28
  end
25
29
  end
30
+
31
+ class Dot
32
+ def visit_Arel_Nodes_TypeCast(o)
33
+ visit_edge(o, 'arg')
34
+ visit_edge(o, 'type_name')
35
+ end
36
+ end
26
37
  end
27
38
  end
28
39
 
@@ -0,0 +1,7 @@
1
+ module Arel
2
+ module Visitors
3
+ class Dot
4
+ alias visit_Arel_Nodes_Unary unary
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # rubocop:disable Naming/MethodName
2
+ # rubocop:disable Naming/UncommunicativeMethodParamName
3
+
4
+ module Arel
5
+ module Visitors
6
+ class Dot
7
+ def visit_Arel_Nodes_UnaryOperation(o)
8
+ visit_edge o, 'operator'
9
+ visit_edge o, 'expr'
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ # rubocop:enable Naming/MethodName
16
+ # rubocop:enable Naming/UncommunicativeMethodParamName
@@ -13,6 +13,10 @@ module Arel
13
13
  collector << 'UNKNOWN'
14
14
  end
15
15
  end
16
+
17
+ class Dot
18
+ alias visit_Arel_Nodes_Unknown terminal
19
+ end
16
20
  end
17
21
  end
18
22
 
@@ -0,0 +1,25 @@
1
+ # rubocop:disable Naming/MethodName
2
+ # rubocop:disable Naming/UncommunicativeMethodParamName
3
+
4
+ module Arel
5
+ class UpdateManager < Arel::TreeManager
6
+ def ==(other)
7
+ other.is_a?(self.class) && @ast == other.ast && @ctx == other.ctx
8
+ end
9
+
10
+ protected
11
+
12
+ attr_reader :ctx
13
+ end
14
+
15
+ module Visitors
16
+ class Dot
17
+ def visit_Arel_UpdateManager(o)
18
+ visit_edge o, 'ast'
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # rubocop:enable Naming/MethodName
25
+ # rubocop:enable Naming/UncommunicativeMethodParamName
@@ -3,11 +3,22 @@
3
3
 
4
4
  module Arel
5
5
  module Nodes
6
- # https://www.postgresql.org/docs/10/sql-update.html
7
- Arel::Nodes::UpdateStatement.class_eval do
8
- attr_accessor :with
9
- attr_accessor :froms
10
- attr_accessor :returning
6
+ class UpdateStatement
7
+ module UpdateStatementExtension
8
+ # https://www.postgresql.org/docs/10/sql-update.html
9
+ attr_accessor :with
10
+ attr_accessor :froms
11
+ attr_accessor :returning
12
+
13
+ def initialize
14
+ super
15
+
16
+ @froms = []
17
+ @returning = []
18
+ end
19
+ end
20
+
21
+ prepend UpdateStatementExtension
11
22
  end
12
23
  end
13
24
 
@@ -19,7 +30,7 @@ module Arel
19
30
  def visit_Arel_Nodes_UpdateStatement(o, collector)
20
31
  if o.with
21
32
  collector = visit o.with, collector
22
- collector << SPACE
33
+ collector << ' '
23
34
  end
24
35
 
25
36
  wheres = if o.orders.empty? && o.limit.nil?
@@ -56,6 +67,20 @@ module Arel
56
67
  # rubocop:enable Metrics/CyclomaticComplexity
57
68
  # rubocop:enable Metrics/PerceivedComplexity
58
69
  end
70
+
71
+ class Dot
72
+ module UpdateStatementExtension
73
+ def visit_Arel_Nodes_UpdateStatement(o)
74
+ super
75
+
76
+ visit_edge o, 'with'
77
+ visit_edge o, 'froms'
78
+ visit_edge o, 'returning'
79
+ end
80
+ end
81
+
82
+ prepend UpdateStatementExtension
83
+ end
59
84
  end
60
85
  end
61
86
 
@@ -13,6 +13,10 @@ module Arel
13
13
  collector << 'user'
14
14
  end
15
15
  end
16
+
17
+ class Dot
18
+ alias visit_Arel_Nodes_User terminal
19
+ end
16
20
  end
17
21
  end
18
22
 
@@ -0,0 +1,15 @@
1
+ # rubocop:disable Naming/MethodName
2
+ # rubocop:disable Naming/UncommunicativeMethodParamName
3
+
4
+ module Arel
5
+ module Visitors
6
+ class Dot
7
+ def visit_Arel_Nodes_ValuesList(o)
8
+ visit_edge o, 'rows'
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ # rubocop:enable Naming/MethodName
15
+ # rubocop:enable Naming/UncommunicativeMethodParamName
@@ -0,0 +1,55 @@
1
+ # rubocop:disable Naming/MethodName
2
+ # rubocop:disable Naming/UncommunicativeMethodParamName
3
+
4
+ module Arel
5
+ module Nodes
6
+ # https://www.postgresql.org/docs/9.3/sql-set.html
7
+ class VariableSet < Arel::Nodes::Node
8
+ attr_reader :type
9
+ attr_reader :args
10
+ attr_reader :name
11
+ attr_reader :local
12
+
13
+ def initialize(type, args, name, local)
14
+ @type = type
15
+ @args = args
16
+ @name = name
17
+ @local = local
18
+ end
19
+ end
20
+ end
21
+
22
+ module Visitors
23
+ class ToSql
24
+ def visit_Arel_Nodes_VariableSet(o, collector)
25
+ collector << 'SET '
26
+ collector << 'LOCAL ' if o.local
27
+
28
+ if o.name == 'timezone'
29
+ collector << 'TIME ZONE '
30
+ else
31
+ collector << o.name
32
+ collector << ' TO '
33
+ end
34
+
35
+ if o.args.empty?
36
+ collector << 'DEFAULT'
37
+ else
38
+ inject_join(o.args, collector, ', ')
39
+ end
40
+ end
41
+ end
42
+
43
+ class Dot
44
+ def visit_Arel_Nodes_VariableSet(o)
45
+ visit_edge o, 'type'
46
+ visit_edge o, 'args'
47
+ visit_edge o, 'name'
48
+ visit_edge o, 'local'
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # rubocop:enable Naming/MethodName
55
+ # rubocop:enable Naming/UncommunicativeMethodParamName
@@ -0,0 +1,26 @@
1
+ # rubocop:disable Naming/MethodName
2
+ # rubocop:disable Naming/UncommunicativeMethodParamName
3
+
4
+ module Arel
5
+ module Nodes
6
+ # https://www.postgresql.org/docs/9.1/sql-show.html
7
+ class VariableShow < Arel::Nodes::Unary
8
+ end
9
+ end
10
+
11
+ module Visitors
12
+ class ToSql
13
+ def visit_Arel_Nodes_VariableShow(o, collector)
14
+ collector << 'SHOW '
15
+ collector << if o.expr == 'timezone'
16
+ 'TIME ZONE'
17
+ else
18
+ o.expr
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # rubocop:enable Naming/MethodName
26
+ # rubocop:enable Naming/UncommunicativeMethodParamName
@@ -0,0 +1,27 @@
1
+ require_relative './middleware/active_record_extension'
2
+ require_relative './middleware/railtie'
3
+ require_relative './middleware/chain'
4
+ require_relative './middleware/database_executor'
5
+ require_relative './middleware/to_sql_executor'
6
+ require_relative './middleware/to_sql_middleware'
7
+ require_relative './middleware/result'
8
+ require_relative './middleware/postgresql_adapter'
9
+
10
+ module Arel
11
+ module Middleware
12
+ class << self
13
+ def current_chain
14
+ Thread.current[:arel_toolkit_middleware_current_chain] ||=
15
+ Arel::Middleware::Chain.new
16
+ end
17
+
18
+ def current_chain=(new_chain)
19
+ Thread.current[:arel_toolkit_middleware_current_chain] = new_chain
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.middleware
25
+ Arel::Middleware.current_chain
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ module Arel
2
+ module Middleware
3
+ module ActiveRecordExtension
4
+ def load_schema!
5
+ # Prevent Rails from memoizing an empty response when using `Arel.middleware.to_sql`.
6
+ # Re-applying the middleware will use the database executor to fetch the actual data.
7
+ Arel.middleware.apply(Arel.middleware.current) do
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ module Arel
2
+ module Middleware
3
+ class CacheAccessor
4
+ attr_reader :cache
5
+
6
+ def initialize(cache)
7
+ @cache = cache
8
+ end
9
+
10
+ def read(original_sql)
11
+ cache.read cache_key(original_sql)
12
+ end
13
+
14
+ def write(transformed_sql:, transformed_binds:, original_sql:, original_binds:)
15
+ # To play it safe, the order of binds was changed and therefore we won't reuse the query
16
+ return if transformed_binds != original_binds
17
+
18
+ cache.write(cache_key(original_sql), transformed_sql)
19
+ end
20
+
21
+ def cache_key_for_sql(sql)
22
+ Digest::SHA256.hexdigest(sql)
23
+ end
24
+
25
+ def cache_key(sql)
26
+ # An important aspect of this cache key method is that it includes hashes of all active
27
+ # middlewares. If multiple Arel middleware chains that are using the same cache backend,
28
+ # this cache key mechanism will prevent cache entries leak in the wrong chain.
29
+
30
+ active_middleware_cache_key = Arel.middleware.current.map(&:hash).join('&') || 0
31
+ active_middleware_cache_key + '|' + cache_key_for_sql(sql)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,172 @@
1
+ require_relative './no_op_cache'
2
+ require_relative './cache_accessor'
3
+
4
+ module Arel
5
+ module Middleware
6
+ class Chain
7
+ attr_reader :executing_middleware_depth
8
+ attr_reader :executor
9
+ attr_reader :cache
10
+
11
+ MAX_RECURSION_DEPTH = 10
12
+
13
+ def initialize(
14
+ internal_middleware = [],
15
+ internal_context = {},
16
+ executor_class = Arel::Middleware::DatabaseExecutor,
17
+ cache: nil
18
+ )
19
+ @internal_middleware = internal_middleware
20
+ @internal_context = internal_context
21
+ @executor = executor_class.new(internal_middleware)
22
+ @executing_middleware_depth = 0
23
+ @cache = cache || NoOpCache
24
+ end
25
+
26
+ def cache_accessor
27
+ @cache_accessor ||= CacheAccessor.new @cache
28
+ end
29
+
30
+ def execute(sql, binds = [], &execute_sql)
31
+ return execute_sql.call(sql, binds).to_casted_result if internal_middleware.length.zero?
32
+
33
+ if (cached_sql = cache_accessor.read(sql))
34
+ return execute_sql.call(cached_sql, binds).to_casted_result
35
+ end
36
+
37
+ execute_with_middleware(sql, binds, execute_sql).to_casted_result
38
+ rescue ::PgQuery::ParseError
39
+ execute_sql.call(sql, binds)
40
+ ensure
41
+ @executing_middleware_depth -= 1
42
+ end
43
+
44
+ def current
45
+ internal_middleware.dup
46
+ end
47
+
48
+ def apply(middleware, cache: @cache, &block)
49
+ new_middleware = Array.wrap(middleware)
50
+ continue_chain(new_middleware, internal_context, cache: cache, &block)
51
+ end
52
+ alias only apply
53
+
54
+ def none(&block)
55
+ continue_chain([], internal_context, cache: cache, &block)
56
+ end
57
+
58
+ def except(without_middleware, cache: @cache, &block)
59
+ without_middleware = Array.wrap(without_middleware)
60
+ new_middleware = internal_middleware - without_middleware
61
+ continue_chain(new_middleware, internal_context, cache: cache, &block)
62
+ end
63
+
64
+ def insert_before(new_middleware, existing_middleware, cache: @cache, &block)
65
+ new_middleware = Array.wrap(new_middleware)
66
+ index = internal_middleware.index(existing_middleware)
67
+ updated_middleware = internal_middleware.insert(index, *new_middleware)
68
+ continue_chain(updated_middleware, internal_context, cache: cache, &block)
69
+ end
70
+
71
+ def prepend(new_middleware, cache: @cache, &block)
72
+ new_middleware = Array.wrap(new_middleware)
73
+ updated_middleware = new_middleware + internal_middleware
74
+ continue_chain(updated_middleware, internal_context, cache: cache, &block)
75
+ end
76
+
77
+ def insert_after(new_middleware, existing_middleware, cache: @cache, &block)
78
+ new_middleware = Array.wrap(new_middleware)
79
+ index = internal_middleware.index(existing_middleware)
80
+ updated_middleware = internal_middleware.insert(index + 1, *new_middleware)
81
+ continue_chain(updated_middleware, internal_context, cache: cache, &block)
82
+ end
83
+
84
+ def append(new_middleware, cache: @cache, &block)
85
+ new_middleware = Array.wrap(new_middleware)
86
+ updated_middleware = internal_middleware + new_middleware
87
+ continue_chain(updated_middleware, internal_context, cache: cache, &block)
88
+ end
89
+
90
+ def context(new_context = nil, &block)
91
+ if new_context.nil? && !block.nil?
92
+ raise 'You cannot do a block statement while calling context without arguments'
93
+ end
94
+
95
+ return internal_context if new_context.nil?
96
+
97
+ continue_chain(internal_middleware, new_context, cache: @cache, &block)
98
+ end
99
+
100
+ def to_sql(type, &block)
101
+ middleware = Arel::Middleware::ToSqlMiddleware.new(type)
102
+
103
+ new_chain = Arel::Middleware::Chain.new(
104
+ internal_middleware + [middleware],
105
+ internal_context,
106
+ Arel::Middleware::ToSqlExecutor,
107
+ )
108
+
109
+ maybe_execute_block(new_chain, &block)
110
+
111
+ middleware.sql
112
+ end
113
+
114
+ protected
115
+
116
+ attr_reader :internal_middleware
117
+ attr_reader :internal_context
118
+
119
+ private
120
+
121
+ def execute_with_middleware(sql, binds, execute_sql)
122
+ check_middleware_recursion(sql)
123
+
124
+ updated_context = context.merge(
125
+ original_sql: sql,
126
+ original_binds: binds,
127
+ cache_accessor: cache_accessor,
128
+ )
129
+
130
+ arel = Arel.sql_to_arel(sql, binds: binds)
131
+ enhanced_arel = Arel.enhance(arel)
132
+
133
+ executor.run(enhanced_arel, updated_context, execute_sql)
134
+ end
135
+
136
+ def continue_chain(middleware, context, cache:, &block)
137
+ new_chain = Arel::Middleware::Chain.new(middleware, context, cache: cache)
138
+ maybe_execute_block(new_chain, &block)
139
+ end
140
+
141
+ def maybe_execute_block(new_chain, &block)
142
+ return new_chain if block.nil?
143
+
144
+ previous_chain = Middleware.current_chain
145
+ Arel::Middleware.current_chain = new_chain
146
+ yield block
147
+ ensure
148
+ Arel::Middleware.current_chain = previous_chain
149
+ end
150
+
151
+ def check_middleware_recursion(sql)
152
+ if executing_middleware_depth > MAX_RECURSION_DEPTH
153
+ message = <<~ERROR
154
+ Middleware is being called from within middleware, aborting execution
155
+ to prevent endless recursion. You can do the following if you want to execute SQL
156
+ inside middleware:
157
+
158
+ - Set middleware context before entering the middleware
159
+ - Use `Arel.middleware.none { ... }` to temporarily disable middleware
160
+
161
+ SQL that triggered the error:
162
+ #{sql}
163
+ ERROR
164
+
165
+ raise message
166
+ else
167
+ @executing_middleware_depth += 1
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end