diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb')
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb | 142 |
1 files changed, 111 insertions, 31 deletions
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 595c635fc0..67e727d8ed 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -68,15 +68,41 @@ module ActiveRecord execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" end - # Returns the list of all tables in the schema search path or a specified schema. + # Returns the list of all tables in the schema search path. def tables(name = nil) + if name + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing arguments to #tables is deprecated without replacement. + MSG + end + select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", 'SCHEMA') end + def data_sources # :nodoc + select_values(<<-SQL, 'SCHEMA') + SELECT c.relname + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('r', 'v','m') -- (r)elation/table, (v)iew, (m)aterialized view + AND n.nspname = ANY (current_schemas(false)) + SQL + end + # Returns true if table exists. # If the schema is not specified as part of +name+ then it will only find tables within # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + #table_exists? currently checks both tables and views. + This behavior is deprecated and will be changed with Rails 5.1 to only check tables. + Use #data_source_exists? instead. + MSG + + data_source_exists?(name) + end + + def data_source_exists?(name) name = Utils.extract_schema_qualified_name(name.to_s) return false unless name.identifier @@ -90,6 +116,30 @@ module ActiveRecord SQL end + def views # :nodoc: + select_values(<<-SQL, 'SCHEMA') + SELECT c.relname + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view + AND n.nspname = ANY (current_schemas(false)) + SQL + end + + def view_exists?(view_name) # :nodoc: + name = Utils.extract_schema_qualified_name(view_name.to_s) + return false unless name.identifier + + select_values(<<-SQL, 'SCHEMA').any? + SELECT c.relname + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view + AND c.relname = '#{name.identifier}' + AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'} + SQL + end + def drop_table(table_name, options = {}) # :nodoc: execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end @@ -101,29 +151,36 @@ module ActiveRecord # Verifies existence of an index with a given name. def index_name_exists?(table_name, index_name, default) + table = Utils.extract_schema_qualified_name(table_name.to_s) + index = Utils.extract_schema_qualified_name(index_name.to_s) + select_value(<<-SQL, 'SCHEMA').to_i > 0 SELECT COUNT(*) FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid + LEFT JOIN pg_namespace n ON n.oid = i.relnamespace WHERE i.relkind = 'i' - AND i.relname = '#{index_name}' - AND t.relname = '#{table_name}' - AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) + AND i.relname = '#{index.identifier}' + AND t.relname = '#{table.identifier}' + AND n.nspname = #{index.schema ? "'#{index.schema}'" : 'ANY (current_schemas(false))'} SQL end # Returns an array of indexes for the given table. def indexes(table_name, name = nil) - result = query(<<-SQL, 'SCHEMA') - SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid - FROM pg_class t - INNER JOIN pg_index d ON t.oid = d.indrelid - INNER JOIN pg_class i ON d.indexrelid = i.oid - WHERE i.relkind = 'i' - AND d.indisprimary = 'f' - AND t.relname = '#{table_name}' - AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) + table = Utils.extract_schema_qualified_name(table_name.to_s) + + result = query(<<-SQL, 'SCHEMA') + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + LEFT JOIN pg_namespace n ON n.oid = i.relnamespace + WHERE i.relkind = 'i' + AND d.indisprimary = 'f' + AND t.relname = '#{table.identifier}' + AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'} ORDER BY i.relname SQL @@ -210,12 +267,12 @@ module ActiveRecord # Creates a schema for the given schema name. def create_schema schema_name - execute "CREATE SCHEMA #{schema_name}" + execute "CREATE SCHEMA #{quote_schema_name(schema_name)}" end # Drops the schema for the given schema name. - def drop_schema schema_name - execute "DROP SCHEMA #{schema_name} CASCADE" + def drop_schema(schema_name, options = {}) + execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE" end # Sets the schema search path to a string of comma-separated schema names. @@ -349,17 +406,19 @@ module ActiveRecord nil end - # Returns just a table's primary key - def primary_key(table) - pks = query(<<-end_sql, 'SCHEMA') - SELECT attr.attname - FROM pg_attribute attr - INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) - WHERE cons.contype = 'p' - AND cons.conrelid = '#{quote_table_name(table)}'::regclass - end_sql - return nil unless pks.count == 1 - pks[0][0] + def primary_keys(table_name) # :nodoc: + select_values(<<-SQL.strip_heredoc, 'SCHEMA') + WITH pk_constraint AS ( + SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint + WHERE contype = 'p' + AND conrelid = '#{quote_table_name(table_name)}'::regclass + ), cons AS ( + SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint + ) + SELECT attr.attname FROM pg_attribute attr + INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum + ORDER BY cons.rownum + SQL end # Renames a table. @@ -376,7 +435,7 @@ module ActiveRecord new_seq = "#{new_name}_#{pk}_seq" idx = "#{table_name}_pkey" new_idx = "#{new_name}_pkey" - execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" end @@ -410,11 +469,12 @@ module ActiveRecord end # Changes the default value of a table column. - def change_column_default(table_name, column_name, default) # :nodoc: + 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 @@ -446,8 +506,28 @@ module ActiveRecord execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}" end - def remove_index!(table_name, index_name) #:nodoc: - execute "DROP INDEX #{quote_table_name(index_name)}" + def remove_index(table_name, options = {}) #:nodoc: + table = Utils.extract_schema_qualified_name(table_name.to_s) + + if options.is_a?(Hash) && options.key?(:name) + provided_index = Utils.extract_schema_qualified_name(options[:name].to_s) + + options[:name] = provided_index.identifier + table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present? + + if provided_index.schema.present? && table.schema != provided_index.schema + raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'") + end + end + + index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options)) + algorithm = + if options.is_a?(Hash) && options.key?(:algorithm) + index_algorithms.fetch(options[:algorithm]) do + raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") + end + end + execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}" end # Renames an index of a table. Raises error if length of new |