activerecord 7.2.0 → 8.0.0.beta1

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -745
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +25 -5
  5. data/lib/active_record/associations/builder/association.rb +7 -6
  6. data/lib/active_record/associations/collection_association.rb +10 -8
  7. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  8. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +5 -5
  11. data/lib/active_record/associations/preloader/association.rb +2 -2
  12. data/lib/active_record/associations/singular_association.rb +8 -3
  13. data/lib/active_record/associations.rb +34 -4
  14. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  15. data/lib/active_record/attribute_assignment.rb +9 -1
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  17. data/lib/active_record/attributes.rb +6 -5
  18. data/lib/active_record/autosave_association.rb +69 -27
  19. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +23 -44
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
  25. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
  31. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  32. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  33. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +44 -46
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  37. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  38. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  40. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
  42. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  43. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  44. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  45. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  46. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
  47. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
  48. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  49. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  50. data/lib/active_record/connection_handling.rb +22 -0
  51. data/lib/active_record/core.rb +16 -9
  52. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  53. data/lib/active_record/encryption/config.rb +3 -1
  54. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  55. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  56. data/lib/active_record/encryption/encryptor.rb +16 -9
  57. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  58. data/lib/active_record/encryption/key_provider.rb +1 -1
  59. data/lib/active_record/encryption/scheme.rb +8 -1
  60. data/lib/active_record/encryption.rb +2 -0
  61. data/lib/active_record/enum.rb +8 -0
  62. data/lib/active_record/errors.rb +13 -5
  63. data/lib/active_record/fixtures.rb +0 -1
  64. data/lib/active_record/future_result.rb +14 -10
  65. data/lib/active_record/gem_version.rb +3 -3
  66. data/lib/active_record/insert_all.rb +1 -1
  67. data/lib/active_record/migration/command_recorder.rb +22 -5
  68. data/lib/active_record/migration/compatibility.rb +5 -2
  69. data/lib/active_record/migration.rb +35 -33
  70. data/lib/active_record/model_schema.rb +6 -3
  71. data/lib/active_record/nested_attributes.rb +11 -2
  72. data/lib/active_record/persistence.rb +128 -130
  73. data/lib/active_record/query_logs.rb +97 -39
  74. data/lib/active_record/query_logs_formatter.rb +17 -28
  75. data/lib/active_record/querying.rb +6 -6
  76. data/lib/active_record/railtie.rb +8 -14
  77. data/lib/active_record/reflection.rb +19 -10
  78. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  79. data/lib/active_record/relation/batches.rb +135 -75
  80. data/lib/active_record/relation/calculations.rb +24 -19
  81. data/lib/active_record/relation/delegation.rb +25 -14
  82. data/lib/active_record/relation/finder_methods.rb +18 -18
  83. data/lib/active_record/relation/merger.rb +8 -8
  84. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  85. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  86. data/lib/active_record/relation/predicate_builder.rb +6 -1
  87. data/lib/active_record/relation/query_methods.rb +58 -37
  88. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  89. data/lib/active_record/relation/spawn_methods.rb +1 -1
  90. data/lib/active_record/relation.rb +72 -61
  91. data/lib/active_record/result.rb +68 -7
  92. data/lib/active_record/sanitization.rb +7 -6
  93. data/lib/active_record/schema_dumper.rb +5 -0
  94. data/lib/active_record/schema_migration.rb +2 -1
  95. data/lib/active_record/scoping/named.rb +6 -2
  96. data/lib/active_record/statement_cache.rb +12 -12
  97. data/lib/active_record/store.rb +7 -3
  98. data/lib/active_record/tasks/database_tasks.rb +36 -16
  99. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  100. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  101. data/lib/active_record/test_fixtures.rb +12 -0
  102. data/lib/active_record/token_for.rb +1 -1
  103. data/lib/active_record/validations/uniqueness.rb +9 -8
  104. data/lib/active_record.rb +15 -0
  105. data/lib/arel/collectors/bind.rb +1 -1
  106. metadata +14 -14
@@ -576,23 +576,31 @@ module ActiveRecord
576
576
  end
577
577
 
578
578
  # This is meant to be implemented by the adapters that support custom enum types
579
- def create_enum(*) # :nodoc:
579
+ def create_enum(...) # :nodoc:
580
580
  end
