activerecord 7.2.0 → 8.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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