redhillonrails_core 1.0.9.1 → 1.1.0

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 (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