581
581
 
582
582
  # This is meant to be implemented by the adapters that support custom enum types
583
- def drop_enum(*) # :nodoc:
583
+ def drop_enum(...) # :nodoc:
584
584
  end
585
585
 
586
586
  # This is meant to be implemented by the adapters that support custom enum types
587
- def rename_enum(*) # :nodoc:
587
+ def rename_enum(...) # :nodoc:
588
588
  end
589
589
 
590
590
  # This is meant to be implemented by the adapters that support custom enum types
591
- def add_enum_value(*) # :nodoc:
591
+ def add_enum_value(...) # :nodoc:
592
592
  end
593
593
 
594
594
  # This is meant to be implemented by the adapters that support custom enum types
595
- def rename_enum_value(*) # :nodoc:
595
+ def rename_enum_value(...) # :nodoc:
596
+ end
597
+
598
+ # This is meant to be implemented by the adapters that support virtual tables
599
+ def create_virtual_table(*) # :nodoc:
600
+ end
601
+
602
+ # This is meant to be implemented by the adapters that support virtual tables
603
+ def drop_virtual_table(*) # :nodoc:
596
604
  end
597
605
 
598
606
  def advisory_locks_enabled? # :nodoc:
@@ -1044,7 +1052,8 @@ module ActiveRecord
1044
1052
  end
1045
1053
 
1046
1054
  def retryable_connection_error?(exception)
1047
- exception.is_a?(ConnectionNotEstablished) || exception.is_a?(ConnectionFailed)
1055
+ (exception.is_a?(ConnectionNotEstablished) && !exception.is_a?(ConnectionNotDefined)) ||
1056
+ exception.is_a?(ConnectionFailed)
1048
1057
  end
1049
1058
 
1050
1059
  def invalidate_transaction(exception)
@@ -1105,24 +1114,25 @@ module ActiveRecord
1105
1114
  end
1106
1115
  end
1107
1116
 
1108
- def translate_exception_class(e, sql, binds)
1109
- message = "#{e.class.name}: #{e.message}"
1117
+ def translate_exception_class(native_error, sql, binds)
1118
+ return native_error if native_error.is_a?(ActiveRecordError)
1119
+
1120
+ message = "#{native_error.class.name}: #{native_error.message}"
1110
1121
 
