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
@@ -57,9 +57,7 @@ module ActiveRecord
57
57
 
58
58
  private
59
59
 
60
- def uri
61
- @uri
62
- end
60
+ attr_reader :uri
63
61
 
64
62
  def uri_parser
65
63
  @uri_parser ||= URI::Parser.new
@@ -116,8 +114,7 @@ module ActiveRecord
116
114
  class Resolver # :nodoc:
117
115
  attr_reader :configurations
118
116
 
119
- # Accepts a hash two layers deep, keys on the first layer represent
120
- # environments such as "production". Keys must be strings.
117
+ # Accepts a list of db config objects.
121
118
  def initialize(configurations)
122
119
  @configurations = configurations
123
120
  end
@@ -138,34 +135,14 @@ module ActiveRecord
138
135
  # Resolver.new(configurations).resolve(:production)
139
136
  # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
140
137
  #
141
- def resolve(config)
142
- if config
143
- resolve_connection config
144
- elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
145
- resolve_symbol_connection env.to_sym
138
+ def resolve(config_or_env, pool_name = nil)
139
+ if config_or_env
140
+ resolve_connection config_or_env, pool_name
146
141
  else
147
142
  raise AdapterNotSpecified
148
143
  end
149
144
  end
150
145
 
151
- # Expands each key in @configurations hash into fully resolved hash
152
- def resolve_all
153
- config = configurations.dup
154
-
155
- if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
156
- env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
157
- end
158
-
159
- config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) }
160
- config.merge! env_config if env_config
161
-
162
- config.each do |key, value|
163
- config[key] = resolve(value) if value
164
- end
165
-
166
- config
167
- end
168
-
169
146
  # Returns an instance of ConnectionSpecification for a given adapter.
170
147
  # Accepts a hash one layer deep that contains all connection information.
171
148
  #
@@ -179,7 +156,9 @@ module ActiveRecord
179
156
  # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
180
157
  #
181
158
  def spec(config)
182
- spec = resolve(config).symbolize_keys
159
+ pool_name = config if config.is_a?(Symbol)
160
+
161
+ spec = resolve(config, pool_name).symbolize_keys
183
162
 
184
163
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
185
164
 
@@ -214,7 +193,6 @@ module ActiveRecord
214
193
  end
215
194
 
216
195
  private
217
-
218
196
  # Returns fully resolved connection, accepts hash, string or symbol.
219
197
  # Always returns a hash.
220
198
  #
@@ -235,32 +213,64 @@ module ActiveRecord
235
213
  # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
236
214
  # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
237
215
  #
238
- def resolve_connection(spec)
239
- case spec
216
+ def resolve_connection(config_or_env, pool_name = nil)
217
+ case config_or_env
240
218
  when Symbol
241
- resolve_symbol_connection spec
219
+ resolve_symbol_connection config_or_env, pool_name
242
220
  when String
243
- resolve_url_connection spec
221
+ resolve_url_connection config_or_env
244
222
  when Hash
245
- resolve_hash_connection spec
223
+ resolve_hash_connection config_or_env
224
+ else
225
+ resolve_connection config_or_env
246
226
  end
247
227
  end
248
228
 
249
- # Takes the environment such as +:production+ or +:development+.
229
+ # Takes the environment such as +:production+ or +:development+ and a
230
+ # pool name the corresponds to the name given by the connection pool
231
+ # to the connection. That pool name is merged into the hash with the
232
+ # name key.
233
+ #
250
234
  # This requires that the @configurations was initialized with a key that
251
235
  # matches.
252
236
  #
253
- # Resolver.new("production" => {}).resolve_symbol_connection(:production)
254
- # # => {}
237
+ # configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
238
+ # @configurations=[
239
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
240
+ # @env_name="production", @spec_name="primary", @config={"database"=>"my_db"}>
241
+ # ]>
255
242
  #
256
- def resolve_symbol_connection(spec)
257
- if config = configurations[spec.to_s]
258
- resolve_connection(config).merge("name" => spec.to_s)
243
+ # Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
244
+ # # => { "database" => "my_db" }
245
+ def resolve_symbol_connection(env_name, pool_name)
246
+ db_config = configurations.find_db_config(env_name)
247
+
248
+ if db_config
249
+ resolve_connection(db_config.config).merge("name" => pool_name.to_s)
259
250
  else
