activerecord 2.1.2 → 2.2.2

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 (110) hide show
  1. data/CHANGELOG +32 -6
  2. data/README +0 -0
  3. data/Rakefile +4 -5
  4. data/lib/active_record.rb +11 -10
  5. data/lib/active_record/aggregations.rb +110 -38
  6. data/lib/active_record/association_preload.rb +104 -15
  7. data/lib/active_record/associations.rb +427 -212
  8. data/lib/active_record/associations/association_collection.rb +101 -16
  9. data/lib/active_record/associations/association_proxy.rb +65 -13
  10. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
  13. data/lib/active_record/associations/has_many_association.rb +28 -28
  14. data/lib/active_record/associations/has_many_through_association.rb +21 -19
  15. data/lib/active_record/associations/has_one_association.rb +24 -7
  16. data/lib/active_record/associations/has_one_through_association.rb +3 -4
  17. data/lib/active_record/attribute_methods.rb +13 -5
  18. data/lib/active_record/base.rb +435 -212
  19. data/lib/active_record/calculations.rb +12 -5
  20. data/lib/active_record/callbacks.rb +28 -9
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
  30. data/lib/active_record/dirty.rb +25 -7
  31. data/lib/active_record/dynamic_finder_match.rb +41 -0
  32. data/lib/active_record/fixtures.rb +10 -9
  33. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  34. data/lib/active_record/locale/en.yml +54 -0
  35. data/lib/active_record/migration.rb +47 -10
  36. data/lib/active_record/named_scope.rb +29 -16
  37. data/lib/active_record/reflection.rb +118 -54
  38. data/lib/active_record/schema_dumper.rb +13 -7
  39. data/lib/active_record/test_case.rb +18 -5
  40. data/lib/active_record/transactions.rb +89 -34
  41. data/lib/active_record/validations.rb +270 -180
  42. data/lib/active_record/version.rb +1 -1
  43. data/test/cases/active_schema_test_mysql.rb +5 -0
  44. data/test/cases/adapter_test.rb +6 -0
  45. data/test/cases/aggregations_test.rb +39 -0
  46. data/test/cases/associations/belongs_to_associations_test.rb +10 -0
  47. data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
  48. data/test/cases/associations/eager_test.rb +54 -5
  49. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
  50. data/test/cases/associations/has_many_associations_test.rb +74 -7
  51. data/test/cases/associations/has_many_through_associations_test.rb +50 -3
  52. data/test/cases/associations/has_one_associations_test.rb +17 -0
  53. data/test/cases/associations/has_one_through_associations_test.rb +49 -1
  54. data/test/cases/associations_test.rb +0 -0
  55. data/test/cases/attribute_methods_test.rb +59 -4
  56. data/test/cases/base_test.rb +93 -21
  57. data/test/cases/binary_test.rb +1 -5
  58. data/test/cases/calculations_test.rb +5 -0
  59. data/test/cases/callbacks_observers_test.rb +38 -0
  60. data/test/cases/connection_test_mysql.rb +1 -1
  61. data/test/cases/defaults_test.rb +32 -1
  62. data/test/cases/deprecated_finder_test.rb +0 -0
  63. data/test/cases/dirty_test.rb +13 -0
  64. data/test/cases/finder_test.rb +162 -12
  65. data/test/cases/fixtures_test.rb +32 -3
  66. data/test/cases/helper.rb +15 -0
  67. data/test/cases/i18n_test.rb +41 -0
  68. data/test/cases/inheritance_test.rb +2 -2
  69. data/test/cases/lifecycle_test.rb +0 -0
  70. data/test/cases/locking_test.rb +4 -9
  71. data/test/cases/method_scoping_test.rb +109 -2
  72. data/test/cases/migration_test.rb +43 -8
  73. data/test/cases/multiple_db_test.rb +25 -0
  74. data/test/cases/named_scope_test.rb +74 -0
  75. data/test/cases/pooled_connections_test.rb +103 -0
  76. data/test/cases/readonly_test.rb +0 -0
  77. data/test/cases/reflection_test.rb +11 -3
  78. data/test/cases/reload_models_test.rb +20 -0
  79. data/test/cases/sanitize_test.rb +25 -0
  80. data/test/cases/schema_authorization_test_postgresql.rb +2 -2
  81. data/test/cases/transactions_test.rb +62 -12
  82. data/test/cases/unconnected_test.rb +0 -0
  83. data/test/cases/validations_i18n_test.rb +921 -0
  84. data/test/cases/validations_test.rb +44 -33
  85. data/test/connections/native_mysql/connection.rb +1 -3
  86. data/test/fixtures/companies.yml +1 -0
  87. data/test/fixtures/customers.yml +10 -1
  88. data/test/fixtures/fixture_database.sqlite3 +0 -0
  89. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  90. data/test/fixtures/organizations.yml +5 -0
  91. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  92. data/test/models/author.rb +3 -0
  93. data/test/models/category.rb +3 -0
  94. data/test/models/club.rb +6 -0
  95. data/test/models/company.rb +25 -1
  96. data/test/models/customer.rb +19 -1
  97. data/test/models/member.rb +2 -0
  98. data/test/models/member_detail.rb +4 -0
  99. data/test/models/organization.rb +4 -0
  100. data/test/models/parrot.rb +1 -0
  101. data/test/models/post.rb +3 -0
  102. data/test/models/reply.rb +0 -0
  103. data/test/models/topic.rb +3 -0
  104. data/test/schema/schema.rb +12 -1
  105. metadata +22 -10
  106. data/lib/active_record/vendor/mysql.rb +0 -1214
  107. data/test/cases/adapter_test_sqlserver.rb +0 -95
  108. data/test/cases/table_name_test_sqlserver.rb +0 -23
  109. data/test/cases/threaded_connections_test.rb +0 -48
  110. data/test/schema/sqlserver_specific_schema.rb +0 -5
