pg_saurus 2.6.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.markdown +141 -98
- data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +4 -2
- data/lib/pg_saurus/connection_adapters/abstract_adapter.rb +0 -2
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/foreign_key_methods.rb +105 -0
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/schema_methods.rb +8 -0
- data/lib/pg_saurus/connection_adapters/postgresql_adapter.rb +14 -2
- data/lib/pg_saurus/connection_adapters/table/comment_methods.rb +6 -6
- data/lib/pg_saurus/connection_adapters/table/trigger_methods.rb +2 -2
- data/lib/pg_saurus/connection_adapters/table.rb +0 -6
- data/lib/pg_saurus/connection_adapters.rb +0 -1
- data/lib/pg_saurus/create_index_concurrently.rb +1 -46
- data/lib/pg_saurus/migration/command_recorder.rb +0 -2
- data/lib/pg_saurus/schema_dumper/foreign_key_methods.rb +49 -0
- data/lib/pg_saurus/schema_dumper.rb +4 -3
- data/lib/pg_saurus/version.rb +1 -1
- metadata +10 -78
- data/lib/pg_saurus/connection_adapters/abstract_adapter/foreigner_methods.rb +0 -67
- data/lib/pg_saurus/connection_adapters/postgresql_adapter/foreigner_methods.rb +0 -221
- data/lib/pg_saurus/connection_adapters/table/foreigner_methods.rb +0 -51
- data/lib/pg_saurus/migration/command_recorder/foreigner_methods.rb +0 -31
- data/lib/pg_saurus/schema_dumper/foreigner_methods.rb +0 -63
@@ -1,221 +0,0 @@
|
|
1
|
-
module PgSaurus # :nodoc:
|
2
|
-
# Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
|
3
|
-
# to support foreign keys feature.
|
4
|
-
module ConnectionAdapters::PostgreSQLAdapter::ForeignerMethods
|
5
|
-
def supports_foreign_keys?
|
6
|
-
true
|
7
|
-
end
|
8
|
-
|
9
|
-
# Fetch information about foreign keys related to the passed table.
|
10
|
-
# @param [String, Symbol] table_name name of table (e.g. "users", "music.bands")
|
11
|
-
# @return [Foreigner::ConnectionAdapters::ForeignKeyDefinition]
|
12
|
-
def foreign_keys(table_name)
|
13
|
-
relation, schema = table_name.to_s.split('.', 2).reverse
|
14
|
-
quoted_schema = schema ? "'#{schema}'" : "ANY (current_schemas(false))"
|
15
|
-
|
16
|
-
fk_info = select_all <<-SQL
|
17
|
-
SELECT nsp.nspname || '.' || t2.relname AS to_table,
|
18
|
-
a1.attname AS column ,
|
19
|
-
a2.attname AS primary_key,
|
20
|
-
c.conname AS name ,
|
21
|
-
c.confdeltype AS dependency
|
22
|
-
FROM pg_constraint c
|
23
|
-
JOIN pg_class t1 ON c.conrelid = t1.oid
|
24
|
-
JOIN pg_class t2 ON c.confrelid = t2.oid
|
25
|
-
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
26
|
-
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
27
|
-
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
28
|
-
JOIN pg_namespace nsp ON nsp.oid = t2.relnamespace
|
29
|
-
WHERE c.contype = 'f'
|
30
|
-
AND t1.relname = '#{relation}'
|
31
|
-
AND t3.nspname = #{quoted_schema}
|
32
|
-
ORDER BY c.conname
|
33
|
-
SQL
|
34
|
-
|
35
|
-
fk_info.map do |row|
|
36
|
-
options = { :column => row['column'],
|
37
|
-
:name => row['name'],
|
38
|
-
:primary_key => row['primary_key'] }
|
39
|
-
|
40
|
-
options[:dependent] =
|
41
|
-
case row['dependency']
|
42
|
-
when 'c' then :delete
|
43
|
-
when 'n' then :nullify
|
44
|
-
when 'r' then :restrict
|
45
|
-
end
|
46
|
-
|
47
|
-
PgSaurus::ConnectionAdapters::ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Drop table and optionally disable triggers.
|
52
|
-
# Changes adapted from https://github.com/matthuhiggins/foreigner/blob/e72ab9c454c156056d3f037d55e3359cd972af32/lib/foreigner/connection_adapters/sql2003.rb
|
53
|
-
# NOTE: Disabling referential integrity requires superuser access in postgres.
|
54
|
-
# Default AR behavior is just to 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
|
-
# Add foreign key.
|
70
|
-
#
|
71
|
-
# Ensures that an index is created for the foreign key, unless :exclude_index is true.
|
72
|
-
#
|
73
|
-
# == Options:
|
74
|
-
# * :column
|
75
|
-
# * :primary_key
|
76
|
-
# * :dependent
|
77
|
-
# * :exclude_index [Boolean]
|
78
|
-
# * :concurrent_index [Boolean]
|
79
|
-
#
|
80
|
-
# @param [String, Symbol] from_table
|
81
|
-
# @param [String, Symbol] to_table
|
82
|
-
# @param [Hash] options
|
83
|
-
# @option options [String, Symbol] :column
|
84
|
-
# @option options [String, Symbol] :primary_key
|
85
|
-
# @option options [Hash] :dependent
|
86
|
-
# @option options [Boolean] :exclude_index
|
87
|
-
# @option options [Boolean] :concurrent_index
|
88
|
-
#
|
89
|
-
# @raise [PgSaurus::IndexExistsError] when :exclude_index is true, but the index already exists
|
90
|
-
def add_foreign_key(from_table, to_table, options = {})
|
91
|
-
options[:column] ||= id_column_name_from_table_name(to_table)
|
92
|
-
options[:exclude_index] ||= false
|
93
|
-
|
94
|
-
if index_exists?(from_table, options[:column]) && !options[:exclude_index]
|
95
|
-
raise PgSaurus::IndexExistsError,
|
96
|
-
"The index, #{index_name(from_table, options[:column])}, already exists." \
|
97
|
-
" Use :exclude_index => true when adding the foreign key."
|
98
|
-
end
|
99
|
-
|
100
|
-
sql = "ALTER TABLE #{quote_table_name(from_table)} #{add_foreign_key_sql(from_table, to_table, options)}"
|
101
|
-
execute(sql)
|
102
|
-
|
103
|
-
# GOTCHA:
|
104
|
-
# Index can not be created concurrently inside transaction in PostgreSQL.
|
105
|
-
# So, in case of concurrently created index with foreign key only
|
106
|
-
# foreign key will be created inside migration transaction and after
|
107
|
-
# closing transaction queries for index creation will be send to database.
|
108
|
-
# That's why I prevent here normal index creation in case of
|
109
|
-
# `concurrent_index` option is given.
|
110
|
-
# NOTE: Index creation after closing migration transaction could lead
|
111
|
-
# to weird effects when transaction moves smoothly, but index
|
112
|
-
# creation with error. In that case transaction will not be rolled back.
|
113
|
-
# As it was closed before even index was attempted to create.
|
114
|
-
# -- zekefast 2012-09-12
|
115
|
-
unless options[:exclude_index] || options[:concurrent_index]
|
116
|
-
add_index(from_table, options[:column])
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# Return the SQL code fragment to add the foreign key based on the table names and options.
|
121
|
-
def add_foreign_key_sql(from_table, to_table, options = {})
|
122
|
-
column = options[:column]
|
123
|
-
options_options = options[:options]
|
124
|
-
foreign_key_name = foreign_key_name(from_table, column, options)
|
125
|
-
primary_key = options[:primary_key] || "id"
|
126
|
-
dependency = dependency_sql(options[:dependent])
|
127
|
-
|
128
|
-
sql =
|
129
|
-
"ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " +
|
130
|
-
"FOREIGN KEY (#{quote_column_name(column)}) " +
|
131
|
-
"REFERENCES #{quote_table_name(ActiveRecord::Migrator.proper_table_name(to_table))}(#{primary_key})"
|
132
|
-
sql << " #{dependency}" if dependency.present?
|
133
|
-
sql << " #{options_options}" if options_options
|
134
|
-
|
135
|
-
sql
|
136
|
-
end
|
137
|
-
|
138
|
-
#
|
139
|
-
# TODO Determine if we can refactor the method signature
|
140
|
-
# remove_foreign_key(from_table, to_table_or_options_hash, options={}) => remove_foreign_key(from_table, to_table, options={})
|
141
|
-
#
|
142
|
-
# Remove the foreign key.
|
143
|
-
# @param [String, Symbol] from_table
|
144
|
-
# @param [String, Hash] to_table_or_options_hash
|
145
|
-
#
|
146
|
-
# The flexible method signature allows calls of two principal forms. Examples:
|
147
|
-
#
|
148
|
-
# remove_foreign_key(from_table, options_hash)
|
149
|
-
#
|
150
|
-
# or:
|
151
|
-
#
|
152
|
-
# remove_foreign_key(from_table, to_table, options_hash)
|
153
|
-
#
|
154
|
-
def remove_foreign_key(from_table, to_table_or_options_hash, options={})
|
155
|
-
if Hash === to_table_or_options_hash
|
156
|
-
options = to_table_or_options_hash
|
157
|
-
column = options[:column]
|
158
|
-
foreign_key_name = foreign_key_name(from_table, column, options)
|
159
|
-
column ||= id_column_name_from_foreign_key_metadata(from_table, foreign_key_name)
|
160
|
-
else
|
161
|
-
column = id_column_name_from_table_name(to_table_or_options_hash)
|
162
|
-
foreign_key_name = foreign_key_name(from_table, column)
|
163
|
-
end
|
164
|
-
|
165
|
-
execute "ALTER TABLE #{quote_table_name(from_table)} #{remove_foreign_key_sql(foreign_key_name)}"
|
166
|
-
|
167
|
-
options[:exclude_index] ||= false
|
168
|
-
unless options[:exclude_index] || !index_exists?(from_table, column) then
|
169
|
-
remove_index(from_table, column)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
# Return the SQL code fragment to remove foreign key based on table name and options.
|
174
|
-
def remove_foreign_key_sql(foreign_key_name)
|
175
|
-
"DROP CONSTRAINT #{quote_column_name(foreign_key_name)}"
|
176
|
-
end
|
177
|
-
|
178
|
-
# Build the foreign key column id from the referenced table.
|
179
|
-
def id_column_name_from_table_name(table)
|
180
|
-
"#{table.to_s.split('.').last.singularize}_id"
|
181
|
-
end
|
182
|
-
|
183
|
-
# Extract the foreign key column id from the foreign key metadata.
|
184
|
-
# @param [String, Symbol] from_table
|
185
|
-
# @param [String] foreign_key_name
|
186
|
-
def id_column_name_from_foreign_key_metadata(from_table, foreign_key_name)
|
187
|
-
keys = foreign_keys(from_table)
|
188
|
-
this_key = keys.find {|key| key.options[:name] == foreign_key_name}
|
189
|
-
this_key.options[:column]
|
190
|
-
end
|
191
|
-
private :id_column_name_from_foreign_key_metadata
|
192
|
-
|
193
|
-
# Build default name for constraint.
|
194
|
-
def foreign_key_name(table, column, options = {})
|
195
|
-
name = options[:name]
|
196
|
-
|
197
|
-
if !name.nil? then
|
198
|
-
name
|
199
|
-
else
|
200
|
-
prefix = table.gsub(".", "_")
|
201
|
-
"#{prefix}_#{column}_fk"
|
202
|
-
end
|
203
|
-
end
|
204
|
-
private :foreign_key_name
|
205
|
-
|
206
|
-
# Get the SQL code fragment that represents dependency for a constraint.
|
207
|
-
#
|
208
|
-
# @param dependency [Symbol] :nullify, :delete or :restrict
|
209
|
-
#
|
210
|
-
# @return [String]
|
211
|
-
def dependency_sql(dependency)
|
212
|
-
case dependency
|
213
|
-
when :nullify then "ON DELETE SET NULL"
|
214
|
-
when :delete then "ON DELETE CASCADE"
|
215
|
-
when :restrict then "ON DELETE RESTRICT"
|
216
|
-
else ""
|
217
|
-
end
|
218
|
-
end
|
219
|
-
private :dependency_sql
|
220
|
-
end
|
221
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# Provides methods to extend ActiveRecord::ConnectionAdapters::Table
|
2
|
-
# to support foreign keys feature.
|
3
|
-
module PgSaurus::ConnectionAdapters::Table::ForeignerMethods
|
4
|
-
# Adds a new foreign key to the table. +to_table+ can be a single Symbol, or
|
5
|
-
# an Array of Symbols. See SchemaStatements#add_foreign_key
|
6
|
-
#
|
7
|
-
# ===== Examples
|
8
|
-
# ====== Creating a simple foreign key
|
9
|
-
# t.foreign_key(:people)
|
10
|
-
# ====== Defining the column
|
11
|
-
# t.foreign_key(:people, :column => :sender_id)
|
12
|
-
# ====== Creating a named foreign key
|
13
|
-
# t.foreign_key(:people, :column => :sender_id, :name => 'sender_foreign_key')
|
14
|
-
# ====== Defining the column of the +to_table+.
|
15
|
-
# t.foreign_key(:people, :column => :sender_id, :primary_key => :person_id)
|
16
|
-
def foreign_key(to_table, options = {})
|
17
|
-
@base.add_foreign_key(@table_name, to_table, options)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Remove the given foreign key from the table.
|
21
|
-
#
|
22
|
-
# ===== Examples
|
23
|
-
# ====== Remove the suppliers_company_id_fk in the suppliers table.
|
24
|
-
# change_table :suppliers do |t|
|
25
|
-
# t.remove_foreign_key :companies
|
26
|
-
# end
|
27
|
-
# ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
|
28
|
-
# change_table :accounts do |t|
|
29
|
-
# t.remove_foreign_key :column => :branch_id
|
30
|
-
# end
|
31
|
-
# ====== Remove the foreign key named party_foreign_key in the accounts table.
|
32
|
-
# change_table :accounts do |t|
|
33
|
-
# t.remove_index :name => :party_foreign_key
|
34
|
-
# end
|
35
|
-
def remove_foreign_key(options)
|
36
|
-
@base.remove_foreign_key(@table_name, options)
|
37
|
-
end
|
38
|
-
|
39
|
-
# Deprecated
|
40
|
-
def references_with_foreign_keys(*args)
|
41
|
-
options = args.extract_options!
|
42
|
-
|
43
|
-
if options.delete(:foreign_key)
|
44
|
-
p ActiveSupport::Deprecation.send(:deprecation_message, caller,
|
45
|
-
":foreign_key in t.references is deprecated. " \
|
46
|
-
"Use t.foreign_key instead")
|
47
|
-
end
|
48
|
-
|
49
|
-
references_without_foreign_keys(*(args.dup << options))
|
50
|
-
end
|
51
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# Provides methods to extend ActiveRecord::Migration::CommandRecorder to
|
2
|
-
# support foreign keys feature.
|
3
|
-
module PgSaurus::Migration::CommandRecorder::ForeignerMethods
|
4
|
-
# :nodoc:
|
5
|
-
def add_foreign_key(*args)
|
6
|
-
record(:add_foreign_key, args)
|
7
|
-
end
|
8
|
-
|
9
|
-
# :nodoc:
|
10
|
-
def remove_foreign_key(*args)
|
11
|
-
record(:remove_foreign_key, args)
|
12
|
-
end
|
13
|
-
|
14
|
-
# :nodoc:
|
15
|
-
def invert_add_foreign_key(args)
|
16
|
-
from_table, to_table, add_options = *args
|
17
|
-
add_options ||= {}
|
18
|
-
add_name_option = add_options[:name]
|
19
|
-
add_column_option = add_options[:column]
|
20
|
-
|
21
|
-
if add_name_option then
|
22
|
-
options = {:name => add_name_option}
|
23
|
-
elsif add_column_option
|
24
|
-
options = {:column => add_column_option}
|
25
|
-
else
|
26
|
-
options = to_table
|
27
|
-
end
|
28
|
-
|
29
|
-
[:remove_foreign_key, [from_table, options]]
|
30
|
-
end
|
31
|
-
end
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# Provides methods to extend ActiveRecord::SchemaDumper to dump
|
2
|
-
# foreign keys.
|
3
|
-
module PgSaurus::SchemaDumper::ForeignerMethods
|
4
|
-
# Hooks ActiveRecord::SchemaDumper#table method to dump foreign keys.
|
5
|
-
def tables_with_foreign_keys(stream)
|
6
|
-
tables_without_foreign_keys(stream)
|
7
|
-
|
8
|
-
table_names = @connection.tables.sort
|
9
|
-
|
10
|
-
table_names.sort.each do |table|
|
11
|
-
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
|
12
|
-
case ignored
|
13
|
-
when String; table == ignored
|
14
|
-
when Regexp; table =~ ignored
|
15
|
-
else
|
16
|
-
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
17
|
-
end
|
18
|
-
end
|
19
|
-
foreign_keys(table, stream)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
# Find all foreign keys on passed table and writes appropriate
|
25
|
-
# statements to stream.
|
26
|
-
def foreign_keys(table_name, stream)
|
27
|
-
if (foreign_keys = @connection.foreign_keys(table_name)).any?
|
28
|
-
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
29
|
-
options = foreign_key.options
|
30
|
-
table_from_key = foreign_key.to_table
|
31
|
-
statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ]
|
32
|
-
statement_parts << table_from_key.inspect
|
33
|
-
statement_parts << (':name => ' + options[:name].inspect)
|
34
|
-
|
35
|
-
column_from_options = options[:column]
|
36
|
-
primary_key_from_options = options[:primary_key]
|
37
|
-
dependent_from_options = options[:dependent]
|
38
|
-
|
39
|
-
if column_from_options != "#{table_from_key.singularize}_id"
|
40
|
-
statement_parts << (":column => #{column_from_options.inspect}")
|
41
|
-
end
|
42
|
-
if primary_key_from_options != 'id'
|
43
|
-
statement_parts << (":primary_key => #{primary_key_from_options.inspect}")
|
44
|
-
end
|
45
|
-
if dependent_from_options.present?
|
46
|
-
statement_parts << (":dependent => #{dependent_from_options.inspect}")
|
47
|
-
end
|
48
|
-
|
49
|
-
# Always exclude the index
|
50
|
-
# If an index was created in a migration, it will get dumped to the schema
|
51
|
-
# separately from the foreign key. This will raise an exception if
|
52
|
-
# add_foreign_key is run without :exclude_index => true.
|
53
|
-
statement_parts << (':exclude_index => true')
|
54
|
-
|
55
|
-
' ' + statement_parts.join(', ')
|
56
|
-
end
|
57
|
-
|
58
|
-
stream.puts add_foreign_key_statements.sort.join("\n")
|
59
|
-
stream.puts
|
60
|
-
end
|
61
|
-
end
|
62
|
-
private :foreign_keys
|
63
|
-
end
|