activerecord 3.0.20 → 3.1.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 (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -2,19 +2,21 @@ require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_support/core_ext/kernel/requires'
3
3
  require 'active_support/core_ext/object/blank'
4
4
 
5
+ # Make sure we're using pg high enough for PGResult#values
6
+ gem 'pg', '~> 0.11'
7
+ require 'pg'
8
+
5
9
  module ActiveRecord
6
10
  class Base
7
11
  # Establishes a connection to the database that's used by all Active Record objects
8
12
  def self.postgresql_connection(config) # :nodoc:
9
- require 'pg'
10
-
11
13
  config = config.symbolize_keys
12
14
  host = config[:host]
13
15
  port = config[:port] || 5432
14
16
  username = config[:username].to_s if config[:username]
15
17
  password = config[:password].to_s if config[:password]
16
18
 
17
- if config.has_key?(:database)
19
+ if config.key?(:database)
18
20
  database = config[:database]
19
21
  else
20
22
  raise ArgumentError, "No database specified. Missing argument: database."
@@ -27,12 +29,6 @@ module ActiveRecord
27
29
  end
28
30
 
29
31
  module ConnectionAdapters
30
- class TableDefinition
31
- def xml(*args)
32
- options = args.extract_options!
33
- column(args[0], 'xml', options)
34
- end
35
- end
36
32
  # PostgreSQL-specific extensions to column definitions in a table.
37
33
  class PostgreSQLColumn < Column #:nodoc:
38
34
  # Instantiates a new PostgreSQL column definition in a table.
@@ -43,16 +39,6 @@ module ActiveRecord
43
39
  # :stopdoc:
44
40
  class << self
45
41
  attr_accessor :money_precision
46
- def string_to_time(string)
47
- return string unless String === string
48
-
49
- case string
50
- when 'infinity' then 1.0 / 0.0
51
- when '-infinity' then -1.0 / 0.0
52
- else
53
- super
54
- end
55
- end
56
42
  end
57
43
  # :startdoc:
58
44
 
@@ -112,6 +98,9 @@ module ActiveRecord
112
98
  # XML type
113
99
  when 'xml'
114
100
  :xml
101
+ # tsvector type
102
+ when 'tsvector'
103
+ :tsvector
115
104
  # Arrays
116
105
  when /^\D+\[\]$/
117
106
  :string
@@ -188,9 +177,7 @@ module ActiveRecord
188
177
  end
189
178
  end
190
179
  end
191
- end
192
180
 
193
- module ConnectionAdapters
194
181
  # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
195
182
  # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
196
183
  #
@@ -207,13 +194,23 @@ module ActiveRecord
207
194
  # <encoding></tt> call on the connection.
208
195
  # * <tt>:min_messages</tt> - An optional client min messages that is used in a
209
196
  # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
210
- # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock;
211
- # otherwise, use blocking query methods.
212
197
  class PostgreSQLAdapter < AbstractAdapter
213
- ADAPTER_NAME = 'PostgreSQL'.freeze
198
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
199
+ def xml(*args)
200
+ options = args.extract_options!
201
+ column(args[0], 'xml', options)
202
+ end
203
+
204
+ def tsvector(*args)
205
+ options = args.extract_options!
206
+ column(args[0], 'tsvector', options)
207
+ end
208
+ end
209
+
210
+ ADAPTER_NAME = 'PostgreSQL'
214
211
 
215
212
  NATIVE_DATABASE_TYPES = {
216
- :primary_key => "serial primary key".freeze,
213
+ :primary_key => "serial primary key",
217
214
  :string => { :name => "character varying", :limit => 255 },
218
215
  :text => { :name => "text" },
219
216
  :integer => { :name => "integer" },
@@ -225,7 +222,8 @@ module ActiveRecord
225
222
  :date => { :name => "date" },
226
223
  :binary => { :name => "bytea" },
227
224
  :boolean => { :name => "boolean" },
228
- :xml => { :name => "xml" }
225
+ :xml => { :name => "xml" },
226
+ :tsvector => { :name => "tsvector" }
229
227
  }
230
228
 
231
229
  # Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -233,6 +231,12 @@ module ActiveRecord
233
231
  ADAPTER_NAME
234
232
  end
235
233
 
234
+ # Returns +true+, since this connection adapter supports prepared statement
235
+ # caching.
236
+ def supports_statement_cache?
237
+ true
238
+ end
239
+
236
240
  # Initializes and connects a PostgreSQL adapter.
237
241
  def initialize(connection, logger, connection_parameters, config)
238
242
  super(connection, logger)
@@ -241,39 +245,48 @@ module ActiveRecord
241
245
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
242
246
  @local_tz = nil
243
247
  @table_alias_length = nil
244
- @postgresql_version = nil
248
+ @statements = {}
245
249
 
246
250
  connect
247
- @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
251
+
252
+ if postgresql_version < 80200
253
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
254
+ end
255
+
256
+ @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
257
+ end
258
+
259
+ # Clears the prepared statements cache.
260
+ def clear_cache!
261
+ @statements.each_value do |value|
262
+ @connection.query "DEALLOCATE #{value}"
263
+ end
264
+ @statements.clear
248
265
  end
249
266
 
250
267
  # Is this connection alive and ready for queries?
251
268
  def active?
252
- if @connection.respond_to?(:status)
253
- @connection.status == PGconn::CONNECTION_OK
254
- else
255
- # We're asking the driver, not Active Record, so use @connection.query instead of #query
256
- @connection.query 'SELECT 1'
257
- true
258
- end
259
- # postgres-pr raises a NoMethodError when querying if no connection is available.
260
- rescue PGError, NoMethodError
269
+ @connection.status == PGconn::CONNECTION_OK
270
+ rescue PGError
261
271
  false
262
272
  end
263
273
 
264
274
  # Close then reopen the connection.
265
275
  def reconnect!
266
- if @connection.respond_to?(:reset)
267
- @connection.reset
268
- configure_connection
269
- else
270
- disconnect!
271
- connect
272
- end
276
+ clear_cache!
277
+ @connection.reset
278
+ configure_connection
273
279
  end
274
280
 
275
- # Close the connection.
281
+ def reset!
282
+ clear_cache!
283
+ super
284
+ end
285
+
286
+ # Disconnects from the database if already connected. Otherwise, this
287
+ # method does nothing.
276
288
  def disconnect!
289
+ clear_cache!
277
290
  @connection.close rescue nil
278
291
  end
279
292
 
@@ -281,7 +294,7 @@ module ActiveRecord
281
294
  NATIVE_DATABASE_TYPES
282
295
  end
283
296
 
284
- # Does PostgreSQL support migrations?
297
+ # Returns true, since this connection adapter supports migrations.
285
298
  def supports_migrations?
286
299
  true
287
300
  end
@@ -294,27 +307,27 @@ module ActiveRecord
294
307
  # Enable standard-conforming strings if available.
295
308
  def set_standard_conforming_strings
296
309
  old, self.client_min_messages = client_min_messages, 'panic'
297
- execute('SET standard_conforming_strings = on') rescue nil
310
+ execute('SET standard_conforming_strings = on', 'SCHEMA') rescue nil
298
311
  ensure
299
312
  self.client_min_messages = old
300
313
  end
301
314
 
302
315
  def supports_insert_with_returning?
303
- postgresql_version >= 80200
316
+ true
304
317
  end
305
318
 
306
319
  def supports_ddl_transactions?
307
320
  true
308
321
  end
309
322
 
323
+ # Returns true, since this connection adapter supports savepoints.
310
324
  def supports_savepoints?
311
325
  true
312
326
  end
313
327
 
314
- # Returns the configured supported identifier length supported by PostgreSQL,
315
- # or report the default of 63 on PostgreSQL 7.x.
328
+ # Returns the configured supported identifier length supported by PostgreSQL
316
329
  def table_alias_length
317
- @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
330
+ @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
318
331
  end
319
332
 
320
333
  # QUOTING ==================================================
@@ -335,28 +348,40 @@ module ActiveRecord
335
348
  def quote(value, column = nil) #:nodoc:
336
349
  return super unless column
337
350
 
338
- if value.kind_of?(String) && column.type == :binary
339
- "'#{escape_bytea(value)}'"
340
- elsif Float === value && column.type == :datetime
341
- return super unless value.infinite?
342
- "'#{value.to_s.downcase}'"
343
- elsif value.kind_of?(String) && column.sql_type == 'xml'
344
- "xml '#{quote_string(value)}'"
345
- elsif value.kind_of?(Numeric) && column.sql_type == 'money'
351
+ case value
352
+ when Numeric
353
+ return super unless column.sql_type == 'money'
346
354
  # Not truly string input, so doesn't require (or allow) escape string syntax.
347
355
  "'#{value}'"
348
- elsif value.kind_of?(String) && column.sql_type =~ /^bit/
349
- case value
350
- when /^[01]*$/
351
- "B'#{value}'" # Bit-string notation
352
- when /^[0-9A-F]*$/i
353
- "X'#{value}'" # Hexadecimal notation
356
+ when String
357
+ case column.sql_type
358
+ when 'bytea' then "'#{escape_bytea(value)}'"
359
+ when 'xml' then "xml '#{quote_string(value)}'"
360
+ when /^bit/
361
+ case value
362
+ when /^[01]*$/ then "B'#{value}'" # Bit-string notation
363
+ when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation
364
+ end
365
+ else
366
+ super
354
367
  end
355
368
  else
356
369
  super
357
370
  end
358
371
  end
359
372
 
373
+ def type_cast(value, column)
374
+ return super unless column
375
+
376
+ case value
377
+ when String
378
+ return super unless 'bytea' == column.sql_type
379
+ { :value => value, :format => 1 }
380
+ else
381
+ super
382
+ end
383
+ end
384
+
360
385
  # Quotes strings for use in SQL input.
361
386
  def quote_string(s) #:nodoc:
362
387
  @connection.escape(s)
@@ -396,19 +421,25 @@ module ActiveRecord
396
421
  end
397
422
  end
398
423
 
424
+ # Set the authorized user for this session
425
+ def session_auth=(user)
426
+ clear_cache!
427
+ exec_query "SET SESSION AUTHORIZATION #{user}"
428
+ end
429
+
399
430
  # REFERENTIAL INTEGRITY ====================================
400
431
 
401
- def supports_disable_referential_integrity?() #:nodoc:
402
- postgresql_version >= 80100
432
+ def supports_disable_referential_integrity? #:nodoc:
433
+ true
403
434
  end
404
435
 
405
436
  def disable_referential_integrity #:nodoc:
406
- if supports_disable_referential_integrity?() then
437
+ if supports_disable_referential_integrity? then
407
438
  execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
408
439
  end
409
440
  yield
410
441
  ensure
411
- if supports_disable_referential_integrity?() then
442
+ if supports_disable_referential_integrity? then
412
443
  execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
413
444
  end
414
445
  end
@@ -422,34 +453,16 @@ module ActiveRecord
422
453
  end
423
454
 
424
455
  # Executes an INSERT query and returns the new record's ID
425
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
456
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
426
457
  # Extract the table from the insert sql. Yuck.
427
- table = sql.split(" ", 4)[2].gsub('"', '')
428
-
429
- # Try an insert with 'returning id' if available (PG >= 8.2)
430
- if supports_insert_with_returning?
431
- pk, sequence_name = *pk_and_sequence_for(table) unless pk
432
- if pk
433
- id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
434
- clear_query_cache
435
- return id
436
- end
437
- end
458
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
438
459
 
439
- # Otherwise, insert then grab last_insert_id.
440
- if insert_id = super
441
- insert_id
442
- else
443
- # If neither pk nor sequence name is given, look them up.
444
- unless pk || sequence_name
445
- pk, sequence_name = *pk_and_sequence_for(table)
446
- end
460
+ pk ||= primary_key(table)
447
461
 
448
- # If a pk is given, fallback to default sequence name.
449
- # Don't fetch last insert id for a table without a pk.
450
- if pk && sequence_name ||= default_sequence_name(table, pk)
451
- last_insert_id(table, sequence_name)
452
- end
462
+ if pk
463
+ select_value("#{sql} RETURNING #{quote_column_name(pk)}")
464
+ else
465
+ super
453
466
  end
454
467
  end
455
468
  alias :create :insert
@@ -457,54 +470,50 @@ module ActiveRecord
457
470
  # create a 2D array representing the result set
458
471
  def result_as_array(res) #:nodoc:
459
472
  # check if we have any binary column and if they need escaping
460
- unescape_col = []
461
- res.nfields.times do |j|
462
- unescape_col << res.ftype(j)
473
+ ftypes = Array.new(res.nfields) do |i|
474
+ [i, res.ftype(i)]
463
475
  end
464
476
 
465
- ary = []
466
- res.ntuples.times do |i|
467
- ary << []
468
- res.nfields.times do |j|
469
- data = res.getvalue(i,j)
470
- case unescape_col[j]
471
-
472
- # unescape string passed BYTEA field (OID == 17)
473
- when BYTEA_COLUMN_TYPE_OID
474
- data = unescape_bytea(data) if String === data
475
-
476
- # If this is a money type column and there are any currency symbols,
477
- # then strip them off. Indeed it would be prettier to do this in
478
- # PostgreSQLColumn.string_to_decimal but would break form input
479
- # fields that call value_before_type_cast.
480
- when MONEY_COLUMN_TYPE_OID
481
- # Because money output is formatted according to the locale, there are two
482
- # cases to consider (note the decimal separators):
483
- # (1) $12,345,678.12
484
- # (2) $12.345.678,12
485
- case data
486
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
487
- data.gsub!(/[^-\d\.]/, '')
488
- when /^-?\D+[\d\.]+,\d{2}$/ # (2)
489
- data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
490
- end
477
+ rows = res.values
478
+ return rows unless ftypes.any? { |_, x|
479
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
480
+ }
481
+
482
+ typehash = ftypes.group_by { |_, type| type }
483
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
484
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
485
+
486
+ rows.each do |row|
487
+ # unescape string passed BYTEA field (OID == 17)
488
+ binaries.each do |index, _|
489
+ row[index] = unescape_bytea(row[index])
490
+ end
491
+
492
+ # If this is a money type column and there are any currency symbols,
493
+ # then strip them off. Indeed it would be prettier to do this in
494
+ # PostgreSQLColumn.string_to_decimal but would break form input
495
+ # fields that call value_before_type_cast.
496
+ monies.each do |index, _|
497
+ data = row[index]
498
+ # Because money output is formatted according to the locale, there are two
499
+ # cases to consider (note the decimal separators):
500
+ # (1) $12,345,678.12
501
+ # (2) $12.345.678,12
502
+ case data
503
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
504
+ data.gsub!(/[^-\d.]/, '')
505
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
506
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
491
507
  end
492
- ary[i] << data
493
508
  end
494
509
  end
495
- return ary
496
510
  end
497
511
 
498
512
 
499
513
  # Queries the database and returns the results in an Array-like object
500
514
  def query(sql, name = nil) #:nodoc:
501
515
  log(sql, name) do
502
- if @async
503
- res = @connection.async_exec(sql)
504
- else
505
- res = @connection.exec(sql)
506
- end
507
- return result_as_array(res)
516
+ result_as_array @connection.async_exec(sql)
508
517
  end
509
518
  end
510
519
 
@@ -512,14 +521,48 @@ module ActiveRecord
512
521
  # or raising a PGError exception otherwise.
513
522
  def execute(sql, name = nil)
514
523
  log(sql, name) do
515
- if @async
516
- @connection.async_exec(sql)
517
- else
518
- @connection.exec(sql)
519
- end
524
+ @connection.async_exec(sql)
525
+ end
526
+ end
527
+
528
+ def substitute_at(column, index)
529
+ Arel.sql("$#{index + 1}")
530
+ end
531
+
532
+ def exec_query(sql, name = 'SQL', binds = [])
533
+ log(sql, name, binds) do
534
+ result = binds.empty? ? exec_no_cache(sql, binds) :
535
+ exec_cache(sql, binds)
536
+
537
+ ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
538
+ result.clear
539
+ return ret
520
540
  end
521
541
  end
522
542
 
543
+ def exec_delete(sql, name = 'SQL', binds = [])
544
+ log(sql, name, binds) do
545
+ result = binds.empty? ? exec_no_cache(sql, binds) :
546
+ exec_cache(sql, binds)
547
+ affected = result.cmd_tuples
548
+ result.clear
549
+ affected
550
+ end
551
+ end
552
+ alias :exec_update :exec_delete
553
+
554
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
555
+ unless pk
556
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
557
+
558
+ pk = primary_key(table)
559
+ end
560
+
561
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
562
+
563
+ [sql, binds]
564
+ end
565
+
523
566
  # Executes an UPDATE query and returns the number of affected tuples.
524
567
  def update_sql(sql, name = nil)
525
568
  super.cmd_tuples
@@ -593,25 +636,17 @@ module ActiveRecord
593
636
  execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
594
637
  end
595
638
 
596
- # Drops a PostgreSQL database
639
+ # Drops a PostgreSQL database.
597
640
  #
598
641
  # Example:
599
642
  # drop_database 'matt_development'
600
643
  def drop_database(name) #:nodoc:
601
- if postgresql_version >= 80200
602
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
603
- else
604
- begin
605
- execute "DROP DATABASE #{quote_table_name(name)}"
606
- rescue ActiveRecord::StatementInvalid
607
- @logger.warn "#{name} database doesn't exist." if @logger
608
- end
609
- end
644
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
610
645
  end
611
646
 
612
647
  # Returns the list of all tables in the schema search path or a specified schema.
613
648
  def tables(name = nil)
614
- query(<<-SQL, name).map { |row| row[0] }
649
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
615
650
  SELECT tablename
616
651
  FROM pg_tables
617
652
  WHERE schemaname = ANY (current_schemas(false))
@@ -619,7 +654,21 @@ module ActiveRecord
619
654
  end
620
655
 
621
656
  def table_exists?(name)
622
- name = name.to_s
657
+ schema, table = extract_schema_and_table(name.to_s)
658
+
659
+ binds = [[nil, table.gsub(/(^"|"$)/,'')]]
660
+ binds << [nil, schema] if schema
661
+
662
+ exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
663
+ SELECT COUNT(*)
664
+ FROM pg_tables
665
+ WHERE tablename = $1
666
+ #{schema ? "AND schemaname = $2" : ''}
667
+ SQL
668
+ end
669
+
670
+ # Extracts the table and schema name from +name+
671
+ def extract_schema_and_table(name)
623
672
  schema, table = name.split('.', 2)
624
673
 
625
674
  unless table # A table was provided without a schema
@@ -631,16 +680,10 @@ module ActiveRecord
631
680
  table = name
632
681
  schema = nil
633
682
  end
634
-
635
- query(<<-SQL).first[0].to_i > 0
636
- SELECT COUNT(*)
637
- FROM pg_tables
638
- WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
639
- AND schemaname = #{schema ? "'#{schema}'" : "ANY (current_schemas(false))"}
640
- SQL
683
+ [schema, table]
641
684
  end
642
685
 
643
- # Returns the list of all indexes for a table.
686
+ # Returns an array of indexes for the given table.
644
687
  def indexes(table_name, name = nil)
645
688
  schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
646
689
  result = query(<<-SQL, name)
@@ -677,8 +720,8 @@ module ActiveRecord
677
720
  # Returns the list of all column definitions for a table.
678
721
  def columns(table_name, name = nil)
679
722
  # Limit, precision, and scale are all handled by the superclass.
680
- column_definitions(table_name).collect do |name, type, default, notnull|
681
- PostgreSQLColumn.new(name, default, type, notnull == 'f')
723
+ column_definitions(table_name).collect do |column_name, type, default, notnull|
724
+ PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
682
725
  end
683
726
  end
684
727
 
@@ -714,37 +757,47 @@ module ActiveRecord
714
757
 
715
758
  # Returns the current client message level.
716
759
  def client_min_messages
717
- query('SHOW client_min_messages')[0][0]
760
+ query('SHOW client_min_messages', 'SCHEMA')[0][0]
718
761
  end
719
762
 
720
763
  # Set the client message level.
721
764
  def client_min_messages=(level)
722
- execute("SET client_min_messages TO '#{level}'")
765
+ execute("SET client_min_messages TO '#{level}'", 'SCHEMA')
723
766
  end
724
767
 
725
768
  # Returns the sequence name for a table's primary key or some other specified key.
726
769
  def default_sequence_name(table_name, pk = nil) #:nodoc:
727
- default_pk, default_seq = pk_and_sequence_for(table_name)
728
- default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
770
+ serial_sequence(table_name, pk || 'id').split('.').last
771
+ rescue ActiveRecord::StatementInvalid
772
+ "#{table_name}_#{pk || 'id'}_seq"
773
+ end
774
+
775
+ def serial_sequence(table, column)
776
+ result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
777
+ SELECT pg_get_serial_sequence($1, $2)
778
+ eosql
779
+ result.rows.first.first
729
780
  end
730
781
 
731
782
  # Resets the sequence of a table's primary key to the maximum value.
732
783
  def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
733
784
  unless pk and sequence
734
785
  default_pk, default_sequence = pk_and_sequence_for(table)
786
+
735
787
  pk ||= default_pk
736
788
  sequence ||= default_sequence
737
789
  end
738
- if pk
739
- if sequence
740
- quoted_sequence = quote_column_name(sequence)
741
790
 
742
- select_value <<-end_sql, 'Reset sequence'
743
- 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)
744
- end_sql
745
- else
746
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
747
- end
791
+ if @logger && pk && !sequence
792
+ @logger.warn "#{table} has primary key #{pk} with no default sequence"
793
+ end
794
+
795
+ if pk && sequence
796
+ quoted_sequence = quote_column_name(sequence)
797
+
798
+ select_value <<-end_sql, 'Reset sequence'
799
+ 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)
800
+ end_sql
748
801
  end
