activerecord 5.2.8.1 → 6.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -816
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +35 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +14 -50
  12. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  14. data/lib/active_record/associations/collection_association.rb +11 -25
  15. data/lib/active_record/associations/collection_proxy.rb +32 -6
  16. data/lib/active_record/associations/foreign_association.rb +7 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +25 -18
  19. data/lib/active_record/associations/has_one_association.rb +28 -30
  20. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  21. data/lib/active_record/associations/join_dependency/join_association.rb +11 -26
  22. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  23. data/lib/active_record/associations/join_dependency.rb +15 -20
  24. data/lib/active_record/associations/preloader/association.rb +1 -2
  25. data/lib/active_record/associations/preloader.rb +32 -29
  26. data/lib/active_record/associations/singular_association.rb +2 -16
  27. data/lib/active_record/associations.rb +16 -12
  28. data/lib/active_record/attribute_assignment.rb +7 -10
  29. data/lib/active_record/attribute_methods/dirty.rb +64 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +8 -7
  31. data/lib/active_record/attribute_methods/read.rb +16 -48
  32. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  34. data/lib/active_record/attribute_methods/write.rb +15 -16
  35. data/lib/active_record/attribute_methods.rb +34 -56
  36. data/lib/active_record/autosave_association.rb +7 -21
  37. data/lib/active_record/base.rb +2 -2
  38. data/lib/active_record/callbacks.rb +3 -17
  39. data/lib/active_record/coders/yaml_column.rb +1 -13
  40. data/lib/active_record/collection_cache_key.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +13 -36
  42. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +25 -84
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -14
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +5 -11
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -11
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +0 -2
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -27
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +81 -52
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +95 -31
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -90
  53. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  54. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +5 -9
  55. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -7
  56. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  57. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +65 -10
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -4
  60. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +16 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  64. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  65. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  67. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  69. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +11 -36
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +9 -2
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +38 -20
  73. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -1
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -56
  75. data/lib/active_record/connection_adapters/schema_cache.rb +5 -0
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -5
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -9
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +95 -62
  79. data/lib/active_record/connection_handling.rb +132 -26
  80. data/lib/active_record/core.rb +75 -52
  81. data/lib/active_record/counter_cache.rb +4 -29
  82. data/lib/active_record/database_configurations/database_config.rb +37 -0
  83. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  84. data/lib/active_record/database_configurations/url_config.rb +74 -0
  85. data/lib/active_record/database_configurations.rb +184 -0
  86. data/lib/active_record/enum.rb +22 -7
  87. data/lib/active_record/errors.rb +24 -21
  88. data/lib/active_record/explain.rb +1 -1
  89. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  90. data/lib/active_record/fixture_set/render_context.rb +17 -0
  91. data/lib/active_record/fixture_set/table_row.rb +153 -0
  92. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  93. data/lib/active_record/fixtures.rb +140 -472
  94. data/lib/active_record/gem_version.rb +4 -4
  95. data/lib/active_record/inheritance.rb +12 -2
  96. data/lib/active_record/integration.rb +56 -16
  97. data/lib/active_record/internal_metadata.rb +5 -1
  98. data/lib/active_record/locking/optimistic.rb +2 -2
  99. data/lib/active_record/locking/pessimistic.rb +3 -3
  100. data/lib/active_record/log_subscriber.rb +7 -26
  101. data/lib/active_record/migration/command_recorder.rb +35 -5
  102. data/lib/active_record/migration/compatibility.rb +34 -16
  103. data/lib/active_record/migration.rb +38 -37
  104. data/lib/active_record/model_schema.rb +30 -9
  105. data/lib/active_record/nested_attributes.rb +2 -2
  106. data/lib/active_record/no_touching.rb +7 -0
  107. data/lib/active_record/persistence.rb +18 -7
  108. data/lib/active_record/query_cache.rb +11 -4
  109. data/lib/active_record/querying.rb +19 -11
  110. data/lib/active_record/railtie.rb +71 -60
  111. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  112. data/lib/active_record/railties/controller_runtime.rb +30 -35
  113. data/lib/active_record/railties/databases.rake +94 -43
  114. data/lib/active_record/reflection.rb +60 -44
  115. data/lib/active_record/relation/batches.rb +13 -10
  116. data/lib/active_record/relation/calculations.rb +38 -28
  117. data/lib/active_record/relation/delegation.rb +4 -13
  118. data/lib/active_record/relation/finder_methods.rb +12 -25
  119. data/lib/active_record/relation/merger.rb +2 -6
  120. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  121. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  122. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  123. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  124. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  125. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  126. data/lib/active_record/relation/predicate_builder.rb +4 -6
  127. data/lib/active_record/relation/query_attribute.rb +15 -12
  128. data/lib/active_record/relation/query_methods.rb +29 -52
  129. data/lib/active_record/relation/where_clause.rb +4 -0
  130. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  131. data/lib/active_record/relation.rb +150 -69
  132. data/lib/active_record/result.rb +30 -11
  133. data/lib/active_record/sanitization.rb +2 -39
  134. data/lib/active_record/schema.rb +1 -10
  135. data/lib/active_record/schema_dumper.rb +12 -6
  136. data/lib/active_record/schema_migration.rb +4 -0
  137. data/lib/active_record/scoping/default.rb +10 -3
  138. data/lib/active_record/scoping/named.rb +10 -14
  139. data/lib/active_record/scoping.rb +9 -8
  140. data/lib/active_record/statement_cache.rb +32 -5
  141. data/lib/active_record/store.rb +39 -8
  142. data/lib/active_record/table_metadata.rb +1 -4
  143. data/lib/active_record/tasks/database_tasks.rb +89 -23
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +2 -4
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  147. data/lib/active_record/test_databases.rb +38 -0
  148. data/lib/active_record/test_fixtures.rb +224 -0
  149. data/lib/active_record/timestamp.rb +4 -6
  150. data/lib/active_record/transactions.rb +3 -22
  151. data/lib/active_record/translation.rb +1 -1
  152. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  153. data/lib/active_record/type.rb +3 -4
  154. data/lib/active_record/type_caster/connection.rb +1 -6
  155. data/lib/active_record/type_caster/map.rb +1 -4
  156. data/lib/active_record/validations/uniqueness.rb +13 -25
  157. data/lib/active_record.rb +2 -1
  158. data/lib/arel/alias_predication.rb +9 -0
  159. data/lib/arel/attributes/attribute.rb +37 -0
  160. data/lib/arel/attributes.rb +22 -0
  161. data/lib/arel/collectors/bind.rb +24 -0
  162. data/lib/arel/collectors/composite.rb +31 -0
  163. data/lib/arel/collectors/plain_string.rb +20 -0
  164. data/lib/arel/collectors/sql_string.rb +20 -0
  165. data/lib/arel/collectors/substitute_binds.rb +28 -0
  166. data/lib/arel/crud.rb +42 -0
  167. data/lib/arel/delete_manager.rb +18 -0
  168. data/lib/arel/errors.rb +9 -0
  169. data/lib/arel/expressions.rb +29 -0
  170. data/lib/arel/factory_methods.rb +49 -0
  171. data/lib/arel/insert_manager.rb +49 -0
  172. data/lib/arel/math.rb +45 -0
  173. data/lib/arel/nodes/and.rb +32 -0
  174. data/lib/arel/nodes/ascending.rb +23 -0
  175. data/lib/arel/nodes/binary.rb +52 -0
  176. data/lib/arel/nodes/bind_param.rb +36 -0
  177. data/lib/arel/nodes/case.rb +55 -0
  178. data/lib/arel/nodes/casted.rb +50 -0
  179. data/lib/arel/nodes/count.rb +12 -0
  180. data/lib/arel/nodes/delete_statement.rb +45 -0
  181. data/lib/arel/nodes/descending.rb +23 -0
  182. data/lib/arel/nodes/equality.rb +18 -0
  183. data/lib/arel/nodes/extract.rb +24 -0
  184. data/lib/arel/nodes/false.rb +16 -0
  185. data/lib/arel/nodes/full_outer_join.rb +8 -0
  186. data/lib/arel/nodes/function.rb +44 -0
  187. data/lib/arel/nodes/grouping.rb +8 -0
  188. data/lib/arel/nodes/in.rb +8 -0
  189. data/lib/arel/nodes/infix_operation.rb +80 -0
  190. data/lib/arel/nodes/inner_join.rb +8 -0
  191. data/lib/arel/nodes/insert_statement.rb +37 -0
  192. data/lib/arel/nodes/join_source.rb +20 -0
  193. data/lib/arel/nodes/matches.rb +18 -0
  194. data/lib/arel/nodes/named_function.rb +23 -0
  195. data/lib/arel/nodes/node.rb +50 -0
  196. data/lib/arel/nodes/node_expression.rb +13 -0
  197. data/lib/arel/nodes/outer_join.rb +8 -0
  198. data/lib/arel/nodes/over.rb +15 -0
  199. data/lib/arel/nodes/regexp.rb +16 -0
  200. data/lib/arel/nodes/right_outer_join.rb +8 -0
  201. data/lib/arel/nodes/select_core.rb +63 -0
  202. data/lib/arel/nodes/select_statement.rb +41 -0
  203. data/lib/arel/nodes/sql_literal.rb +16 -0
  204. data/lib/arel/nodes/string_join.rb +11 -0
  205. data/lib/arel/nodes/table_alias.rb +27 -0
  206. data/lib/arel/nodes/terminal.rb +16 -0
  207. data/lib/arel/nodes/true.rb +16 -0
  208. data/lib/arel/nodes/unary.rb +44 -0
  209. data/lib/arel/nodes/unary_operation.rb +20 -0
  210. data/lib/arel/nodes/unqualified_column.rb +22 -0
  211. data/lib/arel/nodes/update_statement.rb +41 -0
  212. data/lib/arel/nodes/values.rb +16 -0
  213. data/lib/arel/nodes/values_list.rb +24 -0
  214. data/lib/arel/nodes/window.rb +126 -0
  215. data/lib/arel/nodes/with.rb +11 -0
  216. data/lib/arel/nodes.rb +67 -0
  217. data/lib/arel/order_predications.rb +13 -0
  218. data/lib/arel/predications.rb +257 -0
  219. data/lib/arel/select_manager.rb +271 -0
  220. data/lib/arel/table.rb +110 -0
  221. data/lib/arel/tree_manager.rb +72 -0
  222. data/lib/arel/update_manager.rb +34 -0
  223. data/lib/arel/visitors/depth_first.rb +199 -0
  224. data/lib/arel/visitors/dot.rb +292 -0
  225. data/lib/arel/visitors/ibm_db.rb +21 -0
  226. data/lib/arel/visitors/informix.rb +56 -0
  227. data/lib/arel/visitors/mssql.rb +143 -0
  228. data/lib/arel/visitors/mysql.rb +83 -0
  229. data/lib/arel/visitors/oracle.rb +159 -0
  230. data/lib/arel/visitors/oracle12.rb +67 -0
  231. data/lib/arel/visitors/postgresql.rb +116 -0
  232. data/lib/arel/visitors/sqlite.rb +39 -0
  233. data/lib/arel/visitors/to_sql.rb +913 -0
  234. data/lib/arel/visitors/visitor.rb +42 -0
  235. data/lib/arel/visitors/where_sql.rb +23 -0
  236. data/lib/arel/visitors.rb +20 -0
  237. data/lib/arel/window_predications.rb +9 -0
  238. data/lib/arel.rb +44 -0
  239. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  240. data/lib/rails/generators/active_record/migration.rb +14 -1
  241. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  242. metadata +107 -29
