activerecord 7.1.3.4 → 7.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +652 -2032
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- 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 +11 -5
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -12
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- 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 +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +62 -289
- 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/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +17 -31
- 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 +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +23 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +18 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +93 -40
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- 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 +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- 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 -4
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- 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 +0 -21
- data/lib/active_record/marshalling.rb +4 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -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 +85 -76
- data/lib/active_record/model_schema.rb +38 -70
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +106 -38
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +245 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +98 -48
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- 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 +70 -14
- 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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- 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 +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- 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/tree_manager.rb +8 -3
- 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 +31 -17
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +21 -15
@@ -170,6 +170,10 @@ 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
|
+
|
173
177
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
174
178
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
175
179
|
end
|
@@ -214,7 +218,7 @@ module ActiveRecord
|
|
214
218
|
update("SET FOREIGN_KEY_CHECKS = 0")
|
215
219
|
yield
|
216
220
|
ensure
|
217
|
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
221
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
|
218
222
|
end
|
219
223
|
end
|
220
224
|
|
@@ -225,12 +229,12 @@ module ActiveRecord
|
|
225
229
|
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
226
230
|
# to write stuff in an abstract way without concerning ourselves about whether it
|
227
231
|
# needs to be explicitly freed or not.
|
228
|
-
def execute_and_free(sql, name = nil, async: false) # :nodoc:
|
232
|
+
def execute_and_free(sql, name = nil, async: false, allow_retry: false) # :nodoc:
|
229
233
|
sql = transform_query(sql)
|
230
234
|
check_if_write_query(sql)
|
231
235
|
|
232
236
|
mark_transaction_written_if_write(sql)
|
233
|
-
yield raw_execute(sql, name, async: async)
|
237
|
+
yield raw_execute(sql, name, async: async, allow_retry: allow_retry)
|
234
238
|
end
|
235
239
|
|
236
240
|
def begin_db_transaction # :nodoc:
|
@@ -336,7 +340,7 @@ module ActiveRecord
|
|
336
340
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
337
341
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
338
342
|
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
339
|
-
rename_table_indexes(table_name, new_name)
|
343
|
+
rename_table_indexes(table_name, new_name, **options)
|
340
344
|
end
|
341
345
|
|
342
346
|
# Drops a table from the database.
|
@@ -635,27 +639,63 @@ module ActiveRecord
|
|
635
639
|
end
|
636
640
|
|
637
641
|
def build_insert_sql(insert) # :nodoc:
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
sql
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
642
|
+
no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
|
643
|
+
|
644
|
+
# MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
|
645
|
+
# then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
|
646
|
+
if supports_insert_raw_alias_syntax?
|
647
|
+
values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
|
648
|
+
sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
|
649
|
+
|
650
|
+
if insert.skip_duplicates?
|
651
|
+
if no_op_column
|
652
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
|
653
|
+
end
|
654
|
+
elsif insert.update_duplicates?
|
655
|
+
if insert.raw_update_sql?
|
656
|
+
sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
|
657
|
+
else
|
658
|
+
sql << " ON DUPLICATE KEY UPDATE "
|
659
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
|
660
|
+
sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
|
661
|
+
end
|
662
|
+
end
|
663
|
+
else
|
664
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
665
|
+
|
666
|
+
if insert.skip_duplicates?
|
667
|
+
if no_op_column
|
668
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
|
669
|
+
end
|
670
|
+
elsif insert.update_duplicates?
|
671
|
+
sql << " ON DUPLICATE KEY UPDATE "
|
672
|
+
if insert.raw_update_sql?
|
673
|
+
sql << insert.raw_update_sql
|
674
|
+
else
|
675
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
|
676
|
+
sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
|
677
|
+
end
|
650
678
|
end
|
651
679
|
end
|
652
680
|
|
681
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
653
682
|
sql
|
654
683
|
end
|
655
684
|
|
656
685
|
def check_version # :nodoc:
|
657
686
|
if database_version < "5.5.8"
|
658
|
-
raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
|
687
|
+
raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
#--
|
692
|
+
# QUOTING ==================================================
|
693
|
+
#++
|
694
|
+
|
695
|
+
# Quotes strings for use in SQL input.
|
696
|
+
def quote_string(string)
|
697
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
698
|
+
connection.escape(string)
|
659
699
|
end
|
660
700
|
end
|
661
701
|
|
@@ -734,7 +774,11 @@ module ActiveRecord
|
|
734
774
|
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
735
775
|
|
736
776
|
@affected_rows_before_warnings = @raw_connection.affected_rows
|
777
|
+
warning_count = @raw_connection.warning_count
|
737
778
|
result = @raw_connection.query("SHOW WARNINGS")
|
779
|
+
result = [
|
780
|
+
["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
|
781
|
+
] if result.count == 0
|
738
782
|
result.each do |level, code, message|
|
739
783
|
warning = SQLWarning.new(message, code, level, sql, @pool)
|
740
784
|
next if warning_ignored?(warning)
|
@@ -756,6 +800,7 @@ module ActiveRecord
|
|
756
800
|
ER_DB_CREATE_EXISTS = 1007
|
757
801
|
ER_FILSORT_ABORT = 1028
|
758
802
|
ER_DUP_ENTRY = 1062
|
803
|
+
ER_SERVER_SHUTDOWN = 1053
|
759
804
|
ER_NOT_NULL_VIOLATION = 1048
|
760
805
|
ER_NO_REFERENCED_ROW = 1216
|
761
806
|
ER_ROW_IS_REFERENCED = 1217
|
@@ -784,7 +829,7 @@ module ActiveRecord
|
|
784
829
|
else
|
785
830
|
super
|
786
831
|
end
|
787
|
-
when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
|
832
|
+
when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
|
788
833
|
ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
789
834
|
when ER_DB_CREATE_EXISTS
|
790
835
|
DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
@@ -853,6 +898,10 @@ module ActiveRecord
|
|
853
898
|
"DROP INDEX #{quote_column_name(index_name)}"
|
854
899
|
end
|
855
900
|
|
901
|
+
def supports_insert_raw_alias_syntax?
|
902
|
+
!mariadb? && database_version >= "8.0.19"
|
903
|
+
end
|
904
|
+
|
856
905
|
def supports_rename_index?
|
857
906
|
if mariadb?
|
858
907
|
database_version >= "10.5.2"
|
@@ -870,6 +919,7 @@ module ActiveRecord
|
|
870
919
|
end
|
871
920
|
|
872
921
|
def configure_connection
|
922
|
+
super
|
873
923
|
variables = @config.fetch(:variables, {}).stringify_keys
|
874
924
|
|
875
925
|
# Increase timeout so the server doesn't disconnect us.
|
@@ -977,7 +1027,11 @@ module ActiveRecord
|
|
977
1027
|
end
|
978
1028
|
|
979
1029
|
def version_string(full_version_string)
|
980
|
-
full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
|
1030
|
+
if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
|
1031
|
+
matches[1]
|
1032
|
+
else
|
1033
|
+
raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
|
1034
|
+
end
|
981
1035
|
end
|
982
1036
|
end
|
983
1037
|
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
|
@@ -84,43 +116,6 @@ module ActiveRecord
|
|
84
116
|
super
|
85
117
|
end
|
86
118
|
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
119
|
end
|
125
120
|
end
|
126
121
|
end
|
@@ -68,6 +68,12 @@ module ActiveRecord
|
|
68
68
|
|
69
69
|
IndexDefinition.new(*index, **options)
|
70
70
|
end
|
71
|
+
rescue StatementInvalid => e
|
72
|
+
if e.message.match?(/Table '.+' doesn't exist/)
|
73
|
+
[]
|
74
|
+
else
|
75
|
+
raise
|
76
|
+
end
|
71
77
|
end
|
72
78
|
|
73
79
|
def remove_column(table_name, column_name, type = nil, **options)
|
@@ -155,7 +161,7 @@ module ActiveRecord
|
|
155
161
|
end
|
156
162
|
|
157
163
|
def valid_primary_key_options
|
158
|
-
super + [:unsigned]
|
164
|
+
super + [:unsigned, :auto_increment]
|
159
165
|
end
|
160
166
|
|
161
167
|
def create_table_definition(name, **options)
|
@@ -185,6 +191,7 @@ module ActiveRecord
|
|
185
191
|
default, default_function = nil, default
|
186
192
|
elsif type_metadata.extra == "DEFAULT_GENERATED"
|
187
193
|
default = +"(#{default})" unless default.start_with?("(")
|
194
|
+
default = default.gsub("\\'", "'")
|
188
195
|
default, default_function = nil, default
|
189
196
|
elsif type_metadata.type == :text && default&.start_with?("'")
|
190
197
|
# strip and unescape quotes
|
@@ -6,21 +6,16 @@ module ActiveRecord
|
|
6
6
|
module DatabaseStatements
|
7
7
|
# Returns an ActiveRecord::Result instance.
|
8
8
|
def select_all(*, **) # :nodoc:
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
else
|
14
|
-
super
|
15
|
-
end
|
16
|
-
conn.abandon_results!
|
9
|
+
if ExplainRegistry.collect? && prepared_statements
|
10
|
+
unprepared_statement { super }
|
11
|
+
else
|
12
|
+
super
|
17
13
|
end
|
18
|
-
result
|
19
14
|
end
|
20
15
|
|
21
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
16
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
22
17
|
if without_prepared_statement?(binds)
|
23
|
-
execute_and_free(sql, name, async: async) do |result|
|
18
|
+
execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
|
24
19
|
if result
|
25
20
|
build_result(columns: result.fields, rows: result.to_a)
|
26
21
|
else
|
@@ -60,13 +55,16 @@ module ActiveRecord
|
|
60
55
|
combine_multi_statements(statements).each do |statement|
|
61
56
|
with_raw_connection do |conn|
|
62
57
|
raw_execute(statement, name)
|
63
|
-
conn.abandon_results!
|
64
58
|
end
|
65
59
|
end
|
66
60
|
end
|
67
61
|
|
68
62
|
def last_inserted_id(result)
|
69
|
-
|
63
|
+
if supports_insert_returning?
|
64
|
+
super
|
65
|
+
else
|
66
|
+
@raw_connection&.last_id
|
67
|
+
end
|
70
68
|
end
|
71
69
|
|
72
70
|
def multi_statements_enabled?
|
@@ -94,12 +92,14 @@ module ActiveRecord
|
|
94
92
|
end
|
95
93
|
|
96
94
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
97
|
-
log(sql, name, async: async) do
|
95
|
+
log(sql, name, async: async) do |notification_payload|
|
98
96
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
99
97
|
sync_timezone_changes(conn)
|
100
98
|
result = conn.query(sql)
|
99
|
+
conn.abandon_results!
|
101
100
|
verified!
|
102
101
|
handle_warnings(sql)
|
102
|
+
notification_payload[:row_count] = result&.size || 0
|
103
103
|
result
|
104
104
|
end
|
105
105
|
end
|
@@ -113,7 +113,7 @@ module ActiveRecord
|
|
113
113
|
|
114
114
|
type_casted_binds = type_casted_binds(binds)
|
115
115
|
|
116
|
-
log(sql, name, binds, type_casted_binds, async: async) do
|
116
|
+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
117
117
|
with_raw_connection do |conn|
|
118
118
|
sync_timezone_changes(conn)
|
119
119
|
|
@@ -139,6 +139,7 @@ module ActiveRecord
|
|
139
139
|
end
|
140
140
|
|
141
141
|
ret = yield stmt, result
|
142
|
+
notification_payload[:row_count] = result&.size || 0
|
142
143
|
result.free if result
|
143
144
|
stmt.close unless cache_stmt
|
144
145
|
ret
|
@@ -7,17 +7,6 @@ gem "mysql2", "~> 0.5"
|
|
7
7
|
require "mysql2"
|
8
8
|
|
9
9
|
module ActiveRecord
|
10
|
-
module ConnectionHandling # :nodoc:
|
11
|
-
def mysql2_adapter_class
|
12
|
-
ConnectionAdapters::Mysql2Adapter
|
13
|
-
end
|
14
|
-
|
15
|
-
# Establishes a connection to the database that's used by all Active Record objects.
|
16
|
-
def mysql2_connection(config)
|
17
|
-
mysql2_adapter_class.new(config)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
10
|
module ConnectionAdapters
|
22
11
|
# = Active Record MySQL2 Adapter
|
23
12
|
class Mysql2Adapter < AbstractMysqlAdapter
|
@@ -116,22 +105,15 @@ module ActiveRecord
|
|
116
105
|
end
|
117
106
|
|
118
107
|
#--
|
119
|
-
#
|
108
|
+
# CONNECTION MANAGEMENT ====================================
|
120
109
|
#++
|
121
110
|
|
122
|
-
|
123
|
-
|
124
|
-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
125
|
-
connection.escape(string)
|
126
|
-
end
|
111
|
+
def connected?
|
112
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
127
113
|
end
|
128
114
|
|
129
|
-
#--
|
130
|
-
# CONNECTION MANAGEMENT ====================================
|
131
|
-
#++
|
132
|
-
|
133
115
|
def active?
|
134
|
-
|
116
|
+
connected? && @lock.synchronize { @raw_connection&.ping } || false
|
135
117
|
end
|
136
118
|
|
137
119
|
alias :reset! :reconnect!
|
@@ -139,15 +121,19 @@ module ActiveRecord
|
|
139
121
|
# Disconnects from the database if already connected.
|
140
122
|
# Otherwise, this method does nothing.
|
141
123
|
def disconnect!
|
142
|
-
|
143
|
-
|
144
|
-
|
124
|
+
@lock.synchronize do
|
125
|
+
super
|
126
|
+
@raw_connection&.close
|
127
|
+
@raw_connection = nil
|
128
|
+
end
|
145
129
|
end
|
146
130
|
|
147
131
|
def discard! # :nodoc:
|
148
|
-
|
149
|
-
|
150
|
-
|
132
|
+
@lock.synchronize do
|
133
|
+
super
|
134
|
+
@raw_connection&.automatic_close = false
|
135
|
+
@raw_connection = nil
|
136
|
+
end
|
151
137
|
end
|
152
138
|
|
153
139
|
private
|
@@ -162,9 +148,11 @@ module ActiveRecord
|
|
162
148
|
end
|
163
149
|
|
164
150
|
def reconnect
|
165
|
-
@
|
166
|
-
|
167
|
-
|
151
|
+
@lock.synchronize do
|
152
|
+
@raw_connection&.close
|
153
|
+
@raw_connection = nil
|
154
|
+
connect
|
155
|
+
end
|
168
156
|
end
|
169
157
|
|
170
158
|
def configure_connection
|
@@ -174,7 +162,7 @@ module ActiveRecord
|
|
174
162
|
end
|
175
163
|
|
176
164
|
def full_version
|
177
|
-
|
165
|
+
database_version.full_version_string
|
178
166
|
end
|
179
167
|
|
180
168
|
def get_full_version
|
@@ -3,10 +3,10 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
5
|
class PoolConfig # :nodoc:
|
6
|
-
include
|
6
|
+
include MonitorMixin
|
7
7
|
|
8
8
|
attr_reader :db_config, :role, :shard
|
9
|
-
attr_writer :schema_reflection
|
9
|
+
attr_writer :schema_reflection, :server_version
|
10
10
|
attr_accessor :connection_class
|
11
11
|
|
12
12
|
def schema_reflection
|
@@ -28,6 +28,7 @@ module ActiveRecord
|
|
28
28
|
|
29
29
|
def initialize(connection_class, db_config, role, shard)
|
30
30
|
super()
|
31
|
+
@server_version = nil
|
31
32
|
@connection_class = connection_class
|
32
33
|
@db_config = db_config
|
33
34
|
@role = role
|
@@ -36,6 +37,10 @@ module ActiveRecord
|
|
36
37
|
INSTANCES[self] = self
|
37
38
|
end
|
38
39
|
|
40
|
+
def server_version(connection)
|
41
|
+
@server_version || synchronize { @server_version ||= connection.get_database_version }
|
42
|
+
end
|
43
|
+
|
39
44
|
def connection_name
|
40
45
|
if connection_class.primary_class?
|
41
46
|
"ActiveRecord::Base"
|
@@ -45,8 +50,6 @@ module ActiveRecord
|
|
45
50
|
end
|
46
51
|
|
47
52
|
def disconnect!(automatic_reconnect: false)
|
48
|
-
ActiveSupport::ForkTracker.check!
|
49
|
-
|
50
53
|
return unless @pool
|
51
54
|
|
52
55
|
synchronize do
|
@@ -60,8 +63,6 @@ module ActiveRecord
|
|
60
63
|
end
|
61
64
|
|
62
65
|
def pool
|
63
|
-
ActiveSupport::ForkTracker.check!
|
64
|
-
|
65
66
|
@pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
|
66
67
|
end
|
67
68
|
|
@@ -14,10 +14,11 @@ module ActiveRecord
|
|
14
14
|
def query(sql, name = nil) # :nodoc:
|
15
15
|
mark_transaction_written_if_write(sql)
|
16
16
|
|
17
|
-
log(sql, name) do
|
17
|
+
log(sql, name) do |notification_payload|
|
18
18
|
with_raw_connection do |conn|
|
19
19
|
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
20
20
|
verified!
|
21
|
+
notification_payload[:row_count] = result.count
|
21
22
|
result
|
22
23
|
end
|
23
24
|
end
|
@@ -50,11 +51,12 @@ module ActiveRecord
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
53
|
-
log(sql, name, async: async) do
|
54
|
+
log(sql, name, async: async) do |notification_payload|
|
54
55
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
55
56
|
result = conn.async_exec(sql)
|
56
57
|
verified!
|
57
58
|
handle_warnings(result)
|
59
|
+
notification_payload[:row_count] = result.count
|
58
60
|
result
|
59
61
|
end
|
60
62
|
end
|
@@ -69,7 +71,7 @@ module ActiveRecord
|
|
69
71
|
fmod = result.fmod i
|
70
72
|
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
71
73
|
end
|
72
|
-
build_result(columns: fields, rows: result.values, column_types: types)
|
74
|
+
build_result(columns: fields, rows: result.values, column_types: types.freeze)
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
@@ -122,7 +124,7 @@ module ActiveRecord
|
|
122
124
|
end
|
123
125
|
|
124
126
|
# From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
|
125
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
|
127
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
|
126
128
|
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
127
129
|
|
128
130
|
def high_precision_current_timestamp
|
@@ -135,6 +137,27 @@ module ActiveRecord
|
|
135
137
|
"EXPLAIN (#{options.join(", ").upcase})"
|
136
138
|
end
|
137
139
|
|
140
|
+
# Set when constraints will be checked for the current transaction.
|
141
|
+
#
|
142
|
+
# Not passing any specific constraint names will set the value for all deferrable constraints.
|
143
|
+
#
|
144
|
+
# [<tt>deferred</tt>]
|
145
|
+
# Valid values are +:deferred+ or +:immediate+.
|
146
|
+
#
|
147
|
+
# See https://www.postgresql.org/docs/current/sql-set-constraints.html
|
148
|
+
def set_constraints(deferred, *constraints)
|
149
|
+
unless %i[deferred immediate].include?(deferred)
|
150
|
+
raise ArgumentError, "deferred must be :deferred or :immediate"
|
151
|
+
end
|
152
|
+
|
153
|
+
constraints = if constraints.empty?
|
154
|
+
"ALL"
|
155
|
+
else
|
156
|
+
constraints.map { |c| quote_table_name(c) }.join(", ")
|
157
|
+
end
|
158
|
+
execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
|
159
|
+
end
|
160
|
+
|
138
161
|
private
|
139
162
|
IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
|
140
163
|
private_constant :IDLE_TRANSACTION_STATUSES
|
@@ -28,6 +28,12 @@ module ActiveRecord
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
# TODO: Remove when IPAddr#== compares IPAddr#prefix. See
|
32
|
+
# https://github.com/ruby/ipaddr/issues/21
|
33
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
34
|
+
!old_value.eql?(new_value) || !old_value.nil? && old_value.prefix != new_value.prefix
|
35
|
+
end
|
36
|
+
|
31
37
|
def cast_value(value)
|
32
38
|
if value.nil?
|
33
39
|
nil
|
@@ -33,7 +33,7 @@ module ActiveRecord
|
|
33
33
|
when ::Numeric
|
34
34
|
# Sometimes operations on Times returns just float number of seconds so we need to handle that.
|
35
35
|
# Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
|
36
|
-
value.
|
36
|
+
ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
|
37
37
|
else
|
38
38
|
super
|
39
39
|
end
|