260
- raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
251
+ raise AdapterNotSpecified, <<~MSG
252
+ The `#{env_name}` database is not configured for the `#{ActiveRecord::ConnectionHandling::DEFAULT_ENV.call}` environment.
253
+
254
+ Available databases configurations are:
255
+
256
+ #{build_configuration_sentence}
257
+ MSG
261
258
  end
262
259
  end
263
260
 
261
+ def build_configuration_sentence # :nodoc:
262
+ configs = configurations.configs_for(include_replicas: true)
263
+
264
+ configs.group_by(&:env_name).map do |env, config|
265
+ namespaces = config.map(&:spec_name)
266
+ if namespaces.size > 1
267
+ "#{env}: #{namespaces.join(", ")}"
268
+ else
269
+ env
270
+ end
271
+ end.join("\n")
272
+ end
273
+
264
274
  # Accepts a hash. Expands the "url" key that contains a
265
275
  # URL database connection to a full connection
266
276
  # hash and merges with the rest of the hash.
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module DetermineIfPreparableVisitor
6
- attr_accessor :preparable
6
+ attr_reader :preparable
7
7
 
8
8
  def accept(*)
9
9
  @preparable = true
@@ -12,15 +12,11 @@ module ActiveRecord
12
12
 
13
13
  def visit_Arel_Nodes_In(o, collector)
14
14
  @preparable = false
15
+ super
16
+ end
15
17
 
16
- if Array === o.right && !o.right.empty?
17
- o.right.delete_if do |bind|
18
- if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value
19
- !bind.value.boundable?
20
- end
21
- end
22
- end
23
-
18
+ def visit_Arel_Nodes_NotIn(o, collector)
19
+ @preparable = false
24
20
  super
25
21
  end
26
22
 
@@ -19,8 +19,19 @@ module ActiveRecord
19
19
  execute(sql, name).to_a
20
20
  end
21
21
 
22
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc:
23
+ private_constant :READ_QUERY
24
+
25
+ def write_query?(sql) # :nodoc:
26
+ !READ_QUERY.match?(sql)
27
+ end
28
+
22
29
  # Executes the SQL statement in the context of this connection.
23
30
  def execute(sql, name = nil)
31
+ if preventing_writes? && write_query?(sql)
32
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
33
+ end
34
+
24
35
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
25
36
  # made since we established the connection
26
37
  @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
@@ -31,11 +42,19 @@ module ActiveRecord
31
42
  def exec_query(sql, name = "SQL", binds = [], prepare: false)
32
43
  if without_prepared_statement?(binds)
33
44
  execute_and_free(sql, name) do |result|
34
- ActiveRecord::Result.new(result.fields, result.to_a) if result
45
+ if result
46
+ ActiveRecord::Result.new(result.fields, result.to_a)
47
+ else
48
+ ActiveRecord::Result.new([], [])
49
+ end
35
50
  end
36
51
  else
37
52
  exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
38
- ActiveRecord::Result.new(result.fields, result.to_a) if result
53
+ if result
54
+ ActiveRecord::Result.new(result.fields, result.to_a)
55
+ else
56
+ ActiveRecord::Result.new([], [])
57
+ end
39
58
  end
40
59
  end
41
60
  end
@@ -59,7 +78,7 @@ module ActiveRecord
59
78
  end
60
79
 
61
80
  def discard_remaining_results
62
- @connection.next_result while @connection.more_results?
81
+ @connection.abandon_results!
63
82
  end
64
83
 
65
84
  def supports_set_server_option?
@@ -99,6 +118,12 @@ module ActiveRecord
99
118
  end
100
119
 
101
120
  def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
121
+ if preventing_writes? && write_query?(sql)
122
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
123
+ end
124
+
125
+ materialize_transactions
126
+
102
127
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
103
128
  # made since we established the connection
104
129
  @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
@@ -107,10 +132,7 @@ module ActiveRecord
107
132
 
108
133
  log(sql, name, binds, type_casted_binds) do
109
134
  if cache_stmt
110
- cache = @statements[sql] ||= {
111
- stmt: @connection.prepare(sql)
112
- }
113
- stmt = cache[:stmt]
135
+ stmt = @statements[sql] ||= @connection.prepare(sql)
114
136
  else
115
137
  stmt = @connection.prepare(sql)
116
138
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module MySQL
6
6
  module Quoting # :nodoc:
7
7
  def quote_column_name(name)
