activerecord 7.2.2.1 → 8.0.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +523 -677
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +35 -9
- data/lib/active_record/core.rb +57 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +37 -28
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +22 -34
- data/lib/active_record/errors.rb +16 -8
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +40 -43
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +106 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +4 -28
- data/lib/active_record/railties/databases.rake +11 -34
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +71 -65
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +32 -31
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +121 -70
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +9 -3
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +34 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +8 -7
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +22 -49
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +11 -15
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
|
79
79
|
|
|
80
80
|
args << config.database
|
|
81
81
|
|
|
82
|
-
find_cmd_and_exec([
|
|
82
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
@@ -98,7 +98,7 @@ module ActiveRecord
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
def supports_index_sort_order?
|
|
101
|
-
|
|
101
|
+
mariadb? ? database_version >= "10.8.1" : database_version >= "8.0.1"
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def supports_expression_index?
|
|
@@ -138,7 +138,7 @@ module ActiveRecord
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def supports_datetime_with_precision?
|
|
141
|
-
|
|
141
|
+
true
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
def supports_virtual_columns?
|
|
@@ -174,6 +174,10 @@ module ActiveRecord
|
|
|
174
174
|
mariadb? && database_version >= "10.5.0"
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
+
def return_value_after_insert?(column) # :nodoc:
|
|
178
|
+
supports_insert_returning? ? column.auto_populated? : column.auto_increment?
|
|
179
|
+
end
|
|
180
|
+
|
|
177
181
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
|
178
182
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
|
179
183
|
end
|
|
@@ -197,12 +201,6 @@ module ActiveRecord
|
|
|
197
201
|
|
|
198
202
|
# HELPER METHODS ===========================================
|
|
199
203
|
|
|
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
204
|
# Must return the MySQL error number from the exception, if the exception has an
|
|
207
205
|
# error number.
|
|
208
206
|
def error_number(exception) # :nodoc:
|
|
@@ -226,24 +224,19 @@ module ActiveRecord
|
|
|
226
224
|
# DATABASE STATEMENTS ======================================
|
|
227
225
|
#++
|
|
228
226
|
|
|
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
227
|
def begin_db_transaction # :nodoc:
|
|
241
228
|
internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
|
242
229
|
end
|
|
243
230
|
|
|
244
231
|
def begin_isolated_db_transaction(isolation) # :nodoc:
|
|
245
|
-
|
|
246
|
-
|
|
232
|
+
# From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
|
|
233
|
+
# So we don't need to implement #reset_isolation_level
|
|
234
|
+
execute_batch(
|
|
235
|
+
["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
|
|
236
|
+
"TRANSACTION",
|
|
237
|
+
allow_retry: true,
|
|
238
|
+
materialize_transactions: false,
|
|
239
|
+
)
|
|
247
240
|
end
|
|
248
241
|
|
|
249
242
|
def commit_db_transaction # :nodoc:
|
|
@@ -343,7 +336,7 @@ module ActiveRecord
|
|
|
343
336
|
rename_table_indexes(table_name, new_name, **options)
|
|
344
337
|
end
|
|
345
338
|
|
|
346
|
-
# Drops a table from the database.
|
|
339
|
+
# Drops a table or tables from the database.
|
|
347
340
|
#
|
|
348
341
|
# [<tt>:force</tt>]
|
|
349
342
|
# Set to +:cascade+ to drop dependent objects as well.
|
|
@@ -357,10 +350,10 @@ module ActiveRecord
|
|
|
357
350
|
#
|
|
358
351
|
# Although this command ignores most +options+ and the block if one is given,
|
|
359
352
|
# 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(
|
|
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}"
|
|
353
|
+
# 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.
|
|
354
|
+
def drop_table(*table_names, **options)
|
|
355
|
+
table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
|
|
356
|
+
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
357
|
end
|
|
365
358
|
|
|
366
359
|
def rename_index(table_name, old_name, new_name)
|
|
@@ -414,7 +407,11 @@ module ActiveRecord
|
|
|
414
407
|
type ||= column.sql_type
|
|
415
408
|
|
|
416
409
|
unless options.key?(:default)
|
|
417
|
-
options[:default] = column.
|
|
410
|
+
options[:default] = if column.default_function
|
|
411
|
+
-> { column.default_function }
|
|
412
|
+
else
|
|
413
|
+
column.default
|
|
414
|
+
end
|
|
418
415
|
end
|
|
419
416
|
|
|
420
417
|
unless options.key?(:null)
|
|
@@ -580,7 +577,7 @@ module ActiveRecord
|
|
|
580
577
|
|
|
581
578
|
# SHOW VARIABLES LIKE 'name'
|
|
582
579
|
def show_variable(name)
|
|
583
|
-
query_value("SELECT @@#{name}", "SCHEMA")
|
|
580
|
+
query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
|
|
584
581
|
rescue ActiveRecord::StatementInvalid
|
|
585
582
|
nil
|
|
586
583
|
end
|
|
@@ -639,24 +636,26 @@ module ActiveRecord
|
|
|
639
636
|
end
|
|
640
637
|
|
|
641
638
|
def build_insert_sql(insert) # :nodoc:
|
|
639
|
+
# Can use any column as it will be assigned to itself.
|
|
642
640
|
no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
|
|
643
641
|
|
|
644
642
|
# MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
|
|
645
643
|
# then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
|
|
646
644
|
if supports_insert_raw_alias_syntax?
|
|
645
|
+
quoted_table_name = insert.model.quoted_table_name
|
|
647
646
|
values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
|
|
648
647
|
sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
|
|
649
648
|
|
|
650
649
|
if insert.skip_duplicates?
|
|
651
650
|
if no_op_column
|
|
652
|
-
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{
|
|
651
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
|
|
653
652
|
end
|
|
654
653
|
elsif insert.update_duplicates?
|
|
655
654
|
if insert.raw_update_sql?
|
|
656
655
|
sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
|
|
657
656
|
else
|
|
658
657
|
sql << " ON DUPLICATE KEY UPDATE "
|
|
659
|
-
sql << insert.touch_model_timestamps_unless { |column| "#{
|
|
658
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
|
|
660
659
|
sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
|
|
661
660
|
end
|
|
662
661
|
end
|
|
@@ -683,8 +682,8 @@ module ActiveRecord
|
|
|
683
682
|
end
|
|
684
683
|
|
|
685
684
|
def check_version # :nodoc:
|
|
686
|
-
if database_version < "5.
|
|
687
|
-
raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.
|
|
685
|
+
if database_version < "5.6.4"
|
|
686
|
+
raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
|
|
688
687
|
end
|
|
689
688
|
end
|
|
690
689
|
|
|
@@ -757,9 +756,7 @@ module ActiveRecord
|
|
|
757
756
|
|
|
758
757
|
private
|
|
759
758
|
def strip_whitespace_characters(expression)
|
|
760
|
-
expression
|
|
761
|
-
expression = expression.gsub(/\s{2,}/, " ")
|
|
762
|
-
expression
|
|
759
|
+
expression.gsub('\\\n', "").gsub("x0A", "").squish
|
|
763
760
|
end
|
|
764
761
|
|
|
765
762
|
def extended_type_map_key
|
|
@@ -773,7 +770,6 @@ module ActiveRecord
|
|
|
773
770
|
def handle_warnings(sql)
|
|
774
771
|
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
|
775
772
|
|
|
776
|
-
@affected_rows_before_warnings = @raw_connection.affected_rows
|
|
777
773
|
warning_count = @raw_connection.warning_count
|
|
778
774
|
result = @raw_connection.query("SHOW WARNINGS")
|
|
779
775
|
result = [
|
|
@@ -791,11 +787,6 @@ module ActiveRecord
|
|
|
791
787
|
warning.level == "Note" || super
|
|
792
788
|
end
|
|
793
789
|
|
|
794
|
-
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
|
795
|
-
# made since we established the connection
|
|
796
|
-
def sync_timezone_changes(raw_connection)
|
|
797
|
-
end
|
|
798
|
-
|
|
799
790
|
# See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
|
|
800
791
|
ER_DB_CREATE_EXISTS = 1007
|
|
801
792
|
ER_FILSORT_ABORT = 1028
|
|
@@ -965,13 +956,11 @@ module ActiveRecord
|
|
|
965
956
|
end.join(", ")
|
|
966
957
|
|
|
967
958
|
# ...and send them all in one query
|
|
968
|
-
|
|
959
|
+
raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
|
|
969
960
|
end
|
|
970
961
|
|
|
971
962
|
def column_definitions(table_name) # :nodoc:
|
|
972
|
-
|
|
973
|
-
each_hash(result)
|
|
974
|
-
end
|
|
963
|
+
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
|
|
975
964
|
end
|
|
976
965
|
|
|
977
966
|
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
|
|
@@ -110,7 +102,13 @@ module ActiveRecord
|
|
|
110
102
|
else
|
|
111
103
|
value.getlocal
|
|
112
104
|
end
|
|
113
|
-
when
|
|
105
|
+
when Time
|
|
106
|
+
if default_timezone == :utc
|
|
107
|
+
value.utc? ? value : value.getutc
|
|
108
|
+
else
|
|
109
|
+
value.utc? ? value.getlocal : value
|
|
110
|
+
end
|
|
111
|
+
when Date
|
|
114
112
|
value
|
|
115
113
|
else
|
|
116
114
|
super
|
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
if
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
|
@@ -87,6 +85,13 @@ module ActiveRecord
|
|
|
87
85
|
super
|
|
88
86
|
end
|
|
89
87
|
|
|
88
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
|
89
|
+
# RESTRICT is by default in MySQL.
|
|
90
|
+
options.delete(:on_update) if options[:on_update] == :restrict
|
|
91
|
+
options.delete(:on_delete) if options[:on_delete] == :restrict
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
90
95
|
def internal_string_options_for_primary_key
|
|
91
96
|
super.tap do |options|
|
|
92
97
|
if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
|
|
@@ -182,12 +187,12 @@ module ActiveRecord
|
|
|
182
187
|
end
|
|
183
188
|
|
|
184
189
|
def new_column_from_field(table_name, field, _definitions)
|
|
185
|
-
field_name = field.fetch(
|
|
186
|
-
type_metadata = fetch_type_metadata(field[
|
|
187
|
-
default, default_function = field[
|
|
190
|
+
field_name = field.fetch("Field")
|
|
191
|
+
type_metadata = fetch_type_metadata(field["Type"], field["Extra"])
|
|
192
|
+
default, default_function = field["Default"], nil
|
|
188
193
|
|
|
189
194
|
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[
|
|
195
|
+
default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"])
|
|
191
196
|
default, default_function = nil, default
|
|
192
197
|
elsif type_metadata.extra == "DEFAULT_GENERATED"
|
|
193
198
|
default = +"(#{default})" unless default.start_with?("(")
|
|
@@ -203,13 +208,13 @@ module ActiveRecord
|
|
|
203
208
|
end
|
|
204
209
|
|
|
205
210
|
MySQL::Column.new(
|
|
206
|
-
field[
|
|
211
|
+
field["Field"],
|
|
207
212
|
default,
|
|
208
213
|
type_metadata,
|
|
209
|
-
field[
|
|
214
|
+
field["Null"] == "YES",
|
|
210
215
|
default_function,
|
|
211
|
-
collation: field[
|
|
212
|
-
comment: field[
|
|
216
|
+
collation: field["Collation"],
|
|
217
|
+
comment: field["Comment"].presence
|
|
213
218
|
)
|
|
214
219
|
end
|
|
215
220
|
|
|
@@ -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
|
|
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
|
-
|
|
57
|
-
raw_execute(statement, name)
|
|
58
|
-
end
|
|
19
|
+
raw_execute(statement, name, batch: true, **kwargs)
|
|
59
20
|
end
|
|
60
21
|
end
|
|
61
22
|
|
|
@@ -77,73 +38,102 @@ module ActiveRecord
|
|
|
77
38
|
end
|
|
78
39
|
end
|
|
79
40
|
|
|
80
|
-
def
|
|
81
|
-
if multi_statements_enabled?
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
51
|
+
result = nil
|
|
52
|
+
if binds.nil? || binds.empty?
|
|
53
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
54
|
+
result = raw_connection.query(sql)
|
|
55
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
56
|
+
# As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
|
|
57
|
+
# from that same connection was GCed while `#query` released the GVL.
|
|
58
|
+
# By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
|
|
59
|
+
# of hitting the bug.
|
|
60
|
+
@affected_rows_before_warnings = result&.size || raw_connection.affected_rows
|
|
61
|
+
end
|
|
62
|
+
elsif prepare
|
|
63
|
+
stmt = @statements[sql] ||= raw_connection.prepare(sql)
|
|
64
|
+
begin
|
|
65
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
66
|
+
result = stmt.execute(*type_casted_binds)
|
|
67
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
68
|
+
end
|
|
69
|
+
rescue ::Mysql2::Error
|
|
70
|
+
@statements.delete(sql)
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
stmt = raw_connection.prepare(sql)
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
78
|
+
result = stmt.execute(*type_casted_binds)
|
|
79
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
80
|
+
end
|
|
93
81
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
result
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
83
|
+
# by eagerly closing uncached prepared statements, we also reduce the chances of
|
|
84
|
+
# that bug happening. It can still happen if `#execute` is used as we have no callback
|
|
85
|
+
# to eagerly close the statement.
|
|
86
|
+
if result
|
|
87
|
+
result.instance_variable_set(:@_ar_stmt_to_close, stmt)
|
|
88
|
+
else
|
|
89
|
+
stmt.close
|
|
90
|
+
end
|
|
91
|
+
rescue ::Mysql2::Error
|
|
92
|
+
stmt.close
|
|
93
|
+
raise
|
|
104
94
|
end
|
|
105
95
|
end
|
|
96
|
+
|
|
97
|
+
notification_payload[:affected_rows] = @affected_rows_before_warnings
|
|
98
|
+
notification_payload[:row_count] = result&.size || 0
|
|
99
|
+
|
|
100
|
+
raw_connection.abandon_results!
|
|
101
|
+
|
|
102
|
+
verified!
|
|
103
|
+
handle_warnings(sql)
|
|
104
|
+
result
|
|
105
|
+
ensure
|
|
106
|
+
if reset_multi_statement && active?
|
|
107
|
+
raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
|
108
|
+
end
|
|
106
109
|
end
|
|
107
110
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
check_if_write_query(sql)
|
|
111
|
+
def cast_result(raw_result)
|
|
112
|
+
return ActiveRecord::Result.empty if raw_result.nil?
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
fields = raw_result.fields
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
result = if fields.empty?
|
|
117
|
+
ActiveRecord::Result.empty
|
|
118
|
+
else
|
|
119
|
+
ActiveRecord::Result.new(fields, raw_result.to_a)
|
|
120
|
+
end
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
with_raw_connection do |conn|
|
|
118
|
-
sync_timezone_changes(conn)
|
|
122
|
+
free_raw_result(raw_result)
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
stmt = conn.prepare(sql)
|
|
124
|
-
end
|
|
124
|
+
result
|
|
125
|
+
end
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
127
|
+
def affected_rows(raw_result)
|
|
128
|
+
free_raw_result(raw_result) if raw_result
|
|
140
129
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
130
|
+
@affected_rows_before_warnings
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def free_raw_result(raw_result)
|
|
134
|
+
raw_result.free
|
|
135
|
+
if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
|
|
136
|
+
stmt.close
|
|
147
137
|
end
|
|
148
138
|
end
|
|
149
139
|
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
|
|
@@ -113,7 +106,14 @@ module ActiveRecord
|
|
|
113
106
|
end
|
|
114
107
|
|
|
115
108
|
def active?
|
|
116
|
-
connected?
|
|
109
|
+
if connected?
|
|
110
|
+
@lock.synchronize do
|
|
111
|
+
if @raw_connection&.ping
|
|
112
|
+
verified!
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end || false
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
alias :reset! :reconnect!
|