schema_plus 0.4.1 → 1.0.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/.gitignore +3 -1
- data/.travis.yml +6 -9
- data/Gemfile +0 -4
- data/README.rdoc +168 -70
- data/Rakefile +58 -47
- data/gemfiles/rails-3.2/Gemfile.base +4 -0
- data/gemfiles/rails-3.2/Gemfile.mysql +4 -0
- data/gemfiles/rails-3.2/Gemfile.mysql2 +4 -0
- data/gemfiles/rails-3.2/Gemfile.postgresql +4 -0
- data/gemfiles/rails-3.2/Gemfile.sqlite3 +4 -0
- data/lib/schema_plus.rb +2 -0
- data/lib/schema_plus/active_record/column_options_handler.rb +73 -32
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +60 -31
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +7 -2
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +2 -1
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +19 -1
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +68 -17
- data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +28 -3
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +27 -1
- data/lib/schema_plus/active_record/db_default.rb +19 -0
- data/lib/schema_plus/active_record/foreign_keys.rb +40 -32
- data/lib/schema_plus/active_record/schema_dumper.rb +7 -3
- data/lib/schema_plus/version.rb +1 -1
- data/runspecs +5 -8
- data/schema_plus.gemspec +2 -5
- data/spec/column_definition_spec.rb +18 -1
- data/spec/column_spec.rb +39 -2
- data/spec/connection_spec.rb +1 -1
- data/spec/connections/mysql/connection.rb +1 -1
- data/spec/connections/mysql2/connection.rb +1 -1
- data/spec/connections/postgresql/connection.rb +1 -1
- data/spec/foreign_key_definition_spec.rb +0 -4
- data/spec/foreign_key_spec.rb +37 -13
- data/spec/index_definition_spec.rb +54 -2
- data/spec/index_spec.rb +59 -15
- data/spec/migration_spec.rb +336 -85
- data/spec/multiple_schemas_spec.rb +127 -0
- data/spec/schema_dumper_spec.rb +65 -25
- data/spec/schema_spec.rb +16 -18
- data/spec/spec_helper.rb +19 -18
- data/spec/support/matchers/reference.rb +7 -1
- data/spec/views_spec.rb +5 -2
- metadata +43 -54
- data/gemfiles/Gemfile.rails-2.3 +0 -6
- data/gemfiles/Gemfile.rails-2.3.lock +0 -65
- data/gemfiles/Gemfile.rails-3.0 +0 -5
- data/gemfiles/Gemfile.rails-3.0.lock +0 -113
- data/gemfiles/Gemfile.rails-3.1 +0 -5
- data/gemfiles/Gemfile.rails-3.1.lock +0 -123
- data/gemfiles/Gemfile.rails-3.2 +0 -5
- data/gemfiles/Gemfile.rails-3.2.lock +0 -121
- data/spec/models/comment.rb +0 -2
- data/spec/models/post.rb +0 -2
- data/spec/models/user.rb +0 -2
- data/spec/rails3_migration_spec.rb +0 -144
data/lib/schema_plus.rb
CHANGED
@@ -3,6 +3,7 @@ require 'valuable'
|
|
3
3
|
require 'schema_plus/version'
|
4
4
|
require 'schema_plus/active_record/base'
|
5
5
|
require 'schema_plus/active_record/column_options_handler'
|
6
|
+
require 'schema_plus/active_record/db_default'
|
6
7
|
require 'schema_plus/active_record/foreign_keys'
|
7
8
|
require 'schema_plus/active_record/connection_adapters/table_definition'
|
8
9
|
require 'schema_plus/active_record/connection_adapters/schema_statements'
|
@@ -132,6 +133,7 @@ module SchemaPlus
|
|
132
133
|
::ActiveRecord::Base.send(:include, SchemaPlus::ActiveRecord::Base)
|
133
134
|
::ActiveRecord::Schema.send(:include, SchemaPlus::ActiveRecord::Schema)
|
134
135
|
::ActiveRecord::SchemaDumper.send(:include, SchemaPlus::ActiveRecord::SchemaDumper)
|
136
|
+
::ActiveRecord.const_set(:DB_DEFAULT, SchemaPlus::ActiveRecord::DB_DEFAULT)
|
135
137
|
end
|
136
138
|
|
137
139
|
end
|
@@ -2,48 +2,80 @@ module SchemaPlus::ActiveRecord
|
|
2
2
|
module ColumnOptionsHandler
|
3
3
|
def schema_plus_handle_column_options(table_name, column_name, column_options, opts = {}) #:nodoc:
|
4
4
|
config = opts[:config] || SchemaPlus.config
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
fk_args = get_fk_args(table_name, column_name, column_options, config)
|
6
|
+
|
7
|
+
# remove existing fk and auto-generated index in case of change to existing column
|
8
|
+
if fk_args # includes :none for explicitly off
|
9
|
+
remove_foreign_key_if_exists(table_name, column_name)
|
10
|
+
remove_auto_index_if_exists(table_name, column_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
fk_args = nil if fk_args == :none
|
14
|
+
|
15
|
+
# create index if requested explicity or implicitly due to auto_index
|
16
|
+
index = column_options[:index]
|
17
|
+
if index.nil? and fk_args && config.foreign_keys.auto_index?
|
18
|
+
index = { :name => auto_index_name(table_name, column_name) }
|
19
|
+
end
|
20
|
+
column_index(table_name, column_name, index) if index
|
21
|
+
|
22
|
+
if fk_args
|
23
|
+
references = fk_args.delete(:references)
|
24
|
+
add_foreign_key(table_name, column_name, references.first, references.last, fk_args)
|
14
25
|
end
|
15
26
|
end
|
16
27
|
|
17
28
|
protected
|
18
29
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
def get_references(table_name, column_name, column_options = {}, config = {}) #:nodoc:
|
30
|
+
def get_fk_args(table_name, column_name, column_options = {}, config = {}) #:nodoc:
|
31
|
+
|
32
|
+
args = nil
|
33
|
+
|
34
|
+
if column_options.has_key?(:foreign_key)
|
35
|
+
args = column_options[:foreign_key]
|
36
|
+
return :none unless args
|
37
|
+
args = {} if args == true
|
38
|
+
return :none if args.has_key?(:references) and not args[:references]
|
39
|
+
end
|
40
|
+
|
32
41
|
if column_options.has_key?(:references)
|
33
42
|
references = column_options[:references]
|
34
|
-
|
35
|
-
references
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
43
|
+
return :none unless references
|
44
|
+
args = (args || {}).reverse_merge(:references => references)
|
45
|
+
end
|
46
|
+
|
47
|
+
args ||= {} if config.foreign_keys.auto_create? and column_name =~ /_id$/
|
48
|
+
|
49
|
+
return nil if args.nil?
|
50
|
+
|
51
|
+
args[:references] ||= case column_name.to_s
|
52
|
+
when 'parent_id'
|
53
|
+
[table_name, :id]
|
54
|
+
when /^(.*)_id$/
|
55
|
+
references_table_name = ActiveRecord::Base.pluralize_table_names ? $1.to_s.pluralize : $1
|
56
|
+
[references_table_name, :id]
|
57
|
+
else
|
58
|
+
references_table_name = ActiveRecord::Base.pluralize_table_names ? column_name.to_s.pluralize : column_name
|
59
|
+
end
|
60
|
+
args[:references] = [args[:references], :id] unless args[:references].is_a? Array
|
61
|
+
|
62
|
+
[:on_update, :on_delete, :deferrable].each do |shortcut|
|
63
|
+
args[shortcut] ||= column_options[shortcut] if column_options.has_key? shortcut
|
44
64
|
end
|
65
|
+
|
66
|
+
args[:on_update] ||= config.foreign_keys.on_update
|
67
|
+
args[:on_delete] ||= config.foreign_keys.on_delete
|
68
|
+
|
69
|
+
args
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_foreign_key_if_exists(table_name, column_name) #:nodoc:
|
73
|
+
foreign_keys = ActiveRecord::Base.connection.foreign_keys(table_name.to_s) rescue [] # no fks if table_name doesn't exist
|
74
|
+
fk = foreign_keys.detect { |fk| fk.table_name == table_name.to_s && fk.column_names == Array(column_name).collect(&:to_s) }
|
75
|
+
remove_foreign_key(table_name, fk.name) if fk
|
45
76
|
end
|
46
77
|
|
78
|
+
|
47
79
|
def column_index(table_name, column_name, options) #:nodoc:
|
48
80
|
options = {} if options == true
|
49
81
|
options = { :unique => true } if options == :unique
|
@@ -51,5 +83,14 @@ module SchemaPlus::ActiveRecord
|
|
51
83
|
add_index(table_name, column_name, options)
|
52
84
|
end
|
53
85
|
|
86
|
+
def remove_auto_index_if_exists(table_name, column_name)
|
87
|
+
name = auto_index_name(table_name, column_name)
|
88
|
+
remove_index(table_name, :name => name) if index_exists?(table_name, column_name, :name => name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def auto_index_name(table_name, column_name)
|
92
|
+
ConnectionAdapters::ForeignKeyDefinition.auto_index_name(table_name, column_name)
|
93
|
+
end
|
94
|
+
|
54
95
|
end
|
55
96
|
end
|
@@ -35,14 +35,6 @@ module SchemaPlus
|
|
35
35
|
adapter_module = SchemaPlus::ActiveRecord::ConnectionAdapters.const_get(adapter)
|
36
36
|
self.class.send(:include, adapter_module) unless self.class.include?(adapter_module)
|
37
37
|
self.post_initialize if self.respond_to? :post_initialize
|
38
|
-
# rails 3.1 defines a separate Mysql2IndexDefinition which is
|
39
|
-
# compatible with the monkey patches; but the definition only
|
40
|
-
# appears once the adapter is loaded. so wait til now to check
|
41
|
-
# if that constant exists, then include the patches
|
42
|
-
if mysql2index = ::ActiveRecord::ConnectionAdapters::Mysql2IndexDefinition rescue nil # rescues NameError
|
43
|
-
monkeypatch = SchemaPlus::ActiveRecord::ConnectionAdapters::IndexDefinition
|
44
|
-
mysql2index.send(:include, monkeypatch) unless mysql2index.include? monkeypatch
|
45
|
-
end
|
46
38
|
|
47
39
|
if adapter == 'PostgresqlAdapter'
|
48
40
|
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.send(:include, SchemaPlus::ActiveRecord::ConnectionAdapters::PostgreSQLColumn) unless ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.include?(SchemaPlus::ActiveRecord::ConnectionAdapters::PostgreSQLColumn)
|
@@ -57,6 +49,7 @@ module SchemaPlus
|
|
57
49
|
# Create a view given the SQL definition. Specify :force => true
|
58
50
|
# to first drop the view if it already exists.
|
59
51
|
def create_view(view_name, definition, options={})
|
52
|
+
definition = definition.to_sql if definition.respond_to? :to_sql
|
60
53
|
execute "DROP VIEW IF EXISTS #{quote_table_name(view_name)}" if options[:force]
|
61
54
|
execute "CREATE VIEW #{quote_table_name(view_name)} AS #{definition}"
|
62
55
|
end
|
@@ -66,21 +59,6 @@ module SchemaPlus
|
|
66
59
|
execute "DROP VIEW #{quote_table_name(view_name)}"
|
67
60
|
end
|
68
61
|
|
69
|
-
#--
|
70
|
-
# these are all expected to be defined by subclasses, listing them
|
71
|
-
# here only as templates.
|
72
|
-
#++
|
73
|
-
# Returns a list of all views (abstract)
|
74
|
-
def views(name = nil) [] end
|
75
|
-
# Returns the SQL definition of a given view (abstract)
|
76
|
-
def view_definition(view_name, name = nil) end
|
77
|
-
# Return the ForeignKeyDefinition objects for foreign key
|
78
|
-
# constraints defined on this table (abstract)
|
79
|
-
def foreign_keys(table_name, name = nil) [] end
|
80
|
-
# Return the ForeignKeyDefinition objects for foreign key
|
81
|
-
# constraints defined on other tables that reference this table
|
82
|
-
# (abstract)
|
83
|
-
def reverse_foreign_keys(table_name, name = nil) [] end
|
84
62
|
|
85
63
|
# Define a foreign key constraint. Valid options are :on_update,
|
86
64
|
# :on_delete, and :deferrable, with values as described at
|
@@ -117,6 +95,31 @@ module SchemaPlus
|
|
117
95
|
drop_table_without_schema_plus(name)
|
118
96
|
end
|
119
97
|
|
98
|
+
# called from individual adpaters, after renaming table from old
|
99
|
+
# name to
|
100
|
+
def rename_indexes_and_foreign_keys(oldname, newname) #:nodoc:
|
101
|
+
indexes(newname).select{|index| index.name == index_name(oldname, index.columns)}.each do |index|
|
102
|
+
rename_index(newname, index.name, index_name(newname, index.columns))
|
103
|
+
end
|
104
|
+
foreign_keys(newname).each do |fk|
|
105
|
+
index = indexes(newname).find{|index| index.name == ForeignKeyDefinition.auto_index_name(oldname, index.columns)}
|
106
|
+
begin
|
107
|
+
remove_foreign_key(newname, fk.name)
|
108
|
+
rescue NotImplementedError
|
109
|
+
# sqlite3 can't remove foreign keys, so just skip it
|
110
|
+
end
|
111
|
+
# rename the index only when the fk constraint doesn't exist.
|
112
|
+
# mysql doesn't allow the rename (which is a delete & add)
|
113
|
+
# if the index is on a foreign key constraint
|
114
|
+
rename_index(newname, index.name, ForeignKeyDefinition.auto_index_name(newname, index.columns)) if index
|
115
|
+
begin
|
116
|
+
add_foreign_key(newname, fk.column_names, fk.references_table_name, fk.references_column_names, :name => newname, :on_update => fk.on_update, :on_delete => fk.on_delete, :deferrable => fk.deferrable)
|
117
|
+
rescue NotImplementedError
|
118
|
+
# sqlite3 can't add foreign keys, so just skip it
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
120
123
|
# Returns true if the database supports parital indexes (abstract; only
|
121
124
|
# Postgresql returns true)
|
122
125
|
def supports_partial_indexes?
|
@@ -146,14 +149,6 @@ module SchemaPlus
|
|
146
149
|
end
|
147
150
|
end
|
148
151
|
|
149
|
-
def default_expr_valid?(expr)
|
150
|
-
# override in database specific adaptor
|
151
|
-
end
|
152
|
-
|
153
|
-
def sql_for_function(function_name)
|
154
|
-
# override in database specific adaptor
|
155
|
-
end
|
156
|
-
|
157
152
|
# This is define in rails 3.x, but not in rails2.x
|
158
153
|
unless defined? ::ActiveRecord::ConnectionAdapters::SchemaStatements::index_name_exists?
|
159
154
|
# File activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb, line 403
|
@@ -163,6 +158,40 @@ module SchemaPlus
|
|
163
158
|
indexes(table_name).detect { |i| i.name == index_name }
|
164
159
|
end
|
165
160
|
end
|
161
|
+
|
162
|
+
#####################################################################
|
163
|
+
#
|
164
|
+
# The functions below here are abstract; each subclass should
|
165
|
+
# define them all. Defining them here only for reference.
|
166
|
+
#
|
167
|
+
|
168
|
+
# (abstract) Returns the names of all views, as an array of strings
|
169
|
+
def views(name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; [] end
|
170
|
+
|
171
|
+
# (abstract) Returns the SQL definition of a given view. This is
|
172
|
+
# the literal SQL would come after 'CREATVE VIEW viewname AS ' in
|
173
|
+
# the SQL statement to create a view.
|
174
|
+
def view_definition(view_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; end
|
175
|
+
|
176
|
+
# (abstract) Return the ForeignKeyDefinition objects for foreign key
|
177
|
+
# constraints defined on this table
|
178
|
+
def foreign_keys(table_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; [] end
|
179
|
+
|
180
|
+
# (abstract) Return the ForeignKeyDefinition objects for foreign key
|
181
|
+
# constraints defined on other tables that reference this table
|
182
|
+
def reverse_foreign_keys(table_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; [] end
|
183
|
+
|
184
|
+
# (abstract) Return true if the passed expression can be used as a column
|
185
|
+
# default value. (For most databases the specific expression
|
186
|
+
# doesn't matter, and the adapter's function would return a
|
187
|
+
# constant true if default expressions are supported or false if
|
188
|
+
# they're not.)
|
189
|
+
def default_expr_valid?(expr) raise "Internal Error: Connection adapter didn't override abstract function"; end
|
190
|
+
|
191
|
+
# (abstract) Return SQL definition for a given canonical function_name symbol.
|
192
|
+
# Currently, the only function to support is :now, which should
|
193
|
+
# return a DATETIME object for the current time.
|
194
|
+
def sql_for_function(function_name) raise "Internal Error: Connection adapter didn't override abstract function"; end
|
166
195
|
|
167
196
|
end
|
168
197
|
end
|
@@ -31,7 +31,7 @@ module SchemaPlus
|
|
31
31
|
# possible values.
|
32
32
|
attr_reader :on_update
|
33
33
|
|
34
|
-
# The
|
34
|
+
# The ON_DELETE behavior for the constraint. See above for the
|
35
35
|
# possible values.
|
36
36
|
attr_reader :on_delete
|
37
37
|
|
@@ -39,7 +39,7 @@ module SchemaPlus
|
|
39
39
|
attr_reader :deferrable
|
40
40
|
|
41
41
|
# :enddoc:
|
42
|
-
|
42
|
+
|
43
43
|
ACTIONS = { :cascade => "CASCADE", :restrict => "RESTRICT", :set_null => "SET NULL", :set_default => "SET DEFAULT", :no_action => "NO ACTION" }.freeze
|
44
44
|
|
45
45
|
def initialize(name, table_name, column_names, references_table_name, references_column_names, on_update = nil, on_delete = nil, deferrable = nil)
|
@@ -105,6 +105,11 @@ module SchemaPlus
|
|
105
105
|
def __unquote(value)
|
106
106
|
value.to_s.sub(/^["`](.*)["`]$/, '\1')
|
107
107
|
end
|
108
|
+
|
109
|
+
def self.auto_index_name(table_name, column_name)
|
110
|
+
"fk__#{table_name}_#{Array.wrap(column_name).join('_and_')}"
|
111
|
+
end
|
112
|
+
|
108
113
|
end
|
109
114
|
end
|
110
115
|
end
|
@@ -22,7 +22,7 @@ module SchemaPlus
|
|
22
22
|
# same args as add_index(table_name, column_names, options)
|
23
23
|
if args.length == 3 and Hash === args.last
|
24
24
|
table_name, column_names, options = args + [{}]
|
25
|
-
initialize_without_schema_plus(table_name, options[:name], options[:unique], column_names, options[:lengths])
|
25
|
+
initialize_without_schema_plus(table_name, options[:name], options[:unique], column_names, options[:lengths], options[:orders])
|
26
26
|
@conditions = options[:conditions]
|
27
27
|
@expression = options[:expression]
|
28
28
|
@kind = options[:kind]
|
@@ -48,6 +48,7 @@ module SchemaPlus
|
|
48
48
|
|
49
49
|
# tests if the corresponding indexes would be the same
|
50
50
|
def ==(other)
|
51
|
+
return false if other.nil?
|
51
52
|
return false unless self.name == other.name
|
52
53
|
return false unless Array.wrap(self.columns).collect(&:to_s).sort == Array.wrap(other.columns).collect(&:to_s).sort
|
53
54
|
return false unless !!self.unique == !!other.unique
|
@@ -12,6 +12,8 @@ module SchemaPlus
|
|
12
12
|
base.class_eval do
|
13
13
|
alias_method_chain :tables, :schema_plus
|
14
14
|
alias_method_chain :remove_column, :schema_plus
|
15
|
+
alias_method_chain :rename_table, :schema_plus
|
16
|
+
alias_method_chain :exec_stmt, :schema_plus rescue nil # only defined for mysql not mysql2
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -26,11 +28,27 @@ module SchemaPlus
|
|
26
28
|
remove_column_without_schema_plus(table_name, column_name)
|
27
29
|
end
|
28
30
|
|
31
|
+
def rename_table_with_schema_plus(oldname, newname)
|
32
|
+
rename_table_without_schema_plus(oldname, newname)
|
33
|
+
rename_indexes_and_foreign_keys(oldname, newname)
|
34
|
+
end
|
35
|
+
|
36
|
+
def exec_stmt_with_schema_plus(sql, name, binds, &block)
|
37
|
+
if binds.any?{ |col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
38
|
+
binds.each_with_index do |(col, val), i|
|
39
|
+
if val.equal? ::ActiveRecord::DB_DEFAULT
|
40
|
+
sql = sql.sub(/(([^?]*?){#{i}}[^?]*)\?/, "\\1DEFAULT")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
binds = binds.reject{|col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
44
|
+
end
|
45
|
+
exec_stmt_without_schema_plus(sql, name, binds, &block)
|
46
|
+
end
|
47
|
+
|
29
48
|
def remove_foreign_key(table_name, foreign_key_name, options = {})
|
30
49
|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP FOREIGN KEY #{foreign_key_name}"
|
31
50
|
end
|
32
51
|
|
33
|
-
|
34
52
|
def foreign_keys(table_name, name = nil)
|
35
53
|
results = execute("SHOW CREATE TABLE #{quote_table_name(table_name)}", name)
|
36
54
|
|
@@ -25,10 +25,10 @@ module SchemaPlus
|
|
25
25
|
|
26
26
|
module ClassMethods
|
27
27
|
def extract_value_from_default_with_schema_plus(default)
|
28
|
-
|
28
|
+
|
29
29
|
|
30
30
|
value = extract_value_from_default_without_schema_plus(default)
|
31
|
-
|
31
|
+
|
32
32
|
# in some cases (e.g. if change_column_default(table, column,
|
33
33
|
# nil) is used), postgresql will return NULL::xxxxx (rather
|
34
34
|
# than nil) for a null default -- make sure we treat it as nil,
|
@@ -50,6 +50,8 @@ module SchemaPlus
|
|
50
50
|
def self.included(base) #:nodoc:
|
51
51
|
base.class_eval do
|
52
52
|
remove_method :indexes
|
53
|
+
alias_method_chain :rename_table, :schema_plus
|
54
|
+
alias_method_chain :exec_cache, :schema_plus
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
@@ -74,13 +76,24 @@ module SchemaPlus
|
|
74
76
|
index_type = options[:unique] ? "UNIQUE" : ""
|
75
77
|
index_name = options[:name] || index_name(table_name, column_names)
|
76
78
|
conditions = options[:conditions]
|
79
|
+
kind = options[:kind]
|
77
80
|
|
78
|
-
if options[:expression] then
|
79
|
-
|
81
|
+
if expression = options[:expression] then
|
82
|
+
# Wrap expression in parentheses if necessary
|
83
|
+
expression = "(#{expression})" if expression !~ /(using|with|tablespace|where)/i
|
84
|
+
expression = "USING #{kind} #{expression}" if kind
|
85
|
+
expression = "#{expression} WHERE #{conditions}" if conditions
|
86
|
+
|
87
|
+
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{expression}"
|
80
88
|
else
|
81
|
-
|
89
|
+
option_strings = Hash[column_names.map {|name| [name, '']}]
|
90
|
+
option_strings = add_index_sort_order(option_strings, column_names, options)
|
91
|
+
|
92
|
+
quoted_column_names = column_names.map { |e| (options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e)) + option_strings[e] }
|
93
|
+
expression = "(#{quoted_column_names.join(', ')})"
|
94
|
+
expression = "USING #{kind} #{expression}" if kind
|
82
95
|
|
83
|
-
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}
|
96
|
+
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{expression}"
|
84
97
|
sql += " WHERE (#{ ::ActiveRecord::Base.send(:sanitize_sql, conditions, quote_table_name(table_name)) })" if conditions
|
85
98
|
end
|
86
99
|
execute sql
|
@@ -92,23 +105,28 @@ module SchemaPlus
|
|
92
105
|
true
|
93
106
|
end
|
94
107
|
|
108
|
+
# This method entirely duplicated from AR's postgresql_adapter.c,
|
109
|
+
# but includes the extra bit to determine the column name for a
|
110
|
+
# case-insensitive index. (Haven't come up with any clever way to
|
111
|
+
# only code up the case-insensitive column name bit here and
|
112
|
+
# otherwise use the existing method.)
|
95
113
|
def indexes(table_name, name = nil) #:nodoc:
|
96
|
-
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
97
114
|
result = query(<<-SQL, name)
|
98
|
-
|
99
|
-
|
100
|
-
|
115
|
+
|
116
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
117
|
+
m.amname, pg_get_expr(d.indpred, t.oid), pg_get_expr(d.indexprs, t.oid)
|
118
|
+
FROM pg_class t
|
119
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
120
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
121
|
+
INNER JOIN pg_am m ON i.relam = m.oid
|
101
122
|
WHERE i.relkind = 'i'
|
102
|
-
AND i.relam = m.oid
|
103
|
-
AND d.indexrelid = i.oid
|
104
123
|
AND d.indisprimary = 'f'
|
105
|
-
AND t.oid = d.indrelid
|
106
124
|
AND t.relname = '#{table_name}'
|
107
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname
|
125
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
108
126
|
ORDER BY i.relname
|
109
127
|
SQL
|
110
128
|
|
111
|
-
result.map do |(index_name, is_unique, indkey,
|
129
|
+
result.map do |(index_name, is_unique, indkey, inddef, oid, kind, conditions, expression)|
|
112
130
|
unique = (is_unique == 't')
|
113
131
|
index_keys = indkey.split(" ")
|
114
132
|
|
@@ -120,12 +138,20 @@ module SchemaPlus
|
|
120
138
|
SQL
|
121
139
|
|
122
140
|
column_names = columns.values_at(*index_keys).compact
|
141
|
+
# extract column name from the expression, for a
|
142
|
+
# case-insensitive
|
123
143
|
if md = expression.try(:match, /^lower\(\(?([^)]+)\)?(::text)?\)$/i)
|
124
144
|
column_names << md[1]
|
125
145
|
end
|
146
|
+
|
147
|
+
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
148
|
+
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
149
|
+
orders = desc_order_columns.any? ? Hash[column_names.map {|column| [column, desc_order_columns.include?(column) ? :desc : :asc]}] : {}
|
150
|
+
|
126
151
|
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, column_names,
|
127
152
|
:name => index_name,
|
128
153
|
:unique => unique,
|
154
|
+
:orders => orders,
|
129
155
|
:conditions => conditions,
|
130
156
|
:case_sensitive => !(expression =~ /lower/i),
|
131
157
|
:kind => kind.downcase == "btree" ? nil : kind,
|
@@ -133,6 +159,30 @@ module SchemaPlus
|
|
133
159
|
end
|
134
160
|
end
|
135
161
|
|
162
|
+
def rename_table_with_schema_plus(oldname, newname) #:nodoc:
|
163
|
+
rename_table_without_schema_plus(oldname, newname)
|
164
|
+
rename_indexes_and_foreign_keys(oldname, newname)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Prepass to replace each ActiveRecord::DB_DEFAULT with a literal
|
168
|
+
# DEFAULT in the sql string. (The underlying pg gem provides no
|
169
|
+
# way to bind a value that will replace $n with DEFAULT)
|
170
|
+
def exec_cache_with_schema_plus(sql, binds)
|
171
|
+
if binds.any?{ |col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
172
|
+
j = 0
|
173
|
+
binds.each_with_index do |(col, val), i|
|
174
|
+
if val.equal? ::ActiveRecord::DB_DEFAULT
|
175
|
+
sql = sql.sub(/\$#{i+1}/, 'DEFAULT')
|
176
|
+
else
|
177
|
+
sql = sql.sub(/\$#{i+1}/, "$#{j+1}") if i != j
|
178
|
+
j += 1
|
179
|
+
end
|
180
|
+
end
|
181
|
+
binds = binds.reject{|col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
182
|
+
end
|
183
|
+
exec_cache_without_schema_plus(sql, binds)
|
184
|
+
end
|
185
|
+
|
136
186
|
def foreign_keys(table_name, name = nil) #:nodoc:
|
137
187
|
load_foreign_keys(<<-SQL, name)
|
138
188
|
SELECT f.conname, pg_get_constraintdef(f.oid), t.relname
|
@@ -140,6 +190,7 @@ module SchemaPlus
|
|
140
190
|
WHERE f.conrelid = t.oid
|
141
191
|
AND f.contype = 'f'
|
142
192
|
AND t.relname = '#{table_name}'
|
193
|
+
AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
143
194
|
SQL
|
144
195
|
end
|
145
196
|
|
@@ -151,15 +202,15 @@ module SchemaPlus
|
|
151
202
|
AND f.conrelid = t2.oid
|
152
203
|
AND f.contype = 'f'
|
153
204
|
AND t.relname = '#{table_name}'
|
205
|
+
AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
154
206
|
SQL
|
155
207
|
end
|
156
208
|
|
157
209
|
def views(name = nil) #:nodoc:
|
158
|
-
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
159
210
|
query(<<-SQL, name).map { |row| row[0] }
|
160
211
|
SELECT viewname
|
161
212
|
FROM pg_views
|
162
|
-
WHERE schemaname
|
213
|
+
WHERE schemaname = ANY (current_schemas(false))
|
163
214
|
SQL
|
164
215
|
end
|
165
216
|
|