redhillonrails_core 1.0.9.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG +7 -0
  2. data/README.rdoc +31 -47
  3. data/lib/redhillonrails_core/active_record/base.rb +63 -0
  4. data/lib/redhillonrails_core/active_record/connection_adapters/abstract_adapter.rb +75 -0
  5. data/lib/redhillonrails_core/active_record/connection_adapters/column.rb +25 -0
  6. data/lib/redhillonrails_core/active_record/connection_adapters/foreign_key_definition.rb +30 -0
  7. data/lib/redhillonrails_core/active_record/connection_adapters/index_definition.rb +17 -0
  8. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_adapter.rb +78 -0
  9. data/lib/redhillonrails_core/active_record/connection_adapters/mysql_column.rb +12 -0
  10. data/lib/redhillonrails_core/active_record/connection_adapters/postgresql_adapter.rb +160 -0
  11. data/lib/redhillonrails_core/active_record/connection_adapters/sqlite3_adapter.rb +111 -0
  12. data/lib/redhillonrails_core/active_record/connection_adapters/table_definition.rb +31 -0
  13. data/lib/redhillonrails_core/active_record/schema.rb +27 -0
  14. data/lib/redhillonrails_core/active_record/schema_dumper.rb +70 -0
  15. data/lib/redhillonrails_core.rb +20 -26
  16. data/redhillonrails_core.gemspec +34 -40
  17. data/spec/connections/mysql/connection.rb +18 -0
  18. data/spec/connections/mysql2/connection.rb +18 -0
  19. data/spec/connections/postgresql/connection.rb +15 -0
  20. data/spec/connections/sqlite3/connection.rb +14 -0
  21. data/spec/foreign_key_spec.rb +100 -0
  22. data/spec/index_definition_spec.rb +145 -0
  23. data/spec/index_spec.rb +67 -0
  24. data/spec/models/comment.rb +5 -0
  25. data/spec/models/post.rb +6 -0
  26. data/spec/models/user.rb +5 -0
  27. data/spec/schema/schema.rb +21 -0
  28. data/spec/schema_dumper_spec.rb +117 -0
  29. data/spec/spec_helper.rb +16 -0
  30. data/spec/support/reference.rb +66 -0
  31. metadata +32 -57
  32. data/.document +0 -5
  33. data/.gitignore +0 -23
  34. data/.rvmrc +0 -1
  35. data/Gemfile +0 -20
  36. data/Gemfile.lock +0 -50
  37. data/Rakefile +0 -76
  38. data/VERSION +0 -1
  39. data/examples/example_helper.rb +0 -44
  40. data/examples/postgresql_index_parser_example.rb +0 -198
  41. data/lib/red_hill_consulting/core/active_record/base.rb +0 -61
  42. data/lib/red_hill_consulting/core/active_record/connection_adapters/abstract_adapter.rb +0 -71
  43. data/lib/red_hill_consulting/core/active_record/connection_adapters/column.rb +0 -21
  44. data/lib/red_hill_consulting/core/active_record/connection_adapters/foreign_key_definition.rb +0 -26
  45. data/lib/red_hill_consulting/core/active_record/connection_adapters/index_definition.rb +0 -13
  46. data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql4_adapter.rb +0 -37
  47. data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql5_adapter.rb +0 -40
  48. data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_adapter.rb +0 -103
  49. data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_column.rb +0 -8
  50. data/lib/red_hill_consulting/core/active_record/connection_adapters/postgresql_adapter.rb +0 -178
  51. data/lib/red_hill_consulting/core/active_record/connection_adapters/schema_statements.rb +0 -23
  52. data/lib/red_hill_consulting/core/active_record/connection_adapters/sqlite3_adapter.rb +0 -109
  53. data/lib/red_hill_consulting/core/active_record/connection_adapters/table_definition.rb +0 -27
  54. data/lib/red_hill_consulting/core/active_record/schema.rb +0 -25
  55. data/lib/red_hill_consulting/core/active_record/schema_dumper.rb +0 -66
  56. data/lib/red_hill_consulting/core/active_record.rb +0 -4
  57. data/lib/red_hill_consulting/core.rb +0 -4
  58. data/tasks/db/comments.rake +0 -9
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 1.1.0
2
+ * standard gem layout (no more RedHillConsulting module)
3
+ * got rid of table comments support
4
+ * dropped Mysql4 compatibility
5
+ * added specs regarding foreign_keys
6
+ * added specs regarding indexes
7
+ * fixed included module for sqlite3
1
8
  1.0.9.1