8
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze
8
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
9
9
  end
10
10
 
11
11
  def quote_table_name(name)
@@ -4,8 +4,7 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module MySQL
6
6
  class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
7
- delegate :add_sql_comment!, :mariadb?, to: :@conn
8
- private :add_sql_comment!, :mariadb?
7
+ delegate :add_sql_comment!, :mariadb?, to: :@conn, private: true
9
8
 
10
9
  private
11
10
 
@@ -18,7 +17,7 @@ module ActiveRecord
18
17
  end
19
18
 
20
19
  def visit_ChangeColumnDefinition(o)
21
- change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup
20
+ change_column_sql = +"CHANGE #{quote_column_name(o.name)} #{accept(o.column)}"
22
21
  add_column_position!(change_column_sql, column_options(o.column))
23
22
  end
24
23
 
@@ -65,7 +64,7 @@ module ActiveRecord
65
64
 
66
65
  def index_in_create(table_name, column_name, options)
67
66
  index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
68
- add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment)
67
+ add_sql_comment!((+"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})"), comment)
69
68
  end
70
69
  end
71
70
  end
@@ -35,13 +35,39 @@ module ActiveRecord
35
35
  ]
36
36
  end
37
37
 
38
- indexes.last[-2] << row[:Column_name]
39
- indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
40
- indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
38
+ if row[:Expression]
39
+ expression = row[:Expression]
40
+ expression = +"(#{expression})" unless expression.start_with?("(")
41
+ indexes.last[-2] << expression
42
+ indexes.last[-1][:expressions] ||= {}
43
+ indexes.last[-1][:expressions][expression] = expression
44
+ indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D"
45
+ else
46
+ indexes.last[-2] << row[:Column_name]
47
+ indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
48
+ indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D"
49
+ end
41
50
  end
42
51
  end
43
52
 
44
- indexes.map { |index| IndexDefinition.new(*index) }
53
+ indexes.map do |index|
54
+ options = index.last
55
+
56
+ if expressions = options.delete(:expressions)
57
+ orders = options.delete(:orders)
58
+ lengths = options.delete(:lengths)
59
+
60
+ columns = index[-2].map { |name|
61
+ [ name.to_sym, expressions[name] || +quote_column_name(name) ]
62
+ }.to_h
63
+
64
+ index[-2] = add_options_for_index_columns(
65
+ columns, order: orders, length: lengths
66
+ ).values.join(", ")
67
+ end
68
+
69
+ IndexDefinition.new(*index)
70
+ end
45
71
  end
46
72
 
47
73
  def remove_column(table_name, column_name, type = nil, options = {})
@@ -51,9 +77,13 @@ module ActiveRecord
51
77
  super
52
78
  end
53
79
 
80
+ def create_table(table_name, options: default_row_format, **)
81
+ super
82
+ end
83
+
54
84
  def internal_string_options_for_primary_key
55
85
  super.tap do |options|
56
- if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0")
86
+ if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
57
87
  options[:collation] = collation.sub(/\A[^_]+/, "utf8")
58
88
  end
59
89
  end
@@ -70,6 +100,28 @@ module ActiveRecord
70
100
  private
71
101
  CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
72
102
 
103
+ def row_format_dynamic_by_default?
104
+ if mariadb?
105
+ version >= "10.2.2"
106
+ else
107
+ version >= "5.7.9"
108
+ end
109
+ end
110
+
111
+ def default_row_format
112
+ return if row_format_dynamic_by_default?
113
+
114
+ unless defined?(@default_row_format)
115
+ if query_value("SELECT @@innodb_file_per_table = 1 AND @@innodb_file_format = 'Barracuda'") == 1
116
+ @default_row_format = "ROW_FORMAT=DYNAMIC"
117
+ else
118
+ @default_row_format = nil
119
+ end
120
+ end
121
+
122
+ @default_row_format
123
+ end
124
+
73
125
  def schema_creation
74
126
  MySQL::SchemaCreation.new(self)
75
127
  end
@@ -80,10 +132,13 @@ module ActiveRecord
80
132
 
81
133
  def new_column_from_field(table_name, field)
82
134
  type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