@@ -0,0 +1,913 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class UnsupportedVisitError < StandardError
6
+ def initialize(object)
7
+ super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead."
8
+ end
9
+ end
10
+
11
+ class ToSql < Arel::Visitors::Visitor
12
+ ##
13
+ # This is some roflscale crazy stuff. I'm roflscaling this because
14
+ # building SQL queries is a hotspot. I will explain the roflscale so that
15
+ # others will not rm this code.
16
+ #
17
+ # In YARV, string literals in a method body will get duped when the byte
18
+ # code is executed. Let's take a look:
19
+ #
20
+ # > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
21
+ #
22
+ # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
23
+ # 0000 trace 8
24
+ # 0002 trace 1
25
+ # 0004 putstring "bar"
26
+ # 0006 trace 16
27
+ # 0008 leave
28
+ #
29
+ # The `putstring` bytecode will dup the string and push it on the stack.
30
+ # In many cases in our SQL visitor, that string is never mutated, so there
31
+ # is no need to dup the literal.
32
+ #
33
+ # If we change to a constant lookup, the string will not be duped, and we
34
+ # can reduce the objects in our system:
35
+ #
36
+ # > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
37
+ #
38
+ # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
39
+ # 0000 trace 8
40
+ # 0002 trace 1
41
+ # 0004 getinlinecache 11, <ic:0>
42
+ # 0007 getconstant :BAR
43
+ # 0009 setinlinecache <ic:0>
44
+ # 0011 trace 16
45
+ # 0013 leave
46
+ #
47
+ # `getconstant` should be a hash lookup, and no object is duped when the
48
+ # value of the constant is pushed on the stack. Hence the crazy
49
+ # constants below.
50
+ #
51
+ # `matches` and `doesNotMatch` operate case-insensitively via Visitor subclasses
52
+ # specialized for specific databases when necessary.
53
+ #
54
+
55
+ WHERE = " WHERE " # :nodoc:
56
+ SPACE = " " # :nodoc:
57
+ COMMA = ", " # :nodoc:
58
+ GROUP_BY = " GROUP BY " # :nodoc:
59
+ ORDER_BY = " ORDER BY " # :nodoc:
60
+ WINDOW = " WINDOW " # :nodoc:
61
+ AND = " AND " # :nodoc:
62
+
63
+ DISTINCT = "DISTINCT" # :nodoc:
64
+
65
+ def initialize(connection)
66
+ super()
67
+ @connection = connection
68
+ end
69
+
70
+ def compile(node, collector = Arel::Collectors::SQLString.new)
71
+ accept(node, collector).value
72
+ end
73
+
74
+ private
75
+
76
+ def visit_Arel_Nodes_DeleteStatement(o, collector)
77
+ o = prepare_delete_statement(o)
78
+
79
+ if has_join_sources?(o)
80
+ collector << "DELETE "
81
+ visit o.relation.left, collector
82
+ collector << " FROM "
83
+ else
84
+ collector << "DELETE FROM "
85
+ end
86
+ collector = visit o.relation, collector
87
+
88
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
89
+ collect_nodes_for o.orders, collector, " ORDER BY "
90
+ maybe_visit o.limit, collector
91
+ end
92
+
93
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
94
+ o = prepare_update_statement(o)
95
+
96
+ collector << "UPDATE "
97
+ collector = visit o.relation, collector
98
+ collect_nodes_for o.values, collector, " SET "
99
+
100
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
101
+ collect_nodes_for o.orders, collector, " ORDER BY "
102
+ maybe_visit o.limit, collector
103
+ end
104
+
105
+ def visit_Arel_Nodes_InsertStatement(o, collector)
106
+ collector << "INSERT INTO "
107
+ collector = visit o.relation, collector
108
+ if o.columns.any?
109
+ collector << " (#{o.columns.map { |x|
110
+ quote_column_name x.name
111
+ }.join ', '})"
112
+ end
113
+
114
+ if o.values
115
+ maybe_visit o.values, collector
116
+ elsif o.select
117
+ maybe_visit o.select, collector
118
+ else
119
+ collector
120
+ end
121
+ end
122
+
123
+ def visit_Arel_Nodes_Exists(o, collector)
124
+ collector << "EXISTS ("
125
+ collector = visit(o.expressions, collector) << ")"
126
+ if o.alias
127
+ collector << " AS "
128
+ visit o.alias, collector
129
+ else
130
+ collector
131
+ end
132
+ end
133
+
134
+ def visit_Arel_Nodes_Casted(o, collector)
135
+ collector << quoted(o.val, o.attribute).to_s
136
+ end
137
+
138
+ def visit_Arel_Nodes_Quoted(o, collector)
139
+ collector << quoted(o.expr, nil).to_s
140
+ end
141
+
142
+ def visit_Arel_Nodes_True(o, collector)
143
+ collector << "TRUE"
144
+ end
145
+
146
+ def visit_Arel_Nodes_False(o, collector)
147
+ collector << "FALSE"
148
+ end
149
+
150
+ def visit_Arel_Nodes_ValuesList(o, collector)
151
+ collector << "VALUES "
152
+
153
+ len = o.rows.length - 1
154
+ o.rows.each_with_index { |row, i|
155
+ collector << "("
156
+ row_len = row.length - 1
157
+ row.each_with_index do |value, k|
158
+ case value
159
+ when Nodes::SqlLiteral, Nodes::BindParam
160
+ collector = visit(value, collector)
161
+ else
162
+ collector << quote(value)
163
+ end
164
+ collector << COMMA unless k == row_len
165
+ end
166
+ collector << ")"
167
+ collector << COMMA unless i == len
168
+ }
169
+ collector
170
+ end
171
+
172
+ def visit_Arel_Nodes_Values(o, collector)
173
+ collector << "VALUES ("
174
+
175
+ len = o.expressions.length - 1
176
+ o.expressions.each_with_index { |value, i|
177
+ case value
178
+ when Nodes::SqlLiteral, Nodes::BindParam
179
+ collector = visit value, collector
180
+ else
181
+ collector << quote(value).to_s
182
+ end
183
+ unless i == len
184
+ collector << COMMA
185
+ end
186
+ }
187
+
188
+ collector << ")"
189
+ end
190
+
191
+ def visit_Arel_Nodes_SelectStatement(o, collector)
192
+ if o.with
193
+ collector = visit o.with, collector
194
+ collector << SPACE
195
+ end
196
+
197
+ collector = o.cores.inject(collector) { |c, x|
198
+ visit_Arel_Nodes_SelectCore(x, c)
199
+ }
200
+
201
+ unless o.orders.empty?
202
+ collector << ORDER_BY
203
+ len = o.orders.length - 1
204
+ o.orders.each_with_index { |x, i|
205
+ collector = visit(x, collector)
206
+ collector << COMMA unless len == i
207
+ }
208
+ end
209
+
210
+ visit_Arel_Nodes_SelectOptions(o, collector)
211
+ end
212
+
213
+ def visit_Arel_Nodes_SelectOptions(o, collector)
214
+ collector = maybe_visit o.limit, collector
215
+ collector = maybe_visit o.offset, collector
216
+ maybe_visit o.lock, collector
217
+ end
218
+
219
+ def visit_Arel_Nodes_SelectCore(o, collector)
220
+ collector << "SELECT"
221
+
222
+ collector = maybe_visit o.set_quantifier, collector
223
+
224
+ collect_nodes_for o.projections, collector, SPACE
225
+
226
+ if o.source && !o.source.empty?
227
+ collector << " FROM "
228
+ collector = visit o.source, collector
229
+ end
230
+
231
+ collect_nodes_for o.wheres, collector, WHERE, AND
232
+ collect_nodes_for o.groups, collector, GROUP_BY
233
+ collect_nodes_for o.havings, collector, " HAVING ", AND
234
+ collect_nodes_for o.windows, collector, WINDOW
235
+
236
+ collector
237
+ end
238
+
239
+ def collect_nodes_for(nodes, collector, spacer, connector = COMMA)
240
+ unless nodes.empty?
241
+ collector << spacer
242
+ inject_join nodes, collector, connector
243
+ end
244
+ end
245
+
246
+ def visit_Arel_Nodes_Bin(o, collector)
247
+ visit o.expr, collector
248
+ end
249
+
250
+ def visit_Arel_Nodes_Distinct(o, collector)
251
+ collector << DISTINCT
252
+ end
253
+
254
+ def visit_Arel_Nodes_DistinctOn(o, collector)
255
+ raise NotImplementedError, "DISTINCT ON not implemented for this db"
256
+ end
257
+
258
+ def visit_Arel_Nodes_With(o, collector)
259
+ collector << "WITH "
260
+ inject_join o.children, collector, COMMA
261
+ end
262
+
263
+ def visit_Arel_Nodes_WithRecursive(o, collector)
264
+ collector << "WITH RECURSIVE "
265
+ inject_join o.children, collector, COMMA
266
+ end
267
+
268
+ def visit_Arel_Nodes_Union(o, collector)
269
+ infix_value_with_paren(o, collector, " UNION ")
270
+ end
271
+
272
+ def visit_Arel_Nodes_UnionAll(o, collector)
273
+ infix_value_with_paren(o, collector, " UNION ALL ")
274
+ end
275
+
276
+ def visit_Arel_Nodes_Intersect(o, collector)
277
+ collector << "( "
278
+ infix_value(o, collector, " INTERSECT ") << " )"
279
+ end
280
+
281
+ def visit_Arel_Nodes_Except(o, collector)
282
+ collector << "( "
283
+ infix_value(o, collector, " EXCEPT ") << " )"
284
+ end
285
+
286
+ def visit_Arel_Nodes_NamedWindow(o, collector)
287
+ collector << quote_column_name(o.name)
288
+ collector << " AS "
289
+ visit_Arel_Nodes_Window o, collector
290
+ end
291
+
292
+ def visit_Arel_Nodes_Window(o, collector)
293
+ collector << "("
294
+
295
+ collect_nodes_for o.partitions, collector, "PARTITION BY "
296
+
297
+ if o.orders.any?
298
+ collector << SPACE if o.partitions.any?
299
+ collector << "ORDER BY "
300
+ collector = inject_join o.orders, collector, ", "
301
+ end
302
+
303
+ if o.framing
304
+ collector << SPACE if o.partitions.any? || o.orders.any?
305
+ collector = visit o.framing, collector
306
+ end
307
+
308
+ collector << ")"
309
+ end
310
+
311
+ def visit_Arel_Nodes_Rows(o, collector)
312
+ if o.expr
313
+ collector << "ROWS "
314
+ visit o.expr, collector
315
+ else
316
+ collector << "ROWS"
317
+ end
318
+ end
319
+
320
+ def visit_Arel_Nodes_Range(o, collector)
321
+ if o.expr
322
+ collector << "RANGE "
323
+ visit o.expr, collector
324
+ else
325
+ collector << "RANGE"
326
+ end
327
+ end
328
+
329
+ def visit_Arel_Nodes_Preceding(o, collector)
330
+ collector = if o.expr
331
+ visit o.expr, collector
332
+ else
333
+ collector << "UNBOUNDED"
334
+ end
335
+
336
+ collector << " PRECEDING"
337
+ end
338
+
339
+ def visit_Arel_Nodes_Following(o, collector)
340
+ collector = if o.expr
341
+ visit o.expr, collector
342
+ else
343
+ collector << "UNBOUNDED"
344
+ end
345
+
346
+ collector << " FOLLOWING"
347
+ end
348
+
349
+ def visit_Arel_Nodes_CurrentRow(o, collector)
350
+ collector << "CURRENT ROW"
351
+ end
352
+
353
+ def visit_Arel_Nodes_Over(o, collector)
354
+ case o.right
355
+ when nil
356
+ visit(o.left, collector) << " OVER ()"
357
+ when Arel::Nodes::SqlLiteral
358
+ infix_value o, collector, " OVER "
359
+ when String, Symbol
360
+ visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}"
361
+ else
362
+ infix_value o, collector, " OVER "
363
+ end
364
+ end
365
+
366
+ def visit_Arel_Nodes_Offset(o, collector)
367
+ collector << "OFFSET "
368
+ visit o.expr, collector
369
+ end
370
+
371
+ def visit_Arel_Nodes_Limit(o, collector)
372
+ collector << "LIMIT "
373
+ visit o.expr, collector
374
+ end
375
+
376
+ def visit_Arel_Nodes_Lock(o, collector)
377
+ visit o.expr, collector
378
+ end
379
+
380
+ def visit_Arel_Nodes_Grouping(o, collector)
381
+ if o.expr.is_a? Nodes::Grouping
382
+ visit(o.expr, collector)
383
+ else
384
+ collector << "("
385
+ visit(o.expr, collector) << ")"
386
+ end
387
+ end
388
+
389
+ def visit_Arel_SelectManager(o, collector)
390
+ collector << "("
391
+ visit(o.ast, collector) << ")"
392
+ end
393
+
394
+ def visit_Arel_Nodes_Ascending(o, collector)
395
+ visit(o.expr, collector) << " ASC"
396
+ end
397
+
398
+ def visit_Arel_Nodes_Descending(o, collector)
399
+ visit(o.expr, collector) << " DESC"
400
+ end
401
+
402
+ def visit_Arel_Nodes_Group(o, collector)
403
+ visit o.expr, collector
404
+ end
405
+
406
+ def visit_Arel_Nodes_NamedFunction(o, collector)
407
+ collector << o.name
408
+ collector << "("
409
+ collector << "DISTINCT " if o.distinct
410
+ collector = inject_join(o.expressions, collector, ", ") << ")"
411
+ if o.alias
412
+ collector << " AS "
413
+ visit o.alias, collector
414
+ else
415
+ collector
416
+ end
417
+ end
418
+
419
+ def visit_Arel_Nodes_Extract(o, collector)
420
+ collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
421
+ visit(o.expr, collector) << ")"
422
+ end
423
+
424
+ def visit_Arel_Nodes_Count(o, collector)
425
+ aggregate "COUNT", o, collector
426
+ end
427
+
428
+ def visit_Arel_Nodes_Sum(o, collector)
429
+ aggregate "SUM", o, collector
430
+ end
431
+
432
+ def visit_Arel_Nodes_Max(o, collector)
433
+ aggregate "MAX", o, collector
434
+ end
435
+
436
+ def visit_Arel_Nodes_Min(o, collector)
437
+ aggregate "MIN", o, collector
438
+ end
439
+
440
+ def visit_Arel_Nodes_Avg(o, collector)
441
+ aggregate "AVG", o, collector
442
+ end
443
+
444
+ def visit_Arel_Nodes_TableAlias(o, collector)
445
+ collector = visit o.relation, collector
446
+ collector << " "
447
+ collector << quote_table_name(o.name)
448
+ end
449
+
450
+ def visit_Arel_Nodes_Between(o, collector)
451
+ collector = visit o.left, collector
452
+ collector << " BETWEEN "
453
+ visit o.right, collector
454
+ end
455
+
456
+ def visit_Arel_Nodes_GreaterThanOrEqual(o, collector)
457
+ collector = visit o.left, collector
458
+ collector << " >= "
459
+ visit o.right, collector
460
+ end
461
+
462
+ def visit_Arel_Nodes_GreaterThan(o, collector)
463
+ collector = visit o.left, collector
464
+ collector << " > "
465
+ visit o.right, collector
466
+ end
467
+
468
+ def visit_Arel_Nodes_LessThanOrEqual(o, collector)
469
+ collector = visit o.left, collector
470
+ collector << " <= "
471
+ visit o.right, collector
472
+ end
473
+
474
+ def visit_Arel_Nodes_LessThan(o, collector)
475
+ collector = visit o.left, collector
476
+ collector << " < "
477
+ visit o.right, collector
478
+ end
479
+
480
+ def visit_Arel_Nodes_Matches(o, collector)
481
+ collector = visit o.left, collector
482
+ collector << " LIKE "
483
+ collector = visit o.right, collector
484
+ if o.escape
485
+ collector << " ESCAPE "
486
+ visit o.escape, collector
487
+ else
488
+ collector
489
+ end
490
+ end
491
+
492
+ def visit_Arel_Nodes_DoesNotMatch(o, collector)
493
+ collector = visit o.left, collector
494
+ collector << " NOT LIKE "
495
+ collector = visit o.right, collector
496
+ if o.escape
497
+ collector << " ESCAPE "
498
+ visit o.escape, collector
499
+ else
500
+ collector
501
+ end
502
+ end
503
+
504
+ def visit_Arel_Nodes_JoinSource(o, collector)
505
+ if o.left
506
+ collector = visit o.left, collector
507
+ end
508
+ if o.right.any?
509
+ collector << SPACE if o.left
510
+ collector = inject_join o.right, collector, SPACE
511
+ end
512
+ collector
513
+ end
514
+
515
+ def visit_Arel_Nodes_Regexp(o, collector)
516
+ raise NotImplementedError, "~ not implemented for this db"
517
+ end
518
+
519
+ def visit_Arel_Nodes_NotRegexp(o, collector)
520
+ raise NotImplementedError, "!~ not implemented for this db"
521
+ end
522
+
523
+ def visit_Arel_Nodes_StringJoin(o, collector)
524
+ visit o.left, collector
525
+ end
526
+
527
+ def visit_Arel_Nodes_FullOuterJoin(o, collector)
528
+ collector << "FULL OUTER JOIN "
529
+ collector = visit o.left, collector
530
+ collector << SPACE
531
+ visit o.right, collector
532
+ end
533
+
534
+ def visit_Arel_Nodes_OuterJoin(o, collector)
535
+ collector << "LEFT OUTER JOIN "
536
+ collector = visit o.left, collector
537
+ collector << " "
538
+ visit o.right, collector
539
+ end
540
+
541
+ def visit_Arel_Nodes_RightOuterJoin(o, collector)
542
+ collector << "RIGHT OUTER JOIN "
543
+ collector = visit o.left, collector
544
+ collector << SPACE
545
+ visit o.right, collector
546
+ end
547
+
548
+ def visit_Arel_Nodes_InnerJoin(o, collector)
549
+ collector << "INNER JOIN "
550
+ collector = visit o.left, collector
551
+ if o.right
552
+ collector << SPACE
553
+ visit(o.right, collector)
554
+ else
555
+ collector
556
+ end
557
+ end
558
+
559
+ def visit_Arel_Nodes_On(o, collector)
560
+ collector << "ON "
561
+ visit o.expr, collector
562
+ end
563
+
564
+ def visit_Arel_Nodes_Not(o, collector)
565
+ collector << "NOT ("
566
+ visit(o.expr, collector) << ")"
567
+ end
568
+
569
+ def visit_Arel_Table(o, collector)
570
+ if o.table_alias
571
+ collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
572
+ else
573
+ collector << quote_table_name(o.name)
574
+ end
575
+ end
576
+
577
+ def visit_Arel_Nodes_In(o, collector)
578
+ if Array === o.right && !o.right.empty?
579
+ o.right.delete_if { |value| unboundable?(value) }
580
+ end
581
+
582
+ if Array === o.right && o.right.empty?
583
+ collector << "1=0"
584
+ else
585
+ collector = visit o.left, collector
586
+ collector << " IN ("
587
+ visit(o.right, collector) << ")"
588
+ end
589
+ end
590
+
591
+ def visit_Arel_Nodes_NotIn(o, collector)
592
+ if Array === o.right && !o.right.empty?
593
+ o.right.delete_if { |value| unboundable?(value) }
594
+ end
595
+
596
+ if Array === o.right && o.right.empty?
597
+ collector << "1=1"
598
+ else
599
+ collector = visit o.left, collector
600
+ collector << " NOT IN ("
601
+ collector = visit o.right, collector
602
+ collector << ")"
603
+ end
604
+ end
605
+
606
+ def visit_Arel_Nodes_And(o, collector)
607
+ inject_join o.children, collector, " AND "
608
+ end
609
+
610
+ def visit_Arel_Nodes_Or(o, collector)
611
+ collector = visit o.left, collector
612
+ collector << " OR "
613
+ visit o.right, collector
614
+ end
615
+
616
+ def visit_Arel_Nodes_Assignment(o, collector)
617
+ case o.right
618
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
619
+ collector = visit o.left, collector
620
+ collector << " = "
621
+ visit o.right, collector
622
+ else
623
+ collector = visit o.left, collector
624
+ collector << " = "
625
+ collector << quote(o.right).to_s
626
+ end
627
+ end
628
+
629
+ def visit_Arel_Nodes_Equality(o, collector)
630
+ right = o.right
631
+
632
+ return collector << "1=0" if unboundable?(right)
633
+
634
+ collector = visit o.left, collector
635
+
636
+ if right.nil?
637
+ collector << " IS NULL"
638
+ else
639
+ collector << " = "
640
+ visit right, collector
641
+ end
642
+ end
643
+
644
+ def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
645
+ if o.right.nil?
646
+ collector = visit o.left, collector
647
+ collector << " IS NULL"
648
+ else
649
+ collector = is_distinct_from(o, collector)
650
+ collector << " = 0"
651
+ end
652
+ end
653
+
654
+ def visit_Arel_Nodes_IsDistinctFrom(o, collector)
655
+ if o.right.nil?
656
+ collector = visit o.left, collector
657
+ collector << " IS NOT NULL"
658
+ else
659
+ collector = is_distinct_from(o, collector)
660
+ collector << " = 1"
661
+ end
662
+ end
663
+
664
+ def visit_Arel_Nodes_NotEqual(o, collector)
665
+ right = o.right
666
+
667
+ return collector << "1=1" if unboundable?(right)
668
+
669
+ collector = visit o.left, collector
670
+
671
+ if right.nil?
672
+ collector << " IS NOT NULL"
673
+ else
674
+ collector << " != "
675
+ visit right, collector
676
+ end
677
+ end
678
+
679
+ def visit_Arel_Nodes_As(o, collector)
680
+ collector = visit o.left, collector
681
+ collector << " AS "
682
+ visit o.right, collector
683
+ end
684
+
685
+ def visit_Arel_Nodes_Case(o, collector)
686
+ collector << "CASE "
687
+ if o.case
688
+ visit o.case, collector
689
+ collector << " "
690
+ end
691
+ o.conditions.each do |condition|
692
+ visit condition, collector
693
+ collector << " "
694
+ end
695
+ if o.default
696
+ visit o.default, collector
697
+ collector << " "
698
+ end
699
+ collector << "END"
700
+ end
701
+
702
+ def visit_Arel_Nodes_When(o, collector)
703
+ collector << "WHEN "
704
+ visit o.left, collector
705
+ collector << " THEN "
706
+ visit o.right, collector
707
+ end
708
+
709
+ def visit_Arel_Nodes_Else(o, collector)
710
+ collector << "ELSE "
711
+ visit o.expr, collector
712
+ end
713
+
714
+ def visit_Arel_Nodes_UnqualifiedColumn(o, collector)
715
+ collector << "#{quote_column_name o.name}"
716
+ collector
717
+ end
718
+
719
+ def visit_Arel_Attributes_Attribute(o, collector)
720
+ join_name = o.relation.table_alias || o.relation.name
721
+ collector << "#{quote_table_name join_name}.#{quote_column_name o.name}"
722
+ end
723
+ alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
724
+ alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
725
+ alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
726
+ alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
727
+ alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
728
+ alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
729
+
730
+ def literal(o, collector); collector << o.to_s; end
731
+
732
+ def visit_Arel_Nodes_BindParam(o, collector)
733
+ collector.add_bind(o.value) { "?" }
734
+ end
735
+
736
+ alias :visit_Arel_Nodes_SqlLiteral :literal
737
+ alias :visit_Integer :literal
738
+
739
+ def quoted(o, a)
740
+ if a && a.able_to_type_cast?
741
+ quote(a.type_cast_for_database(o))
742
+ else
743
+ quote(o)
744
+ end
745
+ end
746
+
747
+ def unsupported(o, collector)
748
+ raise UnsupportedVisitError.new(o)
749
+ end
750
+
751
+ alias :visit_ActiveSupport_Multibyte_Chars :unsupported
752
+ alias :visit_ActiveSupport_StringInquirer :unsupported
753
+ alias :visit_BigDecimal :unsupported
754
+ alias :visit_Class :unsupported
755
+ alias :visit_Date :unsupported
756
+ alias :visit_DateTime :unsupported
757
+ alias :visit_FalseClass :unsupported
758
+ alias :visit_Float :unsupported
759
+ alias :visit_Hash :unsupported
760
+ alias :visit_NilClass :unsupported
761
+ alias :visit_String :unsupported
762
+ alias :visit_Symbol :unsupported
763
+ alias :visit_Time :unsupported
764
+ alias :visit_TrueClass :unsupported
765
+
766
+ def visit_Arel_Nodes_InfixOperation(o, collector)
767
+ collector = visit o.left, collector
768
+ collector << " #{o.operator} "
769
+ visit o.right, collector
770
+ end
771
+
772
+ alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation
773
+ alias :visit_Arel_Nodes_Subtraction :visit_Arel_Nodes_InfixOperation
774
+ alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
775
+ alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
776
+
777
+ def visit_Arel_Nodes_UnaryOperation(o, collector)
778
+ collector << " #{o.operator} "
779
+ visit o.expr, collector
780
+ end
781
+
782
+ def visit_Array(o, collector)
783
+ inject_join o, collector, ", "
784
+ end
785
+ alias :visit_Set :visit_Array
786
+
787
+ def quote(value)
788
+ return value if Arel::Nodes::SqlLiteral === value
789
+ @connection.quote value
790
+ end
791
+
792
+ def quote_table_name(name)
793
+ return name if Arel::Nodes::SqlLiteral === name
794
+ @connection.quote_table_name(name)
795
+ end
796
+
797
+ def quote_column_name(name)
798
+ return name if Arel::Nodes::SqlLiteral === name
799
+ @connection.quote_column_name(name)
800
+ end
801
+
802
+ def maybe_visit(thing, collector)
803
+ return collector unless thing
804
+ collector << " "
805
+ visit thing, collector
806
+ end
807
+
808
+ def inject_join(list, collector, join_str)
809
+ len = list.length - 1
810
+ list.each_with_index.inject(collector) { |c, (x, i)|
811
+ if i == len
812
+ visit x, c
813
+ else
814
+ visit(x, c) << join_str
815
+ end
816
+ }
817
+ end
818
+
819
+ def unboundable?(value)
820
+ value.respond_to?(:unboundable?) && value.unboundable?
821
+ end
822
+
823
+ def has_join_sources?(o)
824
+ o.relation.is_a?(Nodes::JoinSource) && !o.relation.right.empty?
825
+ end
826
+
827
+ def has_limit_or_offset_or_orders?(o)
828
+ o.limit || o.offset || !o.orders.empty?
829
+ end
830
+
831
+ # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
832
+ # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
833
+ # an UPDATE statement, so in the MySQL visitor we redefine this to do that.
834
+ def prepare_update_statement(o)
835
+ if o.key && (has_limit_or_offset_or_orders?(o) || has_join_sources?(o))
836
+ stmt = o.clone
837
+ stmt.limit = nil
838
+ stmt.offset = nil
839
+ stmt.orders = []
840
+ stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
841
+ stmt.relation = o.relation.left if has_join_sources?(o)
842
+ stmt
843
+ else
844
+ o
845
+ end
846
+ end
847
+ alias :prepare_delete_statement :prepare_update_statement
848
+
849
+ # FIXME: we should probably have a 2-pass visitor for this
850
+ def build_subselect(key, o)
851
+ stmt = Nodes::SelectStatement.new
852
+ core = stmt.cores.first
853
+ core.froms = o.relation
854
+ core.wheres = o.wheres
855
+ core.projections = [key]
856
+ stmt.limit = o.limit
857
+ stmt.offset = o.offset
858
+ stmt.orders = o.orders
859
+ stmt
860
+ end
861
+
862
+ def infix_value(o, collector, value)
863
+ collector = visit o.left, collector
864
+ collector << value
865
+ visit o.right, collector
866
+ end
867
+
868
+ def infix_value_with_paren(o, collector, value, suppress_parens = false)
869
+ collector << "( " unless suppress_parens
870
+ collector = if o.left.class == o.class
871
+ infix_value_with_paren(o.left, collector, value, true)
872
+ else
873
+ visit o.left, collector
874
+ end
875
+ collector << value
876
+ collector = if o.right.class == o.class
877
+ infix_value_with_paren(o.right, collector, value, true)
878
+ else
879
+ visit o.right, collector
880
+ end
881
+ collector << " )" unless suppress_parens
882
+ collector
883
+ end
884
+
885
+ def aggregate(name, o, collector)
886
+ collector << "#{name}("
887
+ if o.distinct
888
+ collector << "DISTINCT "
889
+ end
890
+ collector = inject_join(o.expressions, collector, ", ") << ")"
891
+ if o.alias
892
+ collector << " AS "
893
+ visit o.alias, collector
894
+ else
895
+ collector
896
+ end
897
+ end
898
+
899
+ def is_distinct_from(o, collector)
900
+ collector << "CASE WHEN "
901
+ collector = visit o.left, collector
902
+ collector << " = "
903
+ collector = visit o.right, collector
904
+ collector << " OR ("
905
+ collector = visit o.left, collector
906
+ collector << " IS NULL AND "
907
+ collector = visit o.right, collector
908
+ collector << " IS NULL)"
909
+ collector << " THEN 0 ELSE 1 END"
910
+ end
911
+ end
912
+ end
913
+ end