@@ -31,19 +31,25 @@ module ActiveRecord
31
31
  # See the concrete implementation for details on the expected parameter values.
32
32
  def columns(table_name, name = nil) end
33
33
 
34
- # Creates a new table
34
+ # Creates a new table with the name +table_name+. +table_name+ may either
35
+ # be a String or a Symbol.
36
+ #
35
37
  # There are two ways to work with +create_table+. You can use the block
36
38
  # form or the regular form, like this:
37
39
  #
38
40
  # === Block form
39
- # # create_table() yields a TableDefinition instance
41
+ # # create_table() passes a TableDefinition object to the block.
42
+ # # This form will not only create the table, but also columns for the
43
+ # # table.
40
44
  # create_table(:suppliers) do |t|
41
45
  # t.column :name, :string, :limit => 60
42
46
  # # Other fields here
43
47
  # end
44
48
  #
45
49
  # === Regular form
50
+ # # Creates a table called 'suppliers' with no columns.
46
51
  # create_table(:suppliers)
52
+ # # Add a column to 'suppliers'.
47
53
  # add_column(:suppliers, :name, :string, {:limit => 60})
48
54
  #
49
55
  # The +options+ hash can include the following keys:
@@ -356,7 +362,7 @@ module ActiveRecord
356
362
 
357
363
  def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
358
364
  if native = native_database_types[type]
359
- column_type_sql = native.is_a?(Hash) ? native[:name] : native
365
+ column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
360
366
 
361
367
  if type == :decimal # ignore limit, use precision and scale
362
368
  scale ||= native[:scale]
@@ -371,7 +377,7 @@ module ActiveRecord
371
377
  raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
372
378
  end
373
379
 
374
- elsif limit ||= native.is_a?(Hash) && native[:limit]
380
+ elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
375
381
  column_type_sql << "(#{limit})"
376
382
  end
377
383
 
@@ -7,23 +7,31 @@ require 'active_record/connection_adapters/abstract/schema_definitions'
7
7
  require 'active_record/connection_adapters/abstract/schema_statements'
