activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -5,6 +5,7 @@ module ActiveRecord
5
5
  module MySQL
6
6
  module ColumnMethods
7
7
  extend ActiveSupport::Concern
8
+ extend ConnectionAdapters::ColumnMethods::ClassMethods
8
9
 
9
10
  ##
10
11
  # :method: blob
@@ -42,18 +43,26 @@ module ActiveRecord
42
43
  # :method: unsigned_bigint
43
44
  # :call-seq: unsigned_bigint(*names, **options)
44
45
 
45
- ##
46
- # :method: unsigned_float
47
- # :call-seq: unsigned_float(*names, **options)
46
+ define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
47
+ :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint
48
+ end
48
49
 
49
- ##
50
- # :method: unsigned_decimal
51
- # :call-seq: unsigned_decimal(*names, **options)
50
+ # = Active Record MySQL Adapter \Index Definition
51
+ class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition # :nodoc:
52
+ attr_accessor :enabled
53
+
54
+ def initialize(*args, **kwargs)
55
+ @enabled = kwargs.key?(:enabled) ? kwargs.delete(:enabled) : true
56
+ super
57
+ end
52
58
 
53
- included do
54
- define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
55
- :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
56
- :unsigned_float, :unsigned_decimal
59
+ def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, enabled: nil, **options)
60
+ super(columns, name:, unique:, valid:, include:, nulls_not_distinct:, **options) &&
61
+ (enabled.nil? || self.enabled == enabled)
62
+ end
63
+
64
+ def disabled?
65
+ !@enabled
57
66
  end
58
67
  end
59
68
 
@@ -106,6 +115,28 @@ module ActiveRecord
106
115
  # = Active Record MySQL Adapter \Table
107
116
  class Table < ActiveRecord::ConnectionAdapters::Table
108
117
  include ColumnMethods
118
+
119
+ # Enables an index to be used by query optimizers.
120
+ #
121
+ # t.enable_index(:email)
122
+ #
123
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
124
+ #
125
+ # See {connection.enable_index}[rdoc-ref:SchemaStatements#enable_index]
126
+ def enable_index(index_name)
127
+ @base.enable_index(name, index_name)
128
+ end
129
+
130
+ # Disables an index not to be used by query optimizers.
131
+ #
132
+ # t.disable_index(:email)
133
+ #
134
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
135
+ #
136
+ # See {connection.disable_index}[rdoc-ref:SchemaStatements#disable_index]
137
+ def disable_index(index_name)
138
+ @base.disable_index(name, index_name)
139
+ end
109
140
  end
110
141
  end
111
142
  end
@@ -8,45 +8,49 @@ module ActiveRecord
8
8
  def indexes(table_name)
9
9
  indexes = []
10
10
  current_index = nil
11
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
12
- each_hash(result) do |row|
13
- if current_index != row[:Key_name]
14
- next if row[:Key_name] == "PRIMARY" # skip the primary key
15
- current_index = row[:Key_name]
16
-
17
- mysql_index_type = row[:Index_type].downcase.to_sym
18
- case mysql_index_type
19
- when :fulltext, :spatial
20
- index_type = mysql_index_type
21
- when :btree, :hash
22
- index_using = mysql_index_type
23
- end
24
-
25
- indexes << [
26
- row[:Table],
27
- row[:Key_name],
28
- row[:Non_unique].to_i == 0,
29
- [],
30
- lengths: {},
31
- orders: {},
32
- type: index_type,
33
- using: index_using,
34
- comment: row[:Index_comment].presence
35
- ]
11
+ internal_exec_query("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA").each do |row|
12
+ if current_index != row["Key_name"]
13
+ next if row["Key_name"] == "PRIMARY" # skip the primary key
14
+ current_index = row["Key_name"]
15
+
16
+ mysql_index_type = row["Index_type"].downcase.to_sym
17
+ case mysql_index_type
18
+ when :fulltext, :spatial
19
+ index_type = mysql_index_type
20
+ when :btree, :hash
21
+ index_using = mysql_index_type
36
22
  end
37
23
 
