activerecord 7.0.6 → 7.1.0.beta1
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 +1356 -1425
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -16
- 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 +18 -10
- data/lib/active_record/associations/collection_proxy.rb +21 -11
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -7
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader.rb +12 -9
- data/lib/active_record/associations/singular_association.rb +6 -8
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +193 -97
- 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 +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +60 -18
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- 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 +109 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- 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 +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
- data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +496 -102
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -113
- 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 +21 -14
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
- 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 +1 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +14 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- 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 +42 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
- 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 +42 -36
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +163 -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 +71 -94
- data/lib/active_record/core.rb +128 -138
- data/lib/active_record/counter_cache.rb +46 -25
- 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 +86 -33
- data/lib/active_record/delegated_type.rb +8 -3
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- 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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -26
- data/lib/active_record/errors.rb +89 -15
- data/lib/active_record/explain.rb +23 -3
- 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 +119 -71
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +5 -7
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +100 -4
- data/lib/active_record/migration/compatibility.rb +142 -58
- 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 +265 -112
- data/lib/active_record/model_schema.rb +47 -27
- data/lib/active_record/nested_attributes.rb +28 -3
- data/lib/active_record/normalization.rb +158 -0
- data/lib/active_record/persistence.rb +186 -34
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +139 -145
- 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 +169 -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 +152 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +85 -15
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +27 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +377 -69
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +41 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- 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/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +11 -2
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- 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/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +4 -0
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- 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 +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- 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/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 +52 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "active_record/connection_adapters/abstract_adapter"
|
|
4
4
|
require "active_record/connection_adapters/statement_pool"
|
|
5
5
|
require "active_record/connection_adapters/mysql/column"
|
|
6
|
+
require "active_record/connection_adapters/mysql/database_statements"
|
|
6
7
|
require "active_record/connection_adapters/mysql/explain_pretty_printer"
|
|
7
8
|
require "active_record/connection_adapters/mysql/quoting"
|
|
8
9
|
require "active_record/connection_adapters/mysql/schema_creation"
|
|
@@ -14,6 +15,7 @@ require "active_record/connection_adapters/mysql/type_metadata"
|
|
|
14
15
|
module ActiveRecord
|
|
15
16
|
module ConnectionAdapters
|
|
16
17
|
class AbstractMysqlAdapter < AbstractAdapter
|
|
18
|
+
include MySQL::DatabaseStatements
|
|
17
19
|
include MySQL::Quoting
|
|
18
20
|
include MySQL::SchemaStatements
|
|
19
21
|
|
|
@@ -51,8 +53,34 @@ module ActiveRecord
|
|
|
51
53
|
end
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
class << self
|
|
57
|
+
def dbconsole(config, options = {})
|
|
58
|
+
mysql_config = config.configuration_hash
|
|
59
|
+
|
|
60
|
+
args = {
|
|
61
|
+
host: "--host",
|
|
62
|
+
port: "--port",
|
|
63
|
+
socket: "--socket",
|
|
64
|
+
username: "--user",
|
|
65
|
+
encoding: "--default-character-set",
|
|
66
|
+
sslca: "--ssl-ca",
|
|
67
|
+
sslcert: "--ssl-cert",
|
|
68
|
+
sslcapath: "--ssl-capath",
|
|
69
|
+
sslcipher: "--ssl-cipher",
|
|
70
|
+
sslkey: "--ssl-key",
|
|
71
|
+
ssl_mode: "--ssl-mode"
|
|
72
|
+
}.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
|
|
73
|
+
|
|
74
|
+
if mysql_config[:password] && options[:include_password]
|
|
75
|
+
args << "--password=#{mysql_config[:password]}"
|
|
76
|
+
elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
|
|
77
|
+
args << "-p"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
args << config.database
|
|
81
|
+
|
|
82
|
+
find_cmd_and_exec(["mysql", "mysql5"], *args)
|
|
83
|
+
end
|
|
56
84
|
end
|
|
57
85
|
|
|
58
86
|
def get_database_version # :nodoc:
|
|
@@ -81,6 +109,10 @@ module ActiveRecord
|
|
|
81
109
|
true
|
|
82
110
|
end
|
|
83
111
|
|
|
112
|
+
def supports_restart_db_transaction?
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
|
|
84
116
|
def supports_explain?
|
|
85
117
|
true
|
|
86
118
|
end
|
|
@@ -95,7 +127,7 @@ module ActiveRecord
|
|
|
95
127
|
|
|
96
128
|
def supports_check_constraints?
|
|
97
129
|
if mariadb?
|
|
98
|
-
database_version >= "10.2.
|
|
130
|
+
database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
|
|
99
131
|
else
|
|
100
132
|
database_version >= "8.0.16"
|
|
101
133
|
end
|
|
@@ -138,11 +170,6 @@ module ActiveRecord
|
|
|
138
170
|
true
|
|
139
171
|
end
|
|
140
172
|
|
|
141
|
-
def field_ordered_value(column, values) # :nodoc:
|
|
142
|
-
field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse.map { |value| Arel::Nodes.build_quoted(value) }])
|
|
143
|
-
Arel::Nodes::Descending.new(field)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
173
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
|
147
174
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
|
148
175
|
end
|
|
@@ -195,33 +222,36 @@ module ActiveRecord
|
|
|
195
222
|
# DATABASE STATEMENTS ======================================
|
|
196
223
|
#++
|
|
197
224
|
|
|
198
|
-
# Executes the SQL statement in the context of this connection.
|
|
199
|
-
def execute(sql, name = nil, async: false)
|
|
200
|
-
raw_execute(sql, name, async: async)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
225
|
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
|
204
226
|
# to write stuff in an abstract way without concerning ourselves about whether it
|
|
205
227
|
# needs to be explicitly freed or not.
|
|
206
228
|
def execute_and_free(sql, name = nil, async: false) # :nodoc:
|
|
207
|
-
|
|
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)
|
|
208
234
|
end
|
|
209
235
|
|
|
210
236
|
def begin_db_transaction # :nodoc:
|
|
211
|
-
|
|
237
|
+
internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
|
212
238
|
end
|
|
213
239
|
|
|
214
240
|
def begin_isolated_db_transaction(isolation) # :nodoc:
|
|
215
|
-
|
|
241
|
+
internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
|
216
242
|
begin_db_transaction
|
|
217
243
|
end
|
|
218
244
|
|
|
219
245
|
def commit_db_transaction # :nodoc:
|
|
220
|
-
|
|
246
|
+
internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
|
221
247
|
end
|
|
222
248
|
|
|
223
249
|
def exec_rollback_db_transaction # :nodoc:
|
|
224
|
-
|
|
250
|
+
internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def exec_restart_db_transaction # :nodoc:
|
|
254
|
+
internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
|
225
255
|
end
|
|
226
256
|
|
|
227
257
|
def empty_insert_statement_value(primary_key = nil) # :nodoc:
|
|
@@ -301,7 +331,8 @@ module ActiveRecord
|
|
|
301
331
|
#
|
|
302
332
|
# Example:
|
|
303
333
|
# rename_table('octopuses', 'octopi')
|
|
304
|
-
def rename_table(table_name, new_name)
|
|
334
|
+
def rename_table(table_name, new_name, **options)
|
|
335
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
|
305
336
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
306
337
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
|
307
338
|
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
|
@@ -339,11 +370,20 @@ module ActiveRecord
|
|
|
339
370
|
end
|
|
340
371
|
|
|
341
372
|
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
|
373
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
|
377
|
+
column = column_for(table_name, column_name)
|
|
378
|
+
return unless column
|
|
379
|
+
|
|
342
380
|
default = extract_new_default_value(default_or_changes)
|
|
343
|
-
|
|
381
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
|
344
382
|
end
|
|
345
383
|
|
|
346
384
|
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
|
385
|
+
validate_change_column_null_argument!(null)
|
|
386
|
+
|
|
347
387
|
unless null || default.nil?
|
|
348
388
|
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
349
389
|
end
|
|
@@ -360,18 +400,60 @@ module ActiveRecord
|
|
|
360
400
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
|
|
361
401
|
end
|
|
362
402
|
|
|
403
|
+
# Builds a ChangeColumnDefinition object.
|
|
404
|
+
#
|
|
405
|
+
# This definition object contains information about the column change that would occur
|
|
406
|
+
# if the same arguments were passed to #change_column. See #change_column for information about
|
|
407
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
|
408
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
|
409
|
+
column = column_for(table_name, column_name)
|
|
410
|
+
type ||= column.sql_type
|
|
411
|
+
|
|
412
|
+
unless options.key?(:default)
|
|
413
|
+
options[:default] = column.default
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
unless options.key?(:null)
|
|
417
|
+
options[:null] = column.null
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
unless options.key?(:comment)
|
|
421
|
+
options[:comment] = column.comment
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
if options[:collation] == :no_collation
|
|
425
|
+
options.delete(:collation)
|
|
426
|
+
else
|
|
427
|
+
options[:collation] ||= column.collation if text_type?(type)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
unless options.key?(:auto_increment)
|
|
431
|
+
options[:auto_increment] = column.auto_increment?
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
td = create_table_definition(table_name)
|
|
435
|
+
cd = td.new_column_definition(column.name, type, **options)
|
|
436
|
+
ChangeColumnDefinition.new(cd, column.name)
|
|
437
|
+
end
|
|
438
|
+
|
|
363
439
|
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
|
364
440
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
|
|
365
441
|
rename_column_indexes(table_name, column_name, new_column_name)
|
|
366
442
|
end
|
|
367
443
|
|
|
368
444
|
def add_index(table_name, column_name, **options) # :nodoc:
|
|
445
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
|
446
|
+
return unless create_index
|
|
447
|
+
|
|
448
|
+
execute schema_creation.accept(create_index)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
|
369
452
|
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
|
370
453
|
|
|
371
454
|
return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
|
|
372
455
|
|
|
373
|
-
|
|
374
|
-
execute schema_creation.accept(create_index)
|
|
456
|
+
CreateIndexDefinition.new(index, algorithm)
|
|
375
457
|
end
|
|
376
458
|
|
|
377
459
|
def add_sql_comment!(sql, comment) # :nodoc:
|
|
@@ -384,11 +466,13 @@ module ActiveRecord
|
|
|
384
466
|
|
|
385
467
|
scope = quoted_scope(table_name)
|
|
386
468
|
|
|
387
|
-
|
|
469
|
+
# MySQL returns 1 row for each column of composite foreign keys.
|
|
470
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA")
|
|
388
471
|
SELECT fk.referenced_table_name AS 'to_table',
|
|
389
472
|
fk.referenced_column_name AS 'primary_key',
|
|
390
473
|
fk.column_name AS 'column',
|
|
391
474
|
fk.constraint_name AS 'name',
|
|
475
|
+
fk.ordinal_position AS 'position',
|
|
392
476
|
rc.update_rule AS 'on_update',
|
|
393
477
|
rc.delete_rule AS 'on_delete'
|
|
394
478
|
FROM information_schema.referential_constraints rc
|
|
@@ -401,15 +485,22 @@ module ActiveRecord
|
|
|
401
485
|
AND rc.table_name = #{scope[:name]}
|
|
402
486
|
SQL
|
|
403
487
|
|
|
404
|
-
fk_info.
|
|
488
|
+
grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
|
|
489
|
+
grouped_fk.map do |group|
|
|
490
|
+
row = group.first
|
|
405
491
|
options = {
|
|
406
|
-
column: unquote_identifier(row["column"]),
|
|
407
492
|
name: row["name"],
|
|
408
|
-
|
|
493
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
|
494
|
+
on_delete: extract_foreign_key_action(row["on_delete"])
|
|
409
495
|
}
|
|
410
496
|
|
|
411
|
-
|
|
412
|
-
|
|
497
|
+
if group.one?
|
|
498
|
+
options[:column] = unquote_identifier(row["column"])
|
|
499
|
+
options[:primary_key] = row["primary_key"]
|
|
500
|
+
else
|
|
501
|
+
options[:column] = group.map { |row| unquote_identifier(row["column"]) }
|
|
502
|
+
options[:primary_key] = group.map { |row| row["primary_key"] }
|
|
503
|
+
end
|
|
413
504
|
|
|
414
505
|
ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
|
|
415
506
|
end
|
|
@@ -431,14 +522,15 @@ module ActiveRecord
|
|
|
431
522
|
SQL
|
|
432
523
|
sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
|
|
433
524
|
|
|
434
|
-
chk_info =
|
|
525
|
+
chk_info = internal_exec_query(sql, "SCHEMA")
|
|
435
526
|
|
|
436
527
|
chk_info.map do |row|
|
|
437
528
|
options = {
|
|
438
529
|
name: row["name"]
|
|
439
530
|
}
|
|
440
531
|
expression = row["expression"]
|
|
441
|
-
expression = expression[1..-2]
|
|
532
|
+
expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
|
|
533
|
+
expression = strip_whitespace_characters(expression)
|
|
442
534
|
CheckConstraintDefinition.new(table_name, expression, options)
|
|
443
535
|
end
|
|
444
536
|
else
|
|
@@ -561,15 +653,18 @@ module ActiveRecord
|
|
|
561
653
|
end
|
|
562
654
|
|
|
563
655
|
class << self
|
|
656
|
+
def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
|
|
657
|
+
super(default_timezone: default_timezone).tap do |m|
|
|
658
|
+
if emulate_booleans
|
|
659
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
564
664
|
private
|
|
565
665
|
def initialize_type_map(m)
|
|
566
666
|
super
|
|
567
667
|
|
|
568
|
-
m.register_type(%r(char)i) do |sql_type|
|
|
569
|
-
limit = extract_limit(sql_type)
|
|
570
|
-
Type.lookup(:string, adapter: :mysql2, limit: limit)
|
|
571
|
-
end
|
|
572
|
-
|
|
573
668
|
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
|
|
574
669
|
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
|
|
575
670
|
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
|
|
@@ -589,9 +684,6 @@ module ActiveRecord
|
|
|
589
684
|
|
|
590
685
|
m.alias_type %r(year)i, "integer"
|
|
591
686
|
m.alias_type %r(bit)i, "binary"
|
|
592
|
-
|
|
593
|
-
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
|
|
594
|
-
m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
|
|
595
687
|
end
|
|
596
688
|
|
|
597
689
|
def register_integer_type(mapping, key, **options)
|
|
@@ -613,31 +705,46 @@ module ActiveRecord
|
|
|
613
705
|
end
|
|
614
706
|
end
|
|
615
707
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
|
|
619
|
-
end
|
|
708
|
+
EXTENDED_TYPE_MAPS = Concurrent::Map.new
|
|
709
|
+
EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
|
|
620
710
|
|
|
621
711
|
private
|
|
622
|
-
def
|
|
623
|
-
|
|
712
|
+
def strip_whitespace_characters(expression)
|
|
713
|
+
expression = expression.gsub(/\\n|\\\\/, "")
|
|
714
|
+
expression = expression.gsub(/\s{2,}/, " ")
|
|
715
|
+
expression
|
|
624
716
|
end
|
|
625
717
|
|
|
626
|
-
def
|
|
627
|
-
|
|
718
|
+
def extended_type_map_key
|
|
719
|
+
if @default_timezone
|
|
720
|
+
{ default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
|
|
721
|
+
elsif emulate_booleans
|
|
722
|
+
EMULATE_BOOLEANS_TRUE
|
|
723
|
+
end
|
|
628
724
|
end
|
|
629
725
|
|
|
630
|
-
def
|
|
631
|
-
|
|
632
|
-
mark_transaction_written_if_write(sql)
|
|
726
|
+
def handle_warnings(sql)
|
|
727
|
+
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
|
633
728
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
729
|
+
@affected_rows_before_warnings = @raw_connection.affected_rows
|
|
730
|
+
result = @raw_connection.query("SHOW WARNINGS")
|
|
731
|
+
result.each do |level, code, message|
|
|
732
|
+
warning = SQLWarning.new(message, code, level, sql, @pool)
|
|
733
|
+
next if warning_ignored?(warning)
|
|
734
|
+
|
|
735
|
+
ActiveRecord.db_warnings_action.call(warning)
|
|
638
736
|
end
|
|
639
737
|
end
|
|
640
738
|
|
|
739
|
+
def warning_ignored?(warning)
|
|
740
|
+
warning.level == "Note" || super
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
|
744
|
+
# made since we established the connection
|
|
745
|
+
def sync_timezone_changes(raw_connection)
|
|
746
|
+
end
|
|
747
|
+
|
|
641
748
|
# See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
|
|
642
749
|
ER_DB_CREATE_EXISTS = 1007
|
|
643
750
|
ER_FILSORT_ABORT = 1028
|
|
@@ -655,77 +762,59 @@ module ActiveRecord
|
|
|
655
762
|
ER_CANNOT_CREATE_TABLE = 1005
|
|
656
763
|
ER_LOCK_WAIT_TIMEOUT = 1205
|
|
657
764
|
ER_QUERY_INTERRUPTED = 1317
|
|
765
|
+
ER_CONNECTION_KILLED = 1927
|
|
766
|
+
CR_SERVER_GONE_ERROR = 2006
|
|
767
|
+
CR_SERVER_LOST = 2013
|
|
658
768
|
ER_QUERY_TIMEOUT = 3024
|
|
659
769
|
ER_FK_INCOMPATIBLE_COLUMNS = 3780
|
|
770
|
+
ER_CLIENT_INTERACTION_TIMEOUT = 4031
|
|
660
771
|
|
|
661
772
|
def translate_exception(exception, message:, sql:, binds:)
|
|
662
773
|
case error_number(exception)
|
|
663
774
|
when nil
|
|
664
775
|
if exception.message.match?(/MySQL client is not connected/i)
|
|
665
|
-
ConnectionNotEstablished.new(exception)
|
|
776
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
666
777
|
else
|
|
667
778
|
super
|
|
668
779
|
end
|
|
780
|
+
when ER_CONNECTION_KILLED, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
|
|
781
|
+
ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
669
782
|
when ER_DB_CREATE_EXISTS
|
|
670
|
-
DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
|
|
783
|
+
DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
671
784
|
when ER_DUP_ENTRY
|
|
672
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
785
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
673
786
|
when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
|
|
674
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
787
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
675
788
|
when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
|
|
676
|
-
mismatched_foreign_key(message, sql: sql, binds: binds)
|
|
789
|
+
mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
677
790
|
when ER_CANNOT_CREATE_TABLE
|
|
678
791
|
if message.include?("errno: 150")
|
|
679
|
-
mismatched_foreign_key(message, sql: sql, binds: binds)
|
|
792
|
+
mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
680
793
|
else
|
|
681
794
|
super
|
|
682
795
|
end
|
|
683
796
|
when ER_DATA_TOO_LONG
|
|
684
|
-
ValueTooLong.new(message, sql: sql, binds: binds)
|
|
797
|
+
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
685
798
|
when ER_OUT_OF_RANGE
|
|
686
|
-
RangeError.new(message, sql: sql, binds: binds)
|
|
799
|
+
RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
687
800
|
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
|
|
688
|
-
NotNullViolation.new(message, sql: sql, binds: binds)
|
|
801
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
689
802
|
when ER_LOCK_DEADLOCK
|
|
690
|
-
Deadlocked.new(message, sql: sql, binds: binds)
|
|
803
|
+
Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
691
804
|
when ER_LOCK_WAIT_TIMEOUT
|
|
692
|
-
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
|
805
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
693
806
|
when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
|
|
694
|
-
StatementTimeout.new(message, sql: sql, binds: binds)
|
|
807
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
695
808
|
when ER_QUERY_INTERRUPTED
|
|
696
|
-
QueryCanceled.new(message, sql: sql, binds: binds)
|
|
809
|
+
QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
697
810
|
else
|
|
698
811
|
super
|
|
699
812
|
end
|
|
700
813
|
end
|
|
701
814
|
|
|
702
815
|
def change_column_for_alter(table_name, column_name, type, **options)
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
unless options.key?(:default)
|
|
707
|
-
options[:default] = column.default
|
|
708
|
-
end
|
|
709
|
-
|
|
710
|
-
unless options.key?(:null)
|
|
711
|
-
options[:null] = column.null
|
|
712
|
-
end
|
|
713
|
-
|
|
714
|
-
unless options.key?(:comment)
|
|
715
|
-
options[:comment] = column.comment
|
|
716
|
-
end
|
|
717
|
-
|
|
718
|
-
unless options.key?(:collation)
|
|
719
|
-
options[:collation] = column.collation if text_type?(type)
|
|
720
|
-
end
|
|
721
|
-
|
|
722
|
-
unless options.key?(:auto_increment)
|
|
723
|
-
options[:auto_increment] = column.auto_increment?
|
|
724
|
-
end
|
|
725
|
-
|
|
726
|
-
td = create_table_definition(table_name)
|
|
727
|
-
cd = td.new_column_definition(column.name, type, **options)
|
|
728
|
-
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
|
816
|
+
cd = build_change_column_definition(table_name, column_name, type, **options)
|
|
817
|
+
schema_creation.accept(cd)
|
|
729
818
|
end
|
|
730
819
|
|
|
731
820
|
def rename_column_for_alter(table_name, column_name, new_column_name)
|
|
@@ -739,7 +828,7 @@ module ActiveRecord
|
|
|
739
828
|
comment: column.comment
|
|
740
829
|
}
|
|
741
830
|
|
|
742
|
-
current_type =
|
|
831
|
+
current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
|
|
743
832
|
td = create_table_definition(table_name)
|
|
744
833
|
cd = td.new_column_definition(new_column_name, current_type, **options)
|
|
745
834
|
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
|
@@ -776,9 +865,6 @@ module ActiveRecord
|
|
|
776
865
|
def configure_connection
|
|
777
866
|
variables = @config.fetch(:variables, {}).stringify_keys
|
|
778
867
|
|
|
779
|
-
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
|
780
|
-
variables["sql_auto_is_null"] = 0
|
|
781
|
-
|
|
782
868
|
# Increase timeout so the server doesn't disconnect us.
|
|
783
869
|
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
|
|
784
870
|
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
|
@@ -822,7 +908,7 @@ module ActiveRecord
|
|
|
822
908
|
end.join(", ")
|
|
823
909
|
|
|
824
910
|
# ...and send them all in one query
|
|
825
|
-
|
|
911
|
+
internal_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}")
|
|
826
912
|
end
|
|
827
913
|
|
|
828
914
|
def column_definitions(table_name) # :nodoc:
|
|
@@ -832,7 +918,7 @@ module ActiveRecord
|
|
|
832
918
|
end
|
|
833
919
|
|
|
834
920
|
def create_table_info(table_name) # :nodoc:
|
|
835
|
-
|
|
921
|
+
internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
|
|
836
922
|
end
|
|
837
923
|
|
|
838
924
|
def arel_visitor
|
|
@@ -843,18 +929,17 @@ module ActiveRecord
|
|
|
843
929
|
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
|
844
930
|
end
|
|
845
931
|
|
|
846
|
-
def
|
|
932
|
+
def mismatched_foreign_key_details(message:, sql:)
|
|
933
|
+
foreign_key_pat =
|
|
934
|
+
/Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
|
|
935
|
+
|
|
847
936
|
match = %r/
|
|
848
937
|
(?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
|
|
849
|
-
FOREIGN\s+KEY\s*\(`?(?<foreign_key
|
|
938
|
+
FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
|
|
850
939
|
REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
|
|
851
940
|
/xmi.match(sql)
|
|
852
941
|
|
|
853
|
-
options = {
|
|
854
|
-
message: message,
|
|
855
|
-
sql: sql,
|
|
856
|
-
binds: binds,
|
|
857
|
-
}
|
|
942
|
+
options = {}
|
|
858
943
|
|
|
859
944
|
if match
|
|
860
945
|
options[:table] = match[:table]
|
|
@@ -864,20 +949,29 @@ module ActiveRecord
|
|
|
864
949
|
options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
|
|
865
950
|
end
|
|
866
951
|
|
|
952
|
+
options
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
|
|
956
|
+
options = {
|
|
957
|
+
message: message,
|
|
958
|
+
sql: sql,
|
|
959
|
+
binds: binds,
|
|
960
|
+
connection_pool: connection_pool
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if sql
|
|
964
|
+
options.update mismatched_foreign_key_details(message: message, sql: sql)
|
|
965
|
+
else
|
|
966
|
+
options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
|
|
967
|
+
end
|
|
968
|
+
|
|
867
969
|
MismatchedForeignKey.new(**options)
|
|
868
970
|
end
|
|
869
971
|
|
|
870
972
|
def version_string(full_version_string)
|
|
871
973
|
full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
|
|
872
974
|
end
|
|
873
|
-
|
|
874
|
-
ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
|
|
875
|
-
Type::ImmutableString.new(true: "1", false: "0", **args)
|
|
876
|
-
end
|
|
877
|
-
ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
|
|
878
|
-
Type::String.new(true: "1", false: "0", **args)
|
|
879
|
-
end
|
|
880
|
-
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
|
|
881
975
|
end
|
|
882
976
|
end
|
|
883
977
|
end
|
|
@@ -63,6 +63,15 @@ module ActiveRecord
|
|
|
63
63
|
coder["comment"] = @comment
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
# whether the column is auto-populated by the database using a sequence
|
|
67
|
+
def auto_incremented_by_db?
|
|
68
|
+
false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def auto_populated?
|
|
72
|
+
auto_incremented_by_db? || default_function
|
|
73
|
+
end
|
|
74
|
+
|
|
66
75
|
def ==(other)
|
|
67
76
|
other.is_a?(Column) &&
|
|
68
77
|
name == other.name &&
|