diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
13 files changed, 153 insertions, 36 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index adb5ae82a0..9b3f5260f7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -999,15 +999,30 @@ module ActiveRecord end end + attr_reader :prevent_writes + def initialize # These caches are keyed by spec.name (ConnectionSpecification#name). @owner_to_pool = ConnectionHandler.create_owner_to_pool + @prevent_writes = false # Backup finalizer: if the forked child never needed a pool, the above # early discard has not occurred ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool) end + # Prevent writing to the database regardless of role. + # + # In some cases you may want to prevent writes to the database + # even if you are on a database that can write. `while_preventing_writes` + # will prevent writes to the database for the duration of the block. + def while_preventing_writes + original, @prevent_writes = @prevent_writes, true + yield + ensure + @prevent_writes = original + end + def connection_pool_list owner_to_pool.values.compact end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 1b6ba8ce97..93273f6cf6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -114,16 +114,16 @@ module ActiveRecord # if the value is a Time responding to usec. def quoted_date(value) if value.acts_like?(:time) - zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal - - if value.respond_to?(zone_conversion_method) - value = value.send(zone_conversion_method) + if ActiveRecord::Base.default_timezone == :utc + value = value.getutc if value.respond_to?(:getutc) && !value.utc? + else + value = value.getlocal if value.respond_to?(:getlocal) end end result = value.to_s(:db) if value.respond_to?(:usec) && value.usec > 0 - "#{result}.#{sprintf("%06d", value.usec)}" + result << "." << sprintf("%06d", value.usec) else result end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index c85ea8e1da..79dc98607a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1064,8 +1064,8 @@ module ActiveRecord options end - def dump_schema_information #:nodoc: - versions = ActiveRecord::SchemaMigration.all_versions + def dump_schema_information # :nodoc: + versions = schema_migration.all_versions insert_versions_sql(versions) if versions.any? end @@ -1081,7 +1081,7 @@ module ActiveRecord end version = version.to_i - sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) + sm_table = quote_table_name(schema_migration.table_name) migrated = migration_context.get_all_versions versions = migration_context.migrations.map(&:version) @@ -1454,7 +1454,7 @@ module ActiveRecord end def insert_versions_sql(versions) - sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) + sm_table = quote_table_name(schema_migration.table_name) if versions.is_a?(Array) sql = +"INSERT INTO #{sm_table} (version) VALUES\n" diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cfd986ebf9..c0ead17b3b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -78,7 +78,7 @@ module ActiveRecord SIMPLE_INT = /\A\d+\z/ attr_accessor :pool - attr_reader :visitor, :owner, :logger, :lock, :prepared_statements, :prevent_writes + attr_reader :visitor, :owner, :logger, :lock, :prepared_statements alias :in_use? :owner set_callback :checkin, :after, :enable_lazy_transactions! @@ -117,7 +117,6 @@ module ActiveRecord @pool = ActiveRecord::ConnectionAdapters::NullPool.new @idle_since = Concurrent.monotonic_time @quoted_column_names, @quoted_table_names = {}, {} - @prevent_writes = false @visitor = arel_visitor @statements = build_statement_pool @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new @@ -143,19 +142,7 @@ module ActiveRecord # Returns true if the connection is a replica, or if +prevent_writes+ # is set to true. def preventing_writes? - replica? || prevent_writes - end - - # Prevent writing to the database regardless of role. - # - # In some cases you may want to prevent writes to the database - # even if you are on a database that can write. `while_preventing_writes` - # will prevent writes to the database for the duration of the block. - def while_preventing_writes - original, @prevent_writes = @prevent_writes, true - yield - ensure - @prevent_writes = original + replica? || ActiveRecord::Base.connection_handler.prevent_writes end def migrations_paths # :nodoc: @@ -163,7 +150,22 @@ module ActiveRecord end def migration_context # :nodoc: - MigrationContext.new(migrations_paths) + MigrationContext.new(migrations_paths, schema_migration) + end + + def schema_migration # :nodoc: + @schema_migration ||= begin + conn = self + spec_name = conn.pool.spec.name + name = "#{spec_name}::SchemaMigration" + + Class.new(ActiveRecord::SchemaMigration) do + define_singleton_method(:name) { name } + define_singleton_method(:to_s) { name } + + self.connection_specification_name = spec_name + end + end end class Version diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 279d0b9e84..2708d2756b 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -5,6 +5,8 @@ module ActiveRecord module ConnectionAdapters # An abstract definition of a column in a table. class Column + include Deduplicable + attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true @@ -76,6 +78,7 @@ module ActiveRecord def hash Column.hash ^ name.hash ^ + name.encoding.hash ^ default.hash ^ sql_type_metadata.hash ^ null.hash ^ @@ -83,6 +86,17 @@ module ActiveRecord collation.hash ^ comment.hash end + + private + def deduplicated + @name = -name + @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata + @default = -default if default + @default_function = -default_function if default_function + @collation = -collation if collation + @comment = -comment if comment + super + end end class NullColumn < Column diff --git a/activerecord/lib/active_record/connection_adapters/deduplicable.rb b/activerecord/lib/active_record/connection_adapters/deduplicable.rb new file mode 100644 index 0000000000..fb2fd60bbc --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/deduplicable.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module Deduplicable + extend ActiveSupport::Concern + + module ClassMethods + def registry + @registry ||= {} + end + + def new(*) + super.deduplicate + end + end + + def deduplicate + self.class.registry[self] ||= deduplicated + end + alias :-@ :deduplicate + + private + def deduplicated + freeze + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb index 1df4dea2d8..97d74df529 100644 --- a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +++ b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb @@ -5,7 +5,7 @@ module ActiveRecord module DetermineIfPreparableVisitor attr_accessor :preparable - def accept(*) + def accept(object, collector) @preparable = true super end @@ -20,7 +20,7 @@ module ActiveRecord super end - def visit_Arel_Nodes_SqlLiteral(*) + def visit_Arel_Nodes_SqlLiteral(o, collector) @preparable = false super end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb index 9167593064..a7232fa249 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb @@ -6,9 +6,11 @@ module ActiveRecord class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: undef to_yaml if method_defined?(:to_yaml) + include Deduplicable + attr_reader :extra - def initialize(type_metadata, extra: "") + def initialize(type_metadata, extra: nil) super(type_metadata) @extra = extra end @@ -25,6 +27,13 @@ module ActiveRecord __getobj__.hash ^ extra.hash end + + private + def deduplicated + __setobj__(__getobj__.deduplicate) + @extra = -extra if extra + super + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index ec25bb1e19..b4ea6c42d3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -23,6 +23,19 @@ module ActiveRecord def sql_type super.sub(/\[\]\z/, "") end + + def ==(other) + other.is_a?(Column) && + super && + serial? == other.serial? + end + alias :eql? :== + + def hash + Column.hash ^ + super.hash ^ + serial?.hash + end end end PostgreSQLColumn = PostgreSQL::Column # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb index 8bdec623af..b7f6479357 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -7,6 +7,8 @@ module ActiveRecord class TypeMetadata < DelegateClass(SqlTypeMetadata) undef to_yaml if method_defined?(:to_yaml) + include Deduplicable + attr_reader :oid, :fmod def initialize(type_metadata, oid: nil, fmod: nil) @@ -29,6 +31,12 @@ module ActiveRecord oid.hash ^ fmod.hash end + + private + def deduplicated + __setobj__(__getobj__.deduplicate) + super + end end end PostgreSQLTypeMetadata = PostgreSQL::TypeMetadata diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index dbfe1e4a34..5da26171bb 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -27,7 +27,6 @@ module ActiveRecord def encode_with(coder) coder["columns"] = @columns - coder["columns_hash"] = @columns_hash coder["primary_keys"] = @primary_keys coder["data_sources"] = @data_sources coder["indexes"] = @indexes @@ -37,7 +36,7 @@ module ActiveRecord def init_with(coder) @columns = coder["columns"] - @columns_hash = coder["columns_hash"] + @columns_hash = {} @primary_keys = coder["primary_keys"] @data_sources = coder["data_sources"] @indexes = coder["indexes"] || {} @@ -79,9 +78,7 @@ module ActiveRecord # Get the columns for a table as a hash, key is the column name # value is the column object. def columns_hash(table_name) - @columns_hash[table_name] ||= Hash[columns(table_name).map { |col| - [col.name, col] - }] + @columns_hash[table_name] ||= columns(table_name).index_by(&:name) end # Checks whether the columns hash is already cached for a table. @@ -124,15 +121,34 @@ module ActiveRecord def marshal_dump # if we get current version during initialization, it happens stack over flow. @version = connection.migration_context.current_version - [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version] + [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version] end def marshal_load(array) - @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array - @indexes = @indexes || {} + @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array + @indexes ||= {} + @columns_hash = {} + + @columns = deep_deduplicate(@columns) + @primary_keys = deep_deduplicate(@primary_keys) + @data_sources = deep_deduplicate(@data_sources) + @indexes = deep_deduplicate(@indexes) end private + def deep_deduplicate(value) + case value + when Hash + value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) } + when Array + value.map { |i| deep_deduplicate(i) } + when String, Deduplicable + -value + else + value + end + end + def prepare_data_sources connection.data_sources.each { |source| @data_sources[source] = true } end diff --git a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb index df28df7a7c..969867e70f 100644 --- a/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb +++ b/activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb @@ -1,9 +1,13 @@ # frozen_string_literal: true +require "active_record/connection_adapters/deduplicable" + module ActiveRecord # :stopdoc: module ConnectionAdapters class SqlTypeMetadata + include Deduplicable + attr_reader :sql_type, :type, :limit, :precision, :scale def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil) @@ -32,6 +36,12 @@ module ActiveRecord precision.hash >> 1 ^ scale.hash >> 2 end + + private + def deduplicated + @sql_type = -sql_type + super + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index e6302112de..da971fdba7 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -383,6 +383,7 @@ module ActiveRecord if from_primary_key.is_a?(Array) @definition.primary_keys from_primary_key end + columns(from).each do |column| column_name = options[:rename] ? (options[:rename][column.name] || |