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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/.travis.yml +1 -47
  4. data/CHANGELOG.md +0 -35
  5. data/README.md +73 -107
  6. data/Rakefile +7 -10
  7. data/TODO.md +51 -0
  8. data/gemfiles/Gemfile.base +2 -0
  9. data/lib/schema_column_plus.rb +7 -0
  10. data/lib/{schema_plus → schema_column_plus}/active_record/connection_adapters/column.rb +13 -11
  11. data/lib/schema_column_plus/middleware/model.rb +22 -0
  12. data/lib/schema_db_default.rb +13 -0
  13. data/lib/{schema_plus → schema_db_default}/active_record/attribute.rb +4 -4
  14. data/lib/schema_db_default/db_default.rb +17 -0
  15. data/lib/schema_db_default/middleware.rb +30 -0
  16. data/lib/schema_default_expr.rb +32 -0
  17. data/lib/schema_default_expr/active_record/connection_adapters/mysql_adapter.rb +17 -0
  18. data/lib/schema_default_expr/active_record/connection_adapters/postgresql_adapter.rb +18 -0
  19. data/lib/schema_default_expr/active_record/connection_adapters/sqlite3_adapter.rb +35 -0
  20. data/lib/schema_default_expr/middleware.rb +54 -0
  21. data/lib/schema_pg_enums.rb +6 -0
  22. data/lib/schema_pg_enums/active_record.rb +69 -0
  23. data/lib/schema_pg_enums/middleware.rb +23 -0
  24. data/lib/schema_plus.rb +17 -45
  25. data/lib/schema_plus/active_record/base.rb +6 -23
  26. data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +80 -181
  27. data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +78 -99
  28. data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +34 -114
  29. data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +16 -370
  30. data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +1 -67
  31. data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +18 -112
  32. data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +14 -116
  33. data/lib/schema_plus/active_record/migration/command_recorder.rb +8 -59
  34. data/lib/schema_plus/middleware/dumper.rb +94 -0
  35. data/lib/schema_plus/middleware/migration.rb +167 -0
  36. data/lib/schema_plus/middleware/model.rb +17 -0
  37. data/lib/schema_plus/version.rb +1 -1
  38. data/lib/schema_plus_tables.rb +15 -0
  39. data/lib/schema_plus_tables/active_record/connection_adapters/abstract_adapter.rb +20 -0
  40. data/lib/schema_plus_tables/active_record/connection_adapters/mysql_adapter.rb +25 -0
  41. data/lib/schema_plus_tables/active_record/connection_adapters/postgresql_adapter.rb +13 -0
  42. data/lib/schema_plus_tables/active_record/connection_adapters/sqlite3_adapter.rb +12 -0
  43. data/lib/schema_views.rb +16 -0
  44. data/lib/schema_views/active_record/connection_adapters/abstract_adapter.rb +41 -0
  45. data/lib/schema_views/active_record/connection_adapters/mysql_adapter.rb +30 -0
  46. data/lib/schema_views/active_record/connection_adapters/postgresql_adapter.rb +31 -0
  47. data/lib/schema_views/active_record/connection_adapters/sqlite3_adapter.rb +18 -0
  48. data/lib/schema_views/middleware.rb +47 -0
  49. data/schema_dev.yml +1 -31
  50. data/schema_plus.gemspec +11 -9
  51. data/spec/foreign_key_definition_spec.rb +7 -7
  52. data/spec/foreign_key_spec.rb +63 -48
  53. data/spec/migration_spec.rb +58 -203
  54. data/spec/named_schemas_spec.rb +5 -88
  55. data/spec/{column_spec.rb → schema_column_plus/column_spec.rb} +26 -48
  56. data/spec/schema_db_default/column_spec.rb +58 -0
  57. data/spec/{column_default_spec.rb → schema_default_expr/column_default_spec.rb} +1 -2
  58. data/spec/schema_default_expr/schema_dumper_spec.rb +116 -0
  59. data/spec/schema_dumper_spec.rb +22 -327
  60. data/spec/{enum_spec.rb → schema_pg_enums/enum_spec.rb} +1 -1
  61. data/spec/schema_pg_enums/schema_dumper_spec.rb +37 -0
  62. data/spec/schema_views/named_schemas_spec.rb +97 -0
  63. data/spec/{views_spec.rb → schema_views/views_spec.rb} +1 -1
  64. data/spec/spec_helper.rb +2 -1
  65. data/spec/support/matchers/reference.rb +11 -12
  66. metadata +104 -57
  67. data/gemfiles/rails-3.2/Gemfile.base +0 -3
  68. data/gemfiles/rails-3.2/Gemfile.mysql +0 -10
  69. data/gemfiles/rails-3.2/Gemfile.mysql2 +0 -10
  70. data/gemfiles/rails-3.2/Gemfile.postgresql +0 -10
  71. data/gemfiles/rails-3.2/Gemfile.sqlite3 +0 -10
  72. data/gemfiles/rails-4.0/Gemfile.base +0 -3
  73. data/gemfiles/rails-4.0/Gemfile.mysql2 +0 -10
  74. data/gemfiles/rails-4.0/Gemfile.postgresql +0 -10
  75. data/gemfiles/rails-4.0/Gemfile.sqlite3 +0 -10
  76. data/gemfiles/rails-4.1/Gemfile.base +0 -3
  77. data/gemfiles/rails-4.1/Gemfile.mysql2 +0 -10
  78. data/gemfiles/rails-4.1/Gemfile.postgresql +0 -10
  79. data/gemfiles/rails-4.1/Gemfile.sqlite3 +0 -10
  80. data/lib/schema_plus/active_record/column_options_handler.rb +0 -117
  81. data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +0 -70
  82. data/lib/schema_plus/active_record/db_default.rb +0 -19
  83. data/lib/schema_plus/active_record/foreign_keys.rb +0 -137
  84. data/lib/schema_plus/active_record/schema_dumper.rb +0 -171
  85. data/lib/schema_plus/railtie.rb +0 -20
  86. data/spec/index_definition_spec.rb +0 -211
  87. 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. (This works with both the <tt>mysql</t> and
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.column_names.include?(column_name.to_s) }.each do |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
- rename_indexes_and_foreign_keys(oldname, newname)
27
+ rename_foreign_keys(oldname, newname)
44
28
  end
