activerecord 1.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -1,177 +1,1030 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/kernel/requires'
3
+ require 'active_support/core_ext/object/blank'
1
4
 
2
- # postgresql_adaptor.rb
3
- # author: Luke Holden <lholden@cablelan.net>
4
- # notes: Currently this adaptor does not pass the test_zero_date_fields
5
- # and test_zero_datetime_fields unit tests in the BasicsTest test
6
- # group.
7
- #
8
- # This is due to the fact that, in postgresql you can not have a
9
- # totally zero timestamp. Instead null/nil should be used to
10
- # represent no value.
11
- #
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'
12
10
 
13
- require 'active_record/connection_adapters/abstract_adapter'
14
- require 'parsedate'
15
-
16
- begin
17
- # Only include the PostgreSQL driver if one hasn't already been loaded
18
- require 'postgres' unless self.class.const_defined?(:PGconn)
19
-
20
- module ActiveRecord
21
- class Base
22
- # Establishes a connection to the database that's used by all Active Record objects
23
- def self.postgresql_connection(config) # :nodoc:
24
- symbolize_strings_in_hash(config)
25
- host = config[:host]
26
- port = config[:port] || 5432 unless host.nil?
27
- username = config[:username] || ""
28
- password = config[:password] || ""
29
-
30
- if config.has_key?(:database)
31
- database = config[:database]
32
- else
33
- raise ArgumentError, "No database specified. Missing argument: database."
34
- end
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]
35
16
 
36
- ConnectionAdapters::PostgreSQLAdapter.new(
37
- PGconn.connect(host, port, "", "", database, username, password), logger
38
- )
17
+ if config.has_key?(:database)
18
+ database = config[:database]
19
+ else
20
+ raise ArgumentError, "No database specified. Missing argument: database."
39
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)
40
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
41
57
 
42
- module ConnectionAdapters
43
- class PostgreSQLAdapter < AbstractAdapter # :nodoc:
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
44
72
 
45
- def select_all(sql, name = nil)
46
- select(sql, name)
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
47
121
  end
48
122
 
49
- def select_one(sql, name = nil)
50
- result = select(sql, name)
51
- result.nil? ? nil : result.first
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
52
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
53
319
 
54
- def columns(table_name, name = nil)
55
- table_structure(table_name).inject([]) do |columns, field|
56
- columns << Column.new(field[0], field[2], field[1])
57
- columns
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
58
333
  end
334
+ else
335
+ super
59
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
60
367
 
61
- def insert(sql, name = nil, pk = nil, id_value = nil)
62
- execute(sql, name = nil)
63
- table = sql.split(" ", 4)[2]
64
- return id_value || last_insert_id(table, pk)
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
65
375
  end
376
+ end
377
+
378
+ # REFERENTIAL INTEGRITY ====================================
379
+
380
+ def supports_disable_referential_integrity?() #:nodoc:
381
+ version = query("SHOW server_version")[0][0].split('.')
382
+ version[0].to_i >= 8 && version[1].to_i >= 1
383
+ rescue
384
+ return false
385
+ end
66
386
 
67
- def execute(sql, name = nil)
68
- log(sql, name, @connection) { |connection| connection.query(sql) }
387
+ def disable_referential_integrity #:nodoc:
388
+ if supports_disable_referential_integrity?() then
389
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
390
+ end
391
+ yield
392
+ ensure
393
+ if supports_disable_referential_integrity?() then
394
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
69
395
  end
396
+ end
70
397
 
71
- alias_method :update, :execute
72
- alias_method :delete, :execute
398
+ # DATABASE STATEMENTS ======================================
73
399
 
74
- def begin_db_transaction() execute "BEGIN" end
75
- def commit_db_transaction() execute "COMMIT" end
76
- def rollback_db_transaction() execute "ROLLBACK" end
400
+ # Executes a SELECT query and returns an array of rows. Each row is an
401
+ # array of field values.
402
+ def select_rows(sql, name = nil)
403
+ select_raw(sql, name).last
404
+ end
77
405
 
