activerecord 7.1.5.1 → 8.0.2
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 +369 -2484
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +21 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +92 -295
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +63 -49
- data/lib/active_record/autosave_association.rb +92 -57
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
- data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
- 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 +48 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
- data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- 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 +36 -20
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
- data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +65 -0
- data/lib/active_record/connection_handling.rb +74 -37
- data/lib/active_record/core.rb +132 -51
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +23 -4
- data/lib/active_record/database_configurations/hash_config.rb +46 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +41 -17
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +7 -7
- data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
- data/lib/active_record/encryption/encryptor.rb +28 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +20 -16
- data/lib/active_record/errors.rb +54 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -33
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +19 -16
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +5 -32
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +33 -14
- data/lib/active_record/migration/compatibility.rb +8 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +104 -98
- data/lib/active_record/model_schema.rb +32 -70
- data/lib/active_record/nested_attributes.rb +15 -9
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +127 -451
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +104 -37
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +24 -12
- data/lib/active_record/railtie.rb +26 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +43 -61
- data/lib/active_record/reflection.rb +112 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +138 -72
- data/lib/active_record/relation/calculations.rb +122 -82
- data/lib/active_record/relation/delegation.rb +30 -22
- data/lib/active_record/relation/finder_methods.rb +32 -18
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- 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 +16 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +317 -101
- data/lib/active_record/relation/spawn_methods.rb +3 -19
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +561 -119
- data/lib/active_record/result.rb +95 -46
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +31 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +53 -20
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/signed_id.rb +24 -4
- data/lib/active_record/statement_cache.rb +19 -19
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +2 -13
- data/lib/active_record/tasks/database_tasks.rb +87 -58
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
- data/lib/active_record/test_fixtures.rb +98 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +72 -17
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +138 -57
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +5 -4
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -16
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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?
|
@@ -170,6 +170,14 @@ module ActiveRecord
|
|
170
170
|
true
|
171
171
|
end
|
172
172
|
|
173
|
+
def supports_insert_returning?
|
174
|
+
mariadb? && database_version >= "10.5.0"
|
175
|
+
end
|
176
|
+
|
177
|
+
def return_value_after_insert?(column) # :nodoc:
|
178
|
+
supports_insert_returning? ? column.auto_populated? : column.auto_increment?
|
179
|
+
end
|
180
|
+
|
173
181
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
174
182
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
175
183
|
end
|
@@ -193,12 +201,6 @@ module ActiveRecord
|
|
193
201
|
|
194
202
|
# HELPER METHODS ===========================================
|
195
203
|
|
196
|
-
# The two drivers have slightly different ways of yielding hashes of results, so
|
197
|
-
# this method must be implemented to provide a uniform interface.
|
198
|
-
def each_hash(result) # :nodoc:
|
199
|
-
raise NotImplementedError
|
200
|
-
end
|
201
|
-
|
202
204
|
# Must return the MySQL error number from the exception, if the exception has an
|
203
205
|
# error number.
|
204
206
|
def error_number(exception) # :nodoc:
|
@@ -214,7 +216,7 @@ module ActiveRecord
|
|
214
216
|
update("SET FOREIGN_KEY_CHECKS = 0")
|
215
217
|
yield
|
216
218
|
ensure
|
217
|
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
219
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
|
218
220
|
end
|
219
221
|
end
|
220
222
|
|
@@ -222,24 +224,19 @@ module ActiveRecord
|
|
222
224
|
# DATABASE STATEMENTS ======================================
|
223
225
|
#++
|
224
226
|
|
225
|
-
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
226
|
-
# to write stuff in an abstract way without concerning ourselves about whether it
|
227
|
-
# needs to be explicitly freed or not.
|
228
|
-
def execute_and_free(sql, name = nil, async: false) # :nodoc:
|
229
|
-
sql = transform_query(sql)
|
230
|
-
check_if_write_query(sql)
|
231
|
-
|
232
|
-
mark_transaction_written_if_write(sql)
|
233
|
-
yield raw_execute(sql, name, async: async)
|
234
|
-
end
|
235
|
-
|
236
227
|
def begin_db_transaction # :nodoc:
|
237
228
|
internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
238
229
|
end
|
239
230
|
|
240
231
|
def begin_isolated_db_transaction(isolation) # :nodoc:
|
241
|
-
|
242
|
-
|
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
|
+
)
|
243
240
|
end
|
244
241
|
|
245
242
|
def commit_db_transaction # :nodoc:
|
@@ -339,7 +336,7 @@ module ActiveRecord
|
|
339
336
|
rename_table_indexes(table_name, new_name, **options)
|
340
337
|
end
|
341
338
|
|
342
|
-
# Drops a table from the database.
|
339
|
+
# Drops a table or tables from the database.
|
343
340
|
#
|
344
341
|
# [<tt>:force</tt>]
|
345
342
|
# Set to +:cascade+ to drop dependent objects as well.
|
@@ -353,10 +350,10 @@ module ActiveRecord
|
|
353
350
|
#
|
354
351
|
# Although this command ignores most +options+ and the block if one is given,
|
355
352
|
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
356
|
-
# In that case, +options+ and the block will be used by create_table.
|
357
|
-
def drop_table(
|
358
|
-
schema_cache.clear_data_source_cache!(table_name.to_s)
|
359
|
-
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}"
|
360
357
|
end
|
361
358
|
|
362
359
|
def rename_index(table_name, old_name, new_name)
|
@@ -410,7 +407,11 @@ module ActiveRecord
|
|
410
407
|
type ||= column.sql_type
|
411
408
|
|
412
409
|
unless options.key?(:default)
|
413
|
-
options[:default] = column.
|
410
|
+
options[:default] = if column.default_function
|
411
|
+
-> { column.default_function }
|
412
|
+
else
|
413
|
+
column.default
|
414
|
+
end
|
414
415
|
end
|
415
416
|
|
416
417
|
unless options.key?(:null)
|
@@ -576,7 +577,7 @@ module ActiveRecord
|
|
576
577
|
|
577
578
|
# SHOW VARIABLES LIKE 'name'
|
578
579
|
def show_variable(name)
|
579
|
-
query_value("SELECT @@#{name}", "SCHEMA")
|
580
|
+
query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
|
580
581
|
rescue ActiveRecord::StatementInvalid
|
581
582
|
nil
|
582
583
|
end
|
@@ -635,22 +636,26 @@ module ActiveRecord
|
|
635
636
|
end
|
636
637
|
|
637
638
|
def build_insert_sql(insert) # :nodoc:
|
638
|
-
|
639
|
+
# Can use any column as it will be assigned to itself.
|
640
|
+
no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
|
639
641
|
|
640
642
|
# MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
|
641
643
|
# then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
|
642
644
|
if supports_insert_raw_alias_syntax?
|
643
|
-
|
645
|
+
quoted_table_name = insert.model.quoted_table_name
|
646
|
+
values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
|
644
647
|
sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
|
645
648
|
|
646
649
|
if insert.skip_duplicates?
|
647
|
-
|
650
|
+
if no_op_column
|
651
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
|
652
|
+
end
|
648
653
|
elsif insert.update_duplicates?
|
649
654
|
if insert.raw_update_sql?
|
650
655
|
sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
|
651
656
|
else
|
652
657
|
sql << " ON DUPLICATE KEY UPDATE "
|
653
|
-
sql << insert.touch_model_timestamps_unless { |column| "#{
|
658
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
|
654
659
|
sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
|
655
660
|
end
|
656
661
|
end
|
@@ -658,7 +663,9 @@ module ActiveRecord
|
|
658
663
|
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
659
664
|
|
660
665
|
if insert.skip_duplicates?
|
661
|
-
|
666
|
+
if no_op_column
|
667
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
|
668
|
+
end
|
662
669
|
elsif insert.update_duplicates?
|
663
670
|
sql << " ON DUPLICATE KEY UPDATE "
|
664
671
|
if insert.raw_update_sql?
|
@@ -670,12 +677,24 @@ module ActiveRecord
|
|
670
677
|
end
|
671
678
|
end
|
672
679
|
|
680
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
673
681
|
sql
|
674
682
|
end
|
675
683
|
|
676
684
|
def check_version # :nodoc:
|
677
|
-
if database_version < "5.
|
678
|
-
raise "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."
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
#--
|
691
|
+
# QUOTING ==================================================
|
692
|
+
#++
|
693
|
+
|
694
|
+
# Quotes strings for use in SQL input.
|
695
|
+
def quote_string(string)
|
696
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
697
|
+
connection.escape(string)
|
679
698
|
end
|
680
699
|
end
|
681
700
|
|
@@ -737,9 +756,7 @@ module ActiveRecord
|
|
737
756
|
|
738
757
|
private
|
739
758
|
def strip_whitespace_characters(expression)
|
740
|
-
expression
|
741
|
-
expression = expression.gsub(/\s{2,}/, " ")
|
742
|
-
expression
|
759
|
+
expression.gsub('\\\n', "").gsub("x0A", "").squish
|
743
760
|
end
|
744
761
|
|
745
762
|
def extended_type_map_key
|
@@ -753,8 +770,11 @@ module ActiveRecord
|
|
753
770
|
def handle_warnings(sql)
|
754
771
|
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
755
772
|
|
756
|
-
|
773
|
+
warning_count = @raw_connection.warning_count
|
757
774
|
result = @raw_connection.query("SHOW WARNINGS")
|
775
|
+
result = [
|
776
|
+
["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
|
777
|
+
] if result.count == 0
|
758
778
|
result.each do |level, code, message|
|
759
779
|
warning = SQLWarning.new(message, code, level, sql, @pool)
|
760
780
|
next if warning_ignored?(warning)
|
@@ -767,15 +787,11 @@ module ActiveRecord
|
|
767
787
|
warning.level == "Note" || super
|
768
788
|
end
|
769
789
|
|
770
|
-
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
771
|
-
# made since we established the connection
|
772
|
-
def sync_timezone_changes(raw_connection)
|
773
|
-
end
|
774
|
-
|
775
790
|
# See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
|
776
791
|
ER_DB_CREATE_EXISTS = 1007
|
777
792
|
ER_FILSORT_ABORT = 1028
|
778
793
|
ER_DUP_ENTRY = 1062
|
794
|
+
ER_SERVER_SHUTDOWN = 1053
|
779
795
|
ER_NOT_NULL_VIOLATION = 1048
|
780
796
|
ER_NO_REFERENCED_ROW = 1216
|
781
797
|
ER_ROW_IS_REFERENCED = 1217
|
@@ -804,7 +820,7 @@ module ActiveRecord
|
|
804
820
|
else
|
805
821
|
super
|
806
822
|
end
|
807
|
-
when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
|
823
|
+
when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
|
808
824
|
ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
809
825
|
when ER_DB_CREATE_EXISTS
|
810
826
|
DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
@@ -894,6 +910,7 @@ module ActiveRecord
|
|
894
910
|
end
|
895
911
|
|
896
912
|
def configure_connection
|
913
|
+
super
|
897
914
|
variables = @config.fetch(:variables, {}).stringify_keys
|
898
915
|
|
899
916
|
# Increase timeout so the server doesn't disconnect us.
|
@@ -939,13 +956,11 @@ module ActiveRecord
|
|
939
956
|
end.join(", ")
|
940
957
|
|
941
958
|
# ...and send them all in one query
|
942
|
-
|
959
|
+
raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
|
943
960
|
end
|
944
961
|
|
945
962
|
def column_definitions(table_name) # :nodoc:
|
946
|
-
|
947
|
-
each_hash(result)
|
948
|
-
end
|
963
|
+
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
|
949
964
|
end
|
950
965
|
|
951
966
|
def create_table_info(table_name) # :nodoc:
|
@@ -1001,7 +1016,11 @@ module ActiveRecord
|
|
1001
1016
|
end
|
1002
1017
|
|
1003
1018
|
def version_string(full_version_string)
|
1004
|
-
full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
|
1019
|
+
if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
|
1020
|
+
matches[1]
|
1021
|
+
else
|
1022
|
+
raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
|
1023
|
+
end
|
1005
1024
|
end
|
1006
1025
|
end
|
1007
1026
|
end
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
|
12
12
|
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
|
13
13
|
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
|
14
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
14
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)", retryable: true).freeze # :nodoc:
|
15
15
|
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
16
16
|
|
17
17
|
def write_query?(sql) # :nodoc:
|
@@ -55,6 +55,14 @@ module ActiveRecord
|
|
55
55
|
super unless column.auto_increment?
|
56
56
|
end
|
57
57
|
|
58
|
+
def returning_column_values(result)
|
59
|
+
if supports_insert_returning?
|
60
|
+
result.rows.first
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
58
66
|
def combine_multi_statements(total_sql)
|
59
67
|
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
60
68
|
previous_packet = total_sql_chunks.last
|
@@ -6,9 +6,52 @@ module ActiveRecord
|
|
6
6
|
module ConnectionAdapters
|
7
7
|
module MySQL
|
8
8
|
module Quoting # :nodoc:
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
9
11
|
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
10
12
|
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
11
13
|
|
14
|
+
module ClassMethods # :nodoc:
|
15
|
+
def column_name_matcher
|
16
|
+
/
|
17
|
+
\A
|
18
|
+
(
|
19
|
+
(?:
|
20
|
+
# `table_name`.`column_name` | function(one or no argument)
|
21
|
+
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
|
22
|
+
)
|
23
|
+
(?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
|
24
|
+
)
|
25
|
+
(?:\s*,\s*\g<1>)*
|
26
|
+
\z
|
27
|
+
/ix
|
28
|
+
end
|
29
|
+
|
30
|
+
def column_name_with_order_matcher
|
31
|
+
/
|
32
|
+
\A
|
33
|
+
(
|
34
|
+
(?:
|
35
|
+
# `table_name`.`column_name` | function(one or no argument)
|
36
|
+
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
|
37
|
+
)
|
38
|
+
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
39
|
+
(?:\s+ASC|\s+DESC)?
|
40
|
+
)
|
41
|
+
(?:\s*,\s*\g<1>)*
|
42
|
+
\z
|
43
|
+
/ix
|
44
|
+
end
|
45
|
+
|
46
|
+
def quote_column_name(name)
|
47
|
+
QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub('`', '``')}`".freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
def quote_table_name(name)
|
51
|
+
QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub('`', '``').gsub(".", "`.`")}`".freeze
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
12
55
|
def cast_bound_value(value)
|
13
56
|
case value
|
14
57
|
when Rational
|
@@ -21,22 +64,11 @@ module ActiveRecord
|
|
21
64
|
"1"
|
22
65
|
when false
|
23
66
|
"0"
|
24
|
-
when ActiveSupport::Duration
|
25
|
-
warn_quote_duration_deprecated
|
26
|
-
value.to_s
|
27
67
|
else
|
28
68
|
value
|
29
69
|
end
|
30
70
|
end
|
31
71
|
|
32
|
-
def quote_column_name(name)
|
33
|
-
QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
|
34
|
-
end
|
35
|
-
|
36
|
-
def quote_table_name(name)
|
37
|
-
QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
|
38
|
-
end
|
39
|
-
|
40
72
|
def unquoted_true
|
41
73
|
1
|
42
74
|
end
|
@@ -45,14 +77,6 @@ module ActiveRecord
|
|
45
77
|
0
|
46
78
|
end
|
47
79
|
|
48
|
-
def quoted_date(value)
|
49
|
-
if supports_datetime_with_precision?
|
50
|
-
super
|
51
|
-
else
|
52
|
-
super.sub(/\.\d{6}\z/, "")
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
80
|
def quoted_binary(value)
|
57
81
|
"x'#{value.hex}'"
|
58
82
|
end
|
@@ -78,49 +102,18 @@ module ActiveRecord
|
|
78
102
|
else
|
79
103
|
value.getlocal
|
80
104
|
end
|
81
|
-
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
|
82
112
|
value
|
83
113
|
else
|
84
114
|
super
|
85
115
|
end
|
86
116
|
end
|
87
|
-
|
88
|
-
def column_name_matcher
|
89
|
-
COLUMN_NAME
|
90
|
-
end
|
91
|
-
|
92
|
-
def column_name_with_order_matcher
|
93
|
-
COLUMN_NAME_WITH_ORDER
|
94
|
-
end
|
95
|
-
|
96
|
-
COLUMN_NAME = /
|
97
|
-
\A
|
98
|
-
(
|
99
|
-
(?:
|
100
|
-
# `table_name`.`column_name` | function(one or no argument)
|
101
|
-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
|
102
|
-
)
|
103
|
-
(?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
|
104
|
-
)
|
105
|
-
(?:\s*,\s*\g<1>)*
|
106
|
-
\z
|
107
|
-
/ix
|
108
|
-
|
109
|
-
COLUMN_NAME_WITH_ORDER = /
|
110
|
-
\A
|
111
|
-
(
|
112
|
-
(?:
|
113
|
-
# `table_name`.`column_name` | function(one or no argument)
|
114
|
-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
|
115
|
-
)
|
116
|
-
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
117
|
-
(?:\s+ASC|\s+DESC)?
|
118
|
-
)
|
119
|
-
(?:\s*,\s*\g<1>)*
|
120
|
-
\z
|
121
|
-
/ix
|
122
|
-
|
123
|
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
124
117
|
end
|
125
118
|
end
|
126
119
|
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
|
-
|
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
|
|
@@ -68,6 +66,12 @@ module ActiveRecord
|
|
68
66
|
|
69
67
|
IndexDefinition.new(*index, **options)
|
70
68
|
end
|
69
|
+
rescue StatementInvalid => e
|
70
|
+
if e.message.match?(/Table '.+' doesn't exist/)
|
71
|
+
[]
|
72
|
+
else
|
73
|
+
raise
|
74
|
+
end
|
71
75
|
end
|
72
76
|
|
73
77
|
def remove_column(table_name, column_name, type = nil, **options)
|
@@ -81,6 +85,13 @@ module ActiveRecord
|
|
81
85
|
super
|
82
86
|
end
|
83
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
|
+
|
84
95
|
def internal_string_options_for_primary_key
|
85
96
|
super.tap do |options|
|
86
97
|
if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
|
@@ -176,12 +187,12 @@ module ActiveRecord
|
|
176
187
|
end
|
177
188
|
|
178
189
|
def new_column_from_field(table_name, field, _definitions)
|
179
|
-
field_name = field.fetch(
|
180
|
-
type_metadata = fetch_type_metadata(field[
|
181
|
-
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
|
182
193
|
|
183
194
|
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[
|
195
|
+
default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"])
|
185
196
|
default, default_function = nil, default
|
186
197
|
elsif type_metadata.extra == "DEFAULT_GENERATED"
|
187
198
|
default = +"(#{default})" unless default.start_with?("(")
|
@@ -197,13 +208,13 @@ module ActiveRecord
|
|
197
208
|
end
|
198
209
|
|
199
210
|
MySQL::Column.new(
|
200
|
-
field[
|
211
|
+
field["Field"],
|
201
212
|
default,
|
202
213
|
type_metadata,
|
203
|
-
field[
|
214
|
+
field["Null"] == "YES",
|
204
215
|
default_function,
|
205
|
-
collation: field[
|
206
|
-
comment: field[
|
216
|
+
collation: field["Collation"],
|
217
|
+
comment: field["Comment"].presence
|
207
218
|
)
|
208
219
|
end
|
209
220
|
|