From 340ff918a6b3772971721bc4b7a6c8074789a83f Mon Sep 17 00:00:00 2001 From: Jon Jensen Date: Sat, 27 Sep 2014 20:42:59 -0600 Subject: [PATCH] preserve db-specific options in schema.rb, fixes #148 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ` for postgres that's: * `ON UPDATE ` * `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 ` 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 ¯\_(ツ)_/¯ --- .../connection_adapters/mysql2_adapter.rb | 5 ++-- .../connection_adapters/postgresql_adapter.rb | 26 +++++++++++++++++-- lib/foreigner/schema_dumper.rb | 3 +++ test/foreigner/schema_dumper_test.rb | 1 + 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/foreigner/connection_adapters/mysql2_adapter.rb b/lib/foreigner/connection_adapters/mysql2_adapter.rb index 170d69b..cc83de8 100644 --- a/lib/foreigner/connection_adapters/mysql2_adapter.rb +++ b/lib/foreigner/connection_adapters/mysql2_adapter.rb @@ -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 diff --git a/lib/foreigner/connection_adapters/postgresql_adapter.rb b/lib/foreigner/connection_adapters/postgresql_adapter.rb index 1e78270..82c85d4 100644 --- a/lib/foreigner/connection_adapters/postgresql_adapter.rb +++ b/lib/foreigner/connection_adapters/postgresql_adapter.rb @@ -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 @@ -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 @@ -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 \ No newline at end of file +Foreigner::Adapter.safe_include :PostgreSQLAdapter, Foreigner::ConnectionAdapters::PostgreSQLAdapter diff --git a/lib/foreigner/schema_dumper.rb b/lib/foreigner/schema_dumper.rb index 9c6d749..35d20d7 100644 --- a/lib/foreigner/schema_dumper.rb +++ b/lib/foreigner/schema_dumper.rb @@ -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 diff --git a/test/foreigner/schema_dumper_test.rb b/test/foreigner/schema_dumper_test.rb index e1e5c10..340fead 100644 --- a/test/foreigner/schema_dumper_test.rb +++ b/test/foreigner/schema_dumper_test.rb @@ -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