8
8
  require 'active_record/connection_adapters/abstract/database_statements'
9
9
  require 'active_record/connection_adapters/abstract/quoting'
10
+ require 'active_record/connection_adapters/abstract/connection_pool'
10
11
  require 'active_record/connection_adapters/abstract/connection_specification'
11
12
  require 'active_record/connection_adapters/abstract/query_cache'
12
13
 
13
14
  module ActiveRecord
14
15
  module ConnectionAdapters # :nodoc:
16
+ # ActiveRecord supports multiple database systems. AbstractAdapter and
17
+ # related classes form the abstraction layer which makes this possible.
18
+ # An AbstractAdapter represents a connection to a database, and provides an
19
+ # abstract interface for database-specific functionality such as establishing
20
+ # a connection, escaping values, building the right SQL fragments for ':offset'
21
+ # and ':limit' options, etc.
22
+ #
15
23
  # All the concrete database adapters follow the interface laid down in this class.
16
- # You can use this interface directly by borrowing the database connection from the Base with
17
- # Base.connection.
24
+ # ActiveRecord::Base.connection returns an AbstractAdapter object, which
25
+ # you can use.
18
26
  #
19
- # Most of the methods in the adapter are useful during migrations. Most
20
- # notably, SchemaStatements#create_table, SchemaStatements#drop_table,
21
- # SchemaStatements#add_index, SchemaStatements#remove_index,
22
- # SchemaStatements#add_column, SchemaStatements#change_column and
23
- # SchemaStatements#remove_column are very useful.
27
+ # Most of the methods in the adapter are useful during migrations. Most
28
+ # notably, the instance methods provided by SchemaStatement are very useful.
24
29
  class AbstractAdapter
25
30
  include Quoting, DatabaseStatements, SchemaStatements
26
31
  include QueryCache
32
+ include ActiveSupport::Callbacks
33
+ define_callbacks :checkout, :checkin
34
+
27
35
  @@row_even = true
28
36
 
29
37
  def initialize(connection, logger = nil) #:nodoc:
@@ -51,6 +59,13 @@ module ActiveRecord
51
59
  true
52
60
  end
53
61
 
62
+ # Does this adapter support DDL rollbacks in transactions? That is, would
63
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
64
+ # SQL Server, and others support this. MySQL and others do not.
65
+ def supports_ddl_transactions?
66
+ false
67
+ end
68
+
54
69
  # Should primary key values be selected from their corresponding
55
70
  # sequence before the insert statement? If true, next_sequence_value
56
71
  # is called before each insert to set the record's primary key.
@@ -80,48 +95,74 @@ module ActiveRecord
80
95
 
81
96
  # CONNECTION MANAGEMENT ====================================
82
97
 
83
- # Is this connection active and ready to perform queries?
98
+ # Checks whether the connection to the database is still active. This includes
99
+ # checking whether the database is actually capable of responding, i.e. whether
100
+ # the connection isn't stale.
84
101
  def active?
85
102
  @active != false
86
103
  end
87
104
 
88
- # Close this connection and open a new one in its place.
105
+ # Disconnects from the database if already connected, and establishes a
106
+ # new connection with the database.
89
107
  def reconnect!
90
108
  @active = true
91
109
  end
92
110
 
93
- # Close this connection
111
+ # Disconnects from the database if already connected. Otherwise, this
112
+ # method does nothing.
94
113
  def disconnect!
95
114
  @active = false
96
115
  end
97
116
 
117
+ # Reset the state of this connection, directing the DBMS to clear
118
+ # transactions and other connection-related server-side state. Usually a
119
+ # database-dependent operation.
120
+ #
121
+ # The default implementation does nothing; the implementation should be
122
+ # overridden by concrete adapters.
123
+ def reset!
124
+ # this should be overridden by concrete adapters
125
+ end
126
+
98
127
  # Returns true if its safe to reload the connection between requests for development mode.
99
- # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
100
128
  def requires_reloading?
