pg_power 1.0.0
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.
- data/README.markdown +212 -0
- data/lib/core_ext/active_record/connection_adapters/abstract/schema_statements.rb +139 -0
- data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +135 -0
- data/lib/core_ext/active_record/schema_dumper.rb +40 -0
- data/lib/pg_power.rb +16 -0
- data/lib/pg_power/connection_adapters.rb +9 -0
- data/lib/pg_power/connection_adapters/abstract_adapter.rb +20 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/comment_methods.rb +62 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/foreigner_methods.rb +67 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/index_methods.rb +6 -0
- data/lib/pg_power/connection_adapters/abstract_adapter/schema_methods.rb +18 -0
- data/lib/pg_power/connection_adapters/foreign_key_definition.rb +5 -0
- data/lib/pg_power/connection_adapters/index_definition.rb +6 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter.rb +16 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/comment_methods.rb +79 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/foreigner_methods.rb +190 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/index_methods.rb +42 -0
- data/lib/pg_power/connection_adapters/postgresql_adapter/schema_methods.rb +22 -0
- data/lib/pg_power/connection_adapters/table.rb +17 -0
- data/lib/pg_power/connection_adapters/table/comment_methods.rb +58 -0
- data/lib/pg_power/connection_adapters/table/foreigner_methods.rb +51 -0
- data/lib/pg_power/engine.rb +46 -0
- data/lib/pg_power/migration.rb +4 -0
- data/lib/pg_power/migration/command_recorder.rb +13 -0
- data/lib/pg_power/migration/command_recorder/comment_methods.rb +52 -0
- data/lib/pg_power/migration/command_recorder/foreigner_methods.rb +29 -0
- data/lib/pg_power/migration/command_recorder/schema_methods.rb +39 -0
- data/lib/pg_power/schema_dumper.rb +21 -0
- data/lib/pg_power/schema_dumper/comment_methods.rb +36 -0
- data/lib/pg_power/schema_dumper/foreigner_methods.rb +58 -0
- data/lib/pg_power/schema_dumper/schema_methods.rb +51 -0
- data/lib/pg_power/tools.rb +56 -0
- data/lib/pg_power/version.rb +4 -0
- data/lib/tasks/pg_power_tasks.rake +4 -0
- metadata +213 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
# Patched version: 3.1.3
|
3
|
+
# Patched methods::
|
4
|
+
# * indexes
|
5
|
+
class SchemaDumper #:nodoc:
|
6
|
+
# Writes out index-related details to the schema stream
|
7
|
+
#
|
8
|
+
# == Patch reason:
|
9
|
+
# {ActiveRecord::SchemaDumper#indexes} does not support writing out
|
10
|
+
# details related to partial indexes.
|
11
|
+
#
|
12
|
+
# == Patch:
|
13
|
+
# Append :where clause if there's a partial index
|
14
|
+
#
|
15
|
+
def indexes(table, stream)
|
16
|
+
if (indexes = @connection.indexes(table)).any?
|
17
|
+
add_index_statements = indexes.map do |index|
|
18
|
+
statement_parts = [
|
19
|
+
('add_index ' + index.table.inspect),
|
20
|
+
index.columns.inspect,
|
21
|
+
(':name => ' + index.name.inspect),
|
22
|
+
]
|
23
|
+
statement_parts << ':unique => true' if index.unique
|
24
|
+
|
25
|
+
index_lengths = (index.lengths || []).compact
|
26
|
+
statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
|
27
|
+
|
28
|
+
# Patch
|
29
|
+
# Append :where clause if a partial index
|
30
|
+
statement_parts << (':where => ' + index.where.inspect) if index.where
|
31
|
+
|
32
|
+
' ' + statement_parts.join(', ')
|
33
|
+
end
|
34
|
+
|
35
|
+
stream.puts add_index_statements.sort.join("\n")
|
36
|
+
stream.puts
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/pg_power.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "pg_power/engine"
|
2
|
+
|
3
|
+
# Rails engine which allows to use some PostgreSQL features:
|
4
|
+
# * Schemas.
|
5
|
+
# * Comments on columns and tables.
|
6
|
+
# * Foreign keys.
|
7
|
+
# * Partial indexes.
|
8
|
+
module PgPower
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
autoload :Adapter
|
12
|
+
autoload :SchemaDumper
|
13
|
+
autoload :Tools
|
14
|
+
autoload :Migration
|
15
|
+
autoload :ConnectionAdapters
|
16
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module PgPower::ConnectionAdapters # :nodoc:
|
2
|
+
extend ActiveSupport::Autoload
|
3
|
+
|
4
|
+
autoload :AbstractAdapter
|
5
|
+
autoload :PostgreSQLAdapter, 'pg_power/connection_adapters/postgresql_adapter'
|
6
|
+
autoload :Table
|
7
|
+
autoload :ForeignKeyDefinition
|
8
|
+
autoload :IndexDefinition, 'pg_power/connection_adapters/index_definition'
|
9
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter} class.
|
2
|
+
module PgPower::ConnectionAdapters::AbstractAdapter
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
autoload :CommentMethods
|
7
|
+
autoload :ForeignerMethods
|
8
|
+
autoload :SchemaMethods
|
9
|
+
autoload :IndexMethods
|
10
|
+
|
11
|
+
include CommentMethods
|
12
|
+
include ForeignerMethods
|
13
|
+
include SchemaMethods
|
14
|
+
include IndexMethods
|
15
|
+
|
16
|
+
included do
|
17
|
+
alias_method_chain :create_table, :schema_option
|
18
|
+
alias_method_chain :drop_table , :schema_option
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter} with
|
2
|
+
# empty methods for comments feature.
|
3
|
+
module PgPower::ConnectionAdapters::AbstractAdapter::CommentMethods
|
4
|
+
def supports_comments?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
# Sets a comment on the given table.
|
9
|
+
#
|
10
|
+
# ===== Example
|
11
|
+
# ====== Creating a comment on phone_numbers table
|
12
|
+
# set_table_comment :phone_numbers, 'This table stores phone numbers that conform to the North American Numbering Plan.'
|
13
|
+
def set_table_comment(table_name, comment)
|
14
|
+
# Does nothing
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets a comment on a given column of a given table.
|
18
|
+
#
|
19
|
+
# ===== Example
|
20
|
+
# ====== Creating a comment on npa column of table phone_numbers
|
21
|
+
# set_column_comment :phone_numbers, :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
|
22
|
+
def set_column_comment(table_name, column_name, comment)
|
23
|
+
# Does nothing
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets comments on multiple columns. 'comments' is a hash of column_name => comment pairs.
|
27
|
+
#
|
28
|
+
# ===== Example
|
29
|
+
# ====== Setting comments on the columns of the phone_numbers table
|
30
|
+
# set_column_comments :phone_numbers, :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
|
31
|
+
# :nxx => 'Central Office Number'
|
32
|
+
def set_column_comments(table_name, comments)
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# Removes any comment from the given table.
|
37
|
+
#
|
38
|
+
# ===== Example
|
39
|
+
# ====== Removing comment from phone numbers table
|
40
|
+
# remove_table_comment :phone_numbers
|
41
|
+
def remove_table_comment(table_name)
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# Removes any comment from the given column of a given table.
|
46
|
+
#
|
47
|
+
# ===== Example
|
48
|
+
# ====== Removing comment from the npa column of table phone_numbers
|
49
|
+
# remove_column_comment :phone_numbers, :npa
|
50
|
+
def remove_column_comment(table_name, column_name)
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
# Removes any comment from the given columns of a given table.
|
55
|
+
#
|
56
|
+
# ===== Example
|
57
|
+
# ====== Removing comment from the npa and nxx columns of table phone_numbers
|
58
|
+
# remove_column_comments :phone_numbers, :npa, :nxx
|
59
|
+
def remove_column_comments(table_name, *column_names)
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter} with
|
2
|
+
# empty methods for foreign keys feature.
|
3
|
+
module PgPower::ConnectionAdapters::AbstractAdapter::ForeignerMethods
|
4
|
+
def supports_foreign_keys?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
|
8
|
+
# Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
|
9
|
+
#
|
10
|
+
# The foreign key will be named after the from and to tables unless you pass
|
11
|
+
# <tt>:name</tt> as an option.
|
12
|
+
#
|
13
|
+
# ===== Examples
|
14
|
+
# ====== Creating a foreign key
|
15
|
+
# add_foreign_key(:comments, :posts)
|
16
|
+
# generates
|
17
|
+
# ALTER TABLE `comments` ADD CONSTRAINT
|
18
|
+
# `comments_post_id_fk` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
|
19
|
+
#
|
20
|
+
# ====== Creating a named foreign key
|
21
|
+
# add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
|
22
|
+
# generates
|
23
|
+
# ALTER TABLE `comments` ADD CONSTRAINT
|
24
|
+
# `comments_belongs_to_posts` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
|
25
|
+
#
|
26
|
+
# ====== Creating a cascading foreign_key on a custom column
|
27
|
+
# add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
|
28
|
+
# generates
|
29
|
+
# ALTER TABLE `people` ADD CONSTRAINT
|
30
|
+
# `people_best_friend_id_fk` FOREIGN KEY (`best_friend_id`) REFERENCES `people` (`id`)
|
31
|
+
# ON DELETE SET NULL
|
32
|
+
#
|
33
|
+
# === Supported options
|
34
|
+
# [:column]
|
35
|
+
# Specify the column name on the from_table that references the to_table. By default this is guessed
|
36
|
+
# to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
|
37
|
+
# as the default <tt>:column</tt>.
|
38
|
+
# [:primary_key]
|
39
|
+
# Specify the column name on the to_table that is referenced by this foreign key. By default this is
|
40
|
+
# assumed to be "id".
|
41
|
+
# [:name]
|
42
|
+
# Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
|
43
|
+
# [:dependent]
|
44
|
+
# If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
|
45
|
+
# If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
|
46
|
+
# [:options]
|
47
|
+
# Any extra options you want appended to the foreign key definition.
|
48
|
+
def add_foreign_key(from_table, to_table, options = {})
|
49
|
+
end
|
50
|
+
|
51
|
+
# Remove the given foreign key from the table.
|
52
|
+
#
|
53
|
+
# ===== Examples
|
54
|
+
# ====== Remove the suppliers_company_id_fk in the suppliers table.
|
55
|
+
# remove_foreign_key :suppliers, :companies
|
56
|
+
# ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
|
57
|
+
# remove_foreign_key :accounts, :column => :branch_id
|
58
|
+
# ====== Remove the foreign key named party_foreign_key in the accounts table.
|
59
|
+
# remove_foreign_key :accounts, :name => :party_foreign_key
|
60
|
+
def remove_foreign_key(from_table, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return the foreign keys for the schema_dumper
|
64
|
+
def foreign_keys(table_name)
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Extends {ActiveRecord::ConnectionAdapters::AbstractAdapter}
|
2
|
+
# with methods for multi schema support.
|
3
|
+
module PgPower::ConnectionAdapters::AbstractAdapter::SchemaMethods
|
4
|
+
|
5
|
+
# Provides :schema option to +create_table+ method.
|
6
|
+
def create_table_with_schema_option(table_name, options = {}, &block)
|
7
|
+
schema_name = options.delete(:schema)
|
8
|
+
table_name = "#{schema_name}.#{table_name}" if schema_name
|
9
|
+
create_table_without_schema_option(table_name, options, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Provides :schema option to +drop_table+ method.
|
13
|
+
def drop_table_with_schema_option(table_name, options = {})
|
14
|
+
schema_name = options.delete(:schema)
|
15
|
+
table_name = "#{schema_name}.#{table_name}" if schema_name
|
16
|
+
drop_table_without_schema_option(table_name, options)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
module PgPower::ConnectionAdapters
|
2
|
+
# Structure to store index parameters
|
3
|
+
# Overrides ActiveRecord::ConnectionAdapters::IndexDefinition with the additional :where parameter
|
4
|
+
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :where)
|
5
|
+
end
|
6
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
|
2
|
+
# to support pg_power features.
|
3
|
+
module PgPower::ConnectionAdapters::PostgreSQLAdapter
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
autoload :SchemaMethods , 'pg_power/connection_adapters/postgresql_adapter/schema_methods'
|
8
|
+
autoload :CommentMethods , 'pg_power/connection_adapters/postgresql_adapter/comment_methods'
|
9
|
+
autoload :ForeignerMethods, 'pg_power/connection_adapters/postgresql_adapter/foreigner_methods'
|
10
|
+
autoload :IndexMethods , 'pg_power/connection_adapters/postgresql_adapter/index_methods'
|
11
|
+
|
12
|
+
include SchemaMethods
|
13
|
+
include CommentMethods
|
14
|
+
include ForeignerMethods
|
15
|
+
include IndexMethods
|
16
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
|
2
|
+
# to support comments feature.
|
3
|
+
module PgPower::ConnectionAdapters::PostgreSQLAdapter::CommentMethods
|
4
|
+
def supports_comments?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
# Executes SQL to set comment on table
|
9
|
+
# @param [String, Symbol] table_name name of table to set a comment on
|
10
|
+
# @param [String] comment
|
11
|
+
def set_table_comment(table_name, comment)
|
12
|
+
sql = "COMMENT ON TABLE #{quote_table_name(table_name)} IS $$#{comment}$$;"
|
13
|
+
execute sql
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes SQL to set comment on column.
|
17
|
+
# @param [String, Symbol] table_name
|
18
|
+
# @param [String, Symbol] column_name
|
19
|
+
# @param [String] comment
|
20
|
+
def set_column_comment(table_name, column_name, comment)
|
21
|
+
sql = "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS $$#{comment}$$;"
|
22
|
+
execute sql
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sets comments on columns of passed table.
|
26
|
+
# @param [String, Symbol] table_name
|
27
|
+
# @param [Hash] comments every key is a column name and value is a comment.
|
28
|
+
def set_column_comments(table_name, comments)
|
29
|
+
comments.each_pair do |column_name, comment|
|
30
|
+
set_column_comment table_name, column_name, comment
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Executes SQL to remove comment on passed table.
|
35
|
+
# @param [String, Symbol] table_name
|
36
|
+
def remove_table_comment(table_name)
|
37
|
+
sql = "COMMENT ON TABLE #{quote_table_name(table_name)} IS NULL;"
|
38
|
+
execute sql
|
39
|
+
end
|
40
|
+
|
41
|
+
# Executes SQL to remove comment on column.
|
42
|
+
# @param [String, Symbol] table_name
|
43
|
+
# @param [String, Symbol] column_name
|
44
|
+
def remove_column_comment(table_name, column_name)
|
45
|
+
sql = "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS NULL;"
|
46
|
+
execute sql
|
47
|
+
end
|
48
|
+
|
49
|
+
# Remove comments on passed table columns.
|
50
|
+
def remove_column_comments(table_name, *column_names)
|
51
|
+
column_names.each do |column_name|
|
52
|
+
remove_column_comment table_name, column_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Fetches all comments related to passed table.
|
57
|
+
# I returns table comment and column comments as well.
|
58
|
+
# ===Example
|
59
|
+
# comments("users") # => [[ "" , "Comment on table" ],
|
60
|
+
# ["id" , "Comment on id column" ],
|
61
|
+
# ["email", "Comment on email column"]]
|
62
|
+
def comments(table_name)
|
63
|
+
relation_name, schema_name = table_name.split(".", 2).reverse
|
64
|
+
schema_name ||= "public"
|
65
|
+
|
66
|
+
com = select_all <<-SQL
|
67
|
+
SELECT a.attname AS column_name, d.description AS comment
|
68
|
+
FROM pg_description d
|
69
|
+
JOIN pg_class c on c.oid = d.objoid
|
70
|
+
LEFT OUTER JOIN pg_attribute a ON c.oid = a.attrelid AND a.attnum = d.objsubid
|
71
|
+
JOIN pg_namespace ON c.relnamespace = pg_namespace.oid
|
72
|
+
WHERE c.relkind = 'r' AND c.relname = '#{relation_name}' AND
|
73
|
+
pg_namespace.nspname = '#{schema_name}'
|
74
|
+
SQL
|
75
|
+
com.map do |row|
|
76
|
+
[ row['column_name'], row['comment'] ]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module PgPower # :nodoc:
|
2
|
+
# Raised when an unexpected index exists
|
3
|
+
class IndexExistsError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
|
7
|
+
# to support foreign keys feature.
|
8
|
+
module ConnectionAdapters::PostgreSQLAdapter::ForeignerMethods
|
9
|
+
def supports_foreign_keys?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# Fetches information about foreign keys related to passed table.
|
14
|
+
# @param [String, Symbol] table_name name of table (e.g. "users", "music.bands")
|
15
|
+
# @return [Foreigner::ConnectionAdapters::ForeignKeyDefinition]
|
16
|
+
def foreign_keys(table_name)
|
17
|
+
relation, schema = table_name.to_s.split('.', 2).reverse
|
18
|
+
quoted_schema = schema ? "'#{schema}'" : "ANY (current_schemas(false))"
|
19
|
+
|
20
|
+
fk_info = select_all <<-SQL
|
21
|
+
SELECT nsp.nspname || '.' || t2.relname AS to_table,
|
22
|
+
a1.attname AS column ,
|
23
|
+
a2.attname AS primary_key,
|
24
|
+
c.conname AS name ,
|
25
|
+
c.confdeltype AS dependency
|
26
|
+
FROM pg_constraint c
|
27
|
+
JOIN pg_class t1 ON c.conrelid = t1.oid
|
28
|
+
JOIN pg_class t2 ON c.confrelid = t2.oid
|
29
|
+
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
30
|
+
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
31
|
+
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
32
|
+
JOIN pg_namespace nsp ON nsp.oid = t2.relnamespace
|
33
|
+
WHERE c.contype = 'f'
|
34
|
+
AND t1.relname = '#{relation}'
|
35
|
+
AND t3.nspname = #{quoted_schema}
|
36
|
+
ORDER BY c.conname
|
37
|
+
SQL
|
38
|
+
|
39
|
+
fk_info.map do |row|
|
40
|
+
options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']}
|
41
|
+
|
42
|
+
options[:dependent] = case row['dependency']
|
43
|
+
when 'c' then :delete
|
44
|
+
when 'n' then :nullify
|
45
|
+
when 'r' then :restrict
|
46
|
+
end
|
47
|
+
|
48
|
+
PgPower::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# (optionally disable triggers) and drop table
|
53
|
+
# changes adapted from https://github.com/matthuhiggins/foreigner/blob/e72ab9c454c156056d3f037d55e3359cd972af32/lib/foreigner/connection_adapters/sql2003.rb
|
54
|
+
# NOTE: disabling referential integrity requires superuser access in postgres. Default AR behavior is to just drop_table.
|
55
|
+
#
|
56
|
+
# == Options:
|
57
|
+
# * :force - force disabling of referential integrity
|
58
|
+
#
|
59
|
+
# Note: I don't know a good way to test this -mike 20120420
|
60
|
+
def drop_table(*args)
|
61
|
+
options = args.clone.extract_options!
|
62
|
+
if options[:force]
|
63
|
+
disable_referential_integrity { super }
|
64
|
+
else
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adds foreign key.
|
70
|
+
#
|
71
|
+
# Ensures that an index is created for the foreign key, unless :exclude_index is true.
|
72
|
+
#
|
73
|
+
# Raises a [PgPower::IndexExistsError] when :exclude_index is true, but the index already exists.
|
74
|
+
#
|
75
|
+
# == Options:
|
76
|
+
# * :column
|
77
|
+
# * :primary_key
|
78
|
+
# * :dependent
|
79
|
+
# * :exclude_index [Boolean]
|
80
|
+
#
|
81
|
+
# @param [String, Symbol] from_table
|
82
|
+
# @param [String, Symbol] to_table
|
83
|
+
# @param [Hash] options
|
84
|
+
#
|
85
|
+
def add_foreign_key(from_table, to_table, options = {})
|
86
|
+
options[:column] ||= id_column_name_from_table_name(to_table)
|
87
|
+
options[:exclude_index] ||= false
|
88
|
+
|
89
|
+
if index_exists?(from_table, options[:column]) and !options[:exclude_index]
|
90
|
+
raise PgPower::IndexExistsError, "The index, #{index_name(from_table, options[:column])}, already exists. Use :exclude_index => true when adding the foreign key."
|
91
|
+
end
|
92
|
+
|
93
|
+
sql = "ALTER TABLE #{quote_table_name(from_table)} #{add_foreign_key_sql(from_table, to_table, options)}"
|
94
|
+
execute(sql)
|
95
|
+
|
96
|
+
add_index(from_table, options[:column]) unless options[:exclude_index]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns chunk of SQL to add foreign key based on table names and options.
|
100
|
+
def add_foreign_key_sql(from_table, to_table, options = {})
|
101
|
+
foreign_key_name = foreign_key_name(from_table, options[:column], options)
|
102
|
+
primary_key = options[:primary_key] || "id"
|
103
|
+
dependency = dependency_sql(options[:dependent])
|
104
|
+
|
105
|
+
sql =
|
106
|
+
"ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " +
|
107
|
+
"FOREIGN KEY (#{quote_column_name(options[:column])}) " +
|
108
|
+
"REFERENCES #{quote_table_name(ActiveRecord::Migrator.proper_table_name(to_table))}(#{primary_key})"
|
109
|
+
sql << " #{dependency}" if dependency.present?
|
110
|
+
sql << " #{options[:options]}" if options[:options]
|
111
|
+
|
112
|
+
sql
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# TODO Determine if we can refactor the method signature
|
117
|
+
# remove_foreign_key(from_table, to_table_or_options_hash, options={}) => remove_foreign_key(from_table, to_table, options={})
|
118
|
+
#
|
119
|
+
# Removes foreign key.
|
120
|
+
# @param [String, Symbol] from_table
|
121
|
+
# @param [String, Hash] to_table_or_options_hash
|
122
|
+
#
|
123
|
+
# The flexible method signature allows calls of two principal forms. Examples:
|
124
|
+
#
|
125
|
+
# remove_foreign_key(from_table, options_hash)
|
126
|
+
#
|
127
|
+
# or:
|
128
|
+
#
|
129
|
+
# remove_foreign_key(from_table, to_table, options_hash)
|
130
|
+
#
|
131
|
+
def remove_foreign_key(from_table, to_table_or_options_hash, options={})
|
132
|
+
if Hash === to_table_or_options_hash
|
133
|
+
options = to_table_or_options_hash
|
134
|
+
column = options[:column]
|
135
|
+
foreign_key_name = foreign_key_name(from_table, column, options)
|
136
|
+
column ||= id_column_name_from_foreign_key_metadata(from_table, foreign_key_name)
|
137
|
+
else
|
138
|
+
column = id_column_name_from_table_name(to_table_or_options_hash)
|
139
|
+
foreign_key_name = foreign_key_name(from_table, column)
|
140
|
+
end
|
141
|
+
|
142
|
+
execute "ALTER TABLE #{quote_table_name(from_table)} #{remove_foreign_key_sql(foreign_key_name)}"
|
143
|
+
|
144
|
+
options[:exclude_index] ||= false
|
145
|
+
remove_index(from_table, column) unless options[:exclude_index] || !index_exists?(from_table, column)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns chunk of SQL to remove foreign key based on table name and options.
|
149
|
+
def remove_foreign_key_sql(foreign_key_name)
|
150
|
+
"DROP CONSTRAINT #{quote_column_name(foreign_key_name)}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Builds the foreign key column id from the referenced table
|
154
|
+
def id_column_name_from_table_name(table)
|
155
|
+
"#{table.to_s.split('.').last.singularize}_id"
|
156
|
+
end
|
157
|
+
private :id_column_name_from_table_name
|
158
|
+
|
159
|
+
# Extracts the foreign key column id from the foreign key metadata
|
160
|
+
# @param [String, Symbol] from_table
|
161
|
+
# @param [String] foreign_key_name
|
162
|
+
def id_column_name_from_foreign_key_metadata(from_table, foreign_key_name)
|
163
|
+
keys = foreign_keys(from_table)
|
164
|
+
this_key = keys.find {|key| key.options[:name] == foreign_key_name}
|
165
|
+
this_key.options[:column]
|
166
|
+
end
|
167
|
+
private :id_column_name_from_foreign_key_metadata
|
168
|
+
|
169
|
+
# Builds default name for constraint
|
170
|
+
def foreign_key_name(table, column, options = {})
|
171
|
+
if options[:name]
|
172
|
+
options[:name]
|
173
|
+
else
|
174
|
+
prefix = table.gsub(".", "_")
|
175
|
+
"#{prefix}_#{column}_fk"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
private :foreign_key_name
|
179
|
+
|
180
|
+
def dependency_sql(dependency)
|
181
|
+
case dependency
|
182
|
+
when :nullify then "ON DELETE SET NULL"
|
183
|
+
when :delete then "ON DELETE CASCADE"
|
184
|
+
when :restrict then "ON DELETE RESTRICT"
|
185
|
+
else ""
|
186
|
+
end
|
187
|
+
end
|
188
|
+
private :dependency_sql
|
189
|
+
end
|
190
|
+
end
|