activerecord 7.0.0 → 7.1.0
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 +1607 -1040
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +345 -219
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +136 -148
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +114 -27
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +104 -9
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration.rb +271 -117
- data/lib/active_record/model_schema.rb +82 -50
- data/lib/active_record/nested_attributes.rb +23 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +127 -61
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +142 -143
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +177 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +200 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +429 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +50 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -4,138 +4,57 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module MySQL
|
6
6
|
module DatabaseStatements
|
7
|
-
|
8
|
-
|
9
|
-
result = if ExplainRegistry.collect? && prepared_statements
|
10
|
-
unprepared_statement { super }
|
11
|
-
else
|
12
|
-
super
|
13
|
-
end
|
14
|
-
@connection.abandon_results!
|
15
|
-
result
|
16
|
-
end
|
17
|
-
|
18
|
-
def query(sql, name = nil) # :nodoc:
|
19
|
-
execute(sql, name).to_a
|
20
|
-
end
|
21
|
-
|
22
|
-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
23
|
-
:desc, :describe, :set, :show, :use
|
7
|
+
READ_QUERY = AbstractAdapter.build_read_query_regexp(
|
8
|
+
:desc, :describe, :set, :show, :use, :kill
|
24
9
|
) # :nodoc:
|
25
10
|
private_constant :READ_QUERY
|
26
11
|
|
12
|
+
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
|
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:
|
15
|
+
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
16
|
+
|
27
17
|
def write_query?(sql) # :nodoc:
|
28
18
|
!READ_QUERY.match?(sql)
|
29
19
|
rescue ArgumentError # Invalid encoding
|
30
20
|
!READ_QUERY.match?(sql.b)
|
31
21
|
end
|
32
22
|
|
33
|
-
def
|
34
|
-
|
23
|
+
def high_precision_current_timestamp
|
24
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP
|
25
|
+
end
|
26
|
+
|
27
|
+
def explain(arel, binds = [], options = [])
|
28
|
+
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
35
29
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
36
|
-
result =
|
30
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
37
31
|
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
38
32
|
|
39
33
|
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
40
34
|
end
|
41
35
|
|
42
|
-
|
43
|
-
|
44
|
-
sql = transform_query(sql)
|
45
|
-
check_if_write_query(sql)
|
36
|
+
def build_explain_clause(options = [])
|
37
|
+
return "EXPLAIN" if options.empty?
|
46
38
|
|
47
|
-
|
48
|
-
end
|
39
|
+
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
49
40
|
|
50
|
-
|
51
|
-
|
52
|
-
execute_and_free(sql, name, async: async) do |result|
|
53
|
-
if result
|
54
|
-
build_result(columns: result.fields, rows: result.to_a)
|
55
|
-
else
|
56
|
-
build_result(columns: [], rows: [])
|
57
|
-
end
|
58
|
-
end
|
59
|
-
else
|
60
|
-
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
|
61
|
-
if result
|
62
|
-
build_result(columns: result.fields, rows: result.to_a)
|
63
|
-
else
|
64
|
-
build_result(columns: [], rows: [])
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
71
|
-
if without_prepared_statement?(binds)
|
72
|
-
@lock.synchronize do
|
73
|
-
execute_and_free(sql, name) { @connection.affected_rows }
|
74
|
-
end
|
41
|
+
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
42
|
+
explain_clause.sub("EXPLAIN ", "")
|
75
43
|
else
|
76
|
-
|
44
|
+
explain_clause
|
77
45
|
end
|
78
46
|
end
|
79
|
-
alias :exec_update :exec_delete
|
80
|
-
|
81
|
-
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
|
82
|
-
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
|
83
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
84
|
-
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
85
|
-
|
86
|
-
def high_precision_current_timestamp
|
87
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP
|
88
|
-
end
|
89
47
|
|
90
48
|
private
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
@connection.query_options[:database_timezone] = ActiveRecord.default_timezone
|
95
|
-
|
96
|
-
super
|
97
|
-
end
|
98
|
-
|
99
|
-
def execute_batch(statements, name = nil)
|
100
|
-
statements = statements.map { |sql| transform_query(sql) }
|
101
|
-
combine_multi_statements(statements).each do |statement|
|
102
|
-
raw_execute(statement, name)
|
103
|
-
end
|
104
|
-
@connection.abandon_results!
|
49
|
+
# https://mariadb.com/kb/en/analyze-statement/
|
50
|
+
def analyze_without_explain?
|
51
|
+
mariadb? && database_version >= "10.1.0"
|
105
52
|
end
|
106
53
|
|
107
54
|
def default_insert_value(column)
|
108
55
|
super unless column.auto_increment?
|
109
56
|
end
|
110
57
|
|
111
|
-
def last_inserted_id(result)
|
112
|
-
@connection.last_id
|
113
|
-
end
|
114
|
-
|
115
|
-
def multi_statements_enabled?
|
116
|
-
flags = @config[:flags]
|
117
|
-
|
118
|
-
if flags.is_a?(Array)
|
119
|
-
flags.include?("MULTI_STATEMENTS")
|
120
|
-
else
|
121
|
-
flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def with_multi_statements
|
126
|
-
multi_statements_was = multi_statements_enabled?
|
127
|
-
|
128
|
-
unless multi_statements_was
|
129
|
-
@connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
|
130
|
-
end
|
131
|
-
|
132
|
-
yield
|
133
|
-
ensure
|
134
|
-
unless multi_statements_was
|
135
|
-
@connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
58
|
def combine_multi_statements(total_sql)
|
140
59
|
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
141
60
|
previous_packet = total_sql_chunks.last
|
@@ -162,46 +81,6 @@ module ActiveRecord
|
|
162
81
|
def max_allowed_packet
|
163
82
|
@max_allowed_packet ||= show_variable("max_allowed_packet")
|
164
83
|
end
|
165
|
-
|
166
|
-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
|
167
|
-
sql = transform_query(sql)
|
168
|
-
check_if_write_query(sql)
|
169
|
-
|
170
|
-
materialize_transactions
|
171
|
-
mark_transaction_written_if_write(sql)
|
172
|
-
|
173
|
-
# make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
174
|
-
# made since we established the connection
|
175
|
-
@connection.query_options[:database_timezone] = ActiveRecord.default_timezone
|
176
|
-
|
177
|
-
type_casted_binds = type_casted_binds(binds)
|
178
|
-
|
179
|
-
log(sql, name, binds, type_casted_binds, async: async) do
|
180
|
-
if cache_stmt
|
181
|
-
stmt = @statements[sql] ||= @connection.prepare(sql)
|
182
|
-
else
|
183
|
-
stmt = @connection.prepare(sql)
|
184
|
-
end
|
185
|
-
|
186
|
-
begin
|
187
|
-
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
188
|
-
stmt.execute(*type_casted_binds)
|
189
|
-
end
|
190
|
-
rescue Mysql2::Error => e
|
191
|
-
if cache_stmt
|
192
|
-
@statements.delete(sql)
|
193
|
-
else
|
194
|
-
stmt.close
|
195
|
-
end
|
196
|
-
raise e
|
197
|
-
end
|
198
|
-
|
199
|
-
ret = yield stmt, result
|
200
|
-
result.free if result
|
201
|
-
stmt.close unless cache_stmt
|
202
|
-
ret
|
203
|
-
end
|
204
|
-
end
|
205
84
|
end
|
206
85
|
end
|
207
86
|
end
|
@@ -6,27 +6,35 @@ module ActiveRecord
|
|
6
6
|
module ConnectionAdapters
|
7
7
|
module MySQL
|
8
8
|
module Quoting # :nodoc:
|
9
|
-
|
9
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
10
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
11
|
+
|
12
|
+
def cast_bound_value(value)
|
10
13
|
case value
|
14
|
+
when Rational
|
15
|
+
value.to_f.to_s
|
11
16
|
when Numeric
|
12
|
-
|
17
|
+
value.to_s
|
13
18
|
when BigDecimal
|
14
|
-
|
19
|
+
value.to_s("F")
|
15
20
|
when true
|
16
|
-
"
|
21
|
+
"1"
|
17
22
|
when false
|
18
|
-
"
|
23
|
+
"0"
|
24
|
+
when ActiveSupport::Duration
|
25
|
+
warn_quote_duration_deprecated
|
26
|
+
value.to_s
|
19
27
|
else
|
20
|
-
|
28
|
+
value
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
24
32
|
def quote_column_name(name)
|
25
|
-
|
33
|
+
QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
|
26
34
|
end
|
27
35
|
|
28
36
|
def quote_table_name(name)
|
29
|
-
|
37
|
+
QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
|
30
38
|
end
|
31
39
|
|
32
40
|
def unquoted_true
|
@@ -49,15 +57,23 @@ module ActiveRecord
|
|
49
57
|
"x'#{value.hex}'"
|
50
58
|
end
|
51
59
|
|
60
|
+
def unquote_identifier(identifier)
|
61
|
+
if identifier && identifier.start_with?("`")
|
62
|
+
identifier[1..-2]
|
63
|
+
else
|
64
|
+
identifier
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
52
68
|
# Override +type_cast+ we pass to mysql2 Date and Time objects instead
|
53
|
-
# of Strings since
|
69
|
+
# of Strings since MySQL adapters are able to handle those classes more efficiently.
|
54
70
|
def type_cast(value) # :nodoc:
|
55
71
|
case value
|
56
72
|
when ActiveSupport::TimeWithZone
|
57
73
|
# We need to check explicitly for ActiveSupport::TimeWithZone because
|
58
74
|
# we need to transform it to Time objects but we don't want to
|
59
75
|
# transform Time objects to themselves.
|
60
|
-
if
|
76
|
+
if default_timezone == :utc
|
61
77
|
value.getutc
|
62
78
|
else
|
63
79
|
value.getlocal
|
@@ -82,7 +98,7 @@ module ActiveRecord
|
|
82
98
|
(
|
83
99
|
(?:
|
84
100
|
# `table_name`.`column_name` | function(one or no argument)
|
85
|
-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)
|
101
|
+
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
|
86
102
|
)
|
87
103
|
(?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
|
88
104
|
)
|
@@ -95,8 +111,9 @@ module ActiveRecord
|
|
95
111
|
(
|
96
112
|
(?:
|
97
113
|
# `table_name`.`column_name` | function(one or no argument)
|
98
|
-
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)
|
114
|
+
((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
|
99
115
|
)
|
116
|
+
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
100
117
|
(?:\s+ASC|\s+DESC)?
|
101
118
|
)
|
102
119
|
(?:\s*,\s*\g<1>)*
|
@@ -24,6 +24,15 @@ module ActiveRecord
|
|
24
24
|
add_column_position!(change_column_sql, column_options(o.column))
|
25
25
|
end
|
26
26
|
|
27
|
+
def visit_ChangeColumnDefaultDefinition(o)
|
28
|
+
sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} "
|
29
|
+
if o.default.nil? && !o.column.null
|
30
|
+
sql << "DROP DEFAULT"
|
31
|
+
else
|
32
|
+
sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
27
36
|
def visit_CreateIndexDefinition(o)
|
28
37
|
sql = visit_IndexDefinition(o.index, true)
|
29
38
|
sql << " #{o.algorithm}" if o.algorithm
|
@@ -57,6 +57,7 @@ module ActiveRecord
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
# = Active Record MySQL Adapter \Table Definition
|
60
61
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
61
62
|
include ColumnMethods
|
62
63
|
|
@@ -85,16 +86,24 @@ module ActiveRecord
|
|
85
86
|
end
|
86
87
|
|
87
88
|
private
|
89
|
+
def valid_column_definition_options
|
90
|
+
super + [:auto_increment, :charset, :as, :size, :unsigned, :first, :after, :type, :stored]
|
91
|
+
end
|
92
|
+
|
88
93
|
def aliased_types(name, fallback)
|
89
94
|
fallback
|
90
95
|
end
|
91
96
|
|
92
97
|
def integer_like_primary_key_type(type, options)
|
93
|
-
options[:auto_increment]
|
98
|
+
unless options[:auto_increment] == false
|
99
|
+
options[:auto_increment] = true
|
100
|
+
end
|
101
|
+
|
94
102
|
type
|
95
103
|
end
|
96
104
|
end
|
97
105
|
|
106
|
+
# = Active Record MySQL Adapter \Table
|
98
107
|
class Table < ActiveRecord::ConnectionAdapters::Table
|
99
108
|
include ColumnMethods
|
100
109
|
end
|
@@ -53,14 +53,20 @@ module ActiveRecord
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def schema_precision(column)
|
56
|
-
|
56
|
+
if /\Atime(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
|
57
|
+
nil
|
58
|
+
elsif column.type == :datetime
|
59
|
+
column.precision == 0 ? "nil" : super
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
57
63
|
end
|
58
64
|
|
59
65
|
def schema_collation(column)
|
60
66
|
if column.collation
|
61
67
|
@table_collation_cache ||= {}
|
62
68
|
@table_collation_cache[table_name] ||=
|
63
|
-
@connection.
|
69
|
+
@connection.internal_exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"]
|
64
70
|
column.collation.inspect if column.collation != @table_collation_cache[table_name]
|
65
71
|
end
|
66
72
|
end
|
@@ -36,7 +36,7 @@ module ActiveRecord
|
|
36
36
|
end
|
37
37
|
|
38
38
|
if row[:Expression]
|
39
|
-
expression = row[:Expression]
|
39
|
+
expression = row[:Expression].gsub("\\'", "'")
|
40
40
|
expression = +"(#{expression})" unless expression.start_with?("(")
|
41
41
|
indexes.last[-2] << expression
|
42
42
|
indexes.last[-1][:expressions] ||= {}
|
@@ -57,9 +57,9 @@ module ActiveRecord
|
|
57
57
|
orders = options.delete(:orders)
|
58
58
|
lengths = options.delete(:lengths)
|
59
59
|
|
60
|
-
columns = index[-1].
|
60
|
+
columns = index[-1].to_h { |name|
|
61
61
|
[ name.to_sym, expressions[name] || +quote_column_name(name) ]
|
62
|
-
}
|
62
|
+
}
|
63
63
|
|
64
64
|
index[-1] = add_options_for_index_columns(
|
65
65
|
columns, order: orders, length: lengths
|
@@ -125,6 +125,10 @@ module ActiveRecord
|
|
125
125
|
256 # https://dev.mysql.com/doc/refman/en/identifiers.html
|
126
126
|
end
|
127
127
|
|
128
|
+
def schema_creation # :nodoc:
|
129
|
+
MySQL::SchemaCreation.new(self)
|
130
|
+
end
|
131
|
+
|
128
132
|
private
|
129
133
|
CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
|
130
134
|
|
@@ -150,26 +154,45 @@ module ActiveRecord
|
|
150
154
|
@default_row_format
|
151
155
|
end
|
152
156
|
|
153
|
-
def
|
154
|
-
|
157
|
+
def valid_primary_key_options
|
158
|
+
super + [:unsigned]
|
155
159
|
end
|
156
160
|
|
157
161
|
def create_table_definition(name, **options)
|
158
162
|
MySQL::TableDefinition.new(self, name, **options)
|
159
163
|
end
|
160
164
|
|
161
|
-
def
|
165
|
+
def default_type(table_name, field_name)
|
166
|
+
match = create_table_info(table_name)&.match(/`#{field_name}` (.+) DEFAULT ('|\d+|[A-z]+)/)
|
167
|
+
default_pre = match[2] if match
|
168
|
+
|
169
|
+
if default_pre == "'"
|
170
|
+
:string
|
171
|
+
elsif default_pre&.match?(/^\d+$/)
|
172
|
+
:integer
|
173
|
+
elsif default_pre&.match?(/^[A-z]+$/)
|
174
|
+
:function
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def new_column_from_field(table_name, field, _definitions)
|
179
|
+
field_name = field.fetch(:Field)
|
162
180
|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
|
163
181
|
default, default_function = field[:Default], nil
|
164
182
|
|
165
183
|
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[:Extra])
|
166
185
|
default, default_function = nil, default
|
167
186
|
elsif type_metadata.extra == "DEFAULT_GENERATED"
|
168
187
|
default = +"(#{default})" unless default.start_with?("(")
|
169
188
|
default, default_function = nil, default
|
170
|
-
elsif type_metadata.type == :text && default
|
189
|
+
elsif type_metadata.type == :text && default&.start_with?("'")
|
171
190
|
# strip and unescape quotes
|
172
191
|
default = default[1...-1].gsub("\\'", "'")
|
192
|
+
elsif default&.match?(/\A\d/)
|
193
|
+
# Its a number so we can skip the query to check if it is a function
|
194
|
+
elsif default && default_type(table_name, field_name) == :function
|
195
|
+
default, default_function = nil, default
|
173
196
|
end
|
174
197
|
|
175
198
|
MySQL::Column.new(
|
@@ -206,14 +229,15 @@ module ActiveRecord
|
|
206
229
|
def data_source_sql(name = nil, type: nil)
|
207
230
|
scope = quoted_scope(name, type: type)
|
208
231
|
|
209
|
-
sql = +"SELECT table_name FROM
|
210
|
-
sql << " WHERE table_schema = #{scope[:schema]}
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
sql << " WHERE #{conditions.join(" AND ")}"
|
232
|
+
sql = +"SELECT table_name FROM information_schema.tables"
|
233
|
+
sql << " WHERE table_schema = #{scope[:schema]}"
|
234
|
+
|
235
|
+
if scope[:name]
|
236
|
+
sql << " AND table_name = #{scope[:name]}"
|
237
|
+
sql << " AND table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema = #{scope[:schema]})"
|
216
238
|
end
|
239
|
+
|
240
|
+
sql << " AND table_type = #{scope[:type]}" if scope[:type]
|
217
241
|
sql
|
218
242
|
end
|
219
243
|
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Mysql2
|
6
|
+
module DatabaseStatements
|
7
|
+
# Returns an ActiveRecord::Result instance.
|
8
|
+
def select_all(*, **) # :nodoc:
|
9
|
+
result = nil
|
10
|
+
with_raw_connection do |conn|
|
11
|
+
result = if ExplainRegistry.collect? && prepared_statements
|
12
|
+
unprepared_statement { super }
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
conn.abandon_results!
|
17
|
+
end
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
22
|
+
if without_prepared_statement?(binds)
|
23
|
+
execute_and_free(sql, name, async: async) do |result|
|
24
|
+
if result
|
25
|
+
build_result(columns: result.fields, rows: result.to_a)
|
26
|
+
else
|
27
|
+
build_result(columns: [], rows: [])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
|
32
|
+
if result
|
33
|
+
build_result(columns: result.fields, rows: result.to_a)
|
34
|
+
else
|
35
|
+
build_result(columns: [], rows: [])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
42
|
+
if without_prepared_statement?(binds)
|
43
|
+
with_raw_connection do |conn|
|
44
|
+
@affected_rows_before_warnings = nil
|
45
|
+
execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
|
46
|
+
end
|
47
|
+
else
|
48
|
+
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
alias :exec_update :exec_delete
|
52
|
+
|
53
|
+
private
|
54
|
+
def sync_timezone_changes(raw_connection)
|
55
|
+
raw_connection.query_options[:database_timezone] = default_timezone
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_batch(statements, name = nil)
|
59
|
+
statements = statements.map { |sql| transform_query(sql) }
|
60
|
+
combine_multi_statements(statements).each do |statement|
|
61
|
+
with_raw_connection do |conn|
|
62
|
+
raw_execute(statement, name)
|
63
|
+
conn.abandon_results!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def last_inserted_id(result)
|
69
|
+
@raw_connection&.last_id
|
70
|
+
end
|
71
|
+
|
72
|
+
def multi_statements_enabled?
|
73
|
+
flags = @config[:flags]
|
74
|
+
|
75
|
+
if flags.is_a?(Array)
|
76
|
+
flags.include?("MULTI_STATEMENTS")
|
77
|
+
else
|
78
|
+
flags.anybits?(::Mysql2::Client::MULTI_STATEMENTS)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def with_multi_statements
|
83
|
+
if multi_statements_enabled?
|
84
|
+
return yield
|
85
|
+
end
|
86
|
+
|
87
|
+
with_raw_connection do |conn|
|
88
|
+
conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
|
89
|
+
|
90
|
+
yield
|
91
|
+
ensure
|
92
|
+
conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
97
|
+
log(sql, name, async: async) do
|
98
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
99
|
+
sync_timezone_changes(conn)
|
100
|
+
result = conn.query(sql)
|
101
|
+
handle_warnings(sql)
|
102
|
+
result
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
|
108
|
+
sql = transform_query(sql)
|
109
|
+
check_if_write_query(sql)
|
110
|
+
|
111
|
+
mark_transaction_written_if_write(sql)
|
112
|
+
|
113
|
+
type_casted_binds = type_casted_binds(binds)
|
114
|
+
|
115
|
+
log(sql, name, binds, type_casted_binds, async: async) do
|
116
|
+
with_raw_connection do |conn|
|
117
|
+
sync_timezone_changes(conn)
|
118
|
+
|
119
|
+
if cache_stmt
|
120
|
+
stmt = @statements[sql] ||= conn.prepare(sql)
|
121
|
+
else
|
122
|
+
stmt = conn.prepare(sql)
|
123
|
+
end
|
124
|
+
|
125
|
+
begin
|
126
|
+
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
127
|
+
stmt.execute(*type_casted_binds)
|
128
|
+
end
|
129
|
+
rescue ::Mysql2::Error => e
|
130
|
+
if cache_stmt
|
131
|
+
@statements.delete(sql)
|
132
|
+
else
|
133
|
+
stmt.close
|
134
|
+
end
|
135
|
+
raise e
|
136
|
+
end
|
137
|
+
|
138
|
+
ret = yield stmt, result
|
139
|
+
result.free if result
|
140
|
+
stmt.close unless cache_stmt
|
141
|
+
ret
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|