aspgems-redhillonrails_core 2.0.0.beta1

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.
Files changed (39) hide show
  1. data/CHANGELOG +194 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README +161 -0
  5. data/Rakefile +59 -0
  6. data/init.rb +1 -0
  7. data/lib/redhillonrails_core.rb +46 -0
  8. data/lib/redhillonrails_core/active_record/base.rb +45 -0
  9. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/column.rb +14 -0
  10. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/foreign_key_definition.rb +63 -0
  11. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/index_definition.rb +11 -0
  12. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/schema_statements.rb +23 -0
  13. data/lib/redhillonrails_core/active_record/connection_adapters/abstract/table_definition.rb +27 -0
  14. data/lib/redhillonrails_core/active_record/connection_adapters/abstract_adapter.rb +84 -0
  15. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_adapter.rb +131 -0
  16. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_column.rb +8 -0
  17. data/lib/redhillonrails_core/active_record/connection_adapters/postgresql_adapter.rb +131 -0
  18. data/lib/redhillonrails_core/active_record/connection_adapters/sqlite3_adapter.rb +115 -0
  19. data/lib/redhillonrails_core/active_record/schema.rb +25 -0
  20. data/lib/redhillonrails_core/active_record/schema_dumper.rb +75 -0
  21. data/lib/redhillonrails_core/version.rb +3 -0
  22. data/lib/tasks/db/comments.rake +9 -0
  23. data/redhillonrails_core.gemspec +27 -0
  24. data/spec/connections/mysql/connection.rb +18 -0
  25. data/spec/connections/mysql2/connection.rb +18 -0
  26. data/spec/connections/postgresql/connection.rb +15 -0
  27. data/spec/connections/sqlite3/connection.rb +14 -0
  28. data/spec/foreign_key_definition_spec.rb +21 -0
  29. data/spec/foreign_key_spec.rb +100 -0
  30. data/spec/index_definition_spec.rb +145 -0
  31. data/spec/index_spec.rb +67 -0
  32. data/spec/models/comment.rb +5 -0
  33. data/spec/models/post.rb +6 -0
  34. data/spec/models/user.rb +5 -0
  35. data/spec/schema/schema.rb +21 -0
  36. data/spec/schema_dumper_spec.rb +138 -0
  37. data/spec/spec_helper.rb +16 -0
  38. data/spec/support/reference.rb +66 -0
  39. metadata +159 -0