38
- if row[:Expression]
39
- expression = row[:Expression].gsub("\\'", "'")
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"
24
+ index = [
25
+ row["Table"],
26
+ row["Key_name"],
27
+ row["Non_unique"].to_i == 0,
28
+ [],
29
+ lengths: {},
30
+ orders: {},
31
+ type: index_type,
32
+ using: index_using,
33
+ comment: row["Index_comment"].presence,
34
+ ]
35
+
36
+ if supports_disabling_indexes?
37
+ index[-1][:enabled] = mariadb? ? row["Ignored"] == "NO" : row["Visible"] == "YES"
49
38
  end
39
+
40
+ indexes << index
41
+ end
42
+
43
+ if expression = row["Expression"]
44
+ expression = expression.gsub("\\'", "'")
45
+ expression = +"(#{expression})" unless expression.start_with?("(")
46
+ indexes.last[-2] << expression
47
+ indexes.last[-1][:expressions] ||= {}
48
+ indexes.last[-1][:expressions][expression] = expression
49
+ indexes.last[-1][:orders][expression] = :desc if row["Collation"] == "D"
50
+ else
51
+ indexes.last[-2] << row["Column_name"]
52
+ indexes.last[-1][:lengths][row["Column_name"]] = row["Sub_part"].to_i if row["Sub_part"]
53
+ indexes.last[-1][:orders][row["Column_name"]] = :desc if row["Collation"] == "D"
50
54
  end
51
55
  end
52
56
 
@@ -65,9 +69,24 @@ module ActiveRecord
65
69
  columns, order: orders, length: lengths
66
70
  ).values.join(", ")
67
71
  end
68
-
69
- IndexDefinition.new(*index, **options)
72
+ MySQL::IndexDefinition.new(*index, **options)
70
73
  end
74
+ rescue StatementInvalid => e
75
+ if e.message.match?(/Table '.+' doesn't exist/)
76
+ []
77
+ else
78
+ raise
79
+ end
80
+ end
81
+
82
+ def create_index_definition(table_name, name, unique, columns, **options)
83
+ MySQL::IndexDefinition.new(table_name, name, unique, columns, **options)
84
+ end
85
+
86
+ def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
87
+ index, algorithm, if_not_exists = super
88
+ index.enabled = options[:enabled] unless options[:enabled].nil?
89
+ [index, algorithm, if_not_exists]
71
90
  end
72
91
 
73
92
  def remove_column(table_name, column_name, type = nil, **options)
@@ -81,6 +100,13 @@ module ActiveRecord
81
100
  super
82
101
  end
83
102
 
103
+ def remove_foreign_key(from_table, to_table = nil, **options)
104
+ # RESTRICT is by default in MySQL.
105
+ options.delete(:on_update) if options[:on_update] == :restrict
106
+ options.delete(:on_delete) if options[:on_delete] == :restrict
107
+ super
108
+ end
109
+
84
110
  def internal_string_options_for_primary_key
85
111
  super.tap do |options|
86
112
  if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
@@ -176,12 +202,12 @@ module ActiveRecord
176
202
  end
177
203
 
178
204
  def new_column_from_field(table_name, field, _definitions)
179
- field_name = field.fetch(:Field)
180
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
181
- default, default_function = field[:Default], nil
205
+ field_name = field.fetch("Field")
206
+ type_metadata = fetch_type_metadata(field["Type"], field["Extra"])
207
+ default, default_function = field["Default"], nil
182
208
 
183
209
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
184
- default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[:Extra])
210
+ default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"])
185
211
  default, default_function = nil, default
186
212
  elsif type_metadata.extra == "DEFAULT_GENERATED"
187
213
  default = +"(#{default})" unless default.start_with?("(")
@@ -197,13 +223,14 @@ module ActiveRecord
197
223
  end
198
224
 
199
225
  MySQL::Column.new(
200
- field[:Field],
226
+ field["Field"],
227
+ lookup_cast_type(type_metadata.sql_type),
201
228
  default,
202
229
  type_metadata,
203
- field[:Null] == "YES",
230
+ field["Null"] == "YES",
204
231
  default_function,
205
- collation: field[:Collation],
206
- comment: field[:Comment].presence
232
+ collation: field["Collation"],
233
+ comment: field["Comment"].presence
207
234
  )
208
235
  end
209
236
 
@@ -222,6 +249,12 @@ module ActiveRecord
222
249
  end
223
250
  end
