activerecord 3.2.22.5 → 4.0.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,30 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ module ReferentialIntegrity
5
+ def supports_disable_referential_integrity? #:nodoc:
6
+ true
7
+ end
8
+
9
+ def disable_referential_integrity #:nodoc:
10
+ if supports_disable_referential_integrity?
11
+ begin
12
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
13
+ rescue
14
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
15
+ end
16
+ end
17
+ yield
18
+ ensure
19
+ if supports_disable_referential_integrity?
20
+ begin
21
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
22
+ rescue
23
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,448 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ module SchemaStatements
5
+ # Drops the database specified on the +name+ attribute
6
+ # and creates it again using the provided +options+.
7
+ def recreate_database(name, options = {}) #:nodoc:
8
+ drop_database(name)
9
+ create_database(name, options)
10
+ end
11
+
12
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
13
+ # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
14
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
15
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
16
+ #
17
+ # Example:
18
+ # create_database config[:database], config
19
+ # create_database 'foo_development', encoding: 'unicode'
20
+ def create_database(name, options = {})
21
+ options = { encoding: 'utf8' }.merge!(options.symbolize_keys)
22
+
23
+ option_string = options.sum do |key, value|
24
+ case key
25
+ when :owner
26
+ " OWNER = \"#{value}\""
27
+ when :template
28
+ " TEMPLATE = \"#{value}\""
29
+ when :encoding
30
+ " ENCODING = '#{value}'"
31
+ when :collation
32
+ " LC_COLLATE = '#{value}'"
33
+ when :ctype
34
+ " LC_CTYPE = '#{value}'"
35
+ when :tablespace
36
+ " TABLESPACE = \"#{value}\""
37
+ when :connection_limit
38
+ " CONNECTION LIMIT = #{value}"
39
+ else
40
+ ""
41
+ end
42
+ end
43
+
44
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
45
+ end
46
+
47
+ # Drops a PostgreSQL database.
48
+ #
49
+ # Example:
50
+ # drop_database 'matt_development'
51
+ def drop_database(name) #:nodoc:
52
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
53
+ end
54
+
55
+ # Returns the list of all tables in the schema search path or a specified schema.
56
+ def tables(name = nil)
57
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
58
+ SELECT tablename
59
+ FROM pg_tables
60
+ WHERE schemaname = ANY (current_schemas(false))
61
+ SQL
62
+ end
63
+
64
+ # Returns true if table exists.
65
+ # If the schema is not specified as part of +name+ then it will only find tables within
66
+ # the current schema search path (regardless of permissions to access tables in other schemas)
67
+ def table_exists?(name)
68
+ schema, table = Utils.extract_schema_and_table(name.to_s)
69
+ return false unless table
70
+
71
+ binds = [[nil, table]]
72
+ binds << [nil, schema] if schema
73
+
74
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
75
+ SELECT COUNT(*)
76
+ FROM pg_class c
77
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
78
+ WHERE c.relkind in ('v','r')
79
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
80
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
81
+ SQL
82
+ end
83
+
84
+ # Returns true if schema exists.
85
+ def schema_exists?(name)
86
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
87
+ SELECT COUNT(*)
88
+ FROM pg_namespace
89
+ WHERE nspname = '#{name}'
90
+ SQL
91
+ end
92
+
93
+ # Returns an array of indexes for the given table.
94
+ def indexes(table_name, name = nil)
95
+ result = query(<<-SQL, 'SCHEMA')
96
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
97
+ FROM pg_class t
98
+ INNER JOIN pg_index d ON t.oid = d.indrelid
99
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
100
+ WHERE i.relkind = 'i'
101
+ AND d.indisprimary = 'f'
102
+ AND t.relname = '#{table_name}'
103
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
104
+ ORDER BY i.relname
105
+ SQL
106
+
107
+ result.map do |row|
108
+ index_name = row[0]
109
+ unique = row[1] == 't'
110
+ indkey = row[2].split(" ")
111
+ inddef = row[3]
112
+ oid = row[4]
113
+
114
+ columns = Hash[query(<<-SQL, "SCHEMA")]
115
+ SELECT a.attnum, a.attname
116
+ FROM pg_attribute a
117
+ WHERE a.attrelid = #{oid}
118
+ AND a.attnum IN (#{indkey.join(",")})
119
+ SQL
120
+
121
+ column_names = columns.values_at(*indkey).compact
122
+
123
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
124
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
125
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
126
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
127
+
128
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
129
+ end.compact
130
+ end
131
+
132
+ # Returns the list of all column definitions for a table.
133
+ def columns(table_name)
134
+ # Limit, precision, and scale are all handled by the superclass.
135
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
136
+ oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
137
+ OID::Identity.new
138
+ }
139
+ PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
140
+ end
141
+ end
142
+
143
+ # Returns the current database name.
144
+ def current_database
145
+ query('select current_database()', 'SCHEMA')[0][0]
146
+ end
147
+
148
+ # Returns the current schema name.
149
+ def current_schema
150
+ query('SELECT current_schema', 'SCHEMA')[0][0]
151
+ end
152
+
153
+ # Returns the current database encoding format.
154
+ def encoding
155
+ query(<<-end_sql, 'SCHEMA')[0][0]
156
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
157
+ WHERE pg_database.datname LIKE '#{current_database}'
158
+ end_sql
159
+ end
160
+
161
+ # Returns the current database collation.
162
+ def collation
163
+ query(<<-end_sql, 'SCHEMA')[0][0]
164
+ SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
165
+ end_sql
166
+ end
167
+
168
+ # Returns the current database ctype.
169
+ def ctype
170
+ query(<<-end_sql, 'SCHEMA')[0][0]
171
+ SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}'
172
+ end_sql
173
+ end
174
+
175
+ # Returns an array of schema names.
176
+ def schema_names
177
+ query(<<-SQL, 'SCHEMA').flatten
178
+ SELECT nspname
179
+ FROM pg_namespace
180
+ WHERE nspname !~ '^pg_.*'
181
+ AND nspname NOT IN ('information_schema')
182
+ ORDER by nspname;
183
+ SQL
184
+ end
185
+
186
+ # Creates a schema for the given schema name.
187
+ def create_schema schema_name
188
+ execute "CREATE SCHEMA #{schema_name}"
189
+ end
190
+
191
+ # Drops the schema for the given schema name.
192
+ def drop_schema schema_name
193
+ execute "DROP SCHEMA #{schema_name} CASCADE"
194
+ end
195
+
196
+ # Sets the schema search path to a string of comma-separated schema names.
197
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
198
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
199
+ #
200
+ # This should be not be called manually but set in database.yml.
201
+ def schema_search_path=(schema_csv)
202
+ if schema_csv
203
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
204
+ @schema_search_path = schema_csv
205
+ end
206
+ end
207
+
208
+ # Returns the active schema search path.
209
+ def schema_search_path
210
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
211
+ end
212
+
213
+ # Returns the current client message level.
214
+ def client_min_messages
215
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
216
+ end
217
+
218
+ # Set the client message level.
219
+ def client_min_messages=(level)
220
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
221
+ end
222
+
223
+ # Returns the sequence name for a table's primary key or some other specified key.
224
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
225
+ result = serial_sequence(table_name, pk || 'id')
226
+ return nil unless result
227
+ result.split('.').last
228
+ rescue ActiveRecord::StatementInvalid
229
+ "#{table_name}_#{pk || 'id'}_seq"
230
+ end
231
+
232
+ def serial_sequence(table, column)
233
+ result = exec_query(<<-eosql, 'SCHEMA')
234
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
235
+ eosql
236
+ result.rows.first.first
237
+ end
238
+
239
+ # Resets the sequence of a table's primary key to the maximum value.
240
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
241
+ unless pk and sequence
242
+ default_pk, default_sequence = pk_and_sequence_for(table)
243
+
244
+ pk ||= default_pk
245
+ sequence ||= default_sequence
246
+ end
247
+
248
+ if @logger && pk && !sequence
249
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
250
+ end
251
+
252
+ if pk && sequence
253
+ quoted_sequence = quote_table_name(sequence)
254
+
255
+ select_value <<-end_sql, 'SCHEMA'
256
+ 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)
257
+ end_sql
258
+ end
259
+ end
260
+
261
+ # Returns a table's primary key and belonging sequence.
262
+ def pk_and_sequence_for(table) #:nodoc:
263
+ # First try looking for a sequence with a dependency on the
264
+ # given table's primary key.
265
+ result = query(<<-end_sql, 'SCHEMA')[0]
266
+ SELECT attr.attname, seq.relname
267
+ FROM pg_class seq,
268
+ pg_attribute attr,
269
+ pg_depend dep,
270
+ pg_constraint cons
271
+ WHERE seq.oid = dep.objid
272
+ AND seq.relkind = 'S'
273
+ AND attr.attrelid = dep.refobjid
274
+ AND attr.attnum = dep.refobjsubid
275
+ AND attr.attrelid = cons.conrelid
276
+ AND attr.attnum = cons.conkey[1]
277
+ AND cons.contype = 'p'
278
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
279
+ end_sql
280
+
281
+ if result.nil? or result.empty?
282
+ result = query(<<-end_sql, 'SCHEMA')[0]
283
+ SELECT attr.attname,
284
+ CASE
285
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
286
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
287
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
288
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
289
+ END
290
+ FROM pg_class t
291
+ JOIN pg_attribute attr ON (t.oid = attrelid)
292
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
293
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
294
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
295
+ AND cons.contype = 'p'
296
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval'
297
+ end_sql
298
+ end
299
+
300
+ [result.first, result.last]
301
+ rescue
302
+ nil
303
+ end
304
+
305
+ # Returns just a table's primary key
306
+ def primary_key(table)
307
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
308
+ SELECT attr.attname
309
+ FROM pg_attribute attr
310
+ INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
311
+ WHERE cons.contype = 'p'
312
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
313
+ end_sql
314
+
315
+ row && row.first
316
+ end
317
+
318
+ # Renames a table.
319
+ # Also renames a table's primary key sequence if the sequence name matches the
320
+ # Active Record default.
321
+ #
322
+ # Example:
323
+ # rename_table('octopuses', 'octopi')
324
+ def rename_table(table_name, new_name)
325
+ clear_cache!
326
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
327
+ pk, seq = pk_and_sequence_for(new_name)
328
+ if seq == "#{table_name}_#{pk}_seq"
329
+ new_seq = "#{new_name}_#{pk}_seq"
330
+ execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
331
+ end
332
+
333
+ rename_table_indexes(table_name, new_name)
334
+ end
335
+
336
+ # Adds a new column to the named table.
337
+ # See TableDefinition#column for details of the options you can use.
338
+ def add_column(table_name, column_name, type, options = {})
339
+ clear_cache!
340
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
341
+ add_column_options!(add_column_sql, options)
342
+
343
+ execute add_column_sql
344
+ end
345
+
346
+ # Changes the column of a table.
347
+ def change_column(table_name, column_name, type, options = {})
348
+ clear_cache!
349
+ quoted_table_name = quote_table_name(table_name)
350
+
351
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
352
+
353
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
354
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
355
+ end
356
+
357
+ # Changes the default value of a table column.
358
+ def change_column_default(table_name, column_name, default)
359
+ clear_cache!
360
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
361
+ end
362
+
363
+ def change_column_null(table_name, column_name, null, default = nil)
364
+ clear_cache!
365
+ unless null || default.nil?
366
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
367
+ end
368
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
369
+ end
370
+
371
+ # Renames a column in a table.
372
+ def rename_column(table_name, column_name, new_column_name)
373
+ clear_cache!
374
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
375
+ rename_column_indexes(table_name, column_name, new_column_name)
376
+ end
377
+
378
+ def remove_index!(table_name, index_name) #:nodoc:
379
+ execute "DROP INDEX #{quote_table_name(index_name)}"
380
+ end
381
+
382
+ def rename_index(table_name, old_name, new_name)
383
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
384
+ end
385
+
386
+ def index_name_length
387
+ 63
388
+ end
389
+
390
+ # Maps logical Rails types to PostgreSQL-specific data types.
391
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
392
+ case type.to_s
393
+ when 'binary'
394
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
395
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
396
+ case limit
397
+ when nil, 0..0x3fffffff; super(type)
398
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
399
+ end
400
+ when 'text'
401
+ # PostgreSQL doesn't support limits on text columns.
402
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
403
+ case limit
404
+ when nil, 0..0x3fffffff; super(type)
405
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
406
+ end
407
+ when 'integer'
408
+ return 'integer' unless limit
409
+
410
+ case limit
411
+ when 1, 2; 'smallint'
412
+ when 3, 4; 'integer'
413
+ when 5..8; 'bigint'
414
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
415
+ end
416
+ when 'datetime'
417
+ return super unless precision
418
+
419
+ case precision
420
+ when 0..6; "timestamp(#{precision})"
421
+ else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
422
+ end
423
+ else
424
+ super
425
+ end
426
+ end
427
+
428
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
429
+ #
430
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
431
+ # requires that the ORDER BY include the distinct column.
432
+ #
433
+ # distinct("posts.id", ["posts.created_at desc"])
434
+ # # => "DISTINCT posts.id, posts.created_at AS alias_0"
435
+ def distinct(columns, orders) #:nodoc:
436
+ order_columns = orders.map{ |s|
437
+ # Convert Arel node to string
438
+ s = s.to_sql unless s.is_a?(String)
439
+ # Remove any ASC/DESC modifiers
440
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
441
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
442
+
443
+ [super].concat(order_columns).join(', ')
444
+ end
445
+ end
446
+ end
447
+ end
448
+ end