diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
15 files changed, 392 insertions, 327 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 3f2e86a98d..53dbbd8c21 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -825,7 +825,7 @@ module ActiveRecord # end # # class Book < ActiveRecord::Base - # establish_connection "library_db" + # establish_connection :library_db # end # # class ScaryBook < Book diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index cce8883076..f0c0fbab6c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -61,17 +61,6 @@ module ActiveRecord lookup_cast_type(column.sql_type) end - def fetch_type_metadata(sql_type) - cast_type = lookup_cast_type(sql_type) - SqlTypeMetadata.new( - sql_type: sql_type, - type: cast_type.type, - limit: cast_type.limit, - precision: cast_type.precision, - scale: cast_type.scale, - ) - end - # Quotes a string, escaping any ' (single quote) and \ (backslash) # characters. def quote_string(s) @@ -161,6 +150,10 @@ module ActiveRecord end private + def lookup_cast_type(sql_type) + type_map.lookup(sql_type) + end + def id_value_for_database(value) if primary_key = value.class.primary_key value.instance_variable_get(:@attributes)[primary_key].value_for_database diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index c48a4acff8..a4fecc4a8e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -133,5 +133,6 @@ module ActiveRecord end end end + SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc: end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 4682afc188..46d7f84efd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -2,8 +2,33 @@ module ActiveRecord module ConnectionAdapters #:nodoc: # Abstract representation of an index definition on a table. Instances of # this type are typically created and returned by methods in database - # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes - IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc: + # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes + class IndexDefinition # :nodoc: + attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment + + def initialize( + table, name, + unique = false, + columns = [], + lengths: {}, + orders: {}, + where: nil, + type: nil, + using: nil, + comment: nil + ) + @table = table + @name = name + @unique = unique + @columns = columns + @lengths = lengths + @orders = orders + @where = where + @type = type + @using = using + @comment = comment + end + end # Abstract representation of a column definition. Instances of this type # are typically created by methods in TableDefinition, and added to the 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 1cc10e3b93..13629dee7f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -976,16 +976,6 @@ module ActiveRecord foreign_key_for(from_table, options_or_to_table).present? end - def foreign_key_for(from_table, options_or_to_table = {}) # :nodoc: - return unless supports_foreign_keys? - foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table } - end - - def foreign_key_for!(from_table, options_or_to_table = {}) # :nodoc: - foreign_key_for(from_table, options_or_to_table) || \ - raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}") - end - def foreign_key_column_for(table_name) # :nodoc: prefix = Base.table_name_prefix suffix = Base.table_name_suffix @@ -1005,19 +995,6 @@ module ActiveRecord insert_versions_sql(versions) end - def insert_versions_sql(versions) # :nodoc: - sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) - - if versions.is_a?(Array) - sql = "INSERT INTO #{sm_table} (version) VALUES\n" - sql << versions.map { |v| "(#{quote(v)})" }.join(",\n") - sql << ";\n\n" - sql - else - "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});" - end - end - def initialize_schema_migrations_table # :nodoc: ActiveRecord::SchemaMigration.create_table end @@ -1263,6 +1240,10 @@ module ActiveRecord end end + def schema_creation + SchemaCreation.new(self) + end + def create_table_definition(*args) TableDefinition.new(*args) end @@ -1271,6 +1252,17 @@ module ActiveRecord AlterTable.new create_table_definition(name) end + def fetch_type_metadata(sql_type) + cast_type = lookup_cast_type(sql_type) + SqlTypeMetadata.new( + sql_type: sql_type, + type: cast_type.type, + limit: cast_type.limit, + precision: cast_type.precision, + scale: cast_type.scale, + ) + end + def index_column_names(column_names) if column_names.is_a?(String) && /\W/.match?(column_names) column_names @@ -1295,6 +1287,24 @@ module ActiveRecord end end + def foreign_key_for(from_table, options_or_to_table = {}) + return unless supports_foreign_keys? + foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table } + end + + def foreign_key_for!(from_table, options_or_to_table = {}) + foreign_key_for(from_table, options_or_to_table) || \ + raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}") + end + + def extract_foreign_key_action(specifier) + case specifier + when "CASCADE"; :cascade + when "SET NULL"; :nullify + when "RESTRICT"; :restrict + end + end + def validate_index_length!(table_name, new_name, internal = false) max_index_length = internal ? index_name_length : allowed_index_name_length @@ -1315,6 +1325,19 @@ module ActiveRecord options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? end + def insert_versions_sql(versions) + sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) + + if versions.is_a?(Array) + sql = "INSERT INTO #{sm_table} (version) VALUES\n" + sql << versions.map { |v| "(#{quote(v)})" }.join(",\n") + sql << ";\n\n" + sql + else + "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});" + end + end + def data_source_sql(name = nil, type: nil) raise NotImplementedError end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 6bb072dd73..19b7821494 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -149,57 +149,67 @@ module ActiveRecord end def begin_transaction(options = {}) - run_commit_callbacks = !current_transaction.joinable? - transaction = - if @stack.empty? - RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks) - else - SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options, - run_commit_callbacks: run_commit_callbacks) - end + @connection.lock.synchronize do + run_commit_callbacks = !current_transaction.joinable? + transaction = + if @stack.empty? + RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks) + else + SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options, + run_commit_callbacks: run_commit_callbacks) + end - @stack.push(transaction) - transaction + @stack.push(transaction) + transaction + end end def commit_transaction - transaction = @stack.last + @connection.lock.synchronize do + transaction = @stack.last - begin - transaction.before_commit_records - ensure - @stack.pop - end + begin + transaction.before_commit_records + ensure + @stack.pop + end - transaction.commit - transaction.commit_records + transaction.commit + transaction.commit_records + end end def rollback_transaction(transaction = nil) - transaction ||= @stack.pop - transaction.rollback - transaction.rollback_records + @connection.lock.synchronize do + transaction ||= @stack.pop + transaction.rollback + transaction.rollback_records + end end def within_new_transaction(options = {}) - transaction = begin_transaction options - yield - rescue Exception => error - if transaction - rollback_transaction - after_failure_actions(transaction, error) - end - raise - ensure - unless error - if Thread.current.status == "aborting" - rollback_transaction if transaction - else - begin - commit_transaction - rescue Exception - rollback_transaction(transaction) unless transaction.state.completed? - raise + @connection.lock.synchronize do + begin + transaction = begin_transaction options + yield + rescue Exception => error + if transaction + rollback_transaction + after_failure_actions(transaction, error) + end + raise + ensure + unless error + if Thread.current.status == "aborting" + rollback_transaction if transaction + else + begin + commit_transaction + rescue Exception + rollback_transaction(transaction) unless transaction.state.completed? + raise + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 550b4cc86b..85d6fbe8b3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -74,7 +74,7 @@ module ActiveRecord SIMPLE_INT = /\A\d+\z/ attr_accessor :visitor, :pool - attr_reader :schema_cache, :owner, :logger + attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock alias :in_use? :owner def self.type_cast_config_to_integer(config) @@ -93,8 +93,6 @@ module ActiveRecord end end - attr_reader :prepared_statements - def initialize(connection, logger = nil, config = {}) # :nodoc: super() @@ -142,26 +140,10 @@ module ActiveRecord end end - def collector - if prepared_statements - SQLString.new - else - BindCollector.new - end - end - - def arel_visitor # :nodoc: - Arel::Visitors::ToSql.new(self) - end - def valid_type?(type) # :nodoc: !native_database_types[type].nil? end - def schema_creation - SchemaCreation.new self - end - # this method must only be called while holding connection pool's mutex def lease if in_use? @@ -475,14 +457,6 @@ module ActiveRecord end end - def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc: - Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation) - end - - def lookup_cast_type(sql_type) # :nodoc: - type_map.lookup(sql_type) - end - def column_name_for_operation(operation, node) # :nodoc: visitor.accept(node, collector).value end @@ -629,6 +603,18 @@ module ActiveRecord columns(table_name).detect { |c| c.name == column_name } || raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") end + + def collector + if prepared_statements + SQLString.new + else + BindCollector.new + end + end + + def arel_visitor + Arel::Visitors::ToSql.new(self) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 55ec112c17..c742799aab 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -22,14 +22,6 @@ module ActiveRecord MySQL::Table.new(table_name, base) end - def schema_creation # :nodoc: - MySQL::SchemaCreation.new(self) - end - - def arel_visitor # :nodoc: - Arel::Visitors::MySQL.new(self) - end - ## # :singleton-method: # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt> @@ -56,9 +48,6 @@ module ActiveRecord json: { name: "json" }, } - INDEX_TYPES = [:fulltext, :spatial] - INDEX_USINGS = [:btree, :hash] - class StatementPool < ConnectionAdapters::StatementPool private def dealloc(stmt) stmt[:stmt].close @@ -75,14 +64,6 @@ module ActiveRecord end end - CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] - - def internal_string_options_for_primary_key # :nodoc: - super.tap { |options| - options[:collation] = collation.sub(/\A[^_]+/, "utf8") if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) - } - end - def version #:nodoc: @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0]) end @@ -101,10 +82,8 @@ module ActiveRecord true end - # Technically MySQL allows to create indexes with the sort order syntax - # but at the moment (5.5) it doesn't yet implement them def supports_index_sort_order? - true + !mariadb? && version >= "8.0.1" end def supports_transaction_isolation? @@ -171,10 +150,6 @@ module ActiveRecord raise NotImplementedError end - def new_column(*args) #:nodoc: - MySQL::Column.new(*args) - end - # Must return the MySQL error number from the exception, if the exception has an # error number. def error_number(exception) # :nodoc: @@ -316,46 +291,6 @@ module ActiveRecord execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name end - # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) #:nodoc: - if name - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing name to #indexes is deprecated without replacement. - MSG - end - - indexes = [] - current_index = nil - execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| - each_hash(result) do |row| - if current_index != row[:Key_name] - next if row[:Key_name] == "PRIMARY" # skip the primary key - current_index = row[:Key_name] - - mysql_index_type = row[:Index_type].downcase.to_sym - index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil - index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil - indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence) - end - - indexes.last.columns << row[:Column_name] - indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part] - end - end - - indexes - end - - def new_column_from_field(table_name, field) # :nodoc: - type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) - if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP" - default, default_function = nil, field[:Default] - else - default, default_function = field[:Default], nil - end - new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence) - end - def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name) @@ -658,10 +593,6 @@ module ActiveRecord end end - def fetch_type_metadata(sql_type, extra = "") - MySQL::TypeMetadata.new(super(sql_type), extra: extra) - end - def add_index_length(quoted_columns, **options) if length = options[:length] case length @@ -864,19 +795,12 @@ module ActiveRecord end end - def extract_foreign_key_action(specifier) # :nodoc: - case specifier - when "CASCADE"; :cascade - when "SET NULL"; :nullify - end - end - def create_table_info(table_name) # :nodoc: select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] end - def create_table_definition(*args) # :nodoc: - MySQL::TableDefinition.new(*args) + def arel_visitor + Arel::Visitors::MySQL.new(self) end def mismatched_foreign_key(message) diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 3e0afd9761..e2ba0ba1a0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -55,13 +55,14 @@ module ActiveRecord def extract_expression_for_virtual_column(column) if mariadb? create_table_info = create_table_info(column.table_name) - if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)} AS \((?<expression>.+?)\) #{column.extra}/m =~ create_table_info + if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info $~[:expression].inspect end else + scope = quoted_scope(column.table_name) sql = "SELECT generation_expression FROM information_schema.columns" \ - " WHERE table_schema = #{quote(@config[:database])}" \ - " AND table_name = #{quote(column.table_name)}" \ + " WHERE table_schema = #{scope[:schema]}" \ + " AND table_name = #{scope[:name]}" \ " AND column_name = #{quote(column.name)}" select_value(sql, "SCHEMA").inspect end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index 10c8bd179a..f9e1e046ea 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -2,7 +2,96 @@ module ActiveRecord module ConnectionAdapters module MySQL module SchemaStatements # :nodoc: + # Returns an array of indexes for the given table. + def indexes(table_name, name = nil) + if name + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing name to #indexes is deprecated without replacement. + MSG + end + + indexes = [] + current_index = nil + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) do |row| + if current_index != row[:Key_name] + next if row[:Key_name] == "PRIMARY" # skip the primary key + current_index = row[:Key_name] + + mysql_index_type = row[:Index_type].downcase.to_sym + case mysql_index_type + when :fulltext, :spatial + index_type = mysql_index_type + when :btree, :hash + index_using = mysql_index_type + end + + indexes << IndexDefinition.new( + row[:Table], + row[:Key_name], + row[:Non_unique].to_i == 0, + type: index_type, + using: index_using, + comment: row[:Index_comment].presence + ) + end + + indexes.last.columns << row[:Column_name] + indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part] + indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D" + end + end + + indexes + end + + def internal_string_options_for_primary_key + super.tap do |options| + if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0") + options[:collation] = collation.sub(/\A[^_]+/, "utf8") + end + end + end + private + CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] + + def schema_creation + MySQL::SchemaCreation.new(self) + end + + def create_table_definition(*args) + MySQL::TableDefinition.new(*args) + end + + def new_column_from_field(table_name, field) + type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) + if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP" + default, default_function = nil, field[:Default] + else + default, default_function = field[:Default], nil + end + + MySQL::Column.new( + field[:Field], + default, + type_metadata, + field[:Null] == "YES", + table_name, + default_function, + field[:Collation], + comment: field[:Comment].presence + ) + end + + def fetch_type_metadata(sql_type, extra = "") + MySQL::TypeMetadata.new(super(sql_type), extra: extra) + end + + def extract_foreign_key_action(specifier) + super unless specifier == "RESTRICT" + end + def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index e399b6e499..da8d0c6992 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -77,6 +77,9 @@ module ActiveRecord end private + def lookup_cast_type(sql_type) + super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) + end def _quote(value) case value diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index a332375b78..5b483ad4ab 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -140,27 +140,17 @@ module ActiveRecord ] end - IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence) - end.compact - end - - def new_column_from_field(table_name, field) # :nondoc: - column_name, type, default, notnull, oid, fmod, collation, comment = field - oid = oid.to_i - fmod = fmod.to_i - type_metadata = fetch_type_metadata(column_name, type, oid, fmod) - default_value = extract_value_from_default(default) - default_function = extract_default_function(default_value, default) - PostgreSQLColumn.new( - column_name, - default_value, - type_metadata, - !notnull, - table_name, - default_function, - collation, - comment: comment.presence - ) + IndexDefinition.new( + table_name, + index_name, + unique, + columns, + orders: orders, + where: where, + using: using.to_sym, + comment: comment.presence + ) + end end def table_options(table_name) # :nodoc: @@ -362,16 +352,18 @@ module ActiveRecord end def primary_keys(table_name) # :nodoc: - scope = quoted_scope(table_name) select_values(<<-SQL.strip_heredoc, "SCHEMA") - SELECT column_name - FROM information_schema.key_column_usage kcu - JOIN information_schema.table_constraints tc - USING (table_schema, table_name, constraint_name) - WHERE constraint_type = 'PRIMARY KEY' - AND kcu.table_name = #{scope[:name]} - AND kcu.table_schema = #{scope[:schema]} - ORDER BY kcu.ordinal_position + SELECT a.attname + FROM ( + SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx + FROM pg_index + WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass + AND indisprimary + ) i + JOIN pg_attribute a + ON a.attrelid = i.indrelid + AND a.attnum = i.indkey[i.idx] + ORDER BY i.idx SQL end @@ -538,14 +530,6 @@ module ActiveRecord end end - def extract_foreign_key_action(specifier) # :nodoc: - case specifier - when "c"; :cascade - when "n"; :nullify - when "r"; :restrict - end - end - # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc: sql = \ @@ -593,19 +577,53 @@ module ActiveRecord [super, *order_columns].join(", ") end - def fetch_type_metadata(column_name, sql_type, oid, fmod) - cast_type = get_oid_type(oid, fmod, column_name, sql_type) - simple_type = SqlTypeMetadata.new( - sql_type: sql_type, - type: cast_type.type, - limit: cast_type.limit, - precision: cast_type.precision, - scale: cast_type.scale, - ) - PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod) - end - private + def schema_creation + PostgreSQL::SchemaCreation.new(self) + end + + def create_table_definition(*args) + PostgreSQL::TableDefinition.new(*args) + end + + def new_column_from_field(table_name, field) + column_name, type, default, notnull, oid, fmod, collation, comment = field + type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) + default_value = extract_value_from_default(default) + default_function = extract_default_function(default_value, default) + + PostgreSQLColumn.new( + column_name, + default_value, + type_metadata, + !notnull, + table_name, + default_function, + collation, + comment: comment.presence + ) + end + + def fetch_type_metadata(column_name, sql_type, oid, fmod) + cast_type = get_oid_type(oid, fmod, column_name, sql_type) + simple_type = SqlTypeMetadata.new( + sql_type: sql_type, + type: cast_type.type, + limit: cast_type.limit, + precision: cast_type.precision, + scale: cast_type.scale, + ) + PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod) + end + + def extract_foreign_key_action(specifier) + case specifier + when "c"; :cascade + when "n"; :nullify + when "r"; :restrict + end + end + def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 6273614f0c..7ae9bd9a5f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -121,14 +121,6 @@ module ActiveRecord include PostgreSQL::DatabaseStatements include PostgreSQL::ColumnDumper - def schema_creation # :nodoc: - PostgreSQL::SchemaCreation.new self - end - - def arel_visitor # :nodoc: - Arel::Visitors::PostgreSQL.new(self) - end - # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? @@ -247,7 +239,9 @@ module ActiveRecord # Is this connection alive and ready for queries? def active? - @connection.query "SELECT 1" + @lock.synchronize do + @connection.query "SELECT 1" + end true rescue PG::Error false @@ -255,26 +249,32 @@ module ActiveRecord # Close then reopen the connection. def reconnect! - super - @connection.reset - configure_connection + @lock.synchronize do + super + @connection.reset + configure_connection + end end def reset! - clear_cache! - reset_transaction - unless @connection.transaction_status == ::PG::PQTRANS_IDLE - @connection.query "ROLLBACK" + @lock.synchronize do + clear_cache! + reset_transaction + unless @connection.transaction_status == ::PG::PQTRANS_IDLE + @connection.query "ROLLBACK" + end + @connection.query "DISCARD ALL" + configure_connection end - @connection.query "DISCARD ALL" - configure_connection end # Disconnects from the database if already connected. Otherwise, this # method does nothing. def disconnect! - super - @connection.close rescue nil + @lock.synchronize do + super + @connection.close rescue nil + end end def native_database_types #:nodoc: @@ -382,11 +382,6 @@ module ActiveRecord PostgreSQL::Table.new(table_name, base) end - def lookup_cast_type(sql_type) # :nodoc: - oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first["oid"].to_i - super(oid) - end - def column_name_for_operation(operation, node) # :nodoc: OPERATION_ALIASES.fetch(operation) { operation.downcase } end @@ -783,8 +778,8 @@ module ActiveRecord $1.strip if $1 end - def create_table_definition(*args) - PostgreSQL::TableDefinition.new(*args) + def arel_visitor + Arel::Visitors::PostgreSQL.new(self) end def can_perform_case_insensitive_comparison_for?(column) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb index 4ba245a0d8..e02491edb6 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -2,7 +2,67 @@ module ActiveRecord module ConnectionAdapters module SQLite3 module SchemaStatements # :nodoc: + # Returns an array of indexes for the given table. + def indexes(table_name, name = nil) + if name + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing name to #indexes is deprecated without replacement. + MSG + end + + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| + index_sql = select_value(<<-SQL, "SCHEMA") + SELECT sql + FROM sqlite_master + WHERE name = #{quote(row['name'])} AND type = 'index' + UNION ALL + SELECT sql + FROM sqlite_temp_master + WHERE name = #{quote(row['name'])} AND type = 'index' + SQL + + /\sWHERE\s+(?<where>.+)$/i =~ index_sql + + columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| + col["name"] + end + + IndexDefinition.new( + table_name, + row["name"], + row["unique"] != 0, + columns, + where: where + ) + end + end + private + def schema_creation + SQLite3::SchemaCreation.new(self) + end + + def create_table_definition(*args) + SQLite3::TableDefinition.new(*args) + end + + def new_column_from_field(table_name, field) + default = \ + case field["dflt_value"] + when /^null$/i + nil + when /^'(.*)'$/m + $1.gsub("''", "'") + when /^"(.*)"$/m + $1.gsub('""', '"') + else + field["dflt_value"] + end + + type_metadata = fetch_type_metadata(field["type"]) + Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"]) + end + def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) scope[:type] ||= "'table','view'" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index d24bfc0c93..e2c05ccc4e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -84,14 +84,6 @@ module ActiveRecord SQLite3::Table.new(table_name, base) end - def schema_creation # :nodoc: - SQLite3::SchemaCreation.new self - end - - def arel_visitor # :nodoc: - Arel::Visitors::SQLite.new(self) - end - def initialize(connection, logger, connection_options, config) super(connection, logger, config) @@ -267,53 +259,6 @@ module ActiveRecord # SCHEMA STATEMENTS ======================================== - def new_column_from_field(table_name, field) # :nondoc: - case field["dflt_value"] - when /^null$/i - field["dflt_value"] = nil - when /^'(.*)'$/m - field["dflt_value"] = $1.gsub("''", "'") - when /^"(.*)"$/m - field["dflt_value"] = $1.gsub('""', '"') - end - - collation = field["collation"] - sql_type = field["type"] - type_metadata = fetch_type_metadata(sql_type) - new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation) - end - - # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) #:nodoc: - if name - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing name to #indexes is deprecated without replacement. - MSG - end - - exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| - sql = <<-SQL - SELECT sql - FROM sqlite_master - WHERE name=#{quote(row['name'])} AND type='index' - UNION ALL - SELECT sql - FROM sqlite_temp_master - WHERE name=#{quote(row['name'])} AND type='index' - SQL - index_sql = exec_query(sql).first["sql"] - match = /\sWHERE\s+(.+)$/i.match(index_sql) - where = match[1] if match - IndexDefinition.new( - table_name, - row["name"], - row["unique"] != 0, - exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col| - col["name"] - }, nil, nil, where) - end - end - def primary_keys(table_name) # :nodoc: pks = table_structure(table_name).select { |f| f["pk"] > 0 } pks.sort_by { |f| f["pk"] }.map { |f| f["name"] } @@ -559,16 +504,8 @@ module ActiveRecord end end - def create_table_definition(*args) - SQLite3::TableDefinition.new(*args) - end - - def extract_foreign_key_action(specifier) - case specifier - when "CASCADE"; :cascade - when "SET NULL"; :nullify - when "RESTRICT"; :restrict - end + def arel_visitor + Arel::Visitors::SQLite.new(self) end def configure_connection |