224
251
 
252
+ def valid_index_options
253
+ index_options = super
254
+ index_options << :enabled if supports_disabling_indexes?
255
+ index_options
256
+ end
257
+
225
258
  def add_options_for_index_columns(quoted_columns, **options)
226
259
  quoted_columns = add_index_length(quoted_columns, **options)
227
260
  super
@@ -6,67 +6,26 @@ module ActiveRecord
6
6
  module DatabaseStatements
7
7
  # Returns an ActiveRecord::Result instance.
8
8
  def select_all(*, **) # :nodoc:
9
- result = nil
10
- with_raw_connection do |conn|
11
- result = if ExplainRegistry.collect? && prepared_statements
12
- unprepared_statement { super }
13
- else
14
- super
15
- end
16
- conn.abandon_results!
17
- end
18
- result
19
- end
20
-
21
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
22
- if without_prepared_statement?(binds)
23
- execute_and_free(sql, name, async: async) do |result|
24
- if result
25
- build_result(columns: result.fields, rows: result.to_a)
26
- else
27
- build_result(columns: [], rows: [])
28
- end
29
- end
9
+ if ExplainRegistry.collect? && prepared_statements
10
+ unprepared_statement { super }
30
11
  else
31
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
32
- if result
33
- build_result(columns: result.fields, rows: result.to_a)
34
- else
35
- build_result(columns: [], rows: [])
36
- end
37
- end
12
+ super
38
13
  end
39
14
  end
40
15
 
41
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
42
- if without_prepared_statement?(binds)
43
- with_raw_connection do |conn|
44
- @affected_rows_before_warnings = nil
45
- execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
46
- end
47
- else
48
- exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
49
- end
50
- end
51
- alias :exec_update :exec_delete
52
-
53
16
  private
54
- def sync_timezone_changes(raw_connection)
55
- raw_connection.query_options[:database_timezone] = default_timezone
56
- end
57
-
58
- def execute_batch(statements, name = nil)
59
- statements = statements.map { |sql| transform_query(sql) }
17
+ def execute_batch(statements, name = nil, **kwargs)
60
18
  combine_multi_statements(statements).each do |statement|
61
- with_raw_connection do |conn|
62
- raw_execute(statement, name)
63
- conn.abandon_results!
64
- end
19
+ raw_execute(statement, name, batch: true, **kwargs)
65
20
  end
66
21
  end
67
22
 
68
23
  def last_inserted_id(result)
69
- @raw_connection&.last_id
24
+ if supports_insert_returning?
25
+ super
26
+ else
27
+ @raw_connection&.last_id
28
+ end
70
29
  end
71
30
 
72
31
  def multi_statements_enabled?
@@ -79,70 +38,107 @@ module ActiveRecord
79
38
  end
80
39
  end
81
40
 
82
- def with_multi_statements
83
- if multi_statements_enabled?
84
- return yield
41
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
42
+ reset_multi_statement = if batch && !multi_statements_enabled?
43
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
44
+ true
85
45
  end
86
46
 
87
- with_raw_connection do |conn|
88
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
47
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
48
+ # made since we established the connection
49
+ raw_connection.query_options[:database_timezone] = default_timezone
89
50
 
90
- yield
91
- ensure
92
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
51
+ result = nil
52
+ if binds.nil? || binds.empty?
53
+ result = raw_connection.query(sql)
54
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
55
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
56
+ # from that same connection was GCed while `#query` released the GVL.
57
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
58
+ # of hitting the bug.
59
+ @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
60
+ elsif prepare
61
+ retry_count = 1
62
+ begin
63
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
64
+ result = stmt.execute(*type_casted_binds)
65
+ @affected_rows_before_warnings = stmt.affected_rows
66
+ rescue ::Mysql2::Error => error
67
+ @statements.delete(sql)
68
+ # Sometimes for an unknown reason, we get that error.
69
+ # It suggest somehow that the prepared statement was deallocated
70
+ # but the client doesn't know it.
71
+ # But we know that this error is safe to retry, so we do so after
72
+ # getting rid of the originally cached statement.
73
+ if error.error_number == Mysql2Adapter::ER_UNKNOWN_STMT_HANDLER
74
+ if retry_count.positive?
75
+ retry_count -= 1
76
+ retry
77
+ end
78
+ end
79
+ raise
80
+ end
81
+ else
82
+ stmt = raw_connection.prepare(sql)
83
+
84
+ begin
85
+ result = stmt.execute(*type_casted_binds)
86
+ @affected_rows_before_warnings = stmt.affected_rows
87
+
88
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
89
+ # by eagerly closing uncached prepared statements, we also reduce the chances of
90
+ # that bug happening. It can still happen if `#execute` is used as we have no callback
91
+ # to eagerly close the statement.
92
+ if result
93
+ result.instance_variable_set(:@_ar_stmt_to_close, stmt)
94
+ else
95
+ stmt.close
96
+ end
97
+ rescue ::Mysql2::Error
98
+ stmt.close
99
+ raise
100
+ end
93
101
  end