78
- def quote_column_name(name)
79
- return "\"#{name}\""
406
+ # Executes an INSERT query and returns the new record's ID
407
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
408
+ # Extract the table from the insert sql. Yuck.
409
+ table = sql.split(" ", 4)[2].gsub('"', '')
410
+
411
+ # Try an insert with 'returning id' if available (PG >= 8.2)
412
+ if supports_insert_with_returning?
413
+ pk, sequence_name = *pk_and_sequence_for(table) unless pk
414
+ if pk
415
+ id = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
416
+ clear_query_cache
417
+ return id
418
+ end
80
419
  end
81
420
 
82
- private
83
- def last_insert_id(table, column = "id")
84
- sequence_name = "#{table}_#{column || 'id'}_seq"
85
- @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
421
+ # Otherwise, insert then grab last_insert_id.
422
+ if insert_id = super
423
+ insert_id
424
+ else
425
+ # If neither pk nor sequence name is given, look them up.
426
+ unless pk || sequence_name
427
+ pk, sequence_name = *pk_and_sequence_for(table)
86
428
  end
87
429
 
88
- def select(sql, name = nil)
89
- res = nil
90
- log(sql, name, @connection) { |connection| res = connection.exec(sql) }
91
-
92
- results = res.result
93
- rows = []
94
- if results.length > 0
95
- fields = res.fields
96
- results.each do |row|
97
- hashed_row = {}
98
- row.each_index { |cel_index| hashed_row[fields[cel_index]] = row[cel_index] }
99
- rows << hashed_row
430
+ # If a pk is given, fallback to default sequence name.
431
+ # Don't fetch last insert id for a table without a pk.
432
+ if pk && sequence_name ||= default_sequence_name(table, pk)
433
+ last_insert_id(table, sequence_name)
434
+ end
435
+ end
436
+ end
437
+ alias :create :insert
438
+
439
+ # create a 2D array representing the result set
440
+ def result_as_array(res) #:nodoc:
441
+ # check if we have any binary column and if they need escaping
442
+ unescape_col = []
443
+ res.nfields.times do |j|
444
+ unescape_col << res.ftype(j)
445
+ end
446
+
447
+ ary = []
448
+ res.ntuples.times do |i|
449
+ ary << []
450
+ res.nfields.times do |j|
451
+ data = res.getvalue(i,j)
452
+ case unescape_col[j]
453
+
454
+ # unescape string passed BYTEA field (OID == 17)
455
+ when BYTEA_COLUMN_TYPE_OID
456
+ data = unescape_bytea(data) if String === data
457
+
458
+ # If this is a money type column and there are any currency symbols,
459
+ # then strip them off. Indeed it would be prettier to do this in
460
+ # PostgreSQLColumn.string_to_decimal but would break form input
461
+ # fields that call value_before_type_cast.
462
+ when MONEY_COLUMN_TYPE_OID
463
+ # Because money output is formatted according to the locale, there are two
464
+ # cases to consider (note the decimal separators):
465
+ # (1) $12,345,678.12
466
+ # (2) $12.345.678,12
467
+ case data
468
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
469
+ data.gsub!(/[^-\d\.]/, '')
470
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
471
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
100
472
  end
101
473
  end
102
- return rows
474
+ ary[i] << data
103
475
  end
476
+ end
477
+ return ary
478
+ end
104
479
 
