activerecord 3.2.13 → 3.2.14.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (37) hide show
  1. data/CHANGELOG.md +148 -2
  2. data/lib/active_record/associations/association.rb +9 -3
  3. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  4. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
  5. data/lib/active_record/associations/builder/belongs_to.rb +4 -1
  6. data/lib/active_record/associations/builder/belongs_to.rb.orig +95 -0
  7. data/lib/active_record/associations/collection_association.rb +1 -1
  8. data/lib/active_record/associations/has_many_association.rb +1 -2
  9. data/lib/active_record/associations/has_many_association.rb.orig +116 -0
  10. data/lib/active_record/associations/join_dependency.rb +1 -1
  11. data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
  12. data/lib/active_record/associations/preloader/through_association.rb +1 -2
  13. data/lib/active_record/associations/through_association.rb +1 -1
  14. data/lib/active_record/autosave_association.rb +7 -12
  15. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -2
  16. data/lib/active_record/connection_adapters/abstract/schema_statements.rb.orig +619 -0
  17. data/lib/active_record/connection_adapters/connection_specification.rb.orig +124 -0
  18. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -3
  19. data/lib/active_record/connection_adapters/postgresql/cast.rb.orig +136 -0
  20. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb.orig +485 -0
  21. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -3
  22. data/lib/active_record/core.rb.orig +452 -0
  23. data/lib/active_record/explain_subscriber.rb +1 -1
  24. data/lib/active_record/model_schema.rb +4 -2
  25. data/lib/active_record/nested_attributes.rb +41 -17
  26. data/lib/active_record/railtie.rb +6 -7
  27. data/lib/active_record/railties/databases.rake +2 -1
  28. data/lib/active_record/relation/calculations.rb +5 -6
  29. data/lib/active_record/relation/calculations.rb.orig +378 -0
  30. data/lib/active_record/relation/finder_methods.rb +1 -0
  31. data/lib/active_record/relation/finder_methods.rb.orig +405 -0
  32. data/lib/active_record/relation/spawn_methods.rb +34 -3
  33. data/lib/active_record/store.rb +1 -1
  34. data/lib/active_record/version.rb +2 -2
  35. data/lib/rails/generators/active_record/observer/observer_generator.rb.orig +15 -0
  36. metadata +117 -70
  37. checksums.yaml +0 -7