@@ -0,0 +1,45 @@
1
+ module RedhillonrailsCore::ActiveRecord
2
+ module Base
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def self.extended(base)
9
+ class << base
10
+ alias_method_chain :abstract_class?, :redhillonrails_core
11
+ alias_method_chain :reset_column_information, :redhillonrails_core
12
+ end
13
+ end
14
+
15
+ def base_class?
16
+ self == base_class
17
+ end
18
+
19
+ def abstract_class_with_redhillonrails_core?
20
+ abstract_class_without_redhillonrails_core? || !(name =~ /^Abstract/).nil?
21
+ end
22
+
23
+ def reset_column_information_with_redhillonrails_core
24
+ reset_column_information_without_redhillonrails_core
25
+ @indexes = @foreign_keys = nil
26
+ end
27
+
28
+ def pluralized_table_name(table_name)
29
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
30
+ end
31
+
32
+ def indexes
33
+ @indexes ||= connection.indexes(table_name, "#{name} Indexes")
34
+ end
35
+
36
+ def foreign_keys
37
+ @foreign_keys ||= connection.foreign_keys(table_name, "#{name} Foreign Keys")
38
+ end
39
+
40
+ def reverse_foreign_keys
41
+ connection.reverse_foreign_keys(table_name, "#{name} Reverse Foreign Keys")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module Column
3
+
4
+ def required_on
5
+ if null
6
+ nil
7
+ elsif default.nil?
8
+ :save
9
+ else
10
+ :update
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ class ForeignKeyDefinition < Struct.new(:name, :table_name, :column_names, :references_table_name, :references_column_names, :on_update, :on_delete, :deferrable)
3
+ ACTIONS = {:cascade => "CASCADE", :restrict => "RESTRICT", :set_null => "SET NULL", :set_default => "SET DEFAULT", :no_action => "NO ACTION"}.freeze
4
+
5
+ def initialize(name, table_name, column_names, references_table_name, references_column_names, on_update = nil, on_delete = nil, deferrable = nil)
6
+ super(name, unquote(table_name), unquote(column_names), unquote(references_table_name), unquote(references_column_names), on_update, on_delete, deferrable)
7
+ end
8
+
9
+ def to_dump
10
+ dump = "add_foreign_key"
11
+ dump << " #{table_name.inspect}, [#{Array(column_names).collect { |name| name.inspect }.join(', ')}]"
12
+ dump << ", #{references_table_name.inspect}, [#{Array(references_column_names).collect { |name| name.inspect }.join(', ')}]"
13
+ dump << ", :on_update => :#{on_update}" if on_update
14
+ dump << ", :on_delete => :#{on_delete}" if on_delete
15
+ dump << ", :deferrable => #{deferrable}" if deferrable
16
+ dump << ", :name => #{name.inspect}" if name
17
+ dump
18
+ end
19
+
20
+ def to_sql
21
+ sql = name ? "CONSTRAINT #{name} " : ""
22
+ sql << "FOREIGN KEY (#{Array(quoted_column_names).join(", ")}) REFERENCES #{quoted_references_table_name} (#{Array(quoted_references_column_names).join(", ")})"
23
+ sql << " ON UPDATE #{ACTIONS[on_update]}" if on_update
24
+ sql << " ON DELETE #{ACTIONS[on_delete]}" if on_delete
25
+ sql << " DEFERRABLE" if deferrable
26
+ sql
27
+ end
28
+
29
+ alias :to_s :to_sql
30
+
31
+ def quoted_column_names
32
+ Array(column_names).collect { |name| ::ActiveRecord::Base.connection.quote_column_name(name) }
33
+ end
34
+
35
+ def quoted_references_column_names
36
+ Array(references_column_names).collect { |name| ::ActiveRecord::Base.connection.quote_column_name(name) }
37
+ end
38
+
39
+ # def quoted_table_name
40
+ # ::ActiveRecord::Base.connection.quote_table_name(table_name)
41
+ # end
42
+ #
43
+ def quoted_references_table_name
44
+ ::ActiveRecord::Base.connection.quote_table_name(references_table_name)
45
+ end
46
+
47
+ def quote(name)
48
+ ::ActiveRecord::Base.connection.quote(name)
49
+ end
50
+
51
+ def unquote(names)
52
+ if names.is_a?(Array)
53
+ names.collect { |name| __unqoute(name) }
54
+ else
55
+ __unqoute(names)
56
+ end
57
+ end
58
+
59
+ def __unqoute(value)
60
+ value.to_s.sub(/^["`](.*)["`]$/, '\1')
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module IndexDefinition
3
+ def case_sensitive?
4
+ @case_sensitive.nil? ? true : @case_sensitive
5
+ end
6
+
7
+ def case_sensitive=(case_sensitive)
8
+ @case_sensitive = case_sensitive
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module SchemaStatements
3
+ def self.included(base)
4
+ base.module_eval do
5
+ alias_method_chain :create_table, :redhillonrails_core
6
+ end
7
+ end
8
+
9
+ def create_table_with_redhillonrails_core(name, options = {})
10
+ if options.include?(:comment)
11
+ options = options.dup
12
+ comment = options.delete(:comment)
13
+ end
14
+
15
+ create_table_without_redhillonrails_core(name, options) do |table_defintion|
16
+ table_defintion.name = name
17
+ yield table_defintion if block_given?
18
+ end
19
+
20
+ set_table_comment(name, comment) if comment
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module TableDefinition
3
+ def self.included(base)
4
+ base.class_eval do
5
+ attr_accessor :name
6
+ alias_method_chain :initialize, :redhillonrails_core
7
+ alias_method_chain :to_sql, :redhillonrails_core
8
+ end
9
+ end
10
+
11
+ def initialize_with_redhillonrails_core(*args)
12
+ initialize_without_redhillonrails_core(*args)
13
+ @foreign_keys = []
14
+ end
15
+
16
+ def foreign_key(column_names, references_table_name, references_column_names, options = {})
17
+ @foreign_keys << ForeignKeyDefinition.new(options[:name], nil, column_names, ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
18
+ self
19
+ end
20
+
21
+ def to_sql_with_redhillonrails_core
22
+ sql = to_sql_without_redhillonrails_core
23
+ sql << ', ' << @foreign_keys * ', ' unless @foreign_keys.empty? || ActiveRecord::Schema.defining?
24
+ sql
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,84 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module AbstractAdapter
3
+ def self.included(base)
4
+ base.module_eval do
5
+ alias_method_chain :initialize, :redhillonrails_core
6
+ alias_method_chain :drop_table, :redhillonrails_core
7
+ end
8
+ end
9
+
10
+ def initialize_with_redhillonrails_core(*args)
11
+ initialize_without_redhillonrails_core(*args)
12
+ adapter = nil
13
+ case adapter_name
14
+ # name of MySQL adapter depends on mysql gem
15
+ # * with mysql gem adapter is named MySQL
16
+ # * with mysql2 gem adapter is named Mysql2
17
+ # Here we handle this and hopefully futher adapter names
18
+ when /^MySQL/i
19
+ adapter = 'MysqlAdapter'
20
+ when 'PostgreSQL'
21
+ adapter = 'PostgresqlAdapter'
22
+ when 'SQLite'
23
+ adapter = 'Sqlite3Adapter'
24
+ end
25
+ if adapter
26
+ adapter_module = RedhillonrailsCore::ActiveRecord::ConnectionAdapters.const_get(adapter)
27
+ self.class.send(:include, adapter_module) unless self.class.include?(adapter_module)
28
+ end
29
+ # Needed from mysql2 >= 0.2.7
30
+ if adapter_name =~ /^Mysql2/i && defined?(ActiveRecord::ConnectionAdapters::Mysql2IndexDefinition)
31
+ index_definition_module = RedhillonrailsCore::ActiveRecord::ConnectionAdapters::IndexDefinition
32
+ ActiveRecord::ConnectionAdapters::Mysql2IndexDefinition.send(:include, index_definition_module) unless ActiveRecord::ConnectionAdapters::Mysql2IndexDefinition.include?(index_definition_module)
33
+ end
34
+ end
35
+
36
+ def create_view(view_name, definition)
37
+ execute "CREATE VIEW #{view_name} AS #{definition}"
38
+ end
39
+
40
+ def drop_view(view_name)
41
+ execute "DROP VIEW #{view_name}"
42
+ end
43
+
44
+ def views(name = nil)
45
+ []
46
+ end
47
+
48
+ def view_definition(view_name, name = nil)
49
+ end
50
+
51
+ def foreign_keys(table_name, name = nil)
52
+ []
53
+ end
54
+
55
+ def reverse_foreign_keys(table_name, name = nil)
56
+ []
57
+ end
58
+
59
+ def add_foreign_key(table_name, column_names, references_table_name, references_column_names, options = {})
60
+ foreign_key = ForeignKeyDefinition.new(options[:name], table_name, column_names, ActiveRecord::Migrator.proper_table_name(references_table_name), references_column_names, options[:on_update], options[:on_delete], options[:deferrable])
61
+ execute "ALTER TABLE #{table_name} ADD #{foreign_key}"
62
+ end
63
+
64
+ def remove_foreign_key(table_name, foreign_key_name)
65
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{foreign_key_name}"
66
+ end
67
+
68
+ def drop_table_with_redhillonrails_core(name, options = {})
69
+ reverse_foreign_keys(name).each do |foreign_key|
70
+ begin
71
+ remove_foreign_key(foreign_key.table_name, foreign_key.name)
72
+ rescue Exception => e
73
+ # TODO spec this
74
+ #there is a problem when using rollback if two tables have
75
+ #similar names. In that case, the plugin will try to remove a
76
+ #non-existing column and raise an exception. I rescue the
77
+ #exception so the migration can proceed
78
+ nil
79
+ end
80
+ end
81
+ drop_table_without_redhillonrails_core(name, options)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,131 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module MysqlAdapter
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :remove_column, :redhillonrails_core
6
+ alias_method_chain :remove_columns, :redhillonrails_core
7
+ end
8
+ end
9
+
10
+ def set_table_comment(table_name, comment)
11
+ execute "ALTER TABLE #{table_name} COMMENT='#{quote_string(comment)}'"
12
+ end
13
+
14
+ def clear_table_comment(table_name)
15
+ execute "ALTER TABLE #{table_name} COMMENT=''"
16
+ end
17
+
18
+ def remove_foreign_key(table_name, foreign_key_name)
19
+ execute "ALTER TABLE #{table_name} DROP FOREIGN KEY #{foreign_key_name}"
20
+ end
21
+
22
+ def remove_column_with_redhillonrails_core(table_name, *column_names)
23
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
24
+ column_names.flatten.each do |column_name|
25
+ foreign_keys(table_name).select { |foreign_key| foreign_key.column_names.include?(column_name.to_s) }.each do |foreign_key|
26
+ remove_foreign_key(table_name, foreign_key.name)
27
+ end
28
+ remove_column_without_redhillonrails_core(table_name, column_name)
29
+ end
30
+ end
31
+ alias :remove_columns_with_redhillonrails_core :remove_column_with_redhillonrails_core
32
+
33
+ def foreign_keys(table_name, name = nil)
34
+ results = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}", name)
35
+
36
+ foreign_keys = []
37
+
38
+ results.each do |row|
39
+ row[1].each_line do |line|
40
+ if line =~ /^ CONSTRAINT [`"](.+?)[`"] FOREIGN KEY \([`"](.+?)[`"]\) REFERENCES [`"](.+?)[`"] \((.+?)\)( ON DELETE (.+?))?( ON UPDATE (.+?))?,?$/
41
+ name = $1
42
+ column_names = $2
43
+ references_table_name = $3
44
+ references_column_names = $4
45
+ on_update = $8
46
+ on_delete = $6
47
+ on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
48
+ on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
49
+
50
+ foreign_keys << ForeignKeyDefinition.new(name,
51
+ table_name, column_names.gsub('`', '').split(', '),
52
+ references_table_name, references_column_names.gsub('`', '').split(', '),
53
+ on_update, on_delete)
54
+ end
55
+ end
56
+ end
57
+
58
+ foreign_keys
59
+ end
60
+
61
+ # def reverse_foreign_keys(table_name, name = nil)
62
+ # results = execute(<<-SQL, name)
63
+ # SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
64
+ # FROM information_schema.key_column_usage
65
+ # WHERE table_schema = SCHEMA()
66
+ # AND referenced_table_schema = table_schema
67
+ # ORDER BY constraint_name, ordinal_position;
68
+ # SQL
69
+ # current_foreign_key = nil
70
+ # foreign_keys = []
71
+ #
72
+ # results.each do |row|
73
+ # next unless table_name.casecmp(row[3]) == 0
74
+ # if current_foreign_key != row[0]
75
+ # foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
76
+ # current_foreign_key = row[0]
77
+ # end
78
+ #
79
+ # foreign_keys.last.column_names << row[2]
80
+ # foreign_keys.last.references_column_names << row[4]
81
+ # end
82
+ #
83
+ # foreign_keys
84
+ # end
85
+
86
+ def reverse_foreign_keys(table_name, name = nil)
87
+ # @@schema ||= nil
88
+ # @@schema_version ||= 0
89
+ # current_version = ActiveRecord::Migrator.current_version
90
+ # if @@schema.nil? || @@schema_version != current_version
91
+ # @@schema_version = current_version
92
+ # ans = execute(<<-SQL, name)
93
+ # SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
94
+ # FROM information_schema.key_column_usage
95
+ # WHERE table_schema = SCHEMA()
96
+ # AND referenced_table_schema = table_schema
97
+ # ORDER BY constraint_name, ordinal_position;
98
+ # SQL
99
+ # @@schema = []
100
+ # ans.each do | row |
101
+ # @@schema << [row[0], row[1], row[2], row[3], row[4]]
102
+ # end
103
+ # end
104
+ # results = @@schema
105
+
106
+ results = execute(<<-SQL, name)
107
+ SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
108
+ FROM information_schema.key_column_usage
109
+ WHERE table_schema = SCHEMA()
110
+ AND referenced_table_schema = table_schema
111
+ ORDER BY constraint_name, ordinal_position;
112
+ SQL
113
+
114
+ current_foreign_key = nil
115
+ foreign_keys = []
116
+
117
+ results.each do |row|
118
+ next if row[3] != table_name
119
+ if current_foreign_key != row[0]
120
+ foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
121
+ current_foreign_key = row[0]
122
+ end
123
+
124
+ foreign_keys.last.column_names << row[2]
125
+ foreign_keys.last.references_column_names << row[4]
126
+ end
127
+
128
+ foreign_keys
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,8 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module MysqlColumn
3
+ def initialize(name, default, sql_type = nil, null = true)
4
+ default = nil if !null && default.blank?
5
+ super
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,131 @@
1
+ module RedhillonrailsCore::ActiveRecord::ConnectionAdapters
2
+ module PostgresqlAdapter
3
+ def self.included(base)
4
+ base.class_eval do
5
+ alias_method_chain :indexes, :redhillonrails_core
6
+ end
7
+ end
8
+
9
+ def set_table_comment(table_name, comment)
10
+ execute "COMMENT ON TABLE #{table_name} IS '#{quote_string(comment)}'"
11
+ end
12
+
13
+ def clear_table_comment(table_name)
14
+ execute "COMMENT ON TABLE #{table_name} IS NULL"
15
+ end
16
+
17
+ def add_index(table_name, column_name, options = {})
18
+ column_names = Array(column_name)
19
+ index_name = index_name(table_name, :column => column_names)
20
+
21
+ if Hash === options # legacy support, since this param was a string
22
+ index_type = options[:unique] ? "UNIQUE" : ""
23
+ index_name = options[:name] || index_name
24
+ else
25
+ index_type = options
26
+ end
27
+
28
+ quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
29
+
30
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names.join(", ")})"
31
+ end
32
+
33
+ def indexes_with_redhillonrails_core(table_name, name = nil)
34
+ indexes = indexes_without_redhillonrails_core(table_name, name)
35
+ result = query(<<-SQL, name)
36
+ SELECT c2.relname, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)
37
+ FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
38
+ WHERE c.relname = '#{table_name}'
39
+ AND c.oid = i.indrelid AND i.indexrelid = c2.oid
40
+ AND i.indisprimary = 'f'
41
+ AND i.indexprs IS NOT NULL
42
+ ORDER BY 1
43
+ SQL
44
+
45
+ result.each do |row|
46
+ if row[2]=~ /\((.*LOWER\([^:]+(::text)?\).*)\)$/i
47
+ indexes.delete_if { |index| index.name == row[0] }
48
+ column_names = $1.split(", ").map do |name|
49
+ name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
50
+ name = $1 if name =~ /^"(.*)"$/
51
+ name
52
+ end
53
+ index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", column_names)
54
+ index.case_sensitive = false
55
+ indexes << index
56
+ end
57
+ end
58
+
59
+ indexes
60
+ end
61
+
62
+ def foreign_keys(table_name, name = nil)
63
+ load_foreign_keys(<<-SQL, name)
64
+ SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
65
+ FROM pg_class t, pg_constraint f
66
+ WHERE f.conrelid = t.oid
67
+ AND f.contype = 'f'
68
+ AND t.relname = '#{table_name}'
69
+ SQL
70
+ end
71
+
72
+ def reverse_foreign_keys(table_name, name = nil)
73
+ load_foreign_keys(<<-SQL, name)
74
+ SELECT f.conname, pg_get_constraintdef(f.oid), t2.relname
75
+ FROM pg_class t, pg_class t2, pg_constraint f
76
+ WHERE f.confrelid = t.oid
77
+ AND f.conrelid = t2.oid
78
+ AND f.contype = 'f'
79
+ AND t.relname = '#{table_name}'
80
+ SQL
81
+ end
82
+
83
+ def views(name = nil)
84
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
85
+ query(<<-SQL, name).map { |row| row[0] }
86
+ SELECT viewname
87
+ FROM pg_views
88
+ WHERE schemaname IN (#{schemas})
89
+ SQL
90
+ end
91
+
92
+ def view_definition(view_name, name = nil)
93
+ result = query(<<-SQL, name)
94
+ SELECT pg_get_viewdef(oid)
95
+ FROM pg_class
96
+ WHERE relkind = 'v'
97
+ AND relname = '#{view_name}'
98
+ SQL
99
+ row = result.first
100
+ row.first unless row.nil?
101
+ end
102
+
103
+ private
104
+
105
+ def load_foreign_keys(sql, name = nil)
106
+ foreign_keys = []
107
+
108
+ query(sql, name).each do |row|
109
+ if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE))?$/
110
+ name = row[0]
111
+ from_table_name = row[2]
112
+ column_names = $1
113
+ references_table_name = $2
114
+ references_column_names = $3
115
+ on_update = $5
116
+ on_delete = $7
117
+ deferrable = $9 == "DEFERRABLE"
118
+ on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
119
+ on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
120
+
121
+ foreign_keys << ForeignKeyDefinition.new(name,
122
+ from_table_name, column_names.split(', '),
123
+ references_table_name.sub(/^"(.*)"$/, '\1'), references_column_names.split(', '),
124
+ on_update, on_delete, deferrable)
125
+ end
126
+ end
127
+
128
+ foreign_keys
129
+ end
130
+ end
131
+ end