aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/postgresql
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/postgresql')
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb189
9 files changed, 248 insertions, 121 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index 1b67cee24b..469ef3f5a0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -7,11 +7,38 @@ module ActiveRecord
delegate :array, :oid, :fmod, to: :sql_type_metadata
alias :array? :array
+ def initialize(*, max_identifier_length: 63, **)
+ super
+ @max_identifier_length = max_identifier_length
+ end
+
def serial?
return unless default_function
- %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
+ if %r{\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z} =~ default_function
+ sequence_name_from_parts(table_name, name, suffix) == sequence_name
+ end
end
+
+ protected
+ attr_reader :max_identifier_length
+
+ private
+ def sequence_name_from_parts(table_name, column_name, suffix)
+ over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length
+
+ if over_length > 0
+ column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
+ over_length -= column_name.length - column_name_length
+ column_name = column_name[0, column_name_length - [over_length, 0].min]
+ end
+
+ if over_length > 0
+ table_name = table_name[0, table_name.length - over_length]
+ end
+
+ "#{table_name}_#{column_name}_#{suffix}"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index b28418d74f..542ca75d3e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,27 +1,27 @@
# frozen_string_literal: true
-require_relative "oid/array"
-require_relative "oid/bit"
-require_relative "oid/bit_varying"
-require_relative "oid/bytea"
-require_relative "oid/cidr"
-require_relative "oid/date_time"
-require_relative "oid/decimal"
-require_relative "oid/enum"
-require_relative "oid/hstore"
-require_relative "oid/inet"
-require_relative "oid/jsonb"
-require_relative "oid/money"
-require_relative "oid/oid"
-require_relative "oid/point"
-require_relative "oid/legacy_point"
-require_relative "oid/range"
-require_relative "oid/specialized_string"
-require_relative "oid/uuid"
-require_relative "oid/vector"
-require_relative "oid/xml"
+require "active_record/connection_adapters/postgresql/oid/array"
+require "active_record/connection_adapters/postgresql/oid/bit"
+require "active_record/connection_adapters/postgresql/oid/bit_varying"
+require "active_record/connection_adapters/postgresql/oid/bytea"
+require "active_record/connection_adapters/postgresql/oid/cidr"
+require "active_record/connection_adapters/postgresql/oid/date_time"
+require "active_record/connection_adapters/postgresql/oid/decimal"
+require "active_record/connection_adapters/postgresql/oid/enum"
+require "active_record/connection_adapters/postgresql/oid/hstore"
+require "active_record/connection_adapters/postgresql/oid/inet"
+require "active_record/connection_adapters/postgresql/oid/jsonb"
+require "active_record/connection_adapters/postgresql/oid/money"
+require "active_record/connection_adapters/postgresql/oid/oid"
+require "active_record/connection_adapters/postgresql/oid/point"
+require "active_record/connection_adapters/postgresql/oid/legacy_point"
+require "active_record/connection_adapters/postgresql/oid/range"
+require "active_record/connection_adapters/postgresql/oid/specialized_string"
+require "active_record/connection_adapters/postgresql/oid/uuid"
+require "active_record/connection_adapters/postgresql/oid/vector"
+require "active_record/connection_adapters/postgresql/oid/xml"
-require_relative "oid/type_map_initializer"
+require "active_record/connection_adapters/postgresql/oid/type_map_initializer"
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index 7d5d7d91e6..a89aa5ea09 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -35,7 +35,7 @@ module ActiveRecord
if value.is_a?(::Range)
from = type_cast_single_for_database(value.begin)
to = type_cast_single_for_database(value.end)
- "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}"
+ ::Range.new(from, to, value.exclude_end?)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index a0a22ba0f1..9fdeab06c1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -101,6 +101,8 @@ module ActiveRecord
end
when OID::Array::Data
_quote(encode_array(value))
+ when Range
+ _quote(encode_range(value))
else
super
end
@@ -117,6 +119,8 @@ module ActiveRecord
value.to_s
when OID::Array::Data
encode_array(value)
+ when Range
+ encode_range(value)
else
super
end
@@ -133,6 +137,10 @@ module ActiveRecord
result
end
+ def encode_range(range)
+ "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}"
+ end
+
def determine_encoding_of_strings_in_array(value)
case value
when ::Array then determine_encoding_of_strings_in_array(value.first)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 386d22a9bd..8df91c988b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -4,26 +4,21 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ReferentialIntegrity # :nodoc:
- def supports_disable_referential_integrity? # :nodoc:
- true
- end
-
def disable_referential_integrity # :nodoc:
- if supports_disable_referential_integrity?
- original_exception = nil
+ original_exception = nil
- begin
- transaction(requires_new: true) do
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
- end
- rescue ActiveRecord::ActiveRecordError => e
- original_exception = e
+ begin
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
end
+ rescue ActiveRecord::ActiveRecordError => e
+ original_exception = e
+ end
- begin
- yield
- rescue ActiveRecord::InvalidForeignKey => e
- warn <<-WARNING
+ begin
+ yield
+ rescue ActiveRecord::InvalidForeignKey => e
+ warn <<-WARNING
WARNING: Rails was not able to disable referential integrity.
This is most likely caused due to missing permissions.
@@ -32,17 +27,14 @@ Rails needs superuser privileges to disable referential integrity.
cause: #{original_exception.try(:message)}
WARNING
- raise e
- end
+ raise e
+ end
- begin
- transaction(requires_new: true) do
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
- end
- rescue ActiveRecord::ActiveRecordError
+ begin
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
end
- else
- yield
+ rescue ActiveRecord::ActiveRecordError
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
index 59f661da25..8e381a92cf 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -5,6 +5,18 @@ module ActiveRecord
module PostgreSQL
class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
private
+ def visit_AlterTable(o)
+ super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
+ end
+
+ def visit_AddForeignKey(o)
+ super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
+ end
+
+ def visit_ValidateConstraint(name)
+ "VALIDATE CONSTRAINT #{quote_column_name(name)}"
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index f1489e4d69..6047217fcd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -44,15 +44,8 @@ module ActiveRecord
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, **options)
- options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
if type == :uuid
options[:default] = options.fetch(:default, "gen_random_uuid()")
- elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
- type = if type == :bigint || options[:limit] == 8
- :bigserial
- else
- :serial
- end
end
super
@@ -102,10 +95,6 @@ module ActiveRecord
args.each { |name| column(name, :int8range, options) }
end
- def json(*args, **options)
- args.each { |name| column(name, :json, options) }
- end
-
def jsonb(*args, **options)
args.each { |name| column(name, :jsonb, options) }
end
@@ -185,11 +174,33 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+
+ private
+ def integer_like_primary_key_type(type, options)
+ if type == :bigint || options[:limit] == 8
+ :bigserial
+ else
+ :serial
+ end
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
include ColumnMethods
end
+
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
+ attr_reader :constraint_validations
+
+ def initialize(td)
+ super
+ @constraint_validations = []
+ end
+
+ def validate_constraint(name)
+ @constraint_validations << name
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index c0dbb166b7..84643d20da 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -5,6 +5,18 @@ module ActiveRecord
module PostgreSQL
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
private
+
+ def extensions(stream)
+ extensions = @connection.extensions
+ if extensions.any?
+ stream.puts " # These are extensions that must be enabled in order to support this database"
+ extensions.sort.each do |extension|
+ stream.puts " enable_extension #{extension.inspect}"
+ end
+ stream.puts
+ end
+ end
+
def prepare_column_options(column)
spec = super
spec[:array] = "true" if column.array?
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 9e2f61e6ce..bf5fbb30e1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/string/strip"
-
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -66,12 +64,7 @@ module ActiveRecord
end
# Verifies existence of an index with a given name.
- def index_name_exists?(table_name, index_name, default = nil)
- unless default.nil?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing default to #index_name_exists? is deprecated without replacement.
- MSG
- end
+ def index_name_exists?(table_name, index_name)
table = quoted_scope(table_name)
index = quoted_scope(index_name)
@@ -89,21 +82,12 @@ module ActiveRecord
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
-
+ def indexes(table_name) # :nodoc:
scope = quoted_scope(table_name)
result = query(<<-SQL, "SCHEMA")
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
- (SELECT COUNT(*) FROM pg_opclass o
- JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
- ON o.oid = c.oid WHERE o.opcdefault = 'f')
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -122,11 +106,13 @@ module ActiveRecord
inddef = row[3]
oid = row[4]
comment = row[5]
- opclass = row[6]
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
- if indkey.include?(0) || opclass > 0
+ orders = {}
+ opclasses = {}
+
+ if indkey.include?(0)
columns = expressions
else
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
@@ -136,10 +122,12 @@ module ActiveRecord
AND a.attnum IN (#{indkey.join(",")})
SQL
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- orders = Hash[
- expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
- ]
+ # add info on sort order (only desc order is explicitly specified, asc is the default)
+ # and non-default opclasses
+ expressions.scan(/(\w+)(?: (?!DESC)(\w+))?(?: (DESC))?/).each do |column, opclass, desc|
+ opclasses[column] = opclass.to_sym if opclass
+ orders[column] = :desc if desc
+ end
end
IndexDefinition.new(
@@ -148,6 +136,7 @@ module ActiveRecord
unique,
columns,
orders: orders,
+ opclasses: opclasses,
where: where,
using: using.to_sym,
comment: comment.presence
@@ -405,50 +394,23 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
- quoted_table_name = quote_table_name(table_name)
- quoted_column_name = quote_column_name(column_name)
- sql_type = type_to_sql(type, options)
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
- if options[:collation]
- sql << " COLLATE \"#{options[:collation]}\""
- end
- if options[:using]
- sql << " USING #{options[:using]}"
- elsif options[:cast_as]
- cast_as_type = type_to_sql(options[:cast_as], options)
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
- end
- execute sql
-
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
- change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
+ sqls, procs = change_column_for_alter(table_name, column_name, type, options)
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
+ procs.each(&:call)
end
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
- clear_cache!
- column = column_for(table_name, column_name)
- return unless column
-
- default = extract_new_default_value(default_or_changes)
- alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
- if default.nil?
- # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
- # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
- execute alter_column_query % "DROP DEFAULT"
- else
- execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
- end
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
end
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
+ execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
end
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
end
# Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -471,8 +433,8 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
- execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+ index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
end
end
@@ -512,7 +474,7 @@ module ActiveRecord
def foreign_keys(table_name)
scope = quoted_scope(table_name)
fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
FROM pg_constraint c
JOIN pg_class t1 ON c.conrelid = t1.oid
JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -534,6 +496,7 @@ module ActiveRecord
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
options[:on_update] = extract_foreign_key_action(row["on_update"])
+ options[:validate] = row["valid"]
ForeignKeyDefinition.new(table_name, row["to_table"], options)
end
@@ -594,6 +557,43 @@ module ActiveRecord
PostgreSQL::SchemaDumper.create(self, options)
end
+ # Validates the given constraint.
+ #
+ # Validates the constraint named +constraint_name+ on +accounts+.
+ #
+ # validate_constraint :accounts, :constraint_name
+ def validate_constraint(table_name, constraint_name)
+ return unless supports_validate_constraints?
+
+ at = create_alter_table table_name
+ at.validate_constraint constraint_name
+
+ execute schema_creation.accept(at)
+ end
+
+ # Validates the given foreign key.
+ #
+ # Validates the foreign key on +accounts.branch_id+.
+ #
+ # validate_foreign_key :accounts, :branches
+ #
+ # Validates the foreign key on +accounts.owner_id+.
+ #
+ # validate_foreign_key :accounts, column: :owner_id
+ #
+ # Validates the foreign key named +special_fk_name+ on the +accounts+ table.
+ #
+ # validate_foreign_key :accounts, name: :special_fk_name
+ #
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
+ def validate_foreign_key(from_table, options_or_to_table = {})
+ return unless supports_validate_constraints?
+
+ fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
+
+ validate_constraint from_table, fk_name_to_validate
+ end
+
private
def schema_creation
PostgreSQL::SchemaCreation.new(self)
@@ -603,6 +603,10 @@ module ActiveRecord
PostgreSQL::TableDefinition.new(*args)
end
+ def create_alter_table(name)
+ PostgreSQL::AlterTable.new create_table_definition(name)
+ 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)
@@ -617,7 +621,8 @@ module ActiveRecord
table_name,
default_function,
collation,
- comment: comment.presence
+ comment: comment.presence,
+ max_identifier_length: max_identifier_length
)
end
@@ -641,6 +646,66 @@ module ActiveRecord
end
end
+ def change_column_sql(table_name, column_name, type, options = {})
+ quoted_column_name = quote_column_name(column_name)
+ sql_type = type_to_sql(type, options)
+ sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ if options[:using]
+ sql << " USING #{options[:using]}"
+ elsif options[:cast_as]
+ cast_as_type = type_to_sql(options[:cast_as], options)
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
+ end
+
+ sql
+ end
+
+ def change_column_for_alter(table_name, column_name, type, options = {})
+ sqls = [change_column_sql(table_name, column_name, type, options)]
+ procs = []
+ sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
+ sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
+
+ [sqls, procs]
+ end
+
+
+ # Changes the default value of a table column.
+ def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
+ column = column_for(table_name, column_name)
+ return unless column
+
+ default = extract_new_default_value(default_or_changes)
+ alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
+ if default.nil?
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
+ alter_column_query % "DROP DEFAULT"
+ else
+ alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
+ end
+ end
+
+ def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
+ "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
+ end
+
+ def add_index_opclass(quoted_columns, **options)
+ opclasses = options_for_index_columns(options[:opclass])
+ quoted_columns.each do |name, column|
+ column << " #{opclasses[name]}" if opclasses[name].present?
+ end
+ end
+
+ def add_options_for_index_columns(quoted_columns, **options)
+ quoted_columns = add_index_opclass(quoted_columns, options)
+ super
+ 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