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.
- data/CHANGELOG +7 -0
- data/README.rdoc +31 -47
- data/lib/redhillonrails_core/active_record/base.rb +63 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/abstract_adapter.rb +75 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/column.rb +25 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/foreign_key_definition.rb +30 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/index_definition.rb +17 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/mysql_adapter.rb +78 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/mysql_column.rb +12 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/postgresql_adapter.rb +160 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/sqlite3_adapter.rb +111 -0
- data/lib/redhillonrails_core/active_record/connection_adapters/table_definition.rb +31 -0
- data/lib/redhillonrails_core/active_record/schema.rb +27 -0
- data/lib/redhillonrails_core/active_record/schema_dumper.rb +70 -0
- data/lib/redhillonrails_core.rb +20 -26
- data/redhillonrails_core.gemspec +34 -40
- data/spec/connections/mysql/connection.rb +18 -0
- data/spec/connections/mysql2/connection.rb +18 -0
- data/spec/connections/postgresql/connection.rb +15 -0
- data/spec/connections/sqlite3/connection.rb +14 -0
- data/spec/foreign_key_spec.rb +100 -0
- data/spec/index_definition_spec.rb +145 -0
- data/spec/index_spec.rb +67 -0
- data/spec/models/comment.rb +5 -0
- data/spec/models/post.rb +6 -0
- data/spec/models/user.rb +5 -0
- data/spec/schema/schema.rb +21 -0
- data/spec/schema_dumper_spec.rb +117 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/reference.rb +66 -0
- metadata +32 -57
- data/.document +0 -5
- data/.gitignore +0 -23
- data/.rvmrc +0 -1
- data/Gemfile +0 -20
- data/Gemfile.lock +0 -50
- data/Rakefile +0 -76
- data/VERSION +0 -1
- data/examples/example_helper.rb +0 -44
- data/examples/postgresql_index_parser_example.rb +0 -198
- data/lib/red_hill_consulting/core/active_record/base.rb +0 -61
- data/lib/red_hill_consulting/core/active_record/connection_adapters/abstract_adapter.rb +0 -71
- data/lib/red_hill_consulting/core/active_record/connection_adapters/column.rb +0 -21
- data/lib/red_hill_consulting/core/active_record/connection_adapters/foreign_key_definition.rb +0 -26
- data/lib/red_hill_consulting/core/active_record/connection_adapters/index_definition.rb +0 -13
- data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql4_adapter.rb +0 -37
- data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql5_adapter.rb +0 -40
- data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_adapter.rb +0 -103
- data/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_column.rb +0 -8
- data/lib/red_hill_consulting/core/active_record/connection_adapters/postgresql_adapter.rb +0 -178
- data/lib/red_hill_consulting/core/active_record/connection_adapters/schema_statements.rb +0 -23
- data/lib/red_hill_consulting/core/active_record/connection_adapters/sqlite3_adapter.rb +0 -109
- data/lib/red_hill_consulting/core/active_record/connection_adapters/table_definition.rb +0 -27
- data/lib/red_hill_consulting/core/active_record/schema.rb +0 -25
- data/lib/red_hill_consulting/core/active_record/schema_dumper.rb +0 -66
- data/lib/red_hill_consulting/core/active_record.rb +0 -4
- data/lib/red_hill_consulting/core.rb +0 -4
- 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
|
-
=
|
1
|
+
= Disclaimer
|
2
2
|
|
3
|
-
|
4
|
-
|
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
|
-
|
9
|
+
Goal of redhillonrails_core is to provided missing ActiveRecord support for
|
10
|
+
database specific features:
|
9
11
|
|
10
|
-
*
|
11
|
-
*
|
12
|
-
*
|
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
|
-
|
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
|
-
|
26
|
+
== Compatibility
|
27
27
|
|
28
|
-
|
29
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
119
|
-
|
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
|
-
===
|
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
|
-
|
152
|
+
redhillonrails_core is tested using similar approach to ActiveRecord tests.
|
153
|
+
However we use rspec in favor of Test::Unit
|
176
154
|
|
177
|
-
|
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
|
-
$
|
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
|
-
|
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,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
|