schema_plus 1.8.9 → 2.0.0.pre1
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.
- checksums.yaml +4 -4
- data/.gitignore +4 -4
- data/.travis.yml +1 -47
- data/CHANGELOG.md +0 -35
- data/README.md +73 -107
- data/Rakefile +7 -10
- data/TODO.md +51 -0
- data/gemfiles/Gemfile.base +2 -0
- data/lib/schema_column_plus.rb +7 -0
- data/lib/{schema_plus → schema_column_plus}/active_record/connection_adapters/column.rb +13 -11
- data/lib/schema_column_plus/middleware/model.rb +22 -0
- data/lib/schema_db_default.rb +13 -0
- data/lib/{schema_plus → schema_db_default}/active_record/attribute.rb +4 -4
- data/lib/schema_db_default/db_default.rb +17 -0
- data/lib/schema_db_default/middleware.rb +30 -0
- data/lib/schema_default_expr.rb +32 -0
- data/lib/schema_default_expr/active_record/connection_adapters/mysql_adapter.rb +17 -0
- data/lib/schema_default_expr/active_record/connection_adapters/postgresql_adapter.rb +18 -0
- data/lib/schema_default_expr/active_record/connection_adapters/sqlite3_adapter.rb +35 -0
- data/lib/schema_default_expr/middleware.rb +54 -0
- data/lib/schema_pg_enums.rb +6 -0
- data/lib/schema_pg_enums/active_record.rb +69 -0
- data/lib/schema_pg_enums/middleware.rb +23 -0
- data/lib/schema_plus.rb +17 -45
- data/lib/schema_plus/active_record/base.rb +6 -23
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +80 -181
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +78 -99
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +34 -114
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +16 -370
- data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +1 -67
- data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +18 -112
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +14 -116
- data/lib/schema_plus/active_record/migration/command_recorder.rb +8 -59
- data/lib/schema_plus/middleware/dumper.rb +94 -0
- data/lib/schema_plus/middleware/migration.rb +167 -0
- data/lib/schema_plus/middleware/model.rb +17 -0
- data/lib/schema_plus/version.rb +1 -1
- data/lib/schema_plus_tables.rb +15 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/abstract_adapter.rb +20 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/mysql_adapter.rb +25 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/postgresql_adapter.rb +13 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/sqlite3_adapter.rb +12 -0
- data/lib/schema_views.rb +16 -0
- data/lib/schema_views/active_record/connection_adapters/abstract_adapter.rb +41 -0
- data/lib/schema_views/active_record/connection_adapters/mysql_adapter.rb +30 -0
- data/lib/schema_views/active_record/connection_adapters/postgresql_adapter.rb +31 -0
- data/lib/schema_views/active_record/connection_adapters/sqlite3_adapter.rb +18 -0
- data/lib/schema_views/middleware.rb +47 -0
- data/schema_dev.yml +1 -31
- data/schema_plus.gemspec +11 -9
- data/spec/foreign_key_definition_spec.rb +7 -7
- data/spec/foreign_key_spec.rb +63 -48
- data/spec/migration_spec.rb +58 -203
- data/spec/named_schemas_spec.rb +5 -88
- data/spec/{column_spec.rb → schema_column_plus/column_spec.rb} +26 -48
- data/spec/schema_db_default/column_spec.rb +58 -0
- data/spec/{column_default_spec.rb → schema_default_expr/column_default_spec.rb} +1 -2
- data/spec/schema_default_expr/schema_dumper_spec.rb +116 -0
- data/spec/schema_dumper_spec.rb +22 -327
- data/spec/{enum_spec.rb → schema_pg_enums/enum_spec.rb} +1 -1
- data/spec/schema_pg_enums/schema_dumper_spec.rb +37 -0
- data/spec/schema_views/named_schemas_spec.rb +97 -0
- data/spec/{views_spec.rb → schema_views/views_spec.rb} +1 -1
- data/spec/spec_helper.rb +2 -1
- data/spec/support/matchers/reference.rb +11 -12
- metadata +104 -57
- data/gemfiles/rails-3.2/Gemfile.base +0 -3
- data/gemfiles/rails-3.2/Gemfile.mysql +0 -10
- data/gemfiles/rails-3.2/Gemfile.mysql2 +0 -10
- data/gemfiles/rails-3.2/Gemfile.postgresql +0 -10
- data/gemfiles/rails-3.2/Gemfile.sqlite3 +0 -10
- data/gemfiles/rails-4.0/Gemfile.base +0 -3
- data/gemfiles/rails-4.0/Gemfile.mysql2 +0 -10
- data/gemfiles/rails-4.0/Gemfile.postgresql +0 -10
- data/gemfiles/rails-4.0/Gemfile.sqlite3 +0 -10
- data/gemfiles/rails-4.1/Gemfile.base +0 -3
- data/gemfiles/rails-4.1/Gemfile.mysql2 +0 -10
- data/gemfiles/rails-4.1/Gemfile.postgresql +0 -10
- data/gemfiles/rails-4.1/Gemfile.sqlite3 +0 -10
- data/lib/schema_plus/active_record/column_options_handler.rb +0 -117
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +0 -70
- data/lib/schema_plus/active_record/db_default.rb +0 -19
- data/lib/schema_plus/active_record/foreign_keys.rb +0 -137
- data/lib/schema_plus/active_record/schema_dumper.rb +0 -171
- data/lib/schema_plus/railtie.rb +0 -20
- data/spec/index_definition_spec.rb +0 -211
- data/spec/index_spec.rb +0 -249
@@ -2,94 +2,45 @@ module SchemaPlus
|
|
2
2
|
module ActiveRecord
|
3
3
|
module ConnectionAdapters
|
4
4
|
# SchemaPlus includes a MySQL implementation of the AbstractAdapter
|
5
|
-
# extensions.
|
6
|
-
# <tt>mysql2</tt> gems.)
|
5
|
+
# extensions.
|
7
6
|
module MysqlAdapter
|
8
7
|
|
9
8
|
#:enddoc:
|
10
9
|
|
11
10
|
def self.included(base)
|
12
11
|
base.class_eval do
|
13
|
-
alias_method_chain :tables, :schema_plus
|
14
12
|
alias_method_chain :remove_column, :schema_plus
|
15
13
|
alias_method_chain :rename_table, :schema_plus
|
16
|
-
alias_method_chain :exec_stmt, :schema_plus rescue nil # only defined for mysql not mysql2
|
17
14
|
end
|
18
|
-
|
19
|
-
if ::ActiveRecord::VERSION::MAJOR.to_i >= 4
|
20
|
-
base.class_eval do
|
21
|
-
include ::ActiveRecord::ConnectionAdapters::SchemaStatements::AddIndex
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def tables_with_schema_plus(name=nil, *args)
|
27
|
-
tables_without_schema_plus(name, *args) - views(name)
|
15
|
+
::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::SchemaCreation.send(:include, SchemaPlus::ActiveRecord::ConnectionAdapters::AbstractAdapter::VisitTableDefinition)
|
28
16
|
end
|
29
17
|
|
30
18
|
def remove_column_with_schema_plus(table_name, column_name, type=nil, options={})
|
31
|
-
foreign_keys(table_name).select { |foreign_key| foreign_key.
|
32
|
-
remove_foreign_key(table_name, foreign_key.name)
|
33
|
-
end
|
34
|
-
if ::ActiveRecord::VERSION::MAJOR.to_i >= 4
|
35
|
-
remove_column_without_schema_plus(table_name, column_name, type, options)
|
36
|
-
else
|
37
|
-
remove_column_without_schema_plus(table_name, column_name)
|
19
|
+
foreign_keys(table_name).select { |foreign_key| Array.wrap(foreign_key.column).include?(column_name.to_s) }.each do |foreign_key|
|
20
|
+
remove_foreign_key(table_name, name: foreign_key.name)
|
38
21
|
end
|
22
|
+
remove_column_without_schema_plus(table_name, column_name, type, options)
|
39
23
|
end
|
40
24
|
|
41
25
|
def rename_table_with_schema_plus(oldname, newname)
|
42
26
|
rename_table_without_schema_plus(oldname, newname)
|
43
|
-
|
27
|
+
rename_foreign_keys(oldname, newname)
|
44
28
|
end
|
45
29
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
if val.equal? ::ActiveRecord::DB_DEFAULT
|
52
|
-
sql = sql.sub(/(([^?]*?){#{i}}[^?]*)\?/, "\\1DEFAULT")
|
53
|
-
end
|
54
|
-
end
|
55
|
-
binds = binds.reject{|col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
30
|
+
def remove_foreign_key(*args)
|
31
|
+
from_table, to_table, options = normalize_remove_foreign_key_args(*args)
|
32
|
+
if options[:if_exists]
|
33
|
+
foreign_key_name = get_foreign_key_name(from_table, to_table, options)
|
34
|
+
return if !foreign_key_name or not foreign_keys(from_table).detect{|fk| fk.name == foreign_key_name}
|
56
35
|
end
|
57
|
-
|
36
|
+
options.delete(:if_exists)
|
37
|
+
super from_table, to_table, options
|
58
38
|
end
|
59
39
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
remove_foreign_key(foreign_key.table_name, foreign_key.name)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
sql = 'DROP'
|
69
|
-
sql += ' TEMPORARY' if options[:temporary]
|
70
|
-
sql += ' TABLE'
|
71
|
-
sql += ' IF EXISTS' if options[:if_exists]
|
72
|
-
sql += " #{quote_table_name(name)}"
|
73
|
-
|
74
|
-
execute sql
|
75
|
-
end
|
76
|
-
|
77
|
-
def remove_index_sql(table_name, options)
|
78
|
-
return [] if options.delete(:if_exists) and not index_exists?(table_name, options)
|
79
|
-
super
|
80
|
-
end
|
81
|
-
|
82
|
-
def remove_foreign_key_sql(table_name, *args)
|
83
|
-
case ret = super
|
84
|
-
when String then ret.sub(/DROP CONSTRAINT/, 'DROP FOREIGN KEY')
|
85
|
-
else ret
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def remove_foreign_key(table_name, *args)
|
90
|
-
case sql = remove_foreign_key_sql(table_name, *args)
|
91
|
-
when String then execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
|
92
|
-
end
|
40
|
+
def remove_foreign_key_sql(*args)
|
41
|
+
super.tap { |ret|
|
42
|
+
ret.sub!(/DROP CONSTRAINT/, 'DROP FOREIGN KEY') if ret
|
43
|
+
}
|
93
44
|
end
|
94
45
|
|
95
46
|
def foreign_keys(table_name, name = nil)
|
@@ -105,24 +56,26 @@ module SchemaPlus
|
|
105
56
|
create_table_sql.lines.each do |line|
|
106
57
|
if line =~ /^ CONSTRAINT [`"](.+?)[`"] FOREIGN KEY \([`"](.+?)[`"]\) REFERENCES [`"](.+?)[`"] \((.+?)\)( ON DELETE (.+?))?( ON UPDATE (.+?))?,?$/
|
107
58
|
name = $1
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
59
|
+
columns = $2
|
60
|
+
to_table = $3
|
61
|
+
to_table = namespace_prefix + to_table if table_namespace_prefix(to_table).blank?
|
62
|
+
primary_keys = $4
|
112
63
|
on_update = $8
|
113
64
|
on_delete = $6
|
114
|
-
on_update = on_update
|
115
|
-
on_delete = on_delete
|
65
|
+
on_update = ForeignKeyDefinition::ACTION_LOOKUP[on_update] || :restrict
|
66
|
+
on_delete = ForeignKeyDefinition::ACTION_LOOKUP[on_delete] || :restrict
|
116
67
|
|
117
68
|
options = { :name => name,
|
118
69
|
:on_delete => on_delete,
|
119
70
|
:on_update => on_update,
|
120
|
-
:
|
121
|
-
:
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
71
|
+
:column => columns.gsub('`', '').split(', '),
|
72
|
+
:primary_key => primary_keys.gsub('`', '').split(', ')
|
73
|
+
}
|
74
|
+
|
75
|
+
foreign_keys << ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
|
76
|
+
namespace_prefix + table_name,
|
77
|
+
to_table,
|
78
|
+
options)
|
126
79
|
end
|
127
80
|
end
|
128
81
|
end
|
@@ -153,44 +106,11 @@ module SchemaPlus
|
|
153
106
|
|
154
107
|
options = {
|
155
108
|
:name => constraint_name,
|
156
|
-
:
|
157
|
-
:
|
109
|
+
:column => columns.map { |row| row['column_name'] },
|
110
|
+
:primary_key => columns.map { |row| row['referenced_column_name'] }
|
158
111
|
}
|
159
112
|
|
160
|
-
ForeignKeyDefinition.new(from_table, to_table, options)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def views(name = nil)
|
165
|
-
views = []
|
166
|
-
select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", name).each do |row|
|
167
|
-
views << row["table_name"]
|
168
|
-
end
|
169
|
-
views
|
170
|
-
end
|
171
|
-
|
172
|
-
def view_definition(view_name, name = nil)
|
173
|
-
results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
|
174
|
-
return nil unless results.any?
|
175
|
-
row = results.first
|
176
|
-
sql = row["view_definition"]
|
177
|
-
sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
|
178
|
-
case row["check_option"]
|
179
|
-
when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
|
180
|
-
when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
|
181
|
-
end
|
182
|
-
sql
|
183
|
-
end
|
184
|
-
|
185
|
-
module AddColumnOptions
|
186
|
-
def default_expr_valid?(expr)
|
187
|
-
false # only the TIMESTAMP column accepts SQL column defaults and rails uses DATETIME
|
188
|
-
end
|
189
|
-
|
190
|
-
def sql_for_function(function)
|
191
|
-
case function
|
192
|
-
when :now then 'CURRENT_TIMESTAMP'
|
193
|
-
end
|
113
|
+
::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(from_table, to_table, options)
|
194
114
|
end
|
195
115
|
end
|
196
116
|
|
@@ -1,66 +1,6 @@
|
|
1
1
|
module SchemaPlus
|
2
2
|
module ActiveRecord
|
3
3
|
module ConnectionAdapters
|
4
|
-
# PostgreSQL-specific extensions to column definitions in a table.
|
5
|
-
module PostgreSQLColumn
|
6
|
-
# Extracts the value from a PostgreSQL column default definition.
|
7
|
-
def self.included(base) #:nodoc:
|
8
|
-
base.extend ClassMethods
|
9
|
-
if defined?(JRUBY_VERSION)
|
10
|
-
base.alias_method_chain :default_value, :schema_plus
|
11
|
-
else
|
12
|
-
if "#{::ActiveRecord::VERSION::MAJOR}.#{::ActiveRecord::VERSION::MINOR}".to_r < "4.2".to_r
|
13
|
-
base.class_eval do
|
14
|
-
class << self
|
15
|
-
alias_method_chain :extract_value_from_default, :schema_plus
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
if "#{::ActiveRecord::VERSION::MAJOR}.#{::ActiveRecord::VERSION::MINOR}".to_r <= "4.1".to_r
|
23
|
-
def initialize(name, default, sql_type = nil, null = true)
|
24
|
-
if default.is_a? Hash
|
25
|
-
if default[:expr]
|
26
|
-
@default_expr = default[:expr]
|
27
|
-
end
|
28
|
-
default = nil
|
29
|
-
end
|
30
|
-
super(name, default, sql_type, null)
|
31
|
-
end
|
32
|
-
else
|
33
|
-
def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
|
34
|
-
super(name, default, cast_type, sql_type, null)
|
35
|
-
@default_function = @default_expr = default_function
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def default_value_with_schema_plus(default)
|
40
|
-
value = default_value_without_schema_plus(default)
|
41
|
-
self.class.convert_default_value(default, value)
|
42
|
-
end
|
43
|
-
|
44
|
-
module ClassMethods
|
45
|
-
def extract_value_from_default_with_schema_plus(default)
|
46
|
-
value = extract_value_from_default_without_schema_plus(default)
|
47
|
-
convert_default_value(default, value)
|
48
|
-
end
|
49
|
-
|
50
|
-
# in some cases (e.g. if change_column_default(table, column,
|
51
|
-
# nil) is used), postgresql will return NULL::xxxxx (rather
|
52
|
-
# than nil) for a null default -- make sure we treat it as nil,
|
53
|
-
# not as a function.
|
54
|
-
def convert_default_value(default, value)
|
55
|
-
default = nil if value.nil? && default =~ /\ANULL::(?:character varying|bpchar|text)\z/m
|
56
|
-
|
57
|
-
if value.nil? && !default.nil?
|
58
|
-
value = { :expr => default }
|
59
|
-
end
|
60
|
-
value
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
4
|
|
65
5
|
# The Postgresql adapter implements the SchemaPlus extensions and
|
66
6
|
# enhancements
|
@@ -68,217 +8,13 @@ module SchemaPlus
|
|
68
8
|
|
69
9
|
def self.included(base) #:nodoc:
|
70
10
|
base.class_eval do
|
71
|
-
if ::ActiveRecord::VERSION::MAJOR.to_i < 4 && !defined?(JRUBY_VERSION)
|
72
|
-
remove_method :indexes
|
73
|
-
end
|
74
11
|
alias_method_chain :rename_table, :schema_plus
|
75
|
-
alias_method_chain :exec_cache, :schema_plus unless defined?(JRUBY_VERSION)
|
76
|
-
end
|
77
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.send(:include, PostgreSQLColumn) unless ::ActiveRecord::ConnectionAdapters::PostgreSQLColumn.include?(PostgreSQLColumn)
|
78
|
-
end
|
79
|
-
|
80
|
-
# SchemaPlus provides the following extra options for PostgreSQL
|
81
|
-
# indexes:
|
82
|
-
# * +:conditions+ - SQL conditions for the WHERE clause of the index
|
83
|
-
# * +:expression+ - SQL expression to index. column_name can be nil or ommitted, in which case :name must be provided
|
84
|
-
# * +:kind+ - index method for Postgresql to use
|
85
|
-
# * +:operator_class+ - an operator class name or a hash mapping column name to operator class name
|
86
|
-
# * +:case_sensitive - setting to +false+ is a shorthand for :expression => 'LOWER(column_name)'
|
87
|
-
#
|
88
|
-
# The <tt>:case_sensitive => false</tt> option ties in with Rails built-in support for case-insensitive searching:
|
89
|
-
# validates_uniqueness_of :name, :case_sensitive => false
|
90
|
-
#
|
91
|
-
# Since since <tt>:case_sensitive => false</tt> is implemented by
|
92
|
-
# using <tt>:expression</tt>, this raises an ArgumentError if both
|
93
|
-
# are specified simultaneously.
|
94
|
-
#
|
95
|
-
def add_index(table_name, column_name, options = {})
|
96
|
-
options = {} if options.nil? # some callers explicitly pass options=nil
|
97
|
-
column_name, options = [], column_name if column_name.is_a?(Hash)
|
98
|
-
column_names = Array(column_name).compact
|
99
|
-
column_names += Array(options[:with] || [])
|
100
|
-
if column_names.empty?
|
101
|
-
raise ArgumentError, "No columns and :expression missing from options - cannot create index" unless options[:expression]
|
102
|
-
raise ArgumentError, "Index name not given. Pass :name option" unless options[:name]
|
103
|
-
end
|
104
|
-
|
105
|
-
index_type = options[:unique] ? "UNIQUE" : nil
|
106
|
-
index_name = options[:name] || index_name(table_name, column_names)
|
107
|
-
concurrently = options[:algorithm] == :concurrently
|
108
|
-
conditions = options[:conditions]
|
109
|
-
kind = options[:kind]
|
110
|
-
operator_classes = options[:operator_class]
|
111
|
-
if operator_classes and not operator_classes.is_a? Hash
|
112
|
-
operator_classes = Hash[column_names.map {|name| [name, operator_classes]}]
|
113
|
-
end
|
114
|
-
|
115
|
-
sql = []
|
116
|
-
sql << 'CREATE'
|
117
|
-
sql << index_type
|
118
|
-
sql << 'INDEX'
|
119
|
-
sql << 'CONCURRENTLY' if concurrently
|
120
|
-
sql << quote_column_name(index_name)
|
121
|
-
sql << 'ON'
|
122
|
-
sql << quote_table_name(table_name)
|
123
|
-
|
124
|
-
if expression = options[:expression] then
|
125
|
-
raise ArgumentError, "Cannot specify :case_sensitive => false with an expression. Use LOWER(column_name)" if options[:case_sensitive] == false
|
126
|
-
# Wrap expression in parentheses if necessary
|
127
|
-
expression = "(#{expression})" if expression !~ /(using|with|tablespace|where)/i
|
128
|
-
expression = "USING #{kind} #{expression}" if kind
|
129
|
-
expression = "#{expression} WHERE #{conditions}" if conditions
|
130
|
-
sql << expression
|
131
|
-
else
|
132
|
-
option_strings = Hash[column_names.map {|name| [name, '']}]
|
133
|
-
(operator_classes||{}).each do |column, opclass|
|
134
|
-
option_strings[column] += " #{opclass}" if opclass
|
135
|
-
end
|
136
|
-
option_strings = add_index_sort_order(option_strings, column_names, options)
|
137
|
-
|
138
|
-
if options[:case_sensitive] == false
|
139
|
-
caseable_columns = columns(table_name).select { |col| [:string, :text].include?(col.type) }.map(&:name)
|
140
|
-
quoted_column_names = column_names.map do |col_name|
|
141
|
-
(caseable_columns.include?(col_name.to_s) ? "LOWER(#{quote_column_name(col_name)})" : quote_column_name(col_name)) + option_strings[col_name]
|
142
|
-
end
|
143
|
-
else
|
144
|
-
quoted_column_names = column_names.map { |col_name| quote_column_name(col_name) + option_strings[col_name] }
|
145
|
-
end
|
146
|
-
|
147
|
-
expression = "(#{quoted_column_names.join(', ')})"
|
148
|
-
expression = "USING #{kind} #{expression}" if kind
|
149
|
-
|
150
|
-
sql << expression
|
151
|
-
sql << "WHERE (#{ ::ActiveRecord::Base.send(:sanitize_sql, conditions, quote_table_name(table_name)) })" if conditions
|
152
|
-
end
|
153
|
-
execute sql.compact.join(' ')
|
154
|
-
rescue => e
|
155
|
-
SchemaStatements.add_index_exception_handler(self, table_name, column_names, options, e)
|
156
|
-
end
|
157
|
-
|
158
|
-
def supports_partial_indexes? #:nodoc:
|
159
|
-
true
|
160
|
-
end
|
161
|
-
|
162
|
-
# This method entirely duplicated from AR's postgresql_adapter.c,
|
163
|
-
# but includes the extra bit to determine the column name for a
|
164
|
-
# case-insensitive index. (Haven't come up with any clever way to
|
165
|
-
# only code up the case-insensitive column name bit here and
|
166
|
-
# otherwise use the existing method.)
|
167
|
-
def indexes(table_name, name = nil) #:nodoc:
|
168
|
-
result = query(<<-SQL, name)
|
169
|
-
|
170
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
171
|
-
m.amname, pg_get_expr(d.indpred, t.oid) as conditions, pg_get_expr(d.indexprs, t.oid) as expression,
|
172
|
-
d.indclass
|
173
|
-
FROM pg_class t
|
174
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
175
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
176
|
-
INNER JOIN pg_am m ON i.relam = m.oid
|
177
|
-
WHERE i.relkind = 'i'
|
178
|
-
AND d.indisprimary = 'f'
|
179
|
-
AND t.relname = '#{table_name_without_namespace(table_name)}'
|
180
|
-
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{namespace_sql(table_name)} )
|
181
|
-
ORDER BY i.relname
|
182
|
-
SQL
|
183
|
-
|
184
|
-
result.map do |(index_name, is_unique, indkey, inddef, oid, kind, conditions, expression, indclass)|
|
185
|
-
unique = (is_unique == 't' || is_unique == true) # The test against true is for JDBC which is returning a boolean and not a String.
|
186
|
-
index_keys = indkey.split(" ")
|
187
|
-
opclasses = indclass.split(" ")
|
188
|
-
|
189
|
-
rows = query(<<-SQL, "Columns for index #{index_name} on #{table_name}")
|
190
|
-
SELECT CAST(a.attnum as VARCHAR), a.attname, t.typname
|
191
|
-
FROM pg_attribute a
|
192
|
-
INNER JOIN pg_type t ON a.atttypid = t.oid
|
193
|
-
WHERE a.attrelid = #{oid}
|
194
|
-
SQL
|
195
|
-
columns = {}
|
196
|
-
types = {}
|
197
|
-
rows.each do |num, name, type|
|
198
|
-
columns[num] = name
|
199
|
-
types[name] = type
|
200
|
-
end
|
201
|
-
|
202
|
-
column_names = columns.values_at(*index_keys).compact
|
203
|
-
case_sensitive = true
|
204
|
-
|
205
|
-
# extract column names from the expression, for a
|
206
|
-
# case-insensitive index.
|
207
|
-
# only applies to character, character varying, and text
|
208
|
-
if expression
|
209
|
-
rexp_lower = %r{\blower\(\(?([^)]+)(\)::text)?\)}
|
210
|
-
if expression.match /\A#{rexp_lower}(?:, #{rexp_lower})*\z/
|
211
|
-
case_insensitive_columns = expression.scan(rexp_lower).map(&:first).select{|column| %W[char varchar text].include? types[column]}
|
212
|
-
if case_insensitive_columns.any?
|
213
|
-
case_sensitive = false
|
214
|
-
column_names = index_keys.map { |index_key|
|
215
|
-
index_key == '0' ? case_insensitive_columns.shift : columns[index_key]
|
216
|
-
}.compact
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
opclass_name = {}
|
222
|
-
rows = query(<<-SQL, "Op classes for index #{index_name} on #{table_name}")
|
223
|
-
SELECT oid, opcname FROM pg_opclass
|
224
|
-
WHERE (NOT opcdefault) AND oid IN (#{opclasses.join(',')})
|
225
|
-
SQL
|
226
|
-
rows.each do |oid, opcname|
|
227
|
-
opclass_name[oid.to_s] = opcname
|
228
|
-
end
|
229
|
-
operator_classes = {}
|
230
|
-
column_names.zip(opclasses).each do |column_name, opclass|
|
231
|
-
operator_classes[column_name] = opclass_name[opclass]
|
232
|
-
end
|
233
|
-
operator_classes.delete_if{|k,v| v.nil?}
|
234
|
-
|
235
|
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
236
|
-
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
|
237
|
-
orders = desc_order_columns.any? ? Hash[column_names.map {|column| [column, desc_order_columns.include?(column) ? :desc : :asc]}] : {}
|
238
|
-
|
239
|
-
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, column_names,
|
240
|
-
:name => index_name,
|
241
|
-
:unique => unique,
|
242
|
-
:orders => orders,
|
243
|
-
:conditions => conditions,
|
244
|
-
:case_sensitive => case_sensitive,
|
245
|
-
:kind => kind.downcase == "btree" ? nil : kind,
|
246
|
-
:operator_classes => operator_classes,
|
247
|
-
:expression => expression)
|
248
12
|
end
|
249
13
|
end
|
250
14
|
|
251
|
-
def query(*args)
|
252
|
-
select(*args).map(&:values)
|
253
|
-
end if defined?(JRUBY_VERSION)
|
254
|
-
|
255
15
|
def rename_table_with_schema_plus(oldname, newname) #:nodoc:
|
256
16
|
rename_table_without_schema_plus(oldname, newname)
|
257
|
-
|
258
|
-
end
|
259
|
-
|
260
|
-
# Prepass to replace each ActiveRecord::DB_DEFAULT with a literal
|
261
|
-
# DEFAULT in the sql string. (The underlying pg gem provides no
|
262
|
-
# way to bind a value that will replace $n with DEFAULT)
|
263
|
-
def exec_cache_with_schema_plus(sql, *args)
|
264
|
-
name_passed = (2 == args.size)
|
265
|
-
binds, name = args.reverse
|
266
|
-
|
267
|
-
if binds.any?{ |col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
268
|
-
j = 0
|
269
|
-
binds.each_with_index do |(col, val), i|
|
270
|
-
if val.equal? ::ActiveRecord::DB_DEFAULT
|
271
|
-
sql = sql.sub(/\$#{i+1}/, 'DEFAULT')
|
272
|
-
else
|
273
|
-
sql = sql.sub(/\$#{i+1}/, "$#{j+1}") if i != j
|
274
|
-
j += 1
|
275
|
-
end
|
276
|
-
end
|
277
|
-
binds = binds.reject{|col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
|
278
|
-
end
|
279
|
-
|
280
|
-
args = name_passed ? [name, binds] : [binds]
|
281
|
-
exec_cache_without_schema_plus(sql, *args)
|
17
|
+
rename_foreign_keys(oldname, newname)
|
282
18
|
end
|
283
19
|
|
284
20
|
def foreign_keys(table_name, name = nil) #:nodoc:
|
@@ -304,90 +40,11 @@ module SchemaPlus
|
|
304
40
|
SQL
|
305
41
|
end
|
306
42
|
|
307
|
-
def views(name = nil) #:nodoc:
|
308
|
-
sql = <<-SQL
|
309
|
-
SELECT viewname
|
310
|
-
FROM pg_views
|
311
|
-
WHERE schemaname = ANY (current_schemas(false))
|
312
|
-
AND viewname NOT LIKE 'pg\_%'
|
313
|
-
SQL
|
314
|
-
sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS'
|
315
|
-
query(sql, name).map { |row| row[0] }
|
316
|
-
end
|
317
|
-
|
318
|
-
def view_definition(view_name, name = nil) #:nodoc:
|
319
|
-
result = query(<<-SQL, name)
|
320
|
-
SELECT pg_get_viewdef(oid)
|
321
|
-
FROM pg_class
|
322
|
-
WHERE relkind = 'v'
|
323
|
-
AND relname = '#{view_name}'
|
324
|
-
SQL
|
325
|
-
row = result.first
|
326
|
-
row.first.chomp(';') unless row.nil?
|
327
|
-
end
|
328
|
-
|
329
|
-
def enums #:nodoc:
|
330
|
-
result = query(<<-SQL)
|
331
|
-
SELECT
|
332
|
-
N.nspname AS schema_name,
|
333
|
-
T.typname AS enum_name,
|
334
|
-
E.enumlabel AS enum_label,
|
335
|
-
E.enumsortorder AS enum_sort_order
|
336
|
-
--array_agg(E.enumlabel ORDER BY enumsortorder) AS labels
|
337
|
-
FROM pg_type T
|
338
|
-
JOIN pg_enum E ON E.enumtypid = T.oid
|
339
|
-
JOIN pg_namespace N ON N.oid = T.typnamespace
|
340
|
-
ORDER BY 1, 2, 4
|
341
|
-
SQL
|
342
|
-
|
343
|
-
result.reduce([]) do |res, row|
|
344
|
-
last = res.last
|
345
|
-
if last && last[0] == row[0] && last[1] == row[1]
|
346
|
-
last[2] << row[2]
|
347
|
-
else
|
348
|
-
res << (row[0..1] << [row[2]])
|
349
|
-
end
|
350
|
-
res
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
def create_enum(name, *values)
|
355
|
-
options = values.extract_options!
|
356
|
-
list = values.map { |value| escape_enum_value(value) }
|
357
|
-
execute "CREATE TYPE #{enum_name(name, options[:schema])} AS ENUM (#{list.join(',')})"
|
358
|
-
end
|
359
|
-
|
360
|
-
def alter_enum(name, value, options = {})
|
361
|
-
opts = case
|
362
|
-
when options[:before] then "BEFORE #{escape_enum_value(options[:before])}"
|
363
|
-
when options[:after] then "AFTER #{escape_enum_value(options[:after])}"
|
364
|
-
else
|
365
|
-
''
|
366
|
-
end
|
367
|
-
execute "ALTER TYPE #{enum_name(name, options[:schema])} ADD VALUE #{escape_enum_value(value)} #{opts}"
|
368
|
-
end
|
369
|
-
|
370
|
-
def drop_enum(name, options = {})
|
371
|
-
execute "DROP TYPE #{enum_name(name, options[:schema])}"
|
372
|
-
end
|
373
|
-
|
374
|
-
# pg gem defines a drop_table with fewer options than our Abstract
|
375
|
-
# one, so use the abstract one instead
|
376
|
-
def drop_table(name, options={})
|
377
|
-
SchemaPlus::ActiveRecord::ConnectionAdapters::AbstractAdapter.instance_method(:drop_table).bind(self).call(name, options)
|
378
|
-
end
|
379
|
-
|
380
43
|
private
|
381
44
|
|
382
|
-
def
|
383
|
-
|
384
|
-
|
385
|
-
}.join('.')
|
386
|
-
end
|
387
|
-
|
388
|
-
def escape_enum_value(value)
|
389
|
-
escaped_value = value.sub("'", "''")
|
390
|
-
"'#{escaped_value}'"
|
45
|
+
def unquote(name)
|
46
|
+
return name.map { |name| unquote(name) } if name.is_a?(Array)
|
47
|
+
name.sub(/^["`](.*)["`]$/, '\1')
|
391
48
|
end
|
392
49
|
|
393
50
|
def namespace_sql(table_name)
|
@@ -404,45 +61,34 @@ module SchemaPlus
|
|
404
61
|
query(sql, name).each do |row|
|
405
62
|
if row[1] =~ /^FOREIGN KEY \((.+?)\) REFERENCES (.+?)\((.+?)\)( ON UPDATE (.+?))?( ON DELETE (.+?))?( (DEFERRABLE|NOT DEFERRABLE)( (INITIALLY DEFERRED|INITIALLY IMMEDIATE))?)?$/
|
406
63
|
name = row[0]
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
64
|
+
from_table = unquote(row[2])
|
65
|
+
columns = unquote($1.split(', '))
|
66
|
+
to_table = unquote($2)
|
67
|
+
primary_keys = unquote($3.split(', '))
|
411
68
|
on_update = $5
|
412
69
|
on_delete = $7
|
413
70
|
deferrable = $9 == "DEFERRABLE"
|
414
71
|
deferrable = :initially_deferred if ($11 == "INITIALLY DEFERRED" )
|
415
|
-
on_update = on_update
|
416
|
-
on_delete = on_delete
|
72
|
+
on_update = ForeignKeyDefinition::ACTION_LOOKUP[on_update] || :no_action
|
73
|
+
on_delete = ForeignKeyDefinition::ACTION_LOOKUP[on_delete] || :no_action
|
417
74
|
|
418
75
|
options = { :name => name,
|
419
76
|
:on_delete => on_delete,
|
420
77
|
:on_update => on_update,
|
421
|
-
:
|
422
|
-
:
|
78
|
+
:column => columns,
|
79
|
+
:primary_key => primary_keys,
|
423
80
|
:deferrable => deferrable }
|
424
81
|
|
425
|
-
foreign_keys << ForeignKeyDefinition.new(
|
426
|
-
|
427
|
-
|
82
|
+
foreign_keys << ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
|
83
|
+
from_table,
|
84
|
+
to_table.sub(/^"(.*)"$/, '\1'),
|
85
|
+
options)
|
428
86
|
end
|
429
87
|
end
|
430
88
|
|
431
89
|
foreign_keys
|
432
90
|
end
|
433
91
|
|
434
|
-
module AddColumnOptions
|
435
|
-
def default_expr_valid?(expr)
|
436
|
-
true # arbitrary sql is okay in PostgreSQL
|
437
|
-
end
|
438
|
-
|
439
|
-
def sql_for_function(function)
|
440
|
-
case function
|
441
|
-
when :now
|
442
|
-
"NOW()"
|
443
|
-
end
|
444
|
-
end
|
445
|
-
end
|
446
92
|
end
|
447
93
|
end
|
448
94
|
end
|