101
- false
129
+ true
102
130
  end
103
131
 
104
- # Lazily verify this connection, calling <tt>active?</tt> only if it hasn't
105
- # been called for +timeout+ seconds.
106
- def verify!(timeout)
107
- now = Time.now.to_i
108
- if (now - @last_verification) > timeout
109
- reconnect! unless active?
110
- @last_verification = now
111
- end
132
+ # Checks whether the connection to the database is still active (i.e. not stale).
133
+ # This is done under the hood by calling <tt>active?</tt>. If the connection
134
+ # is no longer active, then this method will reconnect to the database.
135
+ def verify!(*ignored)
136
+ reconnect! unless active?
112
137
  end
113
138
 
114
- # Provides access to the underlying database connection. Useful for
115
- # when you need to call a proprietary method such as postgresql's lo_*
116
- # methods
139
+ # Provides access to the underlying database driver for this adapter. For
140
+ # example, this method returns a Mysql object in case of MysqlAdapter,
141
+ # and a PGconn object in case of PostgreSQLAdapter.
142
+ #
143
+ # This is useful for when you need to call a proprietary method such as
144
+ # PostgreSQL's lo_* methods.
117
145
  def raw_connection
118
146
  @connection
119
147
  end
120
148
 
121
- def log_info(sql, name, runtime)
149
+ def open_transactions
150
+ @open_transactions ||= 0
151
+ end
152
+
153
+ def increment_open_transactions
154
+ @open_transactions ||= 0
155
+ @open_transactions += 1
156
+ end
157
+
158
+ def decrement_open_transactions
159
+ @open_transactions -= 1
160
+ end
161
+
162
+ def log_info(sql, name, seconds)
122
163
  if @logger && @logger.debug?
123
- name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
124
- @logger.debug format_log_entry(name, sql.squeeze(' '))
164
+ name = "#{name.nil? ? "SQL" : name} (#{sprintf("%.1f", seconds * 1000)}ms)"
165
+ @logger.debug(format_log_entry(name, sql.squeeze(' ')))
125
166
  end
126
167
  end
127
168
 
@@ -42,27 +42,6 @@ end
42
42
 
43
43
  module ActiveRecord
44
44
  class Base
45
- def self.require_mysql
46
- # Include the MySQL driver if one hasn't already been loaded
47
- unless defined? Mysql
48
- begin
49
- require_library_or_gem 'mysql'
50
- rescue LoadError => cannot_require_mysql
51
- # Use the bundled Ruby/MySQL driver if no driver is already in place
52
- begin
53
- ActiveSupport::Deprecation.warn "You're using the Ruby-based MySQL library that ships with Rails. This library will be REMOVED FROM RAILS 2.2. Please switch to the offical mysql gem: `gem install mysql`", caller
54
-
55
- require 'active_record/vendor/mysql'
56
- rescue LoadError
57
- raise cannot_require_mysql
58
- end
59
- end
60
- end
61
-
62
- # Define Mysql::Result.all_hashes
63
- MysqlCompat.define_all_hashes_method!
64
- end
65
-
66
45
  # Establishes a connection to the database that's used by all Active Record objects.
67
46
  def self.mysql_connection(config) # :nodoc:
68
47
  config = config.symbolize_keys
@@ -78,9 +57,19 @@ module ActiveRecord
78
57
  raise ArgumentError, "No database specified. Missing argument: database."
79
58
  end
80
59
 
81
- require_mysql
60
+ # Require the MySQL driver and define Mysql::Result.all_hashes
61
+ unless defined? Mysql
62
+ begin
63
+ require_library_or_gem('mysql')
64
+ rescue LoadError
65
+ $stderr.puts '!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.'
66
+ raise
67
+ end
68
+ end
69
+ MysqlCompat.define_all_hashes_method!
70
+
82
71
  mysql = Mysql.init
83
- mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
72
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
84
73
 
85
74
  ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
86
75
  end
@@ -91,7 +80,7 @@ module ActiveRecord
91
80
  def extract_default(default)