2
9
  * get rid of ActiveRecord < 3.0.0 dependency
3
10
  1.0.9
data/README.rdoc CHANGED
@@ -1,19 +1,19 @@
1
- = Credits
1
+ = Disclaimer
2
2
 
3
- This plugin was created by harukizaemon(http://github.com/harukizaemon) but is not supported currently by him.
4
- I've forked it to make it edge-rails compatible and to introduce new features.
3
+ redhillonrails_core was originally created by http://github.com/harukizaemon but it was retired and is no longer supported.
4
+
5
+ That fork is intended to make redhillonrails_core compatible with edge rails and introduce some new features.
5
6
 
6
7
  = RedHill on Rails Core
7
8
 
8
- RedHill on Rails Core provides bunch of useful database features:
9
+ Goal of redhillonrails_core is to provided missing ActiveRecord support for
10
+ database specific features:
9
11
 
10
- * Creating and dropping views;
11
- * Creating and removing foreign-keys;
12
- * Creating partial indexes (postgresql only)
13
- * Obtaining indexes directly from a model class; and
14
- * Determining when <code>Schema.define()</code> is running.
12
+ * foreign_keys
13
+ * case-insensitive and partial indexes (pgsql only)
14
+ * views
15
15
 
16
- === Installation
16
+ == Installation
17
17
 
18
18
  As a gem
19
19
 
@@ -23,13 +23,11 @@ As a gem
23
23
 
24
24
  script/plugin install http://github.com/mlomnicki/redhillonrails_core.git
25
25
 
26
- === View Support
26
+ == Compatibility
27
27
 
28
- The plugin provides a mechanism for creating and dropping views as well as
29
- preserving views when performing a schema dump:
30
-
31
- create_view :normal_customers, "SELECT * FROM customers WHERE status = 'normal'"
32
- drop_view :normal_customers
28
+ * Ruby - 1.8, 1.9
29
+ * ActiveRecord - 2.X, 3.X
30
+ * Databases - PostgreSQL, MySQL, SQLite3 (most features should also run on others)
33
31
 
34
32
  === Foreign Key Support
35
33
 
@@ -96,31 +94,14 @@ each test, you should list your fixtures in order of parent->child. For example:
96
94
 
97
95
  Rails will then set-up and tear-down the fixtures in the correct sequence.
98
96
 
99
- Some databases (PostgreSQL and MySQL for example) allow you to set a comment for a
100
- table. You can do this for existing tables by using:
101
-
102
- set_table_comment :orders, "All pending and processed orders"
103
-
104
- or even at the time of creation:
105
-
106
- create_table :orders, :comment => "All pending and processed orders" do |t|
107
- ...
108
- end
109
-
110
- You can clear table comments using:
111
-
112
- clear_table_comment :orders
113
-
114
- There is also a rake tasks to show all database tables and their comments:
97
+ === View Support
115
98
 
116
- rake db:comments
99
+ The plugin provides a mechanism for creating and dropping views as well as
100
+ preserving views when performing a schema dump:
117
101
 
118
- The plugin fully supports and understands the following active-record
119
- configuration properties:
102
+ create_view :normal_customers, "SELECT * FROM customers WHERE status = 'normal'"
103
+ drop_view :normal_customers
120
104
 
121
- * <code>config.active_record.pluralize_table_names</code>
122
- * <code>config.active_record.table_name_prefix</code>
123
- * <code>config.active_record.table_name_suffix</code>
124
105
 
125
106
  === Model Indexes
126
107
 
@@ -166,23 +147,26 @@ Note also that this ties in well with Rails built-in support for case-insensitiv
166
147
 
167
148
  validates_uniqueness_of :name, :case_sensitive => false
168
149
 
169
- === Schema Defining
170
-
171
- The plugin also adds a method--<code>defining?()</code>--to
172
- <code>ActiveRecord::Schema</code> to indicate when <code>define()</code> is running. This is necessary
173
- as some migration plugins must change their behaviour accordingly.
150
+ === Tests
174
151
 
175
- === Examples
152
+ redhillonrails_core is tested using similar approach to ActiveRecord tests.
153
+ However we use rspec in favor of Test::Unit
176
154
 
177
- redhillonrails_core uses Micronaut for specing. The examples are split per-database adapter. Run using Bundler:
155
+ First you have to fetch sources from github as specs are not inculded in a gem.
178
156
 
157
+ $ git clone https://mlomnicki@github.com/mlomnicki/redhillonrails_core.git
158
+ $ cd redhillonrails_core
179
159
  $ bundle install
180
- $ bundle exec rake postgresql:examples
160
+ $ rake postgresql:build_database # create redhillonrails user first
161
+ $ rake mysql:build_database # create user as above
162
+ $ bundle exec rake spec
181
163
 
182
- Running examples only will fail since the environment won't be setup properly.
164
+ # to run postgresql specs only
165
+ $ bundle exec rake spec postgresql:spec
183
166
 
184
167
  === Contributors
185
168
 
169
+ * Michał Łomnicki
186
170
  * François Beausoleil - http://github.com/francois
187
171
  * Greg Barnett
188
172
 
@@ -0,0 +1,63 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module Base
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def self.extended(base)
10
+ class << base
11
+ alias_method_chain :columns, :redhillonrails_core
12
+ alias_method_chain :abstract_class?, :redhillonrails_core
13
+ alias_method_chain :reset_column_information, :redhillonrails_core
14
+ end
15
+ end
16
+
17
+ def base_class?
18
+ self == base_class
19
+ end
20
+
21
+ def abstract_class_with_redhillonrails_core?
22
+ abstract_class_without_redhillonrails_core? || !(name =~ /^Abstract/).nil?
23
+ end
24
+
25
+ def columns_with_redhillonrails_core
26
+ unless @columns
27
+ columns_without_redhillonrails_core
28
+ cols = columns_hash
29
+ indexes.each do |index|
30
+ next if index.columns.blank?
31
+ column_name = index.columns.reverse.detect { |name| name !~ /_id$/ } || index.columns.last
32
+ column = cols[column_name]
33
+ column.case_sensitive = index.case_sensitive?
34
+ column.unique_scope = index.columns.reject { |name| name == column_name } if index.unique
35
+ end
36
+ end
37
+ @columns
38
+ end
39
+
40
+ def reset_column_information_with_redhillonrails_core
41
+ reset_column_information_without_redhillonrails_core
42
+ @indexes = @foreign_keys = nil
43
+ end
44
+
45
+ def pluralized_table_name(table_name)
46
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
47
+ end
48
+
49
+ def indexes
50
+ @indexes ||= connection.indexes(table_name, "#{name} Indexes")
51
+ end
52
+
53
+ def foreign_keys
54
+ @foreign_keys ||= connection.foreign_keys(table_name, "#{name} Foreign Keys")
55
+ end
56
+
57
+ def reverse_foreign_keys
58
+ connection.reverse_foreign_keys(table_name, "#{name} Reverse Foreign Keys")
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,75 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module AbstractAdapter
5
+ def self.included(base)
6
+ base.alias_method_chain :initialize, :redhillonrails_core
7
+ base.alias_method_chain :drop_table, :redhillonrails_core
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
+ end
30
+
31
+ def create_view(view_name, definition)
32
+ execute "CREATE VIEW #{view_name} AS #{definition}"
33
+ end
34
+
35
+ def drop_view(view_name)
36
+ execute "DROP VIEW #{view_name}"
37
+ end
38
+
39
+ def views(name = nil)
40
+ []
41
+ end
42
+
43
+ def view_definition(view_name, name = nil)
44
+ end
45
+
46
+ def foreign_keys(table_name, name = nil)
47
+ []
48
+ end
49
+
50
+ def reverse_foreign_keys(table_name, name = nil)
51
+ []
52
+ end
53
+
54
+ def add_foreign_key(table_name, column_names, references_table_name, references_column_names, options = {})
55
+ 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])
56
+ execute "ALTER TABLE #{table_name} ADD #{foreign_key}"
57
+ end
58
+
59
+ def remove_foreign_key(table_name, foreign_key_name, options = {})
60
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{foreign_key_name}"
61
+ end
62
+
63
+ def drop_table_with_redhillonrails_core(name, options = {})
64
+ reverse_foreign_keys(name).each { |foreign_key| remove_foreign_key(foreign_key.table_name, foreign_key.name, options) }
65
+ drop_table_without_redhillonrails_core(name, options)
66
+ end
67
+
68
+ def supports_partial_indexes?
69
+ false
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module Column
5
+ attr_accessor :unique_scope
6
+ attr_accessor :case_sensitive
7
+ alias case_sensitive? case_sensitive
8
+
9
+ def unique?
10
+ !unique_scope.nil?
11
+ end
12
+
13
+ def required_on
14
+ if null
15
+ nil
16
+ elsif default.nil?
17
+ :save
18
+ else
19
+ :update
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ class ForeignKeyDefinition < Struct.new(:name, :table_name, :column_names, :references_table_name, :references_column_names, :on_update, :on_delete, :deferrable)
5
+ ACTIONS = { :cascade => "CASCADE", :restrict => "RESTRICT", :set_null => "SET NULL", :set_default => "SET DEFAULT", :no_action => "NO ACTION" }.freeze
6
+
7
+ def to_dump
8
+ dump = "add_foreign_key"
9
+ dump << " #{table_name.inspect}, [#{Array(column_names).collect{ |name| name.inspect }.join(', ')}]"
10
+ dump << ", #{references_table_name.inspect}, [#{Array(references_column_names).collect{ |name| name.inspect }.join(', ')}]"
11
+ dump << ", :on_update => :#{on_update}" if on_update
12
+ dump << ", :on_delete => :#{on_delete}" if on_delete
13
+ dump << ", :deferrable => #{deferrable}" if deferrable
14
+ dump << ", :name => #{name.inspect}" if name
15
+ dump
16
+ end
17
+
18
+ def to_sql
19
+ sql = name ? "CONSTRAINT #{name} " : ""
20
+ sql << "FOREIGN KEY (#{Array(column_names).join(", ")}) REFERENCES #{references_table_name} (#{Array(references_column_names).join(", ")})"
21
+ sql << " ON UPDATE #{ACTIONS[on_update]}" if on_update
22
+ sql << " ON DELETE #{ACTIONS[on_delete]}" if on_delete
23
+ sql << " DEFERRABLE" if deferrable
24
+ sql
25
+ end
26
+ alias :to_s :to_sql
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module IndexDefinition
5
+ attr_accessor :conditions, :expression, :kind
6
+
7
+ def case_sensitive?
8
+ @case_sensitive.nil? ? true : @case_sensitive
9
+ end
10
+
11
+ def case_sensitive=(case_sensitive)
12
+ @case_sensitive = case_sensitive
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,78 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module MysqlAdapter
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias_method_chain :remove_column, :redhillonrails_core
8
+ end
9
+ end
10
+
11
+ def remove_foreign_key(table_name, foreign_key_name, options = {})
12
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{foreign_key_name}"
13
+ end
14
+
15
+ def remove_column_with_redhillonrails_core(table_name, column_name)
16
+ foreign_keys(table_name).select { |foreign_key| foreign_key.column_names.include?(column_name.to_s) }.each do |foreign_key|
17
+ remove_foreign_key(table_name, foreign_key.name)
18
+ end
19
+ remove_column_without_redhillonrails_core(table_name, column_name)
20
+ end
21
+
22
+ def foreign_keys(table_name, name = nil)
23
+ results = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}", name)
24
+
25
+ foreign_keys = []
26
+
27
+ results.each do |row|
28
+ row[1].lines.each do |line|
29
+ if line =~ /^ CONSTRAINT [`"](.+?)[`"] FOREIGN KEY \([`"](.+?)[`"]\) REFERENCES [`"](.+?)[`"] \((.+?)\)( ON DELETE (.+?))?( ON UPDATE (.+?))?,?$/
30
+ name = $1
31
+ column_names = $2
32
+ references_table_name = $3
33
+ references_column_names = $4
34
+ on_update = $8
35
+ on_delete = $6
36
+ on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
37
+ on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
38
+
39
+ foreign_keys << ForeignKeyDefinition.new(name,
40
+ table_name, column_names.gsub('`', '').split(', '),
41
+ references_table_name, references_column_names.gsub('`', '').split(', '),
42
+ on_update, on_delete)
43
+ end
44
+ end
45
+ end
46
+
47
+ foreign_keys
48
+ end
49
+
50
+ def reverse_foreign_keys(table_name, name = nil)
51
+ results = execute(<<-SQL, name)
52
+ SELECT constraint_name, table_name, column_name, referenced_table_name, referenced_column_name
53
+ FROM information_schema.key_column_usage
54
+ WHERE table_schema = SCHEMA()
55
+ AND referenced_table_schema = table_schema
56
+ ORDER BY constraint_name, ordinal_position;
57
+ SQL
58
+ current_foreign_key = nil
59
+ foreign_keys = []
60
+
61
+ results.each do |row|
62
+ next unless table_name.casecmp(row[3]) == 0
63
+ if current_foreign_key != row[0]
64
+ foreign_keys << ForeignKeyDefinition.new(row[0], row[1], [], row[3], [])
65
+ current_foreign_key = row[0]
66
+ end
67
+
68
+ foreign_keys.last.column_names << row[2]
69
+ foreign_keys.last.references_column_names << row[4]
70
+ end
71
+
72
+ foreign_keys
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,12 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module MysqlColumn
5
+ def initialize(name, default, sql_type = nil, null = true)
6
+ default = nil if !null && default.blank?
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,160 @@
1
+ module RedhillonrailsCore
2
+ module ActiveRecord
3
+ module ConnectionAdapters
4
+ module PostgresqlAdapter
5
+ def self.included(base)
6
+ base.class_eval do
7
+ remove_method :indexes
8
+ end
9
+ end
10
+
11
+ def add_index(table_name, column_name, options = {})
12
+ column_name, options = [], column_name if column_name.is_a?(Hash)
13
+ column_names = Array(column_name)
14
+ if column_names.empty?
15
+ raise ArgumentError, "No columns and :expression missing from options - cannot create index" if options[:expression].blank?
16
+ raise ArgumentError, "Index name not given. Pass :name option" if options[:name].blank?
17
+ end
18
+
19
+ index_type = options[:unique] ? "UNIQUE" : ""
20
+ index_name = options[:name] || index_name(table_name, column_names)
21
+ conditions = options[:conditions]
22
+
23
+ if column_names.empty? then
24
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{options[:expression]}"
25
+ else
26
+ quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
27
+
28
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names.join(", ")})"
29
+ sql += " WHERE (#{ ::ActiveRecord::Base.send(:sanitize_sql, conditions, quote_table_name(table_name)) })" if conditions
30
+ end
31
+ execute sql
32
+ end
33
+
34
+ def supports_partial_indexes?
35
+ true
36
+ end
37
+
38
+ def indexes(table_name, name = nil)
39
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
40
+ result = query(<<-SQL, name)
41
+ SELECT distinct i.relname, d.indisunique, d.indkey, m.amname, t.oid,
42
+ pg_get_expr(d.indpred, t.oid), pg_get_expr(d.indexprs, t.oid)
43
+ FROM pg_class t, pg_class i, pg_index d, pg_am m
44
+ WHERE i.relkind = 'i'
45
+ AND i.relam = m.oid
46
+ AND d.indexrelid = i.oid
47
+ AND d.indisprimary = 'f'
48
+ AND t.oid = d.indrelid
49
+ AND t.relname = '#{table_name}'
50
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
51
+ ORDER BY i.relname
52
+ SQL
53
+
54
+ result.map do |(index_name, is_unique, indkey, kind, oid, conditions, expression)|
55
+ unique = (is_unique == 't')
56
+ index_keys = indkey.split(" ")
57
+
58
+ columns = Hash[query(<<-SQL, "Columns for index #{index_name} on #{table_name}")]
59
+ SELECT a.attnum, a.attname
60
+ FROM pg_attribute a
61
+ WHERE a.attrelid = #{oid}
62
+ AND a.attnum IN (#{index_keys.join(",")})
63
+ SQL
64
+
65
+ column_names = columns.values_at(*index_keys).compact
66
+ if md = expression.try(:match, /^lower\(\(?([^)]+)\)?(::text)?\)$/i)
67
+ column_names << md[1]
68
+ end
69
+ index = ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names)
70
+ index.conditions = conditions
71
+ index.case_sensitive = !(expression =~ /lower/i)
72
+ index.kind = kind unless kind.downcase == "btree"
73
+ index.expression = expression
74
+ index
75
+ end
76
+ end
77
+
78
+ def foreign_keys(table_name, name = nil)
79
+ load_foreign_keys(<<-SQL, name)
80
+ SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
81
+ FROM pg_class t, pg_constraint f
82
+ WHERE f.conrelid = t.oid
83
+ AND f.contype = 'f'
84
+ AND t.relname = '#{table_name}'
85
+ SQL
86
+ end
87
+
88
+ def reverse_foreign_keys(table_name, name = nil)
89
+ load_foreign_keys(<<-SQL, name)
90
+ SELECT f.conname, pg_get_constraintdef(f.oid), t2.relname
91
+ FROM pg_class t, pg_class t2, pg_constraint f
92
+ WHERE f.confrelid = t.oid
93
+ AND f.conrelid = t2.oid
94
+ AND f.contype = 'f'
95
+ AND t.relname = '#{table_name}'
96
+ SQL
97
+ end
98
+
99
+ def views(name = nil)
100
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
101
+ query(<<-SQL, name).map { |row| row[0] }
102
+ SELECT viewname
103
+ FROM pg_views
104
+ WHERE schemaname IN (#{schemas})
105
+ SQL
106
+ end
107
+
108
+ def view_definition(view_name, name = nil)
109
+ result = query(<<-SQL, name)
110
+ SELECT pg_get_viewdef(oid)
111
+ FROM pg_class
112
+ WHERE relkind = 'v'
113
+ AND relname = '#{view_name}'
114
+ SQL
115
+ row = result.first
116
+ row.first unless row.nil?
117
+ end
118
+
119
+ private
120
+
121
+ def load_foreign_keys(sql, name = nil)
122
+ foreign_keys = []
123
+
124
+ query(sql, name).each do |row|
125
+ if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE))?$/
126
+ name = row[0]
127
+ from_table_name = row[2]
128
+ column_names = $1
129
+ references_table_name = $2
130
+ references_column_names = $3
131
+ on_update = $5
132
+ on_delete = $7
133
+ deferrable = $9 == "DEFERRABLE"
134
+ on_update = on_update.downcase.gsub(' ', '_').to_sym if on_update
135
+ on_delete = on_delete.downcase.gsub(' ', '_').to_sym if on_delete
136
+
137
+ foreign_keys << ForeignKeyDefinition.new(name,
138
+ from_table_name, column_names.split(', '),
139
+ references_table_name.sub(/^"(.*)"$/, '\1'), references_column_names.split(', '),
140
+ on_update, on_delete, deferrable)
141
+ end
142
+ end
143
+
144
+ foreign_keys
145
+ end
146
+
147
+ # Converts form like: column1, LOWER(column2)
148
+ # to: column1, column2
149
+ def determine_index_column_names(column_definitions)
150
+ column_definitions.split(", ").map do |name|
151
+ name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
152
+ name = $1 if name =~ /^"(.*)"$/
153
+ name
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end
160
+ end