45
29
 
46
- # used only for mysql not mysql2. the quoting methods on ActiveRecord::DB_DEFAULT are
47
- # sufficient for mysql2
48
- def exec_stmt_with_schema_plus(sql, name, binds, &block)
49
- if binds.any?{ |col, val| val.equal? ::ActiveRecord::DB_DEFAULT}
50
- binds.each_with_index do |(col, val), i|
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
- exec_stmt_without_schema_plus(sql, name, binds, &block)
36
+ options.delete(:if_exists)
37
+ super from_table, to_table, options
58
38
  end
59
39
 
60
- # implement cascade by removing foreign keys
61
- def drop_table(name, options={})
62
- if options[:cascade]
63
- reverse_foreign_keys(name).each do |foreign_key|
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
- column_names = $2
109
- references_table_name = $3
110
- references_table_name = namespace_prefix + references_table_name if table_namespace_prefix(references_table_name).blank?
111
- references_column_names = $4
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 ? on_update.downcase.gsub(' ', '_').to_sym : :restrict
115
- on_delete = on_delete ? on_delete.downcase.gsub(' ', '_').to_sym : :restrict
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
- :column_names => column_names.gsub('`', '').split(', '),
121
- :references_column_names => references_column_names.gsub('`', '').split(', ') }
122
-
123
- foreign_keys << ForeignKeyDefinition.new(namespace_prefix + table_name,
124
- references_table_name,
125
- options)
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
- :column_names => columns.map { |row| row['column_name'] },
157
- :references_column_names => columns.map { |row| row['referenced_column_name'] }
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
- rename_indexes_and_foreign_keys(oldname, newname)
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 enum_name(name, schema)
383
- [schema || 'public', name].map { |s|
384
- %Q{"#{s}"}
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
- from_table_name = row[2]
408
- column_names = $1
409
- references_table_name = $2
410
- references_column_names = $3
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 ? on_update.downcase.gsub(' ', '_').to_sym : :no_action
416
- on_delete = on_delete ? on_delete.downcase.gsub(' ', '_').to_sym : :no_action
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
- :column_names => column_names.split(', '),
422
- :references_column_names => references_column_names.split(', '),
78
+ :column => columns,
79
+ :primary_key => primary_keys,
423
80
  :deferrable => deferrable }
424
81
 
425
- foreign_keys << ForeignKeyDefinition.new(from_table_name,
426
- references_table_name.sub(/^"(.*)"$/, '\1'),
427
- options)
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