92
81
  if type == :binary || type == :text
93
82
  if default.blank?
94
- nil
83
+ return null ? nil : ''
95
84
  else
96
85
  raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
97
86
  end
@@ -102,6 +91,11 @@ module ActiveRecord
102
91
  end
103
92
  end
104
93
 
94
+ def has_default?
95
+ return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns
96
+ super
97
+ end
98
+
105
99
  private
106
100
  def simplified_type(field_type)
107
101
  return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
@@ -156,6 +150,7 @@ module ActiveRecord
156
150
  # * <tt>:password</tt> - Defaults to nothing.
157
151
  # * <tt>:database</tt> - The name of the database. No default, must be provided.
158
152
  # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
153
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
159
154
  # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
160
155
  # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
161
156
  # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
@@ -168,8 +163,10 @@ module ActiveRecord
168
163
  #
169
164
  # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
170
165
  class MysqlAdapter < AbstractAdapter
171
- @@emulate_booleans = true
172
166
  cattr_accessor :emulate_booleans
167
+ self.emulate_booleans = true
168
+
169
+ ADAPTER_NAME = 'MySQL'.freeze
173
170
 
174
171
  LOST_CONNECTION_ERROR_MESSAGES = [
175
172
  "Server shutdown in progress",
@@ -177,7 +174,22 @@ module ActiveRecord
177
174
  "Lost connection to MySQL server during query",
178
175
  "MySQL server has gone away" ]
179
176
 
180
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
177
+ QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
178
+
179
+ NATIVE_DATABASE_TYPES = {
180
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
181
+ :string => { :name => "varchar", :limit => 255 },
182
+ :text => { :name => "text" },
183
+ :integer => { :name => "int", :limit => 4 },
184
+ :float => { :name => "float" },
185
+ :decimal => { :name => "decimal" },
186
+ :datetime => { :name => "datetime" },
187
+ :timestamp => { :name => "datetime" },
188
+ :time => { :name => "time" },
189
+ :date => { :name => "date" },
190
+ :binary => { :name => "blob" },
191
+ :boolean => { :name => "tinyint", :limit => 1 }
192
+ }
181
193
 
182
194
  def initialize(connection, logger, connection_options, config)
183
195
  super(connection, logger)
@@ -187,7 +199,7 @@ module ActiveRecord
187
199
  end
188
200
 
189
201
  def adapter_name #:nodoc:
190
- 'MySQL'
202
+ ADAPTER_NAME
191
203
  end
192
204
 
193
205
  def supports_migrations? #:nodoc:
@@ -195,20 +207,7 @@ module ActiveRecord
195
207
  end
196
208
 
197
209
  def native_database_types #:nodoc:
198
- {
199
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
200
- :string => { :name => "varchar", :limit => 255 },
201
- :text => { :name => "text" },
202
- :integer => { :name => "int", :limit => 4 },
203
- :float => { :name => "float" },
204
- :decimal => { :name => "decimal" },
205
- :datetime => { :name => "datetime" },
206
- :timestamp => { :name => "datetime" },
207
- :time => { :name => "time" },
208
- :date => { :name => "date" },
209
- :binary => { :name => "blob" },
210
- :boolean => { :name => "tinyint", :limit => 1 }
211
- }
210
+ NATIVE_DATABASE_TYPES
212
211
  end
213
212
 
214
213
 
@@ -219,7 +218,7 @@ module ActiveRecord
219
218
  s = column.class.string_to_binary(value).unpack("H*")[0]
220
219
  "x'#{s}'"
221
220
  elsif value.kind_of?(BigDecimal)
222
- "'#{value.to_s("F")}'"
221
+ value.to_s("F")
223
222
  else
224
223
  super
225
224
  end
@@ -286,6 +285,14 @@ module ActiveRecord
286
285
  @connection.close rescue nil
287
286
  end
288
287
 