@@ -0,0 +1,124 @@
1
+ require 'uri'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class ConnectionSpecification #:nodoc:
6
+ attr_reader :config, :adapter_method
7
+
8
+ def initialize(config, adapter_method)
9
+ @config, @adapter_method = config, adapter_method
10
+ end
11
+
12
+ def initialize_dup(original)
13
+ @config = original.config.dup
14
+ end
15
+
16
+ ##
17
+ # Builds a ConnectionSpecification from user input
18
+ class Resolver # :nodoc:
19
+ attr_reader :config, :klass, :configurations
20
+
21
+ def initialize(config, configurations)
22
+ @config = config
23
+ @configurations = configurations
24
+ end
25
+
26
+ def spec
27
+ case config
28
+ when nil
29
+ raise AdapterNotSpecified unless defined?(Rails.env)
30
+ resolve_string_connection Rails.env
31
+ when Symbol, String
32
+ resolve_string_connection config.to_s
33
+ when Hash
34
+ resolve_hash_connection config
35
+ end
36
+ end
37
+
38
+ private
39
+ def resolve_string_connection(spec) # :nodoc:
40
+ hash = configurations.fetch(spec) do |k|
41
+ self.class.connection_url_to_hash(k)
42
+ end
43
+
44
+ raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
45
+
46
+ resolve_hash_connection hash
47
+ end
48
+
49
+ def resolve_hash_connection(spec) # :nodoc:
50
+ spec = spec.symbolize_keys
51
+
52
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
53
+
54
+ path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
55
+ begin
56
+ require path_to_adapter
57
+ rescue Gem::LoadError => e
58
+ raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile."
59
+ rescue LoadError => e
60
+ raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
61
+ end
62
+
63
+ adapter_method = "#{spec[:adapter]}_connection"
64
+
65
+ ConnectionSpecification.new(spec, adapter_method)
66
+ end
67
+
68
+ <<<<<<< HEAD
69
+ # For DATABASE_URL, accept a limited concept of ints and floats
70
+ SIMPLE_INT = /\A\d+\z/
71
+ SIMPLE_FLOAT = /\A\d+\.\d+\z/
72
+
73
+ def self.connection_url_to_hash(url) # :nodoc:
74
+ =======
75
+ def connection_url_to_hash(url) # :nodoc:
76
+ >>>>>>> parent of 4b005fb... DATABASE_URL parsing should turn numeric strings into numeric types, and
77
+ config = URI.parse url
78
+ adapter = config.scheme
79
+ adapter = "postgresql" if adapter == "postgres"
80
+ spec = { :adapter => adapter,
81
+ :username => config.user,
82
+ :password => config.password,
83
+ :port => config.port,
84
+ :database => config.path.sub(%r{^/},""),
85
+ :host => config.host }
86
+
87
+ spec.reject!{ |_,value| value.blank? }
88
+
89
+ uri_parser = URI::Parser.new
90
+
91
+ spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
92
+
93
+ if config.query
94
+ options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
95
+ <<<<<<< HEAD
96
+
97
+ options.each { |key, value| options[key] = type_cast_value(value) }
98
+
99
+ =======
100
+ >>>>>>> parent of 4b005fb... DATABASE_URL parsing should turn numeric strings into numeric types, and
101
+ spec.merge!(options)
102
+ end
103
+
104
+ spec
105
+ end
106
+
107
+ def self.type_cast_value(value)
108
+ case value
109
+ when SIMPLE_INT
110
+ value.to_i
111
+ when SIMPLE_FLOAT
112
+ value.to_f
113
+ when 'true'
114
+ true
115
+ when 'false'
116
+ false
117
+ else
118
+ value
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -204,9 +204,11 @@ module ActiveRecord
204
204
 
205
205
  # Executes the SQL statement in the context of this connection.
206
206
  def execute(sql, name = nil)
207
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
208
- # made since we established the connection
209
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
207
+ if @connection
208
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
209
+ # made since we established the connection
210
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
211
+ end
210
212
 
211
213
  super
212
214
  end
@@ -0,0 +1,136 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLColumn < Column
4
+ module Cast
5
+ def string_to_time(string)
6
+ return string unless String === string
7
+
8
+ case string
9
+ when 'infinity'; 1.0 / 0.0
10
+ when '-infinity'; -1.0 / 0.0
11
+ when / BC$/
12
+ super("-" + string.sub(/ BC$/, ""))
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def hstore_to_string(object)
19
+ if Hash === object
20
+ object.map { |k,v|
21
+ "#{escape_hstore(k)}=>#{escape_hstore(v)}"
22
+ }.join ','
23
+ else
24
+ object
25
+ end
26
+ end
27
+
28
+ def string_to_hstore(string)
29
+ if string.nil?
30
+ nil
31
+ elsif String === string
32
+ Hash[string.scan(HstorePair).map { |k,v|
33
+ v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
34
+ <<<<<<< HEAD
35
+ k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
36
+ =======
37
+ k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
38
+ >>>>>>> 33231b5... Fix regex to strip quotations from hstore values
39
+ [k,v]
40
+ }]
41
+ else
42
+ string
43
+ end
44
+ end
45
+
46
+ def json_to_string(object)
47
+ if Hash === object
48
+ ActiveSupport::JSON.encode(object)
49
+ else
50
+ object
51
+ end
52
+ end
53
+
54
+ def array_to_string(value, column, adapter, should_be_quoted = false)
55
+ casted_values = value.map do |val|
56
+ if String === val
57
+ if val == "NULL"
58
+ "\"#{val}\""
59
+ else
60
+ quote_and_escape(adapter.type_cast(val, column, true))
61
+ end
62
+ else
63
+ adapter.type_cast(val, column, true)
64
+ end
65
+ end
66
+ "{#{casted_values.join(',')}}"
67
+ end
68
+
69
+ def range_to_string(object)
70
+ from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
71
+ to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
72
+ "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
73
+ end
74
+
75
+ def string_to_json(string)
76
+ if String === string
77
+ ActiveSupport::JSON.decode(string)
78
+ else
79
+ string
80
+ end
81
+ end
82
+
83
+ def string_to_cidr(string)
84
+ if string.nil?
85
+ nil
86
+ elsif String === string
87
+ IPAddr.new(string)
88
+ else
89
+ string
90
+ end
91
+ end
92
+
93
+ def cidr_to_string(object)
94
+ if IPAddr === object
95
+ "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
96
+ else
97
+ object
98
+ end
99
+ end
100
+
101
+ def string_to_array(string, oid)
102
+ parse_pg_array(string).map{|val| oid.type_cast val}
103
+ end
104
+
105
+ private
106
+
107
+ HstorePair = begin
108
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
109
+ unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
110
+ /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
111
+ end
112
+
113
+ def escape_hstore(value)
114
+ if value.nil?
115
+ 'NULL'
116
+ else
117
+ if value == ""
118
+ '""'
119
+ else
120
+ '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
121
+ end
122
+ end
123
+ end
124
+
125
+ def quote_and_escape(value)
126
+ case value
127
+ when "NULL"
128
+ value
129
+ else
130
+ "\"#{value.gsub(/"/,"\\\"")}\""
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,485 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+ private
6
+
7
+ def visit_AddColumn(o)
8
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
9
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
10
+ add_column_options!(sql, column_options(o))
11
+ end
12
+
13
+ def visit_ColumnDefinition(o)
14
+ sql = super
15
+ if o.primary_key? && o.type == :uuid
16
+ sql << " PRIMARY KEY "
17
+ add_column_options!(sql, column_options(o))
18
+ end
19
+ sql
20
+ end
21
+
22
+ def add_column_options!(sql, options)
23
+ if options[:array] || options[:column].try(:array)
24
+ sql << '[]'
25
+ end
26
+
27
+ column = options.fetch(:column) { return super }
28
+ if column.type == :uuid && options[:default] =~ /\(\)/
29
+ sql << " DEFAULT #{options[:default]}"
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+
36
+ def schema_creation
37
+ SchemaCreation.new self
38
+ end
39
+
40
+ module SchemaStatements
41
+ # Drops the database specified on the +name+ attribute
42
+ # and creates it again using the provided +options+.
43
+ def recreate_database(name, options = {}) #:nodoc:
44
+ drop_database(name)
45
+ create_database(name, options)
46
+ end
47
+
48
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
49
+ # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
50
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
51
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
52
+ #
53
+ # Example:
54
+ # create_database config[:database], config
55
+ # create_database 'foo_development', encoding: 'unicode'
56
+ def create_database(name, options = {})
57
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
58
+
59
+ option_string = options.sum do |key, value|
60
+ case key
61
+ when :owner
62
+ " OWNER = \"#{value}\""
63
+ when :template
64
+ " TEMPLATE = \"#{value}\""
65
+ when :encoding
66
+ " ENCODING = '#{value}'"
67
+ when :collation
68
+ " LC_COLLATE = '#{value}'"
69
+ when :ctype
70
+ " LC_CTYPE = '#{value}'"
71
+ when :tablespace
72
+ " TABLESPACE = \"#{value}\""
73
+ when :connection_limit
74
+ " CONNECTION LIMIT = #{value}"
75
+ else
76
+ ""
77
+ end
78
+ end
79
+
80
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
81
+ end
82
+
83
+ # Drops a PostgreSQL database.
84
+ #
85
+ # Example:
86
+ # drop_database 'matt_development'
87
+ def drop_database(name) #:nodoc:
88
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
89
+ end
90
+
91
+ # Returns the list of all tables in the schema search path or a specified schema.
92
+ def tables(name = nil)
93
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
94
+ SELECT tablename
95
+ FROM pg_tables
96
+ WHERE schemaname = ANY (current_schemas(false))
97
+ SQL
98
+ end
99
+
100
+ # Returns true if table exists.
101
+ # If the schema is not specified as part of +name+ then it will only find tables within
102
+ # the current schema search path (regardless of permissions to access tables in other schemas)
103
+ def table_exists?(name)
104
+ schema, table = Utils.extract_schema_and_table(name.to_s)
105
+ return false unless table
106
+
107
+ binds = [[nil, table]]
108
+ binds << [nil, schema] if schema
109
+
110
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
111
+ SELECT COUNT(*)
112
+ FROM pg_class c
113
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
114
+ WHERE c.relkind in ('v','r')
115
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
116
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
117
+ SQL
118
+ end
119
+
120
+ # Returns true if schema exists.
121
+ def schema_exists?(name)
122
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
123
+ SELECT COUNT(*)
124
+ FROM pg_namespace
125
+ WHERE nspname = '#{name}'
126
+ SQL
127
+ end
128
+
129
+ # Returns an array of indexes for the given table.
130
+ def indexes(table_name, name = nil)
131
+ result = query(<<-SQL, 'SCHEMA')
132
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
133
+ FROM pg_class t
134
+ INNER JOIN pg_index d ON t.oid = d.indrelid
135
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
136
+ WHERE i.relkind = 'i'
137
+ AND d.indisprimary = 'f'
138
+ AND t.relname = '#{table_name}'
139
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
140
+ ORDER BY i.relname
141
+ SQL
142
+
143
+ result.map do |row|
144
+ index_name = row[0]
145
+ unique = row[1] == 't'
146
+ indkey = row[2].split(" ")
147
+ inddef = row[3]
148
+ oid = row[4]
149
+
150
+ columns = Hash[query(<<-SQL, "SCHEMA")]
151
+ SELECT a.attnum, a.attname
152
+ FROM pg_attribute a
153
+ WHERE a.attrelid = #{oid}
154
+ AND a.attnum IN (#{indkey.join(",")})
155
+ SQL
156
+
157
+ column_names = columns.values_at(*indkey).compact
158
+
159
+ unless column_names.empty?
160
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
161
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
162
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
163
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
164
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
165
+
166
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
167
+ end
168
+ end.compact
169
+ end
170
+
171
+ # Returns the list of all column definitions for a table.
172
+ def columns(table_name)
173
+ # Limit, precision, and scale are all handled by the superclass.
174
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
175
+ oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
176
+ OID::Identity.new
177
+ }
178
+ PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
179
+ end
180
+ end
181
+
182
+ # Returns the current database name.
183
+ def current_database
184
+ query('select current_database()', 'SCHEMA')[0][0]
185
+ end
186
+
187
+ # Returns the current schema name.
188
+ def current_schema
189
+ query('SELECT current_schema', 'SCHEMA')[0][0]
190
+ end
191
+
192
+ # Returns the current database encoding format.
193
+ def encoding
194
+ query(<<-end_sql, 'SCHEMA')[0][0]
195
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
196
+ WHERE pg_database.datname LIKE '#{current_database}'
197
+ end_sql
198
+ end
199
+
200
+ # Returns the current database collation.
201
+ def collation
202
+ query(<<-end_sql, 'SCHEMA')[0][0]
203
+ SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
204
+ end_sql
205
+ end
206
+
207
+ # Returns the current database ctype.
208
+ def ctype
209
+ query(<<-end_sql, 'SCHEMA')[0][0]
210
+ SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
211
+ end_sql
212
+ end
213
+
214
+ # Returns an array of schema names.
215
+ def schema_names
216
+ query(<<-SQL, 'SCHEMA').flatten
217
+ SELECT nspname
218
+ FROM pg_namespace
219
+ WHERE nspname !~ '^pg_.*'
220
+ AND nspname NOT IN ('information_schema')
221
+ ORDER by nspname;
222
+ SQL
223
+ end
224
+
225
+ # Creates a schema for the given schema name.
226
+ def create_schema schema_name
227
+ execute "CREATE SCHEMA #{schema_name}"
228
+ end
229
+
230
+ # Drops the schema for the given schema name.
231
+ def drop_schema schema_name
232
+ execute "DROP SCHEMA #{schema_name} CASCADE"
233
+ end
234
+
235
+ # Sets the schema search path to a string of comma-separated schema names.
236
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
237
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
238
+ #
239
+ # This should be not be called manually but set in database.yml.
240
+ def schema_search_path=(schema_csv)
241
+ if schema_csv
242
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
243
+ @schema_search_path = schema_csv
244
+ end
245
+ end
246
+
247
+ # Returns the active schema search path.
248
+ def schema_search_path
249
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
250
+ end
251
+
252
+ # Returns the current client message level.
253
+ def client_min_messages
254
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
255
+ end
256
+
257
+ # Set the client message level.
258
+ def client_min_messages=(level)
259
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
260
+ end
261
+
262
+ # Returns the sequence name for a table's primary key or some other specified key.
263
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
264
+ result = serial_sequence(table_name, pk || 'id')
265
+ return nil unless result
266
+ result.split('.').last
267
+ rescue ActiveRecord::StatementInvalid
268
+ "#{table_name}_#{pk || 'id'}_seq"
269
+ end
270
+
271
+ def serial_sequence(table, column)
272
+ result = exec_query(<<-eosql, 'SCHEMA')
273
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
274
+ eosql
275
+ result.rows.first.first
276
+ end
277
+
278
+ # Resets the sequence of a table's primary key to the maximum value.
279
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
280
+ unless pk and sequence
281
+ default_pk, default_sequence = pk_and_sequence_for(table)
282
+
283
+ pk ||= default_pk
284
+ sequence ||= default_sequence
285
+ end
286
+
287
+ if @logger && pk && !sequence
288
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
289
+ end
290
+
291
+ if pk && sequence
292
+ quoted_sequence = quote_table_name(sequence)
293
+
294
+ select_value <<-end_sql, 'SCHEMA'
295
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
296
+ end_sql
297
+ end
298
+ end
299
+
300
+ # Returns a table's primary key and belonging sequence.
301
+ def pk_and_sequence_for(table) #:nodoc:
302
+ # First try looking for a sequence with a dependency on the
303
+ # given table's primary key.
304
+ result = query(<<-end_sql, 'SCHEMA')[0]
305
+ SELECT attr.attname, seq.relname
306
+ FROM pg_class seq,
307
+ pg_attribute attr,
308
+ pg_depend dep,
309
+ pg_constraint cons
310
+ WHERE seq.oid = dep.objid
311
+ AND seq.relkind = 'S'
312
+ AND attr.attrelid = dep.refobjid
313
+ AND attr.attnum = dep.refobjsubid
314
+ AND attr.attrelid = cons.conrelid
315
+ AND attr.attnum = cons.conkey[1]
316
+ AND cons.contype = 'p'
317
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
318
+ end_sql
319
+
320
+ if result.nil? or result.empty?
321
+ result = query(<<-end_sql, 'SCHEMA')[0]
322
+ SELECT attr.attname,
323
+ CASE
324
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
325
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
326
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
327
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
328
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
329
+ END
330
+ FROM pg_class t
331
+ JOIN pg_attribute attr ON (t.oid = attrelid)
332
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
333
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
334
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
335
+ AND cons.contype = 'p'
336
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
337
+ end_sql
338
+ end
339
+
340
+ [result.first, result.last]
341
+ rescue
342
+ nil
343
+ end
344
+
345
+ # Returns just a table's primary key
346
+ def primary_key(table)
347
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
348
+ SELECT attr.attname
349
+ FROM pg_attribute attr
350
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
351
+ WHERE cons.contype = 'p'
352
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
353
+ end_sql
354
+
355
+ row && row.first
356
+ end
357
+
358
+ # Renames a table.
359
+ # Also renames a table's primary key sequence if the sequence name matches the
360
+ # Active Record default.
361
+ #
362
+ # Example:
363
+ # rename_table('octopuses', 'octopi')
364
+ def rename_table(table_name, new_name)
365
+ clear_cache!
366
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
367
+ pk, seq = pk_and_sequence_for(new_name)
368
+ if seq == "#{table_name}_#{pk}_seq"
369
+ new_seq = "#{new_name}_#{pk}_seq"
370
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
371
+ end
372
+
373
+ rename_table_indexes(table_name, new_name)
374
+ end
375
+
376
+ # Adds a new column to the named table.
377
+ # See TableDefinition#column for details of the options you can use.
378
+ def add_column(table_name, column_name, type, options = {})
379
+ clear_cache!
380
+ super
381
+ end
382
+
383
+ # Changes the column of a table.
384
+ def change_column(table_name, column_name, type, options = {})
385
+ clear_cache!
386
+ quoted_table_name = quote_table_name(table_name)
387
+
388
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
389
+
390
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
391
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
392
+ end
393
+
394
+ # Changes the default value of a table column.
395
+ def change_column_default(table_name, column_name, default)
396
+ clear_cache!
397
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
398
+ end
399
+
400
+ def change_column_null(table_name, column_name, null, default = nil)
401
+ clear_cache!
402
+ unless null || default.nil?
403
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
404
+ end
405
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
406
+ end
407
+
408
+ # Renames a column in a table.
409
+ def rename_column(table_name, column_name, new_column_name)
410
+ clear_cache!
411
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
412
+ rename_column_indexes(table_name, column_name, new_column_name)
413
+ end
414
+
415
+ def add_index(table_name, column_name, options = {}) #:nodoc:
416
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
417
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
418
+ end
419
+
420
+ def remove_index!(table_name, index_name) #:nodoc:
421
+ execute "DROP INDEX #{quote_table_name(index_name)}"
422
+ end
423
+
424
+ def rename_index(table_name, old_name, new_name)
425
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
426
+ end
427
+
428
+ def index_name_length
429
+ 63
430
+ end
431
+
432
+ # Maps logical Rails types to PostgreSQL-specific data types.
433
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
434
+ case type.to_s
435
+ when 'binary'
436
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
437
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
438
+ case limit
439
+ when nil, 0..0x3fffffff; super(type)
440
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
441
+ end
442
+ when 'text'
443
+ # PostgreSQL doesn't support limits on text columns.
444
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
445
+ case limit
446
+ when nil, 0..0x3fffffff; super(type)
447
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
448
+ end
449
+ when 'integer'
450
+ return 'integer' unless limit
451
+
452
+ case limit
453
+ when 1, 2; 'smallint'
454
+ when 3, 4; 'integer'
455
+ when 5..8; 'bigint'
456
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
457
+ end
458
+ when 'datetime'
459
+ return super unless precision
460
+
461
+ case precision
462
+ when 0..6; "timestamp(#{precision})"
463
+ else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
464
+ end
465
+ else
466
+ super
467
+ end
468
+ end
469
+
470
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
471
+ # requires that the ORDER BY include the distinct column.
472
+ def columns_for_distinct(columns, orders) #:nodoc:
473
+ order_columns = orders.map{ |s|
474
+ # Convert Arel node to string
475
+ s = s.to_sql unless s.is_a?(String)
476
+ # Remove any ASC/DESC modifiers
477
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
478
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
479
+
480
+ [super, *order_columns].join(', ')
481
+ end
482
+ end
483
+ end
484
+ end
485
+ end