105
- def split_table_schema(table_name)
106
- schema_split = table_name.split('.')
107
- schema_name = "public"
108
- if schema_split.length > 1
109
- schema_name = schema_split.first.strip
110
- table_name = schema_split.last.strip
111
- end
112
- return [schema_name, table_name]
113
- end
114
-
115
- def table_structure(table_name)
116
- database_name = @connection.db
117
- schema_name, table_name = split_table_schema(table_name)
118
-
119
- # Grab a list of all the default values for the columns.
120
- sql = "SELECT column_name, column_default, character_maximum_length, data_type "
121
- sql << " FROM information_schema.columns "
122
- sql << " WHERE table_catalog = '#{database_name}' "
123
- sql << " AND table_schema = '#{schema_name}' "
124
- sql << " AND table_name = '#{table_name}';"
125
-
126
- column_defaults = nil
127
- log(sql, nil, @connection) { |connection| column_defaults = connection.query(sql) }
128
- column_defaults.collect do |row|
129
- field = row[0]
130
- type = type_as_string(row[3], row[2])
131
- default = default_value(row[1])
132
- length = row[2]
133
-
134
- [field, type, default, length]
480
+
481
+ # Queries the database and returns the results in an Array-like object
482
+ def query(sql, name = nil) #:nodoc:
483
+ log(sql, name) do
484
+ if @async
485
+ res = @connection.async_exec(sql)
486
+ else
487
+ res = @connection.exec(sql)
488
+ end
489
+ return result_as_array(res)
490
+ end
491
+ end
492
+
493
+ # Executes an SQL statement, returning a PGresult object on success
494
+ # or raising a PGError exception otherwise.
495
+ def execute(sql, name = nil)
496
+ log(sql, name) do
497
+ if @async
498
+ @connection.async_exec(sql)
499
+ else
500
+ @connection.exec(sql)
501
+ end
502
+ end
503
+ end
504
+
505
+ # Executes an UPDATE query and returns the number of affected tuples.
506
+ def update_sql(sql, name = nil)
507
+ super.cmd_tuples
508
+ end
509
+
510
+ # Begins a transaction.
511
+ def begin_db_transaction
512
+ execute "BEGIN"
513
+ end
514
+
515
+ # Commits a transaction.
516
+ def commit_db_transaction
517
+ execute "COMMIT"
518
+ end
519
+
520
+ # Aborts a transaction.
521
+ def rollback_db_transaction
522
+ execute "ROLLBACK"
523
+ end
524
+
525
+ def outside_transaction?
526
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
527
+ end
528
+
529
+ def create_savepoint
530
+ execute("SAVEPOINT #{current_savepoint_name}")
531
+ end
532
+
533
+ def rollback_to_savepoint
534
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
535
+ end
536
+
537
+ def release_savepoint
538
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
539
+ end
540
+
541
+ # SCHEMA STATEMENTS ========================================
542
+
543
+ def recreate_database(name) #:nodoc:
544
+ drop_database(name)
545
+ create_database(name)
546
+ end
547
+
548
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
549
+ # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
550
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
551
+ #
552
+ # Example:
553
+ # create_database config[:database], config
554
+ # create_database 'foo_development', :encoding => 'unicode'
555
+ def create_database(name, options = {})
556
+ options = options.reverse_merge(:encoding => "utf8")
557
+
558
+ option_string = options.symbolize_keys.sum do |key, value|
559
+ case key
560
+ when :owner
561
+ " OWNER = \"#{value}\""
562
+ when :template
563
+ " TEMPLATE = \"#{value}\""
564
+ when :encoding
565
+ " ENCODING = '#{value}'"
566
+ when :tablespace
567
+ " TABLESPACE = \"#{value}\""
568
+ when :connection_limit
569
+ " CONNECTION LIMIT = #{value}"
570
+ else
571
+ ""
572
+ end
573
+ end
574
+
575
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
576
+ end
577
+
578
+ # Drops a PostgreSQL database
579
+ #
580
+ # Example:
581
+ # drop_database 'matt_development'
582
+ def drop_database(name) #:nodoc:
583
+ if postgresql_version >= 80200
584
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
585
+ else
586
+ begin
587
+ execute "DROP DATABASE #{quote_table_name(name)}"
588
+ rescue ActiveRecord::StatementInvalid
589
+ @logger.warn "#{name} database doesn't exist." if @logger
590
+ end
591
+ end
592
+ end
593
+
594
+ # Returns the list of all tables in the schema search path or a specified schema.
595
+ def tables(name = nil)
596
+ query(<<-SQL, name).map { |row| row[0] }
597
+ SELECT tablename
598
+ FROM pg_tables
599
+ WHERE schemaname = ANY (current_schemas(false))
600
+ SQL
601
+ end
602
+
603
+ def table_exists?(name)
604
+ name = name.to_s
605
+ schema, table = name.split('.', 2)
606
+
607
+ unless table # A table was provided without a schema
608
+ table = schema
609
+ schema = nil
610
+ end
611
+
612
+ if name =~ /^"/ # Handle quoted table names
613
+ table = name
614
+ schema = nil
615
+ end
616
+
617
+ query(<<-SQL).first[0].to_i > 0
618
+ SELECT COUNT(*)
619
+ FROM pg_tables
620
+ WHERE tablename = '#{table.gsub(/(^"|"$)/,'')}'
621
+ #{schema ? "AND schemaname = '#{schema}'" : ''}
622
+ SQL
623
+ end
624
+
625
+ # Returns the list of all indexes for a table.
626
+ def indexes(table_name, name = nil)
627
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
628
+ result = query(<<-SQL, name)
629
+ SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
630
+ FROM pg_class t, pg_class i, pg_index d
631
+ WHERE i.relkind = 'i'
632
+ AND d.indexrelid = i.oid
633
+ AND d.indisprimary = 'f'
634
+ AND t.oid = d.indrelid
635
+ AND t.relname = '#{table_name}'
636
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
637
+ ORDER BY i.relname
638
+ SQL
639
+
640
+
641
+ result.map do |row|
642
+ index_name = row[0]
643
+ unique = row[1] == 't'
644
+ indkey = row[2].split(" ")
645
+ oid = row[3]
646
+
647
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
648
+ SELECT a.attnum, a.attname
649
+ FROM pg_attribute a
650
+ WHERE a.attrelid = #{oid}
651
+ AND a.attnum IN (#{indkey.join(",")})
652
+ SQL
653
+
654
+ column_names = columns.values_at(*indkey).compact
655
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
656
+ end.compact
657
+ end
658
+
659
+ # Returns the list of all column definitions for a table.
660
+ def columns(table_name, name = nil)
661
+ # Limit, precision, and scale are all handled by the superclass.
662
+ column_definitions(table_name).collect do |name, type, default, notnull|
663
+ PostgreSQLColumn.new(name, default, type, notnull == 'f')
664
+ end
665
+ end
666
+
667
+ # Returns the current database name.
668
+ def current_database
669
+ query('select current_database()')[0][0]
670
+ end
671
+
672
+ # Returns the current database encoding format.
673
+ def encoding
674
+ query(<<-end_sql)[0][0]
675
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
676
+ WHERE pg_database.datname LIKE '#{current_database}'
677
+ end_sql
678
+ end
679
+
680
+ # Sets the schema search path to a string of comma-separated schema names.
681
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
682
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
683
+ #
684
+ # This should be not be called manually but set in database.yml.
685
+ def schema_search_path=(schema_csv)
686
+ if schema_csv
687
+ execute "SET search_path TO #{schema_csv}"
688
+ @schema_search_path = schema_csv
689
+ end
690
+ end
691
+
692
+ # Returns the active schema search path.
693
+ def schema_search_path
694
+ @schema_search_path ||= query('SHOW search_path')[0][0]
695
+ end
696
+
697
+ # Returns the current client message level.
698
+ def client_min_messages
699
+ query('SHOW client_min_messages')[0][0]
700
+ end
701
+
702
+ # Set the client message level.
703
+ def client_min_messages=(level)
704
+ execute("SET client_min_messages TO '#{level}'")
705
+ end
706
+
707
+ # Returns the sequence name for a table's primary key or some other specified key.
708
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
709
+ default_pk, default_seq = pk_and_sequence_for(table_name)
710
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
711
+ end
712
+
713
+ # Resets the sequence of a table's primary key to the maximum value.
714
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
715
+ unless pk and sequence
716
+ default_pk, default_sequence = pk_and_sequence_for(table)
717
+ pk ||= default_pk
718
+ sequence ||= default_sequence
719
+ end
720
+ if pk
721
+ if sequence
722
+ quoted_sequence = quote_column_name(sequence)
723
+
724
+ select_value <<-end_sql, 'Reset sequence'
725
+ 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)
726
+ end_sql
727
+ else
728
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
729
+ end
730
+ end
731
+ end
732
+
733
+ # Returns a table's primary key and belonging sequence.
734
+ def pk_and_sequence_for(table) #:nodoc:
735
+ # First try looking for a sequence with a dependency on the
736
+ # given table's primary key.
737
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
738
+ SELECT attr.attname, seq.relname
739
+ FROM pg_class seq,
740
+ pg_attribute attr,
741
+ pg_depend dep,
742
+ pg_namespace name,
743
+ pg_constraint cons
744
+ WHERE seq.oid = dep.objid
745
+ AND seq.relkind = 'S'
746
+ AND attr.attrelid = dep.refobjid
747
+ AND attr.attnum = dep.refobjsubid
748
+ AND attr.attrelid = cons.conrelid
749
+ AND attr.attnum = cons.conkey[1]
750
+ AND cons.contype = 'p'
751
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
752
+ end_sql
753
+
754
+ if result.nil? or result.empty?
755
+ # If that fails, try parsing the primary key's default value.
756
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
757
+ # the 8.1+ nextval('foo'::regclass).
758
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
759
+ SELECT attr.attname,
760
+ CASE
761
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
762
+ substr(split_part(def.adsrc, '''', 2),
763
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
764
+ ELSE split_part(def.adsrc, '''', 2)
765
+ END
766
+ FROM pg_class t
767
+ JOIN pg_attribute attr ON (t.oid = attrelid)
768
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
769
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
770
+ WHERE t.oid = '#{quote_table_name(table)}'::regclass
771
+ AND cons.contype = 'p'
772
+ AND def.adsrc ~* 'nextval'
773
+ end_sql
774
+ end
775
+
776
+ # [primary_key, sequence]
777
+ [result.first, result.last]
778
+ rescue
779
+ nil
780
+ end
781
+
782
+ # Returns just a table's primary key
783
+ def primary_key(table)
784
+ pk_and_sequence = pk_and_sequence_for(table)
785
+ pk_and_sequence && pk_and_sequence.first
786
+ end
787
+
788
+ # Renames a table.
789
+ def rename_table(name, new_name)
790
+ execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
791
+ end
792
+
793
+ # Adds a new column to the named table.
794
+ # See TableDefinition#column for details of the options you can use.
795
+ def add_column(table_name, column_name, type, options = {})
796
+ default = options[:default]
797
+ notnull = options[:null] == false
798
+
799
+ # Add the column.
800
+ 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])}")
801
+
802
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
803
+ change_column_null(table_name, column_name, false, default) if notnull
804
+ end
805
+
806
+ # Changes the column of a table.
807
+ def change_column(table_name, column_name, type, options = {})
808
+ quoted_table_name = quote_table_name(table_name)
809
+
810
+ begin
811
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
812
+ rescue ActiveRecord::StatementInvalid => e
813
+ raise e if postgresql_version > 80000
814
+ # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
815
+ begin
816
+ begin_db_transaction
817
+ tmp_column_name = "#{column_name}_ar_tmp"
818
+ add_column(table_name, tmp_column_name, type, options)
819
+ 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])})"
820
+ remove_column(table_name, column_name)
821
+ rename_column(table_name, tmp_column_name, column_name)
822
+ commit_db_transaction
823
+ rescue
824
+ rollback_db_transaction
825
+ end
826
+ end
827
+
828
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
829
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
830
+ end
831
+
832
+ # Changes the default value of a table column.
833
+ def change_column_default(table_name, column_name, default)
834
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
835
+ end
836
+
837
+ def change_column_null(table_name, column_name, null, default = nil)
838
+ unless null || default.nil?
839
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
840
+ end
841
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
842
+ end
843
+
844
+ # Renames a column in a table.
845
+ def rename_column(table_name, column_name, new_column_name)
846
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
847
+ end
848
+
849
+ def remove_index!(table_name, index_name) #:nodoc:
850
+ execute "DROP INDEX #{quote_table_name(index_name)}"
851
+ end
852
+
853
+ def index_name_length
854
+ 63
855
+ end
856
+
857
+ # Maps logical Rails types to PostgreSQL-specific data types.
858
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
859
+ return super unless type.to_s == 'integer'
860
+ return 'integer' unless limit
861
+
862
+ case limit
863
+ when 1, 2; 'smallint'
864
+ when 3, 4; 'integer'
865
+ when 5..8; 'bigint'
866
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
867
+ end
868
+ end
869
+
870
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
871
+ #
872
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
873
+ # requires that the ORDER BY include the distinct column.
874
+ #
875
+ # distinct("posts.id", "posts.created_at desc")
876
+ def distinct(columns, order_by) #:nodoc:
877
+ return "DISTINCT #{columns}" if order_by.blank?
878
+
879
+ # Construct a clean list of column names from the ORDER BY clause, removing
880
+ # any ASC/DESC modifiers
881
+ order_columns = order_by.split(',').collect { |s| s.split.first }
882
+ order_columns.delete_if { |c| c.blank? }
883
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
884
+
885
+ # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
886
+ # all the required columns for the ORDER BY to work properly.
887
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
888
+ sql << order_columns * ', '
889
+ end
890
+
891
+ protected
892
+ # Returns the version of the connected PostgreSQL version.
893
+ def postgresql_version
894
+ @postgresql_version ||=
895
+ if @connection.respond_to?(:server_version)
896
+ @connection.server_version
897
+ else
898
+ # Mimic PGconn.server_version behavior
899
+ begin
900
+ query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
901
+ ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
902
+ rescue
903
+ 0
904
+ end
135
905
  end