288
+ def reset!
289
+ if @connection.respond_to?(:change_user)
290
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
291
+ # reset the connection is to change the user to the same user.
292
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
293
+ configure_connection
294
+ end
295
+ end
289
296
 
290
297
  # DATABASE STATEMENTS ======================================
291
298
 
@@ -364,9 +371,9 @@ module ActiveRecord
364
371
  end
365
372
  end
366
373
 
367
- def recreate_database(name) #:nodoc:
374
+ def recreate_database(name, options = {}) #:nodoc:
368
375
  drop_database(name)
369
- create_database(name)
376
+ create_database(name, options)
370
377
  end
371
378
 
372
379
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
@@ -517,14 +524,33 @@ module ActiveRecord
517
524
  keys.length == 1 ? [keys.first, nil] : nil
518
525
  end
519
526
 
527
+ def case_sensitive_equality_operator
528
+ "= BINARY"
529
+ end
530
+
531
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
532
+ where_sql
533
+ end
534
+
520
535
  private
521
536
  def connect
537
+ @connection.reconnect = true if @connection.respond_to?(:reconnect=)
538
+
522
539
  encoding = @config[:encoding]
523
540
  if encoding
524
541
  @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
525
542
  end
526
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
543
+
544
+ if @config[:sslca] || @config[:sslkey]
545
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
546
+ end
547
+
527
548
  @connection.real_connect(*@connection_options)
549
+ configure_connection
550
+ end
551
+
552
+ def configure_connection
553
+ encoding = @config[:encoding]
528
554
  execute("SET NAMES '#{encoding}'") if encoding
529
555
 
530
556
  # By default, MySQL 'where id is null' selects the last inserted id.
@@ -68,72 +68,6 @@ module ActiveRecord
68
68
  super
69
69
  end
70
70
 
71
- # Escapes binary strings for bytea input to the database.
72
- def self.string_to_binary(value)
73
- if PGconn.respond_to?(:escape_bytea)
74
- self.class.module_eval do
75
- define_method(:string_to_binary) do |value|
76
- PGconn.escape_bytea(value) if value
77
- end
78
- end
79
- else
80
- self.class.module_eval do
81
- define_method(:string_to_binary) do |value|
82
- if value
83
- result = ''
84
- value.each_byte { |c| result << sprintf('\\\\%03o', c) }
85
- result
86
- end
87
- end
88
- end
89
- end
90
- self.class.string_to_binary(value)
91
- end
92
-
93
- # Unescapes bytea output from a database to the binary string it represents.
94
- def self.binary_to_string(value)
95
- # In each case, check if the value actually is escaped PostgreSQL bytea output
96
- # or an unescaped Active Record attribute that was just written.
97
- if PGconn.respond_to?(:unescape_bytea)
98
- self.class.module_eval do
99
- define_method(:binary_to_string) do |value|
100
- if value =~ /\\\d{3}/
101
- PGconn.unescape_bytea(value)
102
- else
103
- value
104
- end
105
- end
106
- end
107
- else
108
- self.class.module_eval do
109
- define_method(:binary_to_string) do |value|
110
- if value =~ /\\\d{3}/
111
- result = ''
112
- i, max = 0, value.size
113
- while i < max
114
- char = value[i]
115
- if char == ?\\
116
- if value[i+1] == ?\\
117
- char = ?\\
118
- i += 1
119
- else
120
- char = value[i+1..i+3].oct
121
- i += 3
122
- end
123
- end
124
- result << char
125
- i += 1
126
- end
127
- result
128
- else
129
- value
130
- end
131
- end
132
- end
133
- end
134
- self.class.binary_to_string(value)
135
- end
136
-
137
71
  # Maps PostgreSQL-specific data types to logical Rails types.
138
72
  def simplified_type(field_type)
139
73
  case field_type
@@ -246,9 +180,26 @@ module ActiveRecord
246
180
  # * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
247
181
  # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
248
182
  class PostgreSQLAdapter < AbstractAdapter