749
802
  end
750
803
 
@@ -752,7 +805,7 @@ module ActiveRecord
752
805
  def pk_and_sequence_for(table) #:nodoc:
753
806
  # First try looking for a sequence with a dependency on the
754
807
  # given table's primary key.
755
- result = query(<<-end_sql, 'PK and serial sequence')[0]
808
+ result = exec_query(<<-end_sql, 'SCHEMA').rows.first
756
809
  SELECT attr.attname, seq.relname
757
810
  FROM pg_class seq,
758
811
  pg_attribute attr,
@@ -769,28 +822,6 @@ module ActiveRecord
769
822
  AND dep.refobjid = '#{quote_table_name(table)}'::regclass
770
823
  end_sql
771
824
 
772
- if result.nil? or result.empty?
773
- # If that fails, try parsing the primary key's default value.
774
- # Support the 7.x and 8.0 nextval('foo'::text) as well as
775
- # the 8.1+ nextval('foo'::regclass).
776
- result = query(<<-end_sql, 'PK and custom sequence')[0]
777
- SELECT attr.attname,
778
- CASE
779
- WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
780
- substr(split_part(def.adsrc, '''', 2),
781
- strpos(split_part(def.adsrc, '''', 2), '.')+1)
782
- ELSE split_part(def.adsrc, '''', 2)
783
- END
784
- FROM pg_class t
785
- JOIN pg_attribute attr ON (t.oid = attrelid)
786
- JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
787
- JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
788
- WHERE t.oid = '#{quote_table_name(table)}'::regclass
789
- AND cons.contype = 'p'
790
- AND def.adsrc ~* 'nextval'
791
- end_sql
792
- end
793
-
794
825
  # [primary_key, sequence]
795
826
  [result.first, result.last]
796
827
  rescue
@@ -799,11 +830,27 @@ module ActiveRecord
799
830
 
800
831
  # Returns just a table's primary key
801
832
  def primary_key(table)
802
- pk_and_sequence = pk_and_sequence_for(table)
803
- pk_and_sequence && pk_and_sequence.first
833
+ row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
834
+ SELECT DISTINCT(attr.attname)
835
+ FROM pg_attribute attr,
836
+ pg_depend dep,
837
+ pg_namespace name,
838
+ pg_constraint cons
839
+ WHERE attr.attrelid = dep.refobjid
840
+ AND attr.attnum = dep.refobjsubid
841
+ AND attr.attrelid = cons.conrelid
842
+ AND attr.attnum = cons.conkey[1]
843
+ AND cons.contype = 'p'
844
+ AND dep.refobjid = $1::regclass
845
+ end_sql
846
+
847
+ row && row.first
804
848
  end
805
849
 
806
850
  # Renames a table.
851
+ #
852
+ # Example:
853
+ # rename_table('octopuses', 'octopi')
807
854
  def rename_table(name, new_name)
808
855
  execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
809
856
  end
@@ -811,37 +858,17 @@ module ActiveRecord
811
858
  # Adds a new column to the named table.
812
859
  # See TableDefinition#column for details of the options you can use.
813
860
  def add_column(table_name, column_name, type, options = {})
814
- default = options[:default]
815
- notnull = options[:null] == false
861
+ 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])}"
862
+ add_column_options!(add_column_sql, options)
816
863
 
817
- # Add the column.
818
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
819
-
820
- change_column_default(table_name, column_name, default) if options_include_default?(options)
821
- change_column_null(table_name, column_name, false, default) if notnull
864
+ execute add_column_sql
822
865
  end
823
866
 
824
867
  # Changes the column of a table.
825
868
  def change_column(table_name, column_name, type, options = {})
826
869
  quoted_table_name = quote_table_name(table_name)
827
870
 
828
- begin
829
- execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
830
- rescue ActiveRecord::StatementInvalid => e
831
- raise e if postgresql_version > 80000
832
- # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
833
- begin
834
- begin_db_transaction
835
- tmp_column_name = "#{column_name}_ar_tmp"
836
- add_column(table_name, tmp_column_name, type, options)
837
- execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
838
- remove_column(table_name, column_name)
839
- rename_column(table_name, tmp_column_name, column_name)
840
- commit_db_transaction
841
- rescue
842
- rollback_db_transaction
843
- end
844
- end
871
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
845
872
 
846
873
  change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
847
874
  change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
@@ -868,6 +895,10 @@ module ActiveRecord
868
895
  execute "DROP INDEX #{quote_table_name(index_name)}"
869
896
  end
870
897
 
898
+ def rename_index(table_name, old_name, new_name)
899
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
900
+ end
901
+
871
902
  def index_name_length
872
903
  63
873
904
  end
@@ -891,40 +922,22 @@ module ActiveRecord
891
922
  # requires that the ORDER BY include the distinct column.
892
923
  #
893
924
  # distinct("posts.id", "posts.created_at desc")
894
- def distinct(columns, order_by) #:nodoc:
895
- return "DISTINCT #{columns}" if order_by.blank?
925
+ def distinct(columns, orders) #:nodoc:
926
+ return "DISTINCT #{columns}" if orders.empty?
896
927
 
897
928
  # Construct a clean list of column names from the ORDER BY clause, removing
898
929
  # any ASC/DESC modifiers
899
- order_columns = order_by.split(',').collect { |s| s.split.first }
930
+ order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s }
900
931
  order_columns.delete_if { |c| c.blank? }
901
932
  order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
902
933
 
903
- # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
904
- # all the required columns for the ORDER BY to work properly.
905
- sql = "DISTINCT ON (#{columns}) #{columns}, "
906
- sql << order_columns * ', '
934
+ "DISTINCT #{columns}, #{order_columns * ', '}"
907
935
  end
908
936
 
909
937
  protected
910
- # Returns the version of the connected PostgreSQL version.
938
+ # Returns the version of the connected PostgreSQL server.
911
939
  def postgresql_version
912
- @postgresql_version ||=
913
- if @connection.respond_to?(:server_version)
914
- @connection.server_version
915
- else
916
- # Mimic PGconn.server_version behavior
917
- begin
918
- if query('SELECT version()')[0][0] =~ /PostgreSQL ([0-9.]+)/
919
- major, minor, tiny = $1.split(".")
920
- (major.to_i * 10000) + (minor.to_i * 100) + tiny.to_i
921
- else
922
- 0
923
- end
924
- rescue
925
- 0
926
- end
927
- end
940
+ @connection.server_version
928
941
  end
929
942
 
930
943
  def translate_exception(exception, message)
@@ -939,6 +952,28 @@ module ActiveRecord
939
952
  end
940
953
 
941
954
  private
955
+ def exec_no_cache(sql, binds)
956
+ @connection.async_exec(sql)
957
+ end
958
+
959
+ def exec_cache(sql, binds)
960
+ unless @statements.key? sql
961
+ nextkey = "a#{@statements.length + 1}"
962
+ @connection.prepare nextkey, sql
963
+ @statements[sql] = nextkey
964
+ end
965
+
966
+ key = @statements[sql]
967
+
968
+ # Clear the queue
969
+ @connection.get_last_result
970
+ @connection.send_query_prepared(key, binds.map { |col, val|
971
+ type_cast(val, col)
972
+ })
973
+ @connection.block
974
+ @connection.get_last_result
975
+ end
976
+
942
977
  # The internal PostgreSQL identifier of the money data type.
943
978
  MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
944
979
  # The internal PostgreSQL identifier of the BYTEA data type.
@@ -948,10 +983,6 @@ module ActiveRecord
948
983
  # connected server's characteristics.
949
984
  def connect
950
985
  @connection = PGconn.connect(*@connection_parameters)
951
- PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
952
-
953
- # Ignore async_exec and async_query when using postgres-pr.
954
- @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
955
986
 
956
987
  # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
957
988
  # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
@@ -965,11 +996,7 @@ module ActiveRecord
965
996
  # This is called by #connect and should not be called manually.
966
997
  def configure_connection
967
998
  if @config[:encoding]
968
- if @connection.respond_to?(:set_client_encoding)
969
- @connection.set_client_encoding(@config[:encoding])
970
- else
971
- execute("SET client_encoding TO '#{@config[:encoding]}'")
972
- end
999
+ @connection.set_client_encoding(@config[:encoding])
973
1000
  end
974
1001
  self.client_min_messages = @config[:min_messages] if @config[:min_messages]
975
1002
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
@@ -980,24 +1007,22 @@ module ActiveRecord
980
1007
  # If using Active Record's time zone support configure the connection to return
981
1008
  # TIMESTAMP WITH ZONE types in UTC.
982
1009
  if ActiveRecord::Base.default_timezone == :utc
983
- execute("SET time zone 'UTC'")
1010
+ execute("SET time zone 'UTC'", 'SCHEMA')
984
1011
  elsif @local_tz
985
- execute("SET time zone '#{@local_tz}'")
1012
+ execute("SET time zone '#{@local_tz}'", 'SCHEMA')
986
1013
  end
987
1014
  end
988
1015
 
989
1016
  # Returns the current ID of a table's sequence.
990
- def last_insert_id(table, sequence_name) #:nodoc:
991
- Integer(select_value("SELECT currval('#{sequence_name}')"))
1017
+ def last_insert_id(sequence_name) #:nodoc:
1018
+ r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
1019
+ Integer(r.rows.first.first)
992
1020
  end
993
1021
 
994
1022
  # Executes a SELECT query and returns the results, performing any data type
995
1023
  # conversions that are required to be performed here instead of in PostgreSQLColumn.
996
- def select(sql, name = nil)
997
- fields, rows = select_raw(sql, name)
998
- rows.map do |row|
999
- Hash[fields.zip(row)]
1000
- end
1024
+ def select(sql, name = nil, binds = [])
1025
+ exec_query(sql, name, binds).to_a
1001
1026
  end
1002
1027
 
1003
1028
  def select_raw(sql, name = nil)
@@ -1027,7 +1052,7 @@ module ActiveRecord
1027
1052
  # - format_type includes the column size constraint, e.g. varchar(50)
1028
1053
  # - ::regclass is a function that gives the id for a table name
1029
1054
  def column_definitions(table_name) #:nodoc:
1030
- query <<-end_sql
1055
+ exec_query(<<-end_sql, 'SCHEMA').rows
1031
1056
  SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1032
1057
  FROM pg_attribute a LEFT JOIN pg_attrdef d
1033
1058
  ON a.attrelid = d.adrelid AND a.attnum = d.adnum
@@ -1038,15 +1063,18 @@ module ActiveRecord
1038
1063
  end
1039
1064
 
1040
1065
  def extract_pg_identifier_from_name(name)
1041
- match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1066
+ match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1042
1067
 
1043
1068
  if match_data
1044
- rest = name[match_data[0].length..-1]
1045
- rest = rest[1..-1] if rest[0,1] == "."
1069
+ rest = name[match_data[0].length, name.length]
1070
+ rest = rest[1, rest.length] if rest.start_with? "."
1046
1071
  [match_data[1], (rest.length > 0 ? rest : nil)]
1047
1072
  end
1048
1073
  end
1074
+
1075
+ def table_definition
1076
+ TableDefinition.new(self)
1077
+ end
1049
1078
  end
1050
1079
  end
1051
1080
  end
1052
-