906
+ end
907
+
908
+ def translate_exception(exception, message)
909
+ case exception.message
910
+ when /duplicate key value violates unique constraint/
911
+ RecordNotUnique.new(message, exception)
912
+ when /violates foreign key constraint/
913
+ InvalidForeignKey.new(message, exception)
914
+ else
915
+ super
136
916
  end
917
+ end
918
+
919
+ private
920
+ # The internal PostgreSQL identifier of the money data type.
921
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
922
+ # The internal PostgreSQL identifier of the BYTEA data type.
923
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
924
+
925
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
926
+ # connected server's characteristics.
927
+ def connect
928
+ @connection = PGconn.connect(*@connection_parameters)
929
+ PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
137
930
 
138
- def type_as_string(field_type, field_length)
139
- type = case field_type
140
- when 'numeric', 'real', 'money' then 'float'
141
- when 'character varying', 'interval' then 'string'
142
- when 'timestamp without time zone' then 'datetime'
143
- else field_type
931
+ # Ignore async_exec and async_query when using postgres-pr.
932
+ @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
933
+
934
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
935
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
936
+ # should know about this but can't detect it there, so deal with it here.
937
+ PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
938
+
939
+ configure_connection
940
+ end
941
+
942
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
943
+ # This is called by #connect and should not be called manually.
944
+ def configure_connection
945
+ if @config[:encoding]
946
+ if @connection.respond_to?(:set_client_encoding)
947
+ @connection.set_client_encoding(@config[:encoding])
948
+ else
949
+ execute("SET client_encoding TO '#{@config[:encoding]}'")
144
950
  end
