Skip to content

Commit

Permalink
preserve db-specific options in schema.rb, fixes #148
Browse files Browse the repository at this point in the history
basically any valid db-specific thing that you can already specify via
`options: "..."` will be preserved on the way back out.

for mysql that's:
* `ON UPDATE <action>`

for postgres that's:
* `ON UPDATE <action>`
* `ON DELETE SET DEFAULT` (doesn't map to a `:dependent` value)
* `DEFERRABLE` (`NOT DEFERRABLE` is the default)
* `INITIALLY DEFERRED` (`INITIALLY IMMEDIATE` is the default)
* `NOT VALID`

postgres' `MATCH <type>` is not supported, but that's already problematic
for `:options` since it is incompatible with `:dependent` (due to where it
appears in the statement), so ¯\_(ツ)_/¯
  • Loading branch information
jenseng committed Sep 29, 2014
1 parent 9edb874 commit 340ff91
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 4 deletions.
5 changes: 3 additions & 2 deletions lib/foreigner/connection_adapters/mysql2_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ def foreign_keys(table_name)
fk_info.map do |row|
options = {column: row['column'], name: row['name'], primary_key: row['primary_key']}

if create_table_info =~ /CONSTRAINT #{quote_column_name(row['name'])} FOREIGN KEY .* REFERENCES .* ON DELETE (CASCADE|SET NULL|RESTRICT)/
options[:dependent] = case $1
if create_table_info =~ /CONSTRAINT #{quote_column_name(row['name'])} FOREIGN KEY .* REFERENCES .*\)( ON DELETE (CASCADE|SET NULL|RESTRICT))? ?(.*)$/
options[:dependent] = case $2
when 'CASCADE' then :delete
when 'SET NULL' then :nullify
when 'RESTRICT' then :restrict
end
options[:options] = $3 # e.g. ON UPDATE ...
end
ForeignKeyDefinition.new(table_name, row['to_table'], options)
end
Expand Down
26 changes: 24 additions & 2 deletions lib/foreigner/connection_adapters/postgresql_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ module ConnectionAdapters
module PostgreSQLAdapter
include Foreigner::ConnectionAdapters::Sql2003

DEPENDENCY_CODE_ACTIONS = {'c' => 'CASCADE', 'n' => 'SET NULL', 'r' => 'RESTRICT', 'd' => 'SET DEFAULT'}

def foreign_keys(table_name)
fk_info = select_all %{
SELECT t2.relname AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confdeltype AS dependency
SELECT t2.relname AS to_table
, a1.attname AS column
, a2.attname AS primary_key
, c.conname AS name
, c.confdeltype AS dependency
, c.confupdtype AS update_dependency
, c.condeferrable AS deferrable
, c.condeferred AS deferred
#{", c.convalidated AS valid" if postgresql_version >= 90100}
FROM pg_constraint c
JOIN pg_class t1 ON c.conrelid = t1.oid
JOIN pg_class t2 ON c.confrelid = t2.oid
Expand All @@ -22,11 +32,23 @@ def foreign_keys(table_name)
options = {column: row['column'], name: row['name'], primary_key: row['primary_key']}

options[:dependent] = case row['dependency']
# NO ACTION is the default
# SET DEFAULT is handled below, since it is postgres-specific
when 'c' then :delete
when 'n' then :nullify
when 'r' then :restrict
end

extras = []
extras << "ON DELETE SET DEFAULT" if row['dependency'] == 'd'
if update_action = DEPENDENCY_CODE_ACTIONS[row['update_dependency']]
extras << "ON UPDATE #{update_action}"
end
extras << 'DEFERRABLE' if row['deferrable'] == 't'
extras << 'INITIALLY DEFERRED' if row['deferred'] == 't'
extras << 'NOT VALID' if row['valid'] == 'f'
options[:options] = extras.join(" ")

ForeignKeyDefinition.new(table_name, row['to_table'], options)
end
end
Expand All @@ -35,4 +57,4 @@ def foreign_keys(table_name)
end

Foreigner::Adapter.safe_include :JdbcAdapter, Foreigner::ConnectionAdapters::PostgreSQLAdapter
Foreigner::Adapter.safe_include :PostgreSQLAdapter, Foreigner::ConnectionAdapters::PostgreSQLAdapter
Foreigner::Adapter.safe_include :PostgreSQLAdapter, Foreigner::ConnectionAdapters::PostgreSQLAdapter
3 changes: 3 additions & 0 deletions lib/foreigner/schema_dumper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ def dump_foreign_key(foreign_key)
if foreign_key.options[:dependent].present?
statement_parts << ('dependent: ' + foreign_key.options[:dependent].inspect)
end
if foreign_key.options[:options].present?
statement_parts << ('options: ' + foreign_key.options[:options].inspect)
end

statement_parts.join(', ')
end
Expand Down
1 change: 1 addition & 0 deletions test/foreigner/schema_dumper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def foreign_keys(table, stream)
assert_dump "add_foreign_key \"foos\", \"bars\", name: \"lulz\", primary_key: \"uuid\"", Foreigner::ConnectionAdapters::ForeignKeyDefinition.new('foos', 'bars', column: 'bar_id', primary_key: 'uuid', name: 'lulz')
assert_dump "add_foreign_key \"foos\", \"bars\", name: \"lulz\", dependent: :delete", Foreigner::ConnectionAdapters::ForeignKeyDefinition.new('foos', 'bars', column: 'bar_id', primary_key: 'id', name: 'lulz', dependent: :delete)
assert_dump "add_foreign_key \"foos\", \"bars\", name: \"lulz\", column: \"mamma_id\"", Foreigner::ConnectionAdapters::ForeignKeyDefinition.new('foos', 'bars', column: 'mamma_id', primary_key: 'id', name: 'lulz')
assert_dump "add_foreign_key \"foos\", \"bars\", name: \"lulz\", options: \"YOLO MAYBE DB-SPECIFIC!\"", Foreigner::ConnectionAdapters::ForeignKeyDefinition.new('foos', 'bars', column: 'bar_id', primary_key: 'id', name: 'lulz', options: "YOLO MAYBE DB-SPECIFIC!")
end

test 'all tables' do
Expand Down

0 comments on commit 340ff91

Please sign in to comment.