83
- if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
84
- default, default_function = nil, field[:Default]
85
- else
86
- default, default_function = field[:Default], nil
135
+ default, default_function = field[:Default], nil
136
+
137
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
138
+ default, default_function = nil, default
139
+ elsif type_metadata.extra == "DEFAULT_GENERATED"
140
+ default = +"(#{default})" unless default.start_with?("(")
141
+ default, default_function = nil, default
87
142
  end
88
143
 
89
144
  MySQL::Column.new(
@@ -121,7 +176,7 @@ module ActiveRecord
121
176
  def data_source_sql(name = nil, type: nil)
122
177
  scope = quoted_scope(name, type: type)
123
178
 
124
- sql = "SELECT table_name FROM information_schema.tables".dup
179
+ sql = +"SELECT table_name FROM information_schema.tables"
125
180
  sql << " WHERE table_schema = #{scope[:schema]}"
126
181
  sql << " AND table_name = #{scope[:name]}" if scope[:name]
127
182
  sql << " AND table_type = #{scope[:type]}" if scope[:type]
@@ -3,7 +3,7 @@
3
3
  require "active_record/connection_adapters/abstract_mysql_adapter"
4
4
  require "active_record/connection_adapters/mysql/database_statements"
5
5
 
6
- gem "mysql2", ">= 0.4.4", "< 0.6.0"
6
+ gem "mysql2", ">= 0.4.4"
7
7
  require "mysql2"
8
8
 
9
9
  module ActiveRecord
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  config[:flags] ||= 0
15
15
 
16
16
  if config[:flags].kind_of? Array
17
- config[:flags].push "FOUND_ROWS".freeze
17
+ config[:flags].push "FOUND_ROWS"
18
18
  else
19
19
  config[:flags] |= Mysql2::Client::FOUND_ROWS
20
20
  end
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
 
33
33
  module ConnectionAdapters
34
34
  class Mysql2Adapter < AbstractMysqlAdapter
35
- ADAPTER_NAME = "Mysql2".freeze
35
+ ADAPTER_NAME = "Mysql2"
36
36
 
37
37
  include MySQL::DatabaseStatements
38
38
 
@@ -58,6 +58,10 @@ module ActiveRecord
58
58
  true
59
59
  end
60
60
 
61
+ def supports_lazy_transactions?
62
+ true
63
+ end
64
+
61
65
  # HELPER METHODS ===========================================
62
66
 
63
67
  def each_hash(result) # :nodoc:
@@ -117,7 +121,7 @@ module ActiveRecord
117
121
  end
118
122
 
119
123
  def configure_connection
120
- @connection.query_options.merge!(as: :array)
124
+ @connection.query_options[:as] = :array
121
125
  super
122
126
  end
123
127
 
@@ -20,10 +20,9 @@ module ActiveRecord
20
20
  end
21
21
  end
22
22
 
23
- protected
23
+ private
24
24
  attr_reader :max_identifier_length
25
25
 
26
- private
27
26
  def sequence_name_from_parts(table_name, column_name, suffix)
28
27
  over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length
29
28
 
@@ -58,6 +58,8 @@ module ActiveRecord
58
58
 
59
59
  # Queries the database and returns the results in an Array-like object
60
60
  def query(sql, name = nil) #:nodoc:
61
+ materialize_transactions
62
+
61
63
  log(sql, name) do
62
64
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
63
65
  result_as_array @connection.async_exec(sql)
@@ -65,11 +67,24 @@ module ActiveRecord
65
67
  end
66
68
  end
67
69
 
70
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc:
71
+ private_constant :READ_QUERY
72
+
73
+ def write_query?(sql) # :nodoc:
74
+ !READ_QUERY.match?(sql)
75
+ end
76
+
68
77
  # Executes an SQL statement, returning a PG::Result object on success
69
78
  # or raising a PG::Error exception otherwise.
70
79
  # Note: the PG::Result object is manually memory managed; if you don't
71
80
  # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
72
81
  def execute(sql, name = nil)
82
+ if preventing_writes? && write_query?(sql)
83
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
84
+ end
85
+
86
+ materialize_transactions
87
+
73
88
  log(sql, name) do
74
89
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
75
90
  @connection.async_exec(sql)
@@ -95,7 +110,7 @@ module ActiveRecord
95
110
  end
96
111
  alias :exec_update :exec_delete
97
112
 
98
- def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
113
+ def sql_for_insert(sql, pk, sequence_name, binds) # :nodoc:
99
114
  if pk.nil?
100
115
  # Extract the table from the insert sql. Yuck.
101
116
  table_ref = extract_table_ref_from_insert_sql(sql)
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module PostgreSQL
6
6
  module OID # :nodoc:
7
7
  class Array < Type::Value # :nodoc:
8
- include Type::Helpers::Mutable
8
+ include ActiveModel::Type::Helpers::Mutable
9
9
 
10
10
  Data = Struct.new(:encoder, :values) # :nodoc:
11
11
 
@@ -43,10 +43,7 @@ module ActiveRecord
43
43
  /\A[0-9A-F]*\Z/i.match?(value)
44
44
  end
45
45
 
46
- # TODO Change this to private once we've dropped Ruby 2.2 support.
47
- # Workaround for Ruby 2.2 "private attribute?" warning.
48
- protected
49
-
46
+ private
50
47
  attr_reader :value
51
48
  end
52
49
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module PostgreSQL
6
6
  module OID # :nodoc:
7
7
  class Hstore < Type::Value # :nodoc:
8
- include Type::Helpers::Mutable
8
+ include ActiveModel::Type::Helpers::Mutable
9
9
 
10
10
  def type
11
11
  :hstore
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module PostgreSQL
6
6
  module OID # :nodoc:
7
7
  class LegacyPoint < Type::Value # :nodoc:
8
- include Type::Helpers::Mutable
8
+ include ActiveModel::Type::Helpers::Mutable
9
9
 
10
10
  def type
11
11
  :point
@@ -26,9 +26,9 @@ module ActiveRecord
26
26
 
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
- when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
29
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
30
30
  value.gsub!(/[^-\d.]/, "")
31
- when /^-?\D*+[\d.]+,\d{2}$/ # (2)
31
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
32
32
  value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
33
33
  end
34
34
 
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  module PostgreSQL
8
8
  module OID # :nodoc:
9
9
  class Point < Type::Value # :nodoc:
10
- include Type::Helpers::Mutable
10
+ include ActiveModel::Type::Helpers::Mutable
11
11
 
12
12
  def type
13
13
  :point
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/array/extract"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module PostgreSQL
@@ -16,12 +18,12 @@ module ActiveRecord
16
18
 
17
19
  def run(records)
18
20
  nodes = records.reject { |row| @store.key? row["oid"].to_i }
19
- mapped, nodes = nodes.partition { |row| @store.key? row["typname"] }
20
- ranges, nodes = nodes.partition { |row| row["typtype"] == "r".freeze }
21
- enums, nodes = nodes.partition { |row| row["typtype"] == "e".freeze }
22
- domains, nodes = nodes.partition { |row| row["typtype"] == "d".freeze }
23
- arrays, nodes = nodes.partition { |row| row["typinput"] == "array_in".freeze }
24
- composites, nodes = nodes.partition { |row| row["typelem"].to_i != 0 }
21
+ mapped = nodes.extract! { |row| @store.key? row["typname"] }
22
+ ranges = nodes.extract! { |row| row["typtype"] == "r" }
23
+ enums = nodes.extract! { |row| row["typtype"] == "e" }
24
+ domains = nodes.extract! { |row| row["typtype"] == "d" }
25
+ arrays = nodes.extract! { |row| row["typinput"] == "array_in" }
26
+ composites = nodes.extract! { |row| row["typelem"].to_i != 0 }
25
27
 
26
28
  mapped.each { |row| register_mapped_type(row) }
27
29
  enums.each { |row| register_enum_type(row) }
@@ -34,7 +36,7 @@ module ActiveRecord
34
36
  def query_conditions_for_initial_load
35
37
  known_type_names = @store.keys.map { |n| "'#{n}'" }
36
38
  known_type_types = %w('r' 'e' 'd')
37
- <<-SQL % [known_type_names.join(", "), known_type_types.join(", ")]
39
+ <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
38
40
  WHERE
39
41
  t.typname IN (%s)
40
42
  OR t.typtype IN (%s)
@@ -93,11 +93,11 @@ module ActiveRecord
93
93
  elsif value.hex?
94
94
  "X'#{value}'"
95
95
  end
96
- when Float
97
- if value.infinite? || value.nan?
98
- "'#{value}'"
99
- else
96
+ when Numeric
97
+ if value.finite?
100
98
  super
99
+ else
100
+ "'#{value}'"
101
101
  end
102
102
  when OID::Array::Data
103
103
  _quote(encode_array(value))