951
+ end
952
+ self.client_min_messages = @config[:min_messages] if @config[:min_messages]
953
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
145
954
 
146
- size = field_length.nil? ? "" : "(#{field_length})"
955
+ # Use standard-conforming strings if available so we don't have to do the E'...' dance.
956
+ set_standard_conforming_strings
957
+
958
+ # If using Active Record's time zone support configure the connection to return
959
+ # TIMESTAMP WITH ZONE types in UTC.
960
+ if ActiveRecord::Base.default_timezone == :utc
961
+ execute("SET time zone 'UTC'")
962
+ elsif @local_tz
963
+ execute("SET time zone '#{@local_tz}'")
964
+ end
965
+ end
966
+
967
+ # Returns the current ID of a table's sequence.
968
+ def last_insert_id(table, sequence_name) #:nodoc:
969
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
970
+ end
147
971
 
148
- return type + size
972
+ # Executes a SELECT query and returns the results, performing any data type
973
+ # conversions that are required to be performed here instead of in PostgreSQLColumn.
974
+ def select(sql, name = nil)
975
+ fields, rows = select_raw(sql, name)
976
+ rows.map do |row|
977
+ Hash[*fields.zip(row).flatten]
149
978
  end
979
+ end
980
+
981
+ def select_raw(sql, name = nil)
982
+ res = execute(sql, name)
983
+ results = result_as_array(res)
984
+ fields = res.fields
985
+ res.clear
986
+ return fields, results
987
+ end
150
988
 
