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.
- data/CHANGELOG +220 -91
- data/README.rdoc +3 -3
- data/examples/performance.rb +88 -109
- data/lib/active_record.rb +6 -2
- data/lib/active_record/aggregations.rb +22 -45
- data/lib/active_record/associations.rb +264 -991
- data/lib/active_record/associations/alias_tracker.rb +85 -0
- data/lib/active_record/associations/association.rb +231 -0
- data/lib/active_record/associations/association_scope.rb +120 -0
- data/lib/active_record/associations/belongs_to_association.rb +40 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
- data/lib/active_record/associations/builder/association.rb +53 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
- data/lib/active_record/associations/builder/has_many.rb +65 -0
- data/lib/active_record/associations/builder/has_one.rb +63 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +524 -0
- data/lib/active_record/associations/collection_proxy.rb +125 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
- data/lib/active_record/associations/has_many_association.rb +50 -79
- data/lib/active_record/associations/has_many_through_association.rb +98 -67
- data/lib/active_record/associations/has_one_association.rb +45 -115
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency.rb +215 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +56 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +126 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +55 -0
- data/lib/active_record/associations/through_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +19 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
- data/lib/active_record/attribute_methods/dirty.rb +8 -2
- data/lib/active_record/attribute_methods/primary_key.rb +33 -13
- data/lib/active_record/attribute_methods/read.rb +17 -17
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
- data/lib/active_record/attribute_methods/write.rb +2 -1
- data/lib/active_record/autosave_association.rb +66 -45
- data/lib/active_record/base.rb +445 -273
- data/lib/active_record/callbacks.rb +24 -33
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
- data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
- data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
- data/lib/active_record/connection_adapters/column.rb +268 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
- data/lib/active_record/counter_cache.rb +7 -4
- data/lib/active_record/fixtures.rb +174 -192
- data/lib/active_record/identity_map.rb +131 -0
- data/lib/active_record/locking/optimistic.rb +20 -14
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +24 -4
- data/lib/active_record/migration.rb +265 -144
- data/lib/active_record/migration/command_recorder.rb +103 -0
- data/lib/active_record/named_scope.rb +68 -25
- data/lib/active_record/nested_attributes.rb +58 -15
- data/lib/active_record/observer.rb +3 -7
- data/lib/active_record/persistence.rb +58 -38
- data/lib/active_record/query_cache.rb +25 -3
- data/lib/active_record/railtie.rb +21 -12
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/databases.rake +147 -116
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/reflection.rb +176 -44
- data/lib/active_record/relation.rb +125 -49
- data/lib/active_record/relation/batches.rb +7 -5
- data/lib/active_record/relation/calculations.rb +50 -18
- data/lib/active_record/relation/finder_methods.rb +47 -26
- data/lib/active_record/relation/predicate_builder.rb +24 -21
- data/lib/active_record/relation/query_methods.rb +117 -101
- data/lib/active_record/relation/spawn_methods.rb +27 -20
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/schema.rb +5 -6
- data/lib/active_record/schema_dumper.rb +11 -13
- data/lib/active_record/serialization.rb +2 -2
- data/lib/active_record/serializers/xml_serializer.rb +10 -10
- data/lib/active_record/session_store.rb +8 -2
- data/lib/active_record/test_case.rb +9 -20
- data/lib/active_record/timestamp.rb +21 -9
- data/lib/active_record/transactions.rb +16 -15
- data/lib/active_record/validations.rb +21 -22
- data/lib/active_record/validations/associated.rb +3 -1
- data/lib/active_record/validations/uniqueness.rb +48 -58
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record.rb +6 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
- metadata +106 -77
- checksums.yaml +0 -7
- data/lib/active_record/association_preload.rb +0 -431
- data/lib/active_record/associations/association_collection.rb +0 -572
- data/lib/active_record/associations/association_proxy.rb +0 -304
- 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.
|
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
|
-
|
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"
|
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
|
-
@
|
248
|
+
@statements = {}
|
245
249
|
|
246
250
|
connect
|
247
|
-
|
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
|
-
|
253
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
269
|
-
else
|
270
|
-
disconnect!
|
271
|
-
connect
|
272
|
-
end
|
276
|
+
clear_cache!
|
277
|
+
@connection.reset
|
278
|
+
configure_connection
|
273
279
|
end
|
274
280
|
|
275
|
-
|
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
|
-
#
|
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
|
-
|
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 ||=
|
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
|
-
|
339
|
-
|
340
|
-
|
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
|
-
|
349
|
-
case
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
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?
|
402
|
-
|
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?
|
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?
|
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
|
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]
|
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
|
-
|
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
|
-
|
449
|
-
#
|
450
|
-
|
451
|
-
|
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
|
-
|
461
|
-
|
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
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
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
|
-
|
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
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
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 |
|
681
|
-
PostgreSQLColumn.new(
|
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
|
-
|
728
|
-
|
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
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
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 =
|
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
|
-
|
803
|
-
|
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
|
-
|
815
|
-
|
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
|
-
|
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
|
-
|
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,
|
895
|
-
return "DISTINCT #{columns}" if
|
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 =
|
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
|
-
|
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
|
938
|
+
# Returns the version of the connected PostgreSQL server.
|
911
939
|
def postgresql_version
|
912
|
-
@
|
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
|
-
|
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(
|
991
|
-
|
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
|
-
|
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
|
-
|
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
|
1066
|
+
match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
|
1042
1067
|
|
1043
1068
|
if match_data
|
1044
|
-
rest = name[match_data[0].length
|
1045
|
-
rest = rest[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
|
-
|