square-activerecord 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+