183
+ ADAPTER_NAME = 'PostgreSQL'.freeze
184
+
185
+ NATIVE_DATABASE_TYPES = {
186
+ :primary_key => "serial primary key".freeze,
187
+ :string => { :name => "character varying", :limit => 255 },
188
+ :text => { :name => "text" },
189
+ :integer => { :name => "integer" },
190
+ :float => { :name => "float" },
191
+ :decimal => { :name => "decimal" },
192
+ :datetime => { :name => "timestamp" },
193
+ :timestamp => { :name => "timestamp" },
194
+ :time => { :name => "time" },
195
+ :date => { :name => "date" },
196
+ :binary => { :name => "bytea" },
197
+ :boolean => { :name => "boolean" }
198
+ }
199
+
249
200
  # Returns 'PostgreSQL' as adapter name for identification purposes.
250
201
  def adapter_name
251
- 'PostgreSQL'
202
+ ADAPTER_NAME
252
203
  end
253
204
 
254
205
  # Initializes and connects a PostgreSQL adapter.
@@ -290,20 +241,7 @@ module ActiveRecord
290
241
  end
291
242
 
292
243
  def native_database_types #:nodoc:
293
- {
294
- :primary_key => "serial primary key",
295
- :string => { :name => "character varying", :limit => 255 },
296
- :text => { :name => "text" },
297
- :integer => { :name => "integer" },
298
- :float => { :name => "float" },
299
- :decimal => { :name => "decimal" },
300
- :datetime => { :name => "timestamp" },
301
- :timestamp => { :name => "timestamp" },
302
- :time => { :name => "time" },
303
- :date => { :name => "date" },
304
- :binary => { :name => "bytea" },
305
- :boolean => { :name => "boolean" }
306
- }
244
+ NATIVE_DATABASE_TYPES
307
245
  end
308
246
 
309
247
  # Does PostgreSQL support migrations?
@@ -331,6 +269,10 @@ module ActiveRecord
331
269
  postgresql_version >= 80200
332
270
  end
333
271
 
272
+ def supports_ddl_transactions?
273
+ true
274
+ end
275
+
334
276
  # Returns the configured supported identifier length supported by PostgreSQL,
335
277
  # or report the default of 63 on PostgreSQL 7.x.
336
278
  def table_alias_length
@@ -339,10 +281,78 @@ module ActiveRecord
339
281
 
340
282
  # QUOTING ==================================================
341
283
 
284
+ # Escapes binary strings for bytea input to the database.
285
+ def escape_bytea(value)
286
+ if PGconn.respond_to?(:escape_bytea)
287
+ self.class.instance_eval do
288
+ define_method(:escape_bytea) do |value|
289
+ PGconn.escape_bytea(value) if value
290
+ end
291
+ end
292
+ else
293
+ self.class.instance_eval do
294
+ define_method(:escape_bytea) do |value|
295
+ if value
296
+ result = ''
297
+ value.each_byte { |c| result << sprintf('\\\\%03o', c) }
298
+ result
299
+ end
300
+ end
301
+ end
302
+ end
303
+ escape_bytea(value)
304
+ end
305
+
306
+ # Unescapes bytea output from a database to the binary string it represents.
307
+ # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
308
+ # on escaped binary output from database drive.
309
+ def unescape_bytea(value)
310
+ # In each case, check if the value actually is escaped PostgreSQL bytea output
311
+ # or an unescaped Active Record attribute that was just written.
312
+ if PGconn.respond_to?(:unescape_bytea)
313
+ self.class.instance_eval do
314
+ define_method(:unescape_bytea) do |value|
315
+ if value =~ /\\\d{3}/
316
+ PGconn.unescape_bytea(value)
317
+ else
318
+ value
319
+ end
320
+ end
321
+ end
322
+ else
323
+ self.class.instance_eval do
324
+ define_method(:unescape_bytea) do |value|
325
+ if value =~ /\\\d{3}/
326
+ result = ''
327
+ i, max = 0, value.size
328
+ while i < max
329
+ char = value[i]
330
+ if char == ?\\
331
+ if value[i+1] == ?\\
332
+ char = ?\\
333
+ i += 1
334
+ else
335
+ char = value[i+1..i+3].oct
336
+ i += 3
337
+ end
338
+ end
339
+ result << char
340
+ i += 1
341
+ end
342
+ result
343
+ else
344
+ value
345
+ end
346
+ end
347
+ end
348
+ end
349
+ unescape_bytea(value)
350
+ end
351
+
342
352
  # Quotes PostgreSQL-specific data types for SQL input.