1111
- exception = translate_exception(
1112
- e, message: message, sql: sql, binds: binds
1122
+ active_record_error = translate_exception(
1123
+ native_error, message: message, sql: sql, binds: binds
1113
1124
  )
1114
- exception.set_backtrace e.backtrace
1115
- exception
1125
+ active_record_error.set_backtrace(native_error.backtrace)
1126
+ active_record_error
1116
1127
  end
1117
1128
 
1118
- def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, async: false, &block) # :doc:
1129
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
1119
1130
  @instrumenter.instrument(
1120
1131
  "sql.active_record",
1121
1132
  sql: sql,
1122
1133
  name: name,
1123
1134
  binds: binds,
1124
1135
  type_casted_binds: type_casted_binds,
1125
- statement_name: statement_name,
1126
1136
  async: async,
1127
1137
  connection: self,
1128
1138
  transaction: current_transaction.user_transaction.presence,
@@ -1133,13 +1143,6 @@ module ActiveRecord
1133
1143
  raise ex.set_query(sql, binds)
1134
1144
  end
1135
1145
 
1136
- def transform_query(sql)
1137
- ActiveRecord.query_transformers.each do |transformer|
1138
- sql = transformer.call(sql, self)
1139
- end
1140
- sql
1141
- end
1142
-
1143
1146
  def translate_exception(exception, message:, sql:, binds:)
1144
1147
  # override in derived class
1145
1148
  case exception
@@ -1150,10 +1153,6 @@ module ActiveRecord
1150
1153
  end
1151
1154
  end
1152
1155
 
1153
- def without_prepared_statement?(binds)
1154
- !prepared_statements || binds.empty?
1155
- end
1156
-
1157
1156
  def column_for(table_name, column_name)
1158
1157
  column_name = column_name.to_s
1159
1158
  columns(table_name).detect { |c| c.name == column_name } ||
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
 
80
80
  args << config.database
81
81
 
82
- find_cmd_and_exec(["mysql", "mysql5"], *args)
82
+ find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
83
83
  end
84
84
  end
85
85
 
@@ -138,7 +138,7 @@ module ActiveRecord
138
138
  end
139
139
 
140
140
  def supports_datetime_with_precision?
141
- mariadb? || database_version >= "5.6.4"
141
+ true
142
142
  end
143
143
 
144
144
  def supports_virtual_columns?
@@ -197,12 +197,6 @@ module ActiveRecord
197
197
 
198
198
  # HELPER METHODS ===========================================
199
199
 
200
- # The two drivers have slightly different ways of yielding hashes of results, so
201
- # this method must be implemented to provide a uniform interface.
202
- def each_hash(result) # :nodoc:
203
- raise NotImplementedError
204
- end
205
-
206
200
  # Must return the MySQL error number from the exception, if the exception has an
207
201
  # error number.
208
202
  def error_number(exception) # :nodoc:
@@ -226,24 +220,19 @@ module ActiveRecord
226
220
  # DATABASE STATEMENTS ======================================
227
221
  #++
228
222
 
229
- # Mysql2Adapter doesn't have to free a result after using it, but we use this method
230
- # to write stuff in an abstract way without concerning ourselves about whether it
231
- # needs to be explicitly freed or not.
232
- def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
233
- sql = transform_query(sql)
234
- check_if_write_query(sql)
235
-
236
- mark_transaction_written_if_write(sql)
237
- yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
238
- end
239
-
240
223
  def begin_db_transaction # :nodoc:
241
224
  internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
242
225
  end
243
226
 
244
227
  def begin_isolated_db_transaction(isolation) # :nodoc:
245
- internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
246
- begin_db_transaction
228
+ # From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
229
+ # So we don't need to implement #reset_isolation_level
230
+ execute_batch(
231
+ ["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
232
+ "TRANSACTION",
233
+ allow_retry: true,
234
+ materialize_transactions: false,
235
+ )
247
236
  end
248
237
 
249
238
  def commit_db_transaction # :nodoc:
@@ -343,7 +332,7 @@ module ActiveRecord
343
332
  rename_table_indexes(table_name, new_name, **options)
344
333
  end
345
334
 
346
- # Drops a table from the database.
335
+ # Drops a table or tables from the database.
347
336
  #
348
337
  # [<tt>:force</tt>]
349
338
  # Set to +:cascade+ to drop dependent objects as well.
@@ -357,10 +346,10 @@ module ActiveRecord
357
346
  #
358
347
  # Although this command ignores most +options+ and the block if one is given,
359
348
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
360
- # In that case, +options+ and the block will be used by create_table.
361
- def drop_table(table_name, **options)
362
- schema_cache.clear_data_source_cache!(table_name.to_s)
363
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
349
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
350
+ def drop_table(*table_names, **options)
351
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
352
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
364
353
  end
365
354
 
366
355
  def rename_index(table_name, old_name, new_name)
@@ -580,7 +569,7 @@ module ActiveRecord
580
569
 
581
570
  # SHOW VARIABLES LIKE 'name'
582
571
  def show_variable(name)
583
- query_value("SELECT @@#{name}", "SCHEMA")
572
+ query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
584
573
  rescue ActiveRecord::StatementInvalid
585
574
  nil
586
575
  end
@@ -679,8 +668,8 @@ module ActiveRecord
679
668
  end
680
669
 
681
670
  def check_version # :nodoc:
682
- if database_version < "5.5.8"
683
- raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
671
+ if database_version < "5.6.4"
672
+ raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
684
673
  end
685
674
  end
686
675
 
@@ -787,11 +776,6 @@ module ActiveRecord
787
776
  warning.level == "Note" || super
788
777
  end
789
778
 
790
- # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
791
- # made since we established the connection
792
- def sync_timezone_changes(raw_connection)
793
- end
794
-
795
779
  # See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
796
780
  ER_DB_CREATE_EXISTS = 1007
797
781
  ER_FILSORT_ABORT = 1028
@@ -961,13 +945,11 @@ module ActiveRecord
961
945
  end.join(", ")
962
946
 
963
947
  # ...and send them all in one query
964
- internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
948
+ raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
965
949
  end
966
950
 
967
951
  def column_definitions(table_name) # :nodoc:
968
- execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
969
- each_hash(result)
970
- end
952
+ internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
971
953
  end
972
954
 
973
955
  def create_table_info(table_name) # :nodoc:
@@ -77,14 +77,6 @@ module ActiveRecord
77
77
  0
78
78
  end
79
79
 
80
- def quoted_date(value)
81
- if supports_datetime_with_precision?
82
- super
83
- else
84
- super.sub(/\.\d{6}\z/, "")
85
- end
86
- end
87
-
88
80
  def quoted_binary(value)
89
81
  "x'#{value.hex}'"
90
82
  end
@@ -42,18 +42,12 @@ module ActiveRecord
42
42
  # :method: unsigned_bigint
43
43
  # :call-seq: unsigned_bigint(*names, **options)
44
44
 
45
- ##
46
- # :method: unsigned_float
47
- # :call-seq: unsigned_float(*names, **options)
48
-
49
- ##
50
- # :method: unsigned_decimal
51
- # :call-seq: unsigned_decimal(*names, **options)
52
-
53
45
  included do
54
46
  define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
55
47
  :tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint,
56
48
  :unsigned_float, :unsigned_decimal
49
+
50
+ deprecate :unsigned_float, :unsigned_decimal, deprecator: ActiveRecord.deprecator
57
51
  end
58
52
  end
59
53
 
@@ -8,45 +8,43 @@ 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"
49
- end
24
+ indexes << [
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
+ end
36
+
37
+ if expression = row["Expression"]
38
+ expression = expression.gsub("\\'", "'")
39
+ expression = +"(#{expression})" unless expression.start_with?("(")
40
+ indexes.last[-2] << expression
41
+ indexes.last[-1][:expressions] ||= {}
42
+ indexes.last[-1][:expressions][expression] = expression
43
+ indexes.last[-1][:orders][expression] = :desc if row["Collation"] == "D"
44
+ else
45
+ indexes.last[-2] << row["Column_name"]
46
+ indexes.last[-1][:lengths][row["Column_name"]] = row["Sub_part"].to_i if row["Sub_part"]
47
+ indexes.last[-1][:orders][row["Column_name"]] = :desc if row["Collation"] == "D"
50
48
  end
51
49
  end
52
50
 
@@ -161,7 +159,7 @@ module ActiveRecord
161
159
  end
162
160
 
163
161
  def valid_primary_key_options
164
- super + [:unsigned]
162
+ super + [:unsigned, :auto_increment]
165
163
  end
166
164
 
167
165
  def create_table_definition(name, **options)
@@ -182,12 +180,12 @@ module ActiveRecord
182
180
  end
183
181
 
184
182
  def new_column_from_field(table_name, field, _definitions)
185
- field_name = field.fetch(:Field)
186
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
187
- default, default_function = field[:Default], nil
183
+ field_name = field.fetch("Field")
184
+ type_metadata = fetch_type_metadata(field["Type"], field["Extra"])
185
+ default, default_function = field["Default"], nil
188
186
 
189
187
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
190
- default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[:Extra])
188
+ default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"])
191
189
  default, default_function = nil, default
192
190
  elsif type_metadata.extra == "DEFAULT_GENERATED"
193
191
  default = +"(#{default})" unless default.start_with?("(")
@@ -203,13 +201,13 @@ module ActiveRecord
203
201
  end
204
202
 
205
203
  MySQL::Column.new(
206
- field[:Field],
204
+ field["Field"],
207
205
  default,
208
206
  type_metadata,
209
- field[:Null] == "YES",
207
+ field["Null"] == "YES",
210
208
  default_function,
211
- collation: field[:Collation],
212
- comment: field[:Comment].presence
209
+ collation: field["Collation"],
210
+ comment: field["Comment"].presence
213
211
  )
214
212
  end
215
213
 
@@ -13,49 +13,10 @@ module ActiveRecord
13
13
  end
14
14
  end
15
15
 
16
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
17
- if without_prepared_statement?(binds)
18
- execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
19
- if result
20
- build_result(columns: result.fields, rows: result.to_a)
21
- else
22
- build_result(columns: [], rows: [])
23
- end
24
- end
25
- else
26
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
27
- if result
28
- build_result(columns: result.fields, rows: result.to_a)
29
- else
30
- build_result(columns: [], rows: [])
31
- end
32
- end
33
- end
34
- end
35
-
36
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
37
- if without_prepared_statement?(binds)
38
- with_raw_connection do |conn|
39
- @affected_rows_before_warnings = nil
40
- execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
41
- end
42
- else
43
- exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
44
- end
45
- end
46
- alias :exec_update :exec_delete
47
-
48
16
  private
49
- def sync_timezone_changes(raw_connection)
50
- raw_connection.query_options[:database_timezone] = default_timezone
51
- end
52
-
53
- def execute_batch(statements, name = nil)
54
- statements = statements.map { |sql| transform_query(sql) }
17
+ def execute_batch(statements, name = nil, **kwargs)
55
18
  combine_multi_statements(statements).each do |statement|
56
- with_raw_connection do |conn|
57
- raw_execute(statement, name)
58
- end
19
+ raw_execute(statement, name, batch: true, **kwargs)
59
20
  end
60
21
  end
61
22
 
@@ -77,75 +38,58 @@ module ActiveRecord
77
38
  end
78
39
  end
79
40
 
80
- def with_multi_statements
81
- if multi_statements_enabled?
82
- 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
83
45
  end
84
46
 
85
- with_raw_connection do |conn|
86
- 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
87
50
 
88
- yield
89
- ensure
90
- conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
91
- end
92
- end
51
+ result = if prepare
52
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
93
53
 
94
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
95
- log(sql, name, async: async) do |notification_payload|
96
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
97
- sync_timezone_changes(conn)
98
- result = conn.query(sql)
99
- conn.abandon_results!
100
- verified!
101
- handle_warnings(sql)
102
- notification_payload[:row_count] = result&.size || 0
103
- result
54
+ begin
55
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
56
+ stmt.execute(*type_casted_binds)
57
+ end
58
+ rescue ::Mysql2::Error
59
+ @statements.delete(sql)
60
+ stmt.close
61
+ raise
104
62
  end
63
+ verified!
64
+ else
65
+ raw_connection.query(sql)
105
66
  end
106
- end
107
-
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)
111
67
 
112
- mark_transaction_written_if_write(sql)
68
+ notification_payload[:row_count] = result&.size || 0
113
69
 
114
- type_casted_binds = type_casted_binds(binds)
70
+ @affected_rows_before_warnings = raw_connection.affected_rows
71
+ raw_connection.abandon_results!
115
72
 
116
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
117
- with_raw_connection do |conn|
118
- sync_timezone_changes(conn)
119
-
120
- if cache_stmt
121
- stmt = @statements[sql] ||= conn.prepare(sql)
122
- else
123
- stmt = conn.prepare(sql)
124
- end
125
-
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
73
+ verified!
74
+ handle_warnings(sql)
75
+ result
76
+ ensure
77
+ if reset_multi_statement && active?
78
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
79
+ end
80
+ end
140
81
 
141
- ret = yield stmt, result
142
- notification_payload[:row_count] = result&.size || 0
143
- result.free if result
144
- stmt.close unless cache_stmt
145
- ret
146
- end
82
+ def cast_result(result)
83
+ if result.nil? || result.fields.empty?
84
+ ActiveRecord::Result.empty
85
+ else
86
+ ActiveRecord::Result.new(result.fields, result.to_a)
147
87
  end
148
88
  end
89
+
90
+ def affected_rows(result)
91
+ @affected_rows_before_warnings
92
+ end
149
93
  end
150
94
  end
151
95
  end
@@ -55,6 +55,7 @@ module ActiveRecord
55
55
  def initialize(...)
56
56
  super
57
57
 
58
+ @affected_rows_before_warnings = nil
58
59
  @config[:flags] ||= 0
59
60
 
60
61
  if @config[:flags].kind_of? Array
@@ -92,14 +93,6 @@ module ActiveRecord
92
93
 
93
94
  # HELPER METHODS ===========================================
94
95
 
95
- def each_hash(result, &block) # :nodoc:
96
- if block_given?
97
- result.each(as: :hash, symbolize_keys: true, &block)
98
- else
99
- to_enum(:each_hash, result)
100
- end
101
- end
102
-
103
96
  def error_number(exception)
104
97
  exception.error_number if exception.respond_to?(:error_number)
105
98
  end