151
- def default_value(value)
152
- # Boolean types
153
- return "t" if value =~ /true/i
154
- return "f" if value =~ /false/i
155
-
156
- # Char/String type values
157
- return $1 if value =~ /^'(.*)'::(bpchar|text|character varying)$/
158
-
159
- # Numeric values
160
- return value if value =~ /^[0-9]+(\.[0-9]*)?/
989
+ # Returns the list of a table's column names, data types, and default values.
990
+ #
991
+ # The underlying query is roughly:
992
+ # SELECT column.name, column.type, default.value
993
+ # FROM column LEFT JOIN default
994
+ # ON column.table_id = default.table_id
995
+ # AND column.num = default.column_num
996
+ # WHERE column.table_id = get_table_id('table_name')
997
+ # AND column.num > 0
998
+ # AND NOT column.is_dropped
999
+ # ORDER BY column.num
1000
+ #
1001
+ # If the table name is not prefixed with a schema, the database will
1002
+ # take the first match from the schema search path.
1003
+ #
1004
+ # Query implementation notes:
1005
+ # - format_type includes the column size constraint, e.g. varchar(50)
1006
+ # - ::regclass is a function that gives the id for a table name
1007
+ def column_definitions(table_name) #:nodoc:
1008
+ query <<-end_sql
1009
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
1010
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
1011
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1012
+ WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
1013
+ AND a.attnum > 0 AND NOT a.attisdropped
1014
+ ORDER BY a.attnum
1015
+ end_sql
1016
+ end
161
1017
 
162
- # Date / Time magic values
163
- return Time.now.to_s if value =~ /^\('now'::text\)::(date|timestamp)/
1018
+ def extract_pg_identifier_from_name(name)
1019
+ match_data = name[0,1] == '"' ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
164
1020
 
165
- # Fixed dates / times
166
- return $1 if value =~ /^'(.+)'::(date|timestamp)/
167
-
168
- # Anything else is blank, some user type, or some function
169
- # and we can't know the value of that, so return nil.
170
- return nil
1021
+ if match_data
1022
+ rest = name[match_data[0].length..-1]
1023
+ rest = rest[1..-1] if rest[0,1] == "."
1024
+ [match_data[1], (rest.length > 0 ? rest : nil)]
171
1025
  end
172
- end
1026
+ end
173
1027
  end
174
1028
  end
175
- rescue LoadError
176
- # PostgreSQL driver is not availible
177
1029
  end
1030
+