343
353
  def quote(value, column = nil) #:nodoc:
344
354
  if value.kind_of?(String) && column && column.type == :binary
345
- "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
355
+ "#{quoted_string_prefix}'#{escape_bytea(value)}'"
346
356
  elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
347
357
  "xml '#{quote_string(value)}'"
348
358
  elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
@@ -455,11 +465,20 @@ module ActiveRecord
455
465
 
456
466
  # create a 2D array representing the result set
457
467
  def result_as_array(res) #:nodoc:
468
+ # check if we have any binary column and if they need escaping
469
+ unescape_col = []
470
+ for j in 0...res.nfields do
471
+ # unescape string passed BYTEA field (OID == 17)
472
+ unescape_col << ( res.ftype(j)==17 )
473
+ end
474
+
458
475
  ary = []
459
476
  for i in 0...res.ntuples do
460
477
  ary << []
461
478
  for j in 0...res.nfields do
462
- ary[i] << res.getvalue(i,j)
479
+ data = res.getvalue(i,j)
480
+ data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
481
+ ary[i] << data
463
482
  end
464
483
  end
465
484
  return ary
@@ -510,6 +529,45 @@ module ActiveRecord
510
529
  execute "ROLLBACK"
511
530
  end
512
531
 
532
+ # ruby-pg defines Ruby constants for transaction status,
533
+ # ruby-postgres does not.
534
+ PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
535
+
536
+ # Check whether a transaction is active.
537
+ def transaction_active?
538
+ @connection.transaction_status != PQTRANS_IDLE
539
+ end
540
+
541
+ # Wrap a block in a transaction. Returns result of block.
542
+ def transaction(start_db_transaction = true)
543
+ transaction_open = false
544
+ begin
545
+ if block_given?
546
+ if start_db_transaction
547
+ begin_db_transaction
548
+ transaction_open = true
549
+ end
550
+ yield
551
+ end
552
+ rescue Exception => database_transaction_rollback
553
+ if transaction_open && transaction_active?
554
+ transaction_open = false
555
+ rollback_db_transaction
556
+ end
557
+ raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
558
+ end
559
+ ensure
560
+ if transaction_open && transaction_active?
561
+ begin
562
+ commit_db_transaction
563
+ rescue Exception => database_transaction_rollback
564
+ rollback_db_transaction
565
+ raise
566
+ end
567
+ end
568
+ end
569
+
570
+
513
571
  # SCHEMA STATEMENTS ========================================
514
572
 
515
573
  def recreate_database(name) #:nodoc:
@@ -618,6 +676,19 @@ module ActiveRecord
618
676
  end
619
677
  end
620
678
 
679
+ # Returns the current database name.
680
+ def current_database
681
+ query('select current_database()')[0][0]
682
+ end
683
+
684
+ # Returns the current database encoding format.
685
+ def encoding
686
+ query(<<-end_sql)[0][0]
687
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
688
+ WHERE pg_database.datname LIKE '#{current_database}'
689
+ end_sql
690
+ end
691
+
621
692
  # Sets the schema search path to a string of comma-separated schema names.
622
693
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
623
694
  # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
@@ -851,7 +922,7 @@ module ActiveRecord
851
922
  end
852
923
 
853
924
  private
854
- # The internal PostgreSQL identifer of the money data type.
925
+ # The internal PostgreSQL identifier of the money data type.
855
926
  MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
856
927
 
857
928
  # Connects to a PostgreSQL server and sets up the adapter depending on the