square-activerecord 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +359 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,1031 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/kernel/requires'
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ module ActiveRecord
6
+ class Base
7
+ # Establishes a connection to the database that's used by all Active Record objects
8
+ def self.postgresql_connection(config) # :nodoc:
9
+ require 'pg'
10
+
11
+ config = config.symbolize_keys
12
+ host = config[:host]
13
+ port = config[:port] || 5432
14
+ username = config[:username].to_s if config[:username]
15
+ password = config[:password].to_s if config[:password]
16
+
17
+ if config.has_key?(:database)
18
+ database = config[:database]
19
+ else
20
+ raise ArgumentError, "No database specified. Missing argument: database."
21
+ end
22
+
23
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
24
+ # so just pass a nil connection object for the time being.
25
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
26
+ end
27
+ end
28
+
29
+ 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
+ # PostgreSQL-specific extensions to column definitions in a table.
37
+ class PostgreSQLColumn < Column #:nodoc:
38
+ # Instantiates a new PostgreSQL column definition in a table.
39
+ def initialize(name, default, sql_type = nil, null = true)
40
+ super(name, self.class.extract_value_from_default(default), sql_type, null)
41
+ end
42
+
43
+ # :stopdoc:
44
+ class << self
45
+ attr_accessor :money_precision
46
+ end
47
+ # :startdoc:
48
+
49
+ private
50
+ def extract_limit(sql_type)
51
+ case sql_type
52
+ when /^bigint/i; 8
53
+ when /^smallint/i; 2
54
+ else super
55
+ end
56
+ end
57
+
58
+ # Extracts the scale from PostgreSQL-specific data types.
59
+ def extract_scale(sql_type)
60
+ # Money type has a fixed scale of 2.
61
+ sql_type =~ /^money/ ? 2 : super
62
+ end
63
+
64
+ # Extracts the precision from PostgreSQL-specific data types.
65
+ def extract_precision(sql_type)
66
+ if sql_type == 'money'
67
+ self.class.money_precision
68
+ else
69
+ super
70
+ end
71
+ end
72
+
73
+ # Maps PostgreSQL-specific data types to logical Rails types.
74
+ def simplified_type(field_type)
75
+ case field_type
76
+ # Numeric and monetary types
77
+ when /^(?:real|double precision)$/
78
+ :float
79
+ # Monetary types
80
+ when 'money'
81
+ :decimal
82
+ # Character types
83
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
84
+ :string
85
+ # Binary data types
86
+ when 'bytea'
87
+ :binary
88
+ # Date/time types
89
+ when /^timestamp with(?:out)? time zone$/
90
+ :datetime
91
+ when 'interval'
92
+ :string
93
+ # Geometric types
94
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
95
+ :string
96
+ # Network address types
97
+ when /^(?:cidr|inet|macaddr)$/
98
+ :string
99
+ # Bit strings
100
+ when /^bit(?: varying)?(?:\(\d+\))?$/
101
+ :string
102
+ # XML type
103
+ when 'xml'
104
+ :xml
105
+ # Arrays
106
+ when /^\D+\[\]$/
107
+ :string
108
+ # Object identifier types
109
+ when 'oid'
110
+ :integer
111
+ # UUID type
112
+ when 'uuid'
113
+ :string
114
+ # Small and big integer types
115
+ when /^(?:small|big)int$/
116
+ :integer
117
+ # Pass through all types that are not specific to PostgreSQL.
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ # Extracts the value from a PostgreSQL column default definition.
124
+ def self.extract_value_from_default(default)
125
+ case default
126
+ # Numeric types
127
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
128
+ $1
129
+ # Character types
130
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
131
+ $1
132
+ # Character types (8.1 formatting)
133
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
134
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
135
+ # Binary data types
136
+ when /\A'(.*)'::bytea\z/m
137
+ $1
138
+ # Date/time types
139
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
140
+ $1
141
+ when /\A'(.*)'::interval\z/
142
+ $1
143
+ # Boolean type
144
+ when 'true'
145
+ true
146
+ when 'false'
147
+ false
148
+ # Geometric types
149
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
150
+ $1
151
+ # Network address types
152
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
153
+ $1
154
+ # Bit string types
155
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
156
+ $1
157
+ # XML type
158
+ when /\A'(.*)'::xml\z/m
159
+ $1
160
+ # Arrays
161
+ when /\A'(.*)'::"?\D+"?\[\]\z/
162
+ $1
163
+ # Object identifier types
164
+ when /\A-?\d+\z/
165
+ $1
166
+ else
167
+ # Anything else is blank, some user type, or some function
168
+ # and we can't know the value of that, so return nil.
169
+ nil
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ module ConnectionAdapters
176
+ # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
177
+ # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
178
+ #
179
+ # Options:
180
+ #
181
+ # * <tt>:host</tt> - Defaults to "localhost".
182
+ # * <tt>:port</tt> - Defaults to 5432.
183
+ # * <tt>:username</tt> - Defaults to nothing.
184
+ # * <tt>:password</tt> - Defaults to nothing.
185
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
186
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
187
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
188
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
189
+ # <encoding></tt> call on the connection.
190
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
191
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
192
+ # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock;
193
+ # otherwise, use blocking query methods.
194
+ class PostgreSQLAdapter < AbstractAdapter
195
+ ADAPTER_NAME = 'PostgreSQL'.freeze
196
+
197
+ NATIVE_DATABASE_TYPES = {
198
+ :primary_key => "serial primary key".freeze,
199
+ :string => { :name => "character varying", :limit => 255 },
200
+ :text => { :name => "text" },
201
+ :integer => { :name => "integer" },
202
+ :float => { :name => "float" },
203
+ :decimal => { :name => "decimal" },
204
+ :datetime => { :name => "timestamp" },
205
+ :timestamp => { :name => "timestamp" },
206
+ :time => { :name => "time" },
207
+ :date => { :name => "date" },
208
+ :binary => { :name => "bytea" },
209
+ :boolean => { :name => "boolean" },
210
+ :xml => { :name => "xml" }
211
+ }
212
+
213
+ # Returns 'PostgreSQL' as adapter name for identification purposes.
214
+ def adapter_name
215
+ ADAPTER_NAME
216
+ end
217
+
218
+ # Initializes and connects a PostgreSQL adapter.
219
+ def initialize(connection, logger, connection_parameters, config)
220
+ super(connection, logger)
221
+ @connection_parameters, @config = connection_parameters, config
222
+
223
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
224
+ @local_tz = nil
225
+ @table_alias_length = nil
226
+ @postgresql_version = nil
227
+
228
+ connect
229
+ @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
230
+ end
231
+
232
+ # Is this connection alive and ready for queries?
233
+ def active?
234
+ if @connection.respond_to?(:status)
235
+ @connection.status == PGconn::CONNECTION_OK
236
+ else
237
+ # We're asking the driver, not Active Record, so use @connection.query instead of #query
238
+ @connection.query 'SELECT 1'
239
+ true
240
+ end
241
+ # postgres-pr raises a NoMethodError when querying if no connection is available.
242
+ rescue PGError, NoMethodError
243
+ false
244
+ end
245
+
246
+ # Close then reopen the connection.
247
+ def reconnect!
248
+ if @connection.respond_to?(:reset)
249
+ @connection.reset
250
+ configure_connection
251
+ else
252
+ disconnect!
253
+ connect
254
+ end
255
+ end
256
+
257
+ # Close the connection.
258
+ def disconnect!
259
+ @connection.close rescue nil
260
+ end
261
+
262
+ def native_database_types #:nodoc:
263
+ NATIVE_DATABASE_TYPES
264
+ end
265
+
266
+ # Does PostgreSQL support migrations?
267
+ def supports_migrations?
268
+ true
269
+ end
270
+
271
+ # Does PostgreSQL support finding primary key on non-Active Record tables?
272
+ def supports_primary_key? #:nodoc:
273
+ true
274
+ end
275
+
276
+ # Enable standard-conforming strings if available.
277
+ def set_standard_conforming_strings
278
+ old, self.client_min_messages = client_min_messages, 'panic'
279
+ execute('SET standard_conforming_strings = on') rescue nil
280
+ ensure
281
+ self.client_min_messages = old
282
+ end
283
+
284
+ def supports_insert_with_returning?
285
+ postgresql_version >= 80200
286
+ end
287
+
288
+ def supports_ddl_transactions?
289
+ true
290
+ end
291
+
292
+ def supports_savepoints?
293
+ true
294
+ end
295
+
296
+ # Returns the configured supported identifier length supported by PostgreSQL,
297
+ # or report the default of 63 on PostgreSQL 7.x.
298
+ def table_alias_length
299
+ @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
300
+ end
301
+
302
+ # QUOTING ==================================================
303
+
304
+ # Escapes binary strings for bytea input to the database.
305
+ def escape_bytea(value)
306
+ @connection.escape_bytea(value) if value
307
+ end
308
+
309
+ # Unescapes bytea output from a database to the binary string it represents.
310
+ # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
311
+ # on escaped binary output from database drive.
312
+ def unescape_bytea(value)
313
+ @connection.unescape_bytea(value) if value
314
+ end
315
+
316
+ # Quotes PostgreSQL-specific data types for SQL input.
317
+ def quote(value, column = nil) #:nodoc:
318
+ return super unless column
319
+
320
+ if value.kind_of?(String) && column.type == :binary
321
+ "'#{escape_bytea(value)}'"
322
+ elsif value.kind_of?(String) && column.sql_type == 'xml'
323
+ "xml '#{quote_string(value)}'"
324
+ elsif value.kind_of?(Numeric) && column.sql_type == 'money'
325
+ # Not truly string input, so doesn't require (or allow) escape string syntax.
326
+ "'#{value}'"
327
+ elsif value.kind_of?(String) && column.sql_type =~ /^bit/
328
+ case value
329
+ when /^[01]*$/
330
+ "B'#{value}'" # Bit-string notation
331
+ when /^[0-9A-F]*$/i
332
+ "X'#{value}'" # Hexadecimal notation
333
+ end
334
+ else
335
+ super
336
+ end
337
+ end
338
+
339
+ # Quotes strings for use in SQL input.
340
+ def quote_string(s) #:nodoc:
341
+ @connection.escape(s)
342
+ end
343
+
344
+ # Checks the following cases:
345
+ #
346
+ # - table_name
347
+ # - "table.name"
348
+ # - schema_name.table_name
349
+ # - schema_name."table.name"
350
+ # - "schema.name".table_name
351
+ # - "schema.name"."table.name"
352
+ def quote_table_name(name)
353
+ schema, name_part = extract_pg_identifier_from_name(name.to_s)
354
+
355
+ unless name_part
356
+ quote_column_name(schema)
357
+ else
358
+ table_name, name_part = extract_pg_identifier_from_name(name_part)
359
+ "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
360
+ end
361
+ end
362
+
363
+ # Quotes column names for use in SQL queries.
364
+ def quote_column_name(name) #:nodoc:
365
+ PGconn.quote_ident(name.to_s)
366
+ end
367
+
368
+ # Quote date/time values for use in SQL input. Includes microseconds
369
+ # if the value is a Time responding to usec.
370
+ def quoted_date(value) #:nodoc:
371
+ if value.acts_like?(:time) && value.respond_to?(:usec)
372
+ "#{super}.#{sprintf("%06d", value.usec)}"
373
+ else
374
+ super
375
+ end
376
+ end
377
+
378
+ # REFERENTIAL INTEGRITY ====================================
379
+
380
+ def supports_disable_referential_integrity?() #:nodoc:
381
+ postgresql_version >= 80100
382
+ end
383
+
384
+ def disable_referential_integrity #:nodoc:
385
+ if supports_disable_referential_integrity?() then
386
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
387
+ end
388
+ yield
389
+ ensure
390
+ if supports_disable_referential_integrity?() then
391
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
392
+ end
393
+ end
394
+
395
+ # DATABASE STATEMENTS ======================================
396
+
397
+ # Executes a SELECT query and returns an array of rows. Each row is an
398
+ # array of field values.
399
+ def select_rows(sql, name = nil)
400
+ select_raw(sql, name).last
401
+ end
402
+
403
+ # Executes an INSERT query and returns the new record's ID
404
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
405
+ # Extract the table from the insert sql. Yuck.
406
+ table = sql.split(" ", 4)[2].gsub('"', '')
407
+
408
+ # Try an insert with 'returning id' if available (PG >= 8.2)
409
+ if supports_insert_with_returning?
410
+ pk, sequence_name = *pk_and_sequence_for(table) unless pk
411
+ if pk
412
+ id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
413
+ clear_query_cache
414
+ return id
415
+ end
416
+ end
417
+
418
+ # Otherwise, insert then grab last_insert_id.
419
+ if insert_id = super
420
+ insert_id
421
+ else
422
+ # If neither pk nor sequence name is given, look them up.
423
+ unless pk || sequence_name
424
+ pk, sequence_name = *pk_and_sequence_for(table)
425
+ end
426
+
427
+ # If a pk is given, fallback to default sequence name.
428
+ # Don't fetch last insert id for a table without a pk.
429
+ if pk && sequence_name ||= default_sequence_name(table, pk)
430
+ last_insert_id(table, sequence_name)
431
+ end
432
+ end
433
+ end
434
+ alias :create :insert
435
+
436
+ # create a 2D array representing the result set
437
+ def result_as_array(res) #:nodoc:
438
+ # check if we have any binary column and if they need escaping
439
+ unescape_col = []
440
+ res.nfields.times do |j|
441
+ unescape_col << res.ftype(j)
442
+ end
443
+
444
+ ary = []
445
+ res.ntuples.times do |i|
446
+ ary << []
447
+ res.nfields.times do |j|
448
+ data = res.getvalue(i,j)
449
+ case unescape_col[j]
450
+
451
+ # unescape string passed BYTEA field (OID == 17)
452
+ when BYTEA_COLUMN_TYPE_OID
453
+ data = unescape_bytea(data) if String === data
454
+
455
+ # If this is a money type column and there are any currency symbols,
456
+ # then strip them off. Indeed it would be prettier to do this in
457
+ # PostgreSQLColumn.string_to_decimal but would break form input
458
+ # fields that call value_before_type_cast.
459
+ when MONEY_COLUMN_TYPE_OID
460
+ # Because money output is formatted according to the locale, there are two
461
+ # cases to consider (note the decimal separators):
462
+ # (1) $12,345,678.12
463
+ # (2) $12.345.678,12
464
+ case data
465
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
466
+ data.gsub!(/[^-\d\.]/, '')
467
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
468
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
469
+ end
470
+ end
471
+ ary[i] << data
472
+ end
473
+ end
474
+ return ary
475
+ end
476
+
477
+
478
+ # Queries the database and returns the results in an Array-like object
479
+ def query(sql, name = nil) #:nodoc:
480
+ log(sql, name) do
481
+ if @async
482
+ res = @connection.async_exec(sql)
483
+ else
484
+ res = @connection.exec(sql)
485
+ end
486
+ return result_as_array(res)
487
+ end
488
+ end
489
+
490
+ # Executes an SQL statement, returning a PGresult object on success
491
+ # or raising a PGError exception otherwise.
492
+ def execute(sql, name = nil)
493
+ log(sql, name) do
494
+ if @async
495
+ @connection.async_exec(sql)
496
+ else
497
+ @connection.exec(sql)
498
+ end
499
+ end
500
+ end
501
+
502
+ # Executes an UPDATE query and returns the number of affected tuples.
503
+ def update_sql(sql, name = nil)
504
+ super.cmd_tuples
505
+ end
506
+
507
+ # Begins a transaction.
508
+ def begin_db_transaction
509
+ execute "BEGIN"
510
+ end
511
+
512
+ # Commits a transaction.
513
+ def commit_db_transaction
514
+ execute "COMMIT"
515
+ end
516
+
517
+ # Aborts a transaction.
518
+ def rollback_db_transaction
519
+ execute "ROLLBACK"
520
+ end
521
+
522
+ def outside_transaction?
523
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
524
+ end
525
+
526
+ def create_savepoint
527
+ execute("SAVEPOINT #{current_savepoint_name}")
528
+ end
529
+
530
+ def rollback_to_savepoint
531
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
532
+ end
533
+
534
+ def release_savepoint
535
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
536
+ end
537
+
538
+ # SCHEMA STATEMENTS ========================================
539
+
540
+ def recreate_database(name) #:nodoc:
541
+ drop_database(name)
542
+ create_database(name)
543
+ end
544
+
545
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
546
+ # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
547
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
548
+ #
549
+ # Example:
550
+ # create_database config[:database], config
551
+ # create_database 'foo_development', :encoding => 'unicode'
552
+ def create_database(name, options = {})
553
+ options = options.reverse_merge(:encoding => "utf8")
554
+
555
+ option_string = options.symbolize_keys.sum do |key, value|
556
+ case key
557
+ when :owner
558
+ " OWNER = \"#{value}\""
559
+ when :template
560
+ " TEMPLATE = \"#{value}\""
561
+ when :encoding
562
+ " ENCODING = '#{value}'"
563
+ when :tablespace
564
+ " TABLESPACE = \"#{value}\""
565
+ when :connection_limit
566
+ " CONNECTION LIMIT = #{value}"
567
+ else
568
+ ""
569
+ end
570
+ end
571
+
572
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
573
+ end
574
+
575
+ # Drops a PostgreSQL database
576
+ #
577
+ # Example:
578
+ # drop_database 'matt_development'
579
+ def drop_database(name) #:nodoc:
580
+ if postgresql_version >= 80200
581
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
582
+ else
583
+ begin
584
+ execute "DROP DATABASE #{quote_table_name(name)}"
585
+ rescue ActiveRecord::StatementInvalid
586
+ @logger.warn "#{name} database doesn't exist." if @logger
587
+ end
588
+ end
589
+ end
590
+
591
+ # Returns the list of all tables in the schema search path or a specified schema.
592
+ def tables(name = nil)
593
+ query(<<-SQL, name).map { |row| row[0] }
594
+ SELECT tablename
595
+ FROM pg_tables
596
+ WHERE schemaname = ANY (current_schemas(false))
597
+ SQL
598
+ end
599
+
600
+ def table_exists?(name)
601
+ name = name.to_s
602
+ schema, table = name.split('.', 2)
603
+
604
+ unless table # A table was provided without a schema
605
+ table = schema
606
+ schema = nil
607
+ end
608
+
609
+ if name =~ /^"/ # Handle quoted table names
610
+ table = name
611
+ schema = nil
612
+ end
613
+
614
+ query(<<-SQL).first[0].to_i > 0
615
+ SELECT COUNT(*)
616
+ FROM pg_tables
617
+ WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
618
+ #{schema ? "AND schemaname = '#{schema}'" : ''}
619
+ SQL
620
+ end
621
+
622
+ # Returns the list of all indexes for a table.
623
+ def indexes(table_name, name = nil)
624
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
625
+ result = query(<<-SQL, name)
626
+ SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
627
+ FROM pg_class t, pg_class i, pg_index d
628
+ WHERE i.relkind = 'i'
629
+ AND d.indexrelid = i.oid
630
+ AND d.indisprimary = 'f'
631
+ AND t.oid = d.indrelid
632
+ AND t.relname = '#{table_name}'
633
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
634
+ ORDER BY i.relname
635
+ SQL
636
+
637
+
638
+ result.map do |row|
639
+ index_name = row[0]
640
+ unique = row[1] == 't'
641
+ indkey = row[2].split(" ")
642
+ oid = row[3]
643
+
644
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
645
+ SELECT a.attnum, a.attname
646
+ FROM pg_attribute a
647
+ WHERE a.attrelid = #{oid}
648
+ AND a.attnum IN (#{indkey.join(",")})
649
+ SQL
650
+
651
+ column_names = columns.values_at(*indkey).compact
652
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
653
+ end.compact
654
+ end
655
+
656
+ # Returns the list of all column definitions for a table.
657
+ def columns(table_name, name = nil)
658
+ # Limit, precision, and scale are all handled by the superclass.
659
+ column_definitions(table_name).collect do |name, type, default, notnull|
660
+ PostgreSQLColumn.new(name, default, type, notnull == 'f')
661
+ end
662
+ end
663
+
664
+ # Returns the current database name.
665
+ def current_database
666
+ query('select current_database()')[0][0]
667
+ end
668
+
669
+ # Returns the current database encoding format.
670
+ def encoding
671
+ query(<<-end_sql)[0][0]
672
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
673
+ WHERE pg_database.datname LIKE '#{current_database}'
674
+ end_sql
675
+ end
676
+
677
+ # Sets the schema search path to a string of comma-separated schema names.
678
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
679
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
680
+ #
681
+ # This should be not be called manually but set in database.yml.
682
+ def schema_search_path=(schema_csv)
683
+ if schema_csv
684
+ execute "SET search_path TO #{schema_csv}"
685
+ @schema_search_path = schema_csv
686
+ end
687
+ end
688
+
689
+ # Returns the active schema search path.
690
+ def schema_search_path
691
+ @schema_search_path ||= query('SHOW search_path')[0][0]
692
+ end
693
+
694
+ # Returns the current client message level.
695
+ def client_min_messages
696
+ query('SHOW client_min_messages')[0][0]
697
+ end
698
+
699
+ # Set the client message level.
700
+ def client_min_messages=(level)
701
+ execute("SET client_min_messages TO '#{level}'")
702
+ end
703
+
704
+ # Returns the sequence name for a table's primary key or some other specified key.
705
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
706
+ default_pk, default_seq = pk_and_sequence_for(table_name)
707
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
708
+ end
709
+
710
+ # Resets the sequence of a table's primary key to the maximum value.
711
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
712
+ unless pk and sequence
713
+ default_pk, default_sequence = pk_and_sequence_for(table)
714
+ pk ||= default_pk
715
+ sequence ||= default_sequence
716
+ end
717
+ if pk
718
+ if sequence
719
+ quoted_sequence = quote_column_name(sequence)
720
+
721
+ select_value <<-end_sql, 'Reset sequence'
722
+ 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)
723
+ end_sql
724
+ else
725
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
726
+ end
727
+ end
728
+ end
729
+
730
+ # Returns a table's primary key and belonging sequence.
731
+ def pk_and_sequence_for(table) #:nodoc:
732
+ # First try looking for a sequence with a dependency on the
733
+ # given table's primary key.
734
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
735
+ SELECT attr.attname, seq.relname
736
+ FROM pg_class seq,
737
+ pg_attribute attr,
738
+ pg_depend dep,
739
+ pg_namespace name,
740
+ pg_constraint cons
741
+ WHERE seq.oid = dep.objid
742
+ AND seq.relkind = 'S'
743
+ AND attr.attrelid = dep.refobjid
744
+ AND attr.attnum = dep.refobjsubid
745
+ AND attr.attrelid = cons.conrelid
746
+ AND attr.attnum = cons.conkey[1]
747
+ AND cons.contype = 'p'
748
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
749
+ end_sql
750
+
751
+ if result.nil? or result.empty?
752
+ # If that fails, try parsing the primary key's default value.
753
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
754
+ # the 8.1+ nextval('foo'::regclass).
755
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
756
+ SELECT attr.attname,
757
+ CASE
758
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
759
+ substr(split_part(def.adsrc, '''', 2),
760
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
761
+ ELSE split_part(def.adsrc, '''', 2)
762
+ END
763
+ FROM pg_class t
764
+ JOIN pg_attribute attr ON (t.oid = attrelid)
765
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
766
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
767
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
768
+ AND cons.contype = 'p'
769
+ AND def.adsrc ~* 'nextval'
770
+ end_sql
771
+ end
772
+
773
+ # [primary_key, sequence]
774
+ [result.first, result.last]
775
+ rescue
776
+ nil
777
+ end
778
+
779
+ # Returns just a table's primary key
780
+ def primary_key(table)
781
+ pk_and_sequence = pk_and_sequence_for(table)
782
+ pk_and_sequence && pk_and_sequence.first
783
+ end
784
+
785
+ # Renames a table.
786
+ def rename_table(name, new_name)
787
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
788
+ end
789
+
790
+ # Adds a new column to the named table.
791
+ # See TableDefinition#column for details of the options you can use.
792
+ def add_column(table_name, column_name, type, options = {})
793
+ default = options[:default]
794
+ notnull = options[:null] == false
795
+
796
+ # Add the column.
797
+ 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])}")
798
+
799
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
800
+ change_column_null(table_name, column_name, false, default) if notnull
801
+ end
802
+
803
+ # Changes the column of a table.
804
+ def change_column(table_name, column_name, type, options = {})
805
+ quoted_table_name = quote_table_name(table_name)
806
+
807
+ begin
808
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
809
+ rescue ActiveRecord::StatementInvalid => e
810
+ raise e if postgresql_version > 80000
811
+ # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
812
+ begin
813
+ begin_db_transaction
814
+ tmp_column_name = "#{column_name}_ar_tmp"
815
+ add_column(table_name, tmp_column_name, type, options)
816
+ 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])})"
817
+ remove_column(table_name, column_name)
818
+ rename_column(table_name, tmp_column_name, column_name)
819
+ commit_db_transaction
820
+ rescue
821
+ rollback_db_transaction
822
+ end
823
+ end
824
+
825
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
826
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
827
+ end
828
+
829
+ # Changes the default value of a table column.
830
+ def change_column_default(table_name, column_name, default)
831
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
832
+ end
833
+
834
+ def change_column_null(table_name, column_name, null, default = nil)
835
+ unless null || default.nil?
836
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
837
+ end
838
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
839
+ end
840
+
841
+ # Renames a column in a table.
842
+ def rename_column(table_name, column_name, new_column_name)
843
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
844
+ end
845
+
846
+ def remove_index!(table_name, index_name) #:nodoc:
847
+ execute "DROP INDEX #{quote_table_name(index_name)}"
848
+ end
849
+
850
+ def index_name_length
851
+ 63
852
+ end
853
+
854
+ # Maps logical Rails types to PostgreSQL-specific data types.
855
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
856
+ return super unless type.to_s == 'integer'
857
+ return 'integer' unless limit
858
+
859
+ case limit
860
+ when 1, 2; 'smallint'
861
+ when 3, 4; 'integer'
862
+ when 5..8; 'bigint'
863
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
864
+ end
865
+ end
866
+
867
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
868
+ #
869
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
870
+ # requires that the ORDER BY include the distinct column.
871
+ #
872
+ # distinct("posts.id", "posts.created_at desc")
873
+ def distinct(columns, order_by) #:nodoc:
874
+ return "DISTINCT #{columns}" if order_by.blank?
875
+
876
+ # Construct a clean list of column names from the ORDER BY clause, removing
877
+ # any ASC/DESC modifiers
878
+ order_columns = order_by.split(',').collect { |s| s.split.first }
879
+ order_columns.delete_if { |c| c.blank? }
880
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
881
+
882
+ # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
883
+ # all the required columns for the ORDER BY to work properly.
884
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
885
+ sql << order_columns * ', '
886
+ end
887
+
888
+ protected
889
+ # Returns the version of the connected PostgreSQL version.
890
+ def postgresql_version
891
+ @postgresql_version ||=
892
+ if @connection.respond_to?(:server_version)
893
+ @connection.server_version
894
+ else
895
+ # Mimic PGconn.server_version behavior
896
+ begin
897
+ if query('SELECT version()')[0][0] =~ /PostgreSQL ([0-9.]+)/
898
+ major, minor, tiny = $1.split(".")
899
+ (major.to_i * 10000) + (minor.to_i * 100) + tiny.to_i
900
+ else
901
+ 0
902
+ end
903
+ rescue
904
+ 0
905
+ end
906
+ end
907
+ end
908
+
909
+ def translate_exception(exception, message)
910
+ case exception.message
911
+ when /duplicate key value violates unique constraint/
912
+ RecordNotUnique.new(message, exception)
913
+ when /violates foreign key constraint/
914
+ InvalidForeignKey.new(message, exception)
915
+ else
916
+ super
917
+ end
918
+ end
919
+
920
+ private
921
+ # The internal PostgreSQL identifier of the money data type.
922
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
923
+ # The internal PostgreSQL identifier of the BYTEA data type.
924
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
925
+
926
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
927
+ # connected server's characteristics.
928
+ def connect
929
+ @connection = PGconn.connect(*@connection_parameters)
930
+ PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
931
+
932
+ # Ignore async_exec and async_query when using postgres-pr.
933
+ @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
934
+
935
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
936
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
937
+ # should know about this but can't detect it there, so deal with it here.
938
+ PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
939
+
940
+ configure_connection
941
+ end
942
+
943
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
944
+ # This is called by #connect and should not be called manually.
945
+ def configure_connection
946
+ if @config[:encoding]
947
+ if @connection.respond_to?(:set_client_encoding)
948
+ @connection.set_client_encoding(@config[:encoding])
949
+ else
950
+ execute("SET client_encoding TO '#{@config[:encoding]}'")
951
+ end
952
+ end
953
+ self.client_min_messages = @config[:min_messages] if @config[:min_messages]
954
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
955
+
956
+ # Use standard-conforming strings if available so we don't have to do the E'...' dance.
957
+ set_standard_conforming_strings
958
+
959
+ # If using Active Record's time zone support configure the connection to return
960
+ # TIMESTAMP WITH ZONE types in UTC.
961
+ if ActiveRecord::Base.default_timezone == :utc
962
+ execute("SET time zone 'UTC'")
963
+ elsif @local_tz
964
+ execute("SET time zone '#{@local_tz}'")
965
+ end
966
+ end
967
+
968
+ # Returns the current ID of a table's sequence.
969
+ def last_insert_id(table, sequence_name) #:nodoc:
970
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
971
+ end
972
+
973
+ # Executes a SELECT query and returns the results, performing any data type
974
+ # conversions that are required to be performed here instead of in PostgreSQLColumn.
975
+ def select(sql, name = nil)
976
+ fields, rows = select_raw(sql, name)
977
+ rows.map do |row|
978
+ Hash[*fields.zip(row).flatten]
979
+ end
980
+ end
981
+
982
+ def select_raw(sql, name = nil)
983
+ res = execute(sql, name)
984
+ results = result_as_array(res)
985
+ fields = res.fields
986
+ res.clear
987
+ return fields, results
988
+ end
989
+
990
+ # Returns the list of a table's column names, data types, and default values.
991
+ #
992
+ # The underlying query is roughly:
993
+ # SELECT column.name, column.type, default.value
994
+ # FROM column LEFT JOIN default
995
+ # ON column.table_id = default.table_id
996
+ # AND column.num = default.column_num
997
+ # WHERE column.table_id = get_table_id('table_name')
998
+ # AND column.num > 0
999
+ # AND NOT column.is_dropped
1000
+ # ORDER BY column.num
1001
+ #
1002
+ # If the table name is not prefixed with a schema, the database will
1003
+ # take the first match from the schema search path.
1004
+ #
1005
+ # Query implementation notes:
1006
+ # - format_type includes the column size constraint, e.g. varchar(50)
1007
+ # - ::regclass is a function that gives the id for a table name
1008
+ def column_definitions(table_name) #:nodoc:
1009
+ query <<-end_sql
1010
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1011
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
1012
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1013
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
1014
+ AND a.attnum > 0 AND NOT a.attisdropped
1015
+ ORDER BY a.attnum
1016
+ end_sql
1017
+ end
1018
+
1019
+ def extract_pg_identifier_from_name(name)
1020
+ match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
1021
+
1022
+ if match_data
1023
+ rest = name[match_data[0].length..-1]
1024
+ rest = rest[1..-1] if rest[0,1] == "."
1025
+ [match_data[1], (rest.length > 0 ? rest : nil)]
1026
+ end
1027
+ end
1028
+ end
1029
+ end
1030
+ end
1031
+