94
- end
95
102
 
96
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
97
- log(sql, name, async: async) do
98
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99
- sync_timezone_changes(conn)
100
- result = conn.query(sql)
101
- verified!
102
- handle_warnings(sql)
103
- result
104
- end
103
+ notification_payload[:affected_rows] = @affected_rows_before_warnings
104
+ notification_payload[:row_count] = result&.size || 0
105
+
106
+ raw_connection.abandon_results!
107
+
108
+ verified!
109
+ result
110
+ ensure
111
+ if reset_multi_statement && active?
112
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
105
113
  end
106
114
  end
107
115
 
108
- def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
109
- sql = transform_query(sql)
110
- check_if_write_query(sql)
116
+ def cast_result(raw_result)
117
+ return ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings) if raw_result.nil?
111
118
 
112
- mark_transaction_written_if_write(sql)
119
+ fields = raw_result.fields
113
120
 
114
- type_casted_binds = type_casted_binds(binds)
121
+ result = if fields.empty?
122
+ ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings)
123
+ else
124
+ ActiveRecord::Result.new(fields, raw_result.to_a)
125
+ end
115
126
 
116
- log(sql, name, binds, type_casted_binds, async: async) do
117
- with_raw_connection do |conn|
118
- sync_timezone_changes(conn)
127
+ free_raw_result(raw_result)
119
128
 
120
- if cache_stmt
121
- stmt = @statements[sql] ||= conn.prepare(sql)
122
- else
123
- stmt = conn.prepare(sql)
124
- end
129
+ result
130
+ end
125
131
 
126
- begin
127
- result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
128
- stmt.execute(*type_casted_binds)
129
- end
130
- verified!
131
- result
132
- rescue ::Mysql2::Error => e
133
- if cache_stmt
134
- @statements.delete(sql)
135
- else
136
- stmt.close
137
- end
138
- raise e
139
- end
132
+ def affected_rows(raw_result)
133
+ free_raw_result(raw_result) if raw_result
140
134
 
141
- ret = yield stmt, result
142
- result.free if result
143
- stmt.close unless cache_stmt
144
- ret
145
- end
135
+ @affected_rows_before_warnings
136
+ end
137
+
138
+ def free_raw_result(raw_result)
139
+ raw_result.free
140
+ if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
141
+ stmt.close
146
142
  end
147
143
  end
148
144
  end
@@ -7,23 +7,13 @@ gem "mysql2", "~> 0.5"
7
7
  require "mysql2"
8
8
 
9
9
  module ActiveRecord
10
- module ConnectionHandling # :nodoc:
11
- def mysql2_adapter_class
12
- ConnectionAdapters::Mysql2Adapter
13
- end
14
-
15
- # Establishes a connection to the database that's used by all Active Record objects.
16
- def mysql2_connection(config)
17
- mysql2_adapter_class.new(config)
18
- end
19
- end
20
-
21
10
  module ConnectionAdapters
22
11
  # = Active Record MySQL2 Adapter
23
12
  class Mysql2Adapter < AbstractMysqlAdapter
24
13
  ER_BAD_DB_ERROR = 1049
25
14
  ER_DBACCESS_DENIED_ERROR = 1044
26
15
  ER_ACCESS_DENIED_ERROR = 1045
16
+ ER_UNKNOWN_STMT_HANDLER = 1243
27
17
  ER_CONN_HOST_ERROR = 2003
28
18
  ER_UNKNOWN_HOST_ERROR = 2005
29
19
 
@@ -66,6 +56,7 @@ module ActiveRecord
66
56
  def initialize(...)
67
57
  super
68
58
 
59
+ @affected_rows_before_warnings = nil
69
60
  @config[:flags] ||= 0
70
61
 
71
62
  if @config[:flags].kind_of? Array
@@ -101,37 +92,27 @@ module ActiveRecord
101
92
  true
102
93
  end
103
94
 
104
- # HELPER METHODS ===========================================
105
-
106
- def each_hash(result, &block) # :nodoc:
107
- if block_given?
108
- result.each(as: :hash, symbolize_keys: true, &block)
109
- else
110
- to_enum(:each_hash, result)
111
- end
112
- end
113
-
114
95
  def error_number(exception)
115
96
  exception.error_number if exception.respond_to?(:error_number)
116
97
  end
117
98
 
118
99
  #--
119
- # QUOTING ==================================================
100
+ # CONNECTION MANAGEMENT ====================================
120
101
  #++
121
102
 
122
- # Quotes strings for use in SQL input.
123
- def quote_string(string)
124
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
125
- connection.escape(string)
126
- end
103
+ def connected?
104
+ !(@raw_connection.nil? || @raw_connection.closed?)
127
105
  end
128
106
 
129
- #--
130
- # CONNECTION MANAGEMENT ====================================
131
- #++
132
-
133
107
  def active?
134
- !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
108
+ if connected?
109
+ @lock.synchronize do
110
+ if @raw_connection&.ping
111
+ verified!
112
+ true
113
+ end
114
+ end
115
+ end || false
135
116
  end
136
117
 
137
118
  alias :reset! :reconnect!
@@ -180,7 +161,7 @@ module ActiveRecord
180
161
  end
181
162
 
182
163
  def full_version
183
- schema_cache.database_version.full_version_string
164
+ database_version.full_version_string
184
165
  end
185
166
 
186
167
  def get_full_version
@@ -3,11 +3,10 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  class PoolConfig # :nodoc:
6
- include Mutex_m
6
+ include MonitorMixin
7
7
 
8
- attr_reader :db_config, :role, :shard
9
- attr_writer :schema_reflection
10
- attr_accessor :connection_class
8
+ attr_reader :db_config, :role, :shard, :connection_descriptor
9
+ attr_writer :schema_reflection, :server_version
11
10
 
12
11
  def schema_reflection
13
12
  @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
@@ -28,7 +27,8 @@ module ActiveRecord
28
27
 
29
28
  def initialize(connection_class, db_config, role, shard)
30
29
  super()
31
- @connection_class = connection_class
30
+ @server_version = nil
31
+ self.connection_descriptor = connection_class
32
32
  @db_config = db_config
33
33
  @role = role
34
34
  @shard = shard
@@ -36,17 +36,20 @@ module ActiveRecord
36
36
  INSTANCES[self] = self
37
37
  end
38
38
 
39
- def connection_name
40
- if connection_class.primary_class?
41
- "ActiveRecord::Base"
39
+ def server_version(connection)
40
+ @server_version || synchronize { @server_version ||= connection.get_database_version }
41
+ end
42
+
43
+ def connection_descriptor=(connection_descriptor)
44
+ case connection_descriptor
45
+ when ConnectionHandler::ConnectionDescriptor
46
+ @connection_descriptor = connection_descriptor
42
47
  else
43
- connection_class.name
48
+ @connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
44
49
  end
45
50
  end
46
51
 
47
52
  def disconnect!(automatic_reconnect: false)
48
- ActiveSupport::ForkTracker.check!
49
-
50
53
  return unless @pool
51
54
 
52
55
  synchronize do
@@ -60,8 +63,6 @@ module ActiveRecord
60
63
  end
61
64
 
62
65
  def pool
63
- ActiveSupport::ForkTracker.check!
64
-
65
66
  @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
66
67
  end
67
68
 
@@ -30,6 +30,10 @@ module ActiveRecord
30
30
  @generated.present?
31
31
  end
32
32
 
33
+ def virtual_stored?
34
+ @generated == "s"
35
+ end
36
+
33
37
  def has_default?
34
38
  super && !virtual?
35
39
  end