activerecord 3.2.22.4 → 4.0.13

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