activerecord 3.1.12 → 3.2.22.1

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +804 -338
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +20 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +3 -6
  7. data/lib/active_record/associations/association.rb +13 -45
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/belongs_to_association.rb +1 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
  11. data/lib/active_record/associations/builder/association.rb +6 -4
  12. data/lib/active_record/associations/builder/belongs_to.rb +7 -4
  13. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +4 -4
  15. data/lib/active_record/associations/builder/has_one.rb +5 -6
  16. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  17. data/lib/active_record/associations/collection_association.rb +65 -32
  18. data/lib/active_record/associations/collection_proxy.rb +8 -41
  19. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  20. data/lib/active_record/associations/has_many_association.rb +11 -7
  21. data/lib/active_record/associations/has_many_through_association.rb +19 -9
  22. data/lib/active_record/associations/has_one_association.rb +23 -13
  23. data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
  24. data/lib/active_record/associations/join_dependency.rb +3 -3
  25. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  26. data/lib/active_record/associations/preloader.rb +14 -10
  27. data/lib/active_record/associations/through_association.rb +8 -4
  28. data/lib/active_record/associations.rb +92 -76
  29. data/lib/active_record/attribute_assignment.rb +221 -0
  30. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  31. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  32. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  33. data/lib/active_record/attribute_methods/read.rb +73 -83
  34. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  36. data/lib/active_record/attribute_methods/write.rb +32 -6
  37. data/lib/active_record/attribute_methods.rb +231 -30
  38. data/lib/active_record/autosave_association.rb +44 -26
  39. data/lib/active_record/base.rb +227 -1708
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  41. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  49. data/lib/active_record/connection_adapters/column.rb +37 -11
  50. data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
  51. data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
  53. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  55. data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
  56. data/lib/active_record/counter_cache.rb +9 -4
  57. data/lib/active_record/dynamic_finder_match.rb +12 -0
  58. data/lib/active_record/dynamic_matchers.rb +84 -0
  59. data/lib/active_record/errors.rb +11 -1
  60. data/lib/active_record/explain.rb +86 -0
  61. data/lib/active_record/explain_subscriber.rb +25 -0
  62. data/lib/active_record/fixtures/file.rb +65 -0
  63. data/lib/active_record/fixtures.rb +57 -86
  64. data/lib/active_record/identity_map.rb +3 -4
  65. data/lib/active_record/inheritance.rb +174 -0
  66. data/lib/active_record/integration.rb +60 -0
  67. data/lib/active_record/locking/optimistic.rb +33 -26
  68. data/lib/active_record/locking/pessimistic.rb +23 -1
  69. data/lib/active_record/log_subscriber.rb +8 -4
  70. data/lib/active_record/migration/command_recorder.rb +8 -8
  71. data/lib/active_record/migration.rb +68 -35
  72. data/lib/active_record/model_schema.rb +368 -0
  73. data/lib/active_record/nested_attributes.rb +60 -24
  74. data/lib/active_record/persistence.rb +57 -11
  75. data/lib/active_record/query_cache.rb +6 -6
  76. data/lib/active_record/querying.rb +58 -0
  77. data/lib/active_record/railtie.rb +37 -29
  78. data/lib/active_record/railties/controller_runtime.rb +3 -1
  79. data/lib/active_record/railties/databases.rake +213 -117
  80. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  81. data/lib/active_record/readonly_attributes.rb +26 -0
  82. data/lib/active_record/reflection.rb +7 -15
  83. data/lib/active_record/relation/batches.rb +7 -4
  84. data/lib/active_record/relation/calculations.rb +55 -16
  85. data/lib/active_record/relation/delegation.rb +49 -0
  86. data/lib/active_record/relation/finder_methods.rb +16 -11
  87. data/lib/active_record/relation/predicate_builder.rb +8 -6
  88. data/lib/active_record/relation/query_methods.rb +75 -9
  89. data/lib/active_record/relation/spawn_methods.rb +48 -7
  90. data/lib/active_record/relation.rb +78 -32
  91. data/lib/active_record/result.rb +10 -4
  92. data/lib/active_record/sanitization.rb +194 -0
  93. data/lib/active_record/schema_dumper.rb +12 -5
  94. data/lib/active_record/scoping/default.rb +142 -0
  95. data/lib/active_record/scoping/named.rb +200 -0
  96. data/lib/active_record/scoping.rb +152 -0
  97. data/lib/active_record/serialization.rb +1 -43
  98. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  99. data/lib/active_record/session_store.rb +18 -16
  100. data/lib/active_record/store.rb +52 -0
  101. data/lib/active_record/test_case.rb +11 -7
  102. data/lib/active_record/timestamp.rb +17 -3
  103. data/lib/active_record/transactions.rb +27 -6
  104. data/lib/active_record/translation.rb +22 -0
  105. data/lib/active_record/validations/associated.rb +5 -4
  106. data/lib/active_record/validations/uniqueness.rb +8 -8
  107. data/lib/active_record/validations.rb +1 -1
  108. data/lib/active_record/version.rb +3 -3
  109. data/lib/active_record.rb +38 -3
  110. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  111. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  112. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  113. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  114. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  115. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  116. metadata +49 -28
  117. data/lib/active_record/named_scope.rb +0 -200
@@ -1,9 +1,6 @@
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
- require 'set'
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
5
2
  require 'active_record/connection_adapters/statement_pool'
6
- require 'arel/visitors/bind_visitor'
3
+ require 'active_support/core_ext/hash/keys'
7
4
 
8
5
  gem 'mysql', '~> 2.8'
9
6
  require 'mysql'
@@ -43,9 +40,29 @@ module ActiveRecord
43
40
  end
44
41
 
45
42
  module ConnectionAdapters
46
- class MysqlColumn < Column #:nodoc:
47
- class << self
48
- def string_to_time(value)
43
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
44
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
45
+ #
46
+ # Options:
47
+ #
48
+ # * <tt>:host</tt> - Defaults to "localhost".
49
+ # * <tt>:port</tt> - Defaults to 3306.
50
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
51
+ # * <tt>:username</tt> - Defaults to "root"
52
+ # * <tt>:password</tt> - Defaults to nothing.
53
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
54
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
55
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
56
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
57
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
58
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
59
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
60
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
61
+ #
62
+ class MysqlAdapter < AbstractMysqlAdapter
63
+
64
+ class Column < AbstractMysqlAdapter::Column #:nodoc:
65
+ def self.string_to_time(value)
49
66
  return super unless Mysql::Time === value
50
67
  new_time(
51
68
  value.year,
@@ -57,135 +74,23 @@ module ActiveRecord
57
74
  value.second_part)
58
75
  end
59
76
 
60
- def string_to_dummy_time(v)
77
+ def self.string_to_dummy_time(v)
61
78
  return super unless Mysql::Time === v
62
79
  new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
63
80
  end
64
81
 
65
- def string_to_date(v)
82
+ def self.string_to_date(v)
66
83
  return super unless Mysql::Time === v
67
84
  new_date(v.year, v.month, v.day)
68
85
  end
69
- end
70
86
 
71
- def extract_default(default)
72
- if sql_type =~ /blob/i || type == :text
73
- if default.blank?
74
- return null ? nil : ''
75
- else
76
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
77
- end
78
- elsif missing_default_forged_as_empty_string?(default)
79
- nil
80
- else
81
- super
87
+ def adapter
88
+ MysqlAdapter
82
89
  end
83
90
  end
84
91
 
85
- def has_default?
86
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
87
- super
88
- end
89
-
90
- private
91
- def simplified_type(field_type)
92
- return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
93
- return :string if field_type =~ /enum/i
94
- super
95
- end
96
-
97
- def extract_limit(sql_type)
98
- case sql_type
99
- when /blob|text/i
100
- case sql_type
101
- when /tiny/i
102
- 255
103
- when /medium/i
104
- 16777215
105
- when /long/i
106
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
107
- else
108
- super # we could return 65535 here, but we leave it undecorated by default
109
- end
110
- when /^bigint/i; 8
111
- when /^int/i; 4
112
- when /^mediumint/i; 3
113
- when /^smallint/i; 2
114
- when /^tinyint/i; 1
115
- else
116
- super
117
- end
118
- end
119
-
120
- # MySQL misreports NOT NULL column default when none is given.
121
- # We can't detect this for columns which may have a legitimate ''
122
- # default (string) but we can for others (integer, datetime, boolean,
123
- # and the rest).
124
- #
125
- # Test whether the column has default '', is not null, and is not
126
- # a type allowing default ''.
127
- def missing_default_forged_as_empty_string?(default)
128
- type != :string && !null && default == ''
129
- end
130
- end
131
-
132
- # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
133
- # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
134
- #
135
- # Options:
136
- #
137
- # * <tt>:host</tt> - Defaults to "localhost".
138
- # * <tt>:port</tt> - Defaults to 3306.
139
- # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
140
- # * <tt>:username</tt> - Defaults to "root"
141
- # * <tt>:password</tt> - Defaults to nothing.
142
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
143
- # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
144
- # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
145
- # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
146
- # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
147
- # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
148
- # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
149
- # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
150
- #
151
- class MysqlAdapter < AbstractAdapter
152
-
153
- ##
154
- # :singleton-method:
155
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
156
- # as boolean. If you wish to disable this emulation (which was the default
157
- # behavior in versions 0.13.1 and earlier) you can add the following line
158
- # to your application.rb file:
159
- #
160
- # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
161
- cattr_accessor :emulate_booleans
162
- self.emulate_booleans = true
163
-
164
92
  ADAPTER_NAME = 'MySQL'
165
93
 
166
- LOST_CONNECTION_ERROR_MESSAGES = [
167
- "Server shutdown in progress",
168
- "Broken pipe",
169
- "Lost connection to MySQL server during query",
170
- "MySQL server has gone away" ]
171
-
172
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
173
-
174
- NATIVE_DATABASE_TYPES = {
175
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
176
- :string => { :name => "varchar", :limit => 255 },
177
- :text => { :name => "text" },
178
- :integer => { :name => "int", :limit => 4 },
179
- :float => { :name => "float" },
180
- :decimal => { :name => "decimal" },
181
- :datetime => { :name => "datetime" },
182
- :timestamp => { :name => "datetime" },
183
- :time => { :name => "time" },
184
- :date => { :name => "date" },
185
- :binary => { :name => "blob" },
186
- :boolean => { :name => "tinyint", :limit => 1 }
187
- }
188
-
189
94
  class StatementPool < ConnectionAdapters::StatementPool
190
95
  def initialize(connection, max = 1000)
191
96
  super
@@ -219,114 +124,52 @@ module ActiveRecord
219
124
  end
220
125
 
221
126
  def initialize(connection, logger, connection_options, config)
222
- super(connection, logger)
223
- @connection_options, @config = connection_options, config
224
- @quoted_column_names, @quoted_table_names = {}, {}
225
- @statements = {}
127
+ super
226
128
  @statements = StatementPool.new(@connection,
227
129
  config.fetch(:statement_limit) { 1000 })
228
130
  @client_encoding = nil
229
131
  connect
230
132
  end
231
133
 
232
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
233
- include Arel::Visitors::BindVisitor
234
- end
235
-
236
- def self.visitor_for(pool) # :nodoc:
237
- config = pool.spec.config
238
-
239
- if config.fetch(:prepared_statements) { true }
240
- Arel::Visitors::MySQL.new pool
241
- else
242
- BindSubstitution.new pool
243
- end
244
- end
245
-
246
- def adapter_name #:nodoc:
247
- ADAPTER_NAME
248
- end
249
-
250
- def supports_bulk_alter? #:nodoc:
251
- true
252
- end
253
-
254
134
  # Returns true, since this connection adapter supports prepared statement
255
135
  # caching.
256
136
  def supports_statement_cache?
257
137
  true
258
138
  end
259
139
 
260
- # Returns true, since this connection adapter supports migrations.
261
- def supports_migrations? #:nodoc:
262
- true
263
- end
140
+ # HELPER METHODS ===========================================
264
141
 
265
- # Returns true.
266
- def supports_primary_key? #:nodoc:
267
- true
142
+ def each_hash(result) # :nodoc:
143
+ if block_given?
144
+ result.each_hash do |row|
145
+ row.symbolize_keys!
146
+ yield row
147
+ end
148
+ else
149
+ to_enum(:each_hash, result)
150
+ end
268
151
  end
269
152
 
270
- # Returns true, since this connection adapter supports savepoints.
271
- def supports_savepoints? #:nodoc:
272
- true
153
+ def new_column(field, default, type, null, collation) # :nodoc:
154
+ Column.new(field, default, type, null, collation)
273
155
  end
274
156
 
275
- def native_database_types #:nodoc:
276
- NATIVE_DATABASE_TYPES
157
+ def error_number(exception) # :nodoc:
158
+ exception.errno if exception.respond_to?(:errno)
277
159
  end
278
160
 
279
-
280
161
  # QUOTING ==================================================
281
162
 
282
- def quote(value, column = nil)
283
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
284
- s = column.class.string_to_binary(value).unpack("H*")[0]
285
- "x'#{s}'"
286
- else
287
- super
288
- end
289
- end
290
-
291
163
  def type_cast(value, column)
292
164
  return super unless value == true || value == false
293
165
 
294
166
  value ? 1 : 0
295
167
  end
296
168
 
297
- def quote_column_name(name) #:nodoc:
298
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
299
- end
300
-
301
- def quote_table_name(name) #:nodoc:
302
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
303
- end
304
-
305
169
  def quote_string(string) #:nodoc:
306
170
  @connection.quote(string)
307
171
  end
308
172
 
309
- def quoted_true
310
- QUOTED_TRUE
311
- end
312
-
313
- def quoted_false
314
- QUOTED_FALSE
315
- end
316
-
317
- # REFERENTIAL INTEGRITY ====================================
318
-
319
- def disable_referential_integrity #:nodoc:
320
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
321
-
322
- begin
323
- update("SET FOREIGN_KEY_CHECKS = 0")
324
- yield
325
- ensure
326
- update("SET FOREIGN_KEY_CHECKS = #{old}")
327
- end
328
- end
329
-
330
173
  # CONNECTION MANAGEMENT ====================================
331
174
 
332
175
  def active?
@@ -371,7 +214,7 @@ module ActiveRecord
371
214
 
372
215
  def select_rows(sql, name = nil)
373
216
  @connection.query_with_result = true
374
- rows = exec_without_stmt(sql, name).rows
217
+ rows = exec_query(sql, name).rows
375
218
  @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
376
219
  rows
377
220
  end
@@ -439,11 +282,19 @@ module ActiveRecord
439
282
  end
440
283
 
441
284
  def exec_query(sql, name = 'SQL', binds = [])
442
- log(sql, name, binds) do
443
- exec_stmt(sql, name, binds) do |cols, stmt|
444
- ActiveRecord::Result.new(cols, stmt.to_a) if cols
445
- end
285
+ # If the configuration sets prepared_statements:false, binds will
286
+ # always be empty, since the bind variables will have been already
287
+ # substituted and removed from binds by BindVisitor, so this will
288
+ # effectively disable prepared statement usage completely.
289
+ if binds.empty?
290
+ result_set, affected_rows = exec_without_stmt(sql, name)
291
+ else
292
+ result_set, affected_rows = exec_stmt(sql, name, binds)
446
293
  end
294
+
295
+ yield affected_rows if block_given?
296
+
297
+ result_set
447
298
  end
448
299
 
449
300
  def last_inserted_id(result)
@@ -452,35 +303,28 @@ module ActiveRecord
452
303
 
453
304
  def exec_without_stmt(sql, name = 'SQL') # :nodoc:
454
305
  # Some queries, like SHOW CREATE TABLE don't work through the prepared
455
- # statement API. For those queries, we need to use this method. :'(
306
+ # statement API. For those queries, we need to use this method. :'(
456
307
  log(sql, name) do
457
308
  result = @connection.query(sql)
458
- cols = []
459
- rows = []
309
+ affected_rows = @connection.affected_rows
460
310
 
461
311
  if result
462
312
  cols = result.fetch_fields.map { |field| field.name }
463
- rows = result.to_a
313
+ result_set = ActiveRecord::Result.new(cols, result.to_a)
464
314
  result.free
315
+ else
316
+ result_set = ActiveRecord::Result.new([], [])
465
317
  end
466
- ActiveRecord::Result.new(cols, rows)
318
+
319
+ [result_set, affected_rows]
467
320
  end
468
321
  end
469
322
 
470
- # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
471
- # the Result object after you're done using it.
472
- def execute(sql, name = nil) #:nodoc:
473
- if name == :skip_logging
474
- @connection.query(sql)
475
- else
476
- log(sql, name) { @connection.query(sql) }
477
- end
478
- rescue ActiveRecord::StatementInvalid => exception
479
- if exception.message.split(":").first =~ /Packets out of order/
480
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
481
- else
482
- raise
483
- end
323
+ def execute_and_free(sql, name = nil)
324
+ result = execute(sql, name)
325
+ ret = yield result
326
+ result.free
327
+ ret
484
328
  end
485
329
 
486
330
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
@@ -489,510 +333,109 @@ module ActiveRecord
489
333
  end
490
334
  alias :create :insert_sql
491
335
 
492
- def update_sql(sql, name = nil) #:nodoc:
493
- super
494
- @connection.affected_rows
495
- end
496
-
497
336
  def exec_delete(sql, name, binds)
498
- log(sql, name, binds) do
499
- exec_stmt(sql, name, binds) do |cols, stmt|
500
- stmt.affected_rows
501
- end
337
+ affected_rows = 0
338
+
339
+ exec_query(sql, name, binds) do |n|
340
+ affected_rows = n
502
341
  end
342
+
343
+ affected_rows
503
344
  end
504
345
  alias :exec_update :exec_delete
505
346
 
506
347
  def begin_db_transaction #:nodoc:
507
- exec_without_stmt "BEGIN"
348
+ exec_query "BEGIN"
508
349
  rescue Mysql::Error
509
350
  # Transactions aren't supported
510
351
  end
511
352
 
512
- def commit_db_transaction #:nodoc:
513
- execute "COMMIT"
514
- rescue Exception
515
- # Transactions aren't supported
516
- end
517
-
518
- def rollback_db_transaction #:nodoc:
519
- execute "ROLLBACK"
520
- rescue Exception
521
- # Transactions aren't supported
522
- end
523
-
524
- def create_savepoint
525
- execute("SAVEPOINT #{current_savepoint_name}")
526
- end
527
-
528
- def rollback_to_savepoint
529
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
530
- end
531
-
532
- def release_savepoint
533
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
534
- end
535
-
536
- def add_limit_offset!(sql, options) #:nodoc:
537
- limit, offset = options[:limit], options[:offset]
538
- if limit && offset
539
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
540
- elsif limit
541
- sql << " LIMIT #{sanitize_limit(limit)}"
542
- elsif offset
543
- sql << " OFFSET #{offset.to_i}"
544
- end
545
- sql
546
- end
547
- deprecate :add_limit_offset!
548
-
549
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
550
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
551
- # these, we must use a subquery. However, MySQL is too stupid to create a
552
- # temporary table for this automatically, so we have to give it some prompting
553
- # in the form of a subsubquery. Ugh!
554
- def join_to_update(update, select) #:nodoc:
555
- if select.limit || select.offset || select.orders.any?
556
- subsubselect = select.clone
557
- subsubselect.projections = [update.key]
558
-
559
- subselect = Arel::SelectManager.new(select.engine)
560
- subselect.project Arel.sql(update.key.name)
561
- subselect.from subsubselect.as('__active_record_temp')
562
-
563
- update.where update.key.in(subselect)
564
- else
565
- update.table select.source
566
- update.wheres = select.constraints
567
- end
568
- end
569
-
570
- # SCHEMA STATEMENTS ========================================
571
-
572
- def structure_dump #:nodoc:
573
- if supports_views?
574
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
575
- else
576
- sql = "SHOW TABLES"
577
- end
578
-
579
- select_all(sql).map do |table|
580
- table.delete('Table_type')
581
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
582
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
583
- end.join("")
584
- end
585
-
586
- # Drops the database specified on the +name+ attribute
587
- # and creates it again using the provided +options+.
588
- def recreate_database(name, options = {}) #:nodoc:
589
- drop_database(name)
590
- create_database(name, options)
591
- end
592
-
593
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
594
- # Charset defaults to utf8.
595
- #
596
- # Example:
597
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
598
- # create_database 'matt_development'
599
- # create_database 'matt_development', :charset => :big5
600
- def create_database(name, options = {})
601
- if options[:collation]
602
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
603
- else
604
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
605
- end
606
- end
607
-
608
- # Drops a MySQL database.
609
- #
610
- # Example:
611
- # drop_database 'sebastian_development'
612
- def drop_database(name) #:nodoc:
613
- execute "DROP DATABASE IF EXISTS `#{name}`"
614
- end
615
-
616
- def current_database
617
- select_value 'SELECT DATABASE() as db'
618
- end
619
-
620
- # Returns the database character set.
621
- def charset
622
- show_variable 'character_set_database'
623
- end
624
-
625
- # Returns the database collation strategy.
626
- def collation
627
- show_variable 'collation_database'
628
- end
629
-
630
- def tables(name = nil, database = nil) #:nodoc:
631
- sql = "SHOW TABLES "
632
- sql << "IN #{quote_table_name(database)} " if database
633
-
634
- result = execute(sql, 'SCHEMA')
635
- tables = result.collect { |field| field[0] }
636
- result.free
637
- tables
638
- end
639
-
640
- def table_exists?(name)
641
- return true if super
642
-
643
- name = name.to_s
644
- schema, table = name.split('.', 2)
645
-
646
- unless table # A table was provided without a schema
647
- table = schema
648
- schema = nil
649
- end
650
-
651
- tables(nil, schema).include? table
652
- end
653
-
654
- def drop_table(table_name, options = {})
655
- super(table_name, options)
656
- end
657
-
658
- # Returns an array of indexes for the given table.
659
- def indexes(table_name, name = nil)#:nodoc:
660
- indexes = []
661
- current_index = nil
662
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
663
- result.each do |row|
664
- if current_index != row[2]
665
- next if row[2] == "PRIMARY" # skip the primary key
666
- current_index = row[2]
667
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
668
- end
669
-
670
- indexes.last.columns << row[4]
671
- indexes.last.lengths << row[7]
672
- end
673
- result.free
674
- indexes
675
- end
676
-
677
- # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
678
- def columns(table_name, name = nil)#:nodoc:
679
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
680
- result = execute(sql, 'SCHEMA')
681
- columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
682
- result.free
683
- columns
684
- end
685
-
686
- def create_table(table_name, options = {}) #:nodoc:
687
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
688
- end
689
-
690
- # Renames a table.
691
- #
692
- # Example:
693
- # rename_table('octopuses', 'octopi')
694
- def rename_table(table_name, new_name)
695
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
696
- end
697
-
698
- def bulk_change_table(table_name, operations) #:nodoc:
699
- sqls = operations.map do |command, args|
700
- table, arguments = args.shift, args
701
- method = :"#{command}_sql"
353
+ private
702
354
 
703
- if respond_to?(method, true)
704
- send(method, table, *arguments)
355
+ def exec_stmt(sql, name, binds)
356
+ cache = {}
357
+ log(sql, name, binds) do
358
+ if binds.empty?
359
+ stmt = @connection.prepare(sql)
705
360
  else
706
- raise "Unknown method called : #{method}(#{arguments.inspect})"
361
+ cache = @statements[sql] ||= {
362
+ :stmt => @connection.prepare(sql)
363
+ }
364
+ stmt = cache[:stmt]
707
365
  end
708
- end.flatten.join(", ")
709
366
 
710
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
711
- end
367
+ begin
368
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
369
+ rescue Mysql::Error => e
370
+ # Older versions of MySQL leave the prepared statement in a bad
371
+ # place when an error occurs. To support older mysql versions, we
372
+ # need to close the statement and delete the statement from the
373
+ # cache.
374
+ stmt.close
375
+ @statements.delete sql
376
+ raise e
377
+ end
712
378
 
713
- def add_column(table_name, column_name, type, options = {})
714
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
715
- end
379
+ cols = nil
380
+ if metadata = stmt.result_metadata
381
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
382
+ field.name
383
+ }
384
+ end
716
385
 
717
- def change_column_default(table_name, column_name, default) #:nodoc:
718
- column = column_for(table_name, column_name)
719
- change_column table_name, column_name, column.sql_type, :default => default
720
- end
386
+ result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
387
+ affected_rows = stmt.affected_rows
721
388
 
722
- def change_column_null(table_name, column_name, null, default = nil)
723
- column = column_for(table_name, column_name)
389
+ stmt.result_metadata.free if cols
390
+ stmt.free_result
391
+ stmt.close if binds.empty?
724
392
 
725
- unless null || default.nil?
726
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
393
+ [result_set, affected_rows]
727
394
  end
728
-
729
- change_column table_name, column_name, column.sql_type, :null => null
730
- end
731
-
732
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
733
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
734
395
  end
735
396
 
736
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
737
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
738
- end
739
-
740
- # Maps logical Rails types to MySQL-specific data types.
741
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
742
- case type.to_s
743
- when 'integer'
744
- case limit
745
- when 1; 'tinyint'
746
- when 2; 'smallint'
747
- when 3; 'mediumint'
748
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
749
- when 5..8; 'bigint'
750
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
751
- end
752
- when 'text'
753
- case limit
754
- when 0..0xff; 'tinytext'
755
- when nil, 0x100..0xffff; 'text'
756
- when 0x10000..0xffffff; 'mediumtext'
757
- when 0x1000000..0xffffffff; 'longtext'
758
- else raise(ActiveRecordError, "No text type has character length #{limit}")
759
- end
760
- else
761
- super
397
+ def connect
398
+ encoding = @config[:encoding]
399
+ if encoding
400
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
762
401
  end
763
- end
764
402
 
765
- def add_column_position!(sql, options)
766
- if options[:first]
767
- sql << " FIRST"
768
- elsif options[:after]
769
- sql << " AFTER #{quote_column_name(options[:after])}"
403
+ if @config[:sslca] || @config[:sslkey]
404
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
770
405
  end
771
- end
772
406
 
773
- # SHOW VARIABLES LIKE 'name'
774
- def show_variable(name)
775
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
776
- variables.first['Value'] unless variables.empty?
777
- end
407
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
408
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
409
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
778
410
 
779
- # Returns a table's primary key and belonging sequence.
780
- def pk_and_sequence_for(table) #:nodoc:
781
- result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
782
- create_table = result.fetch_hash["Create Table"]
783
- result.free
411
+ @connection.real_connect(*@connection_options)
784
412
 
785
- if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
786
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
787
- keys.length == 1 ? [keys.first, nil] : nil
788
- else
789
- nil
790
- end
791
- end
413
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
414
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
792
415
 
793
- # Returns just a table's primary key
794
- def primary_key(table)
795
- pk_and_sequence = pk_and_sequence_for(table)
796
- pk_and_sequence && pk_and_sequence.first
416
+ configure_connection
797
417
  end
798
418
 
799
- def case_sensitive_equality_operator
800
- "= BINARY"
801
- end
802
- deprecate :case_sensitive_equality_operator
419
+ def configure_connection
420
+ encoding = @config[:encoding]
421
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
803
422
 
804
- def case_sensitive_modifier(node)
805
- Arel::Nodes::Bin.new(node)
423
+ # By default, MySQL 'where id is null' selects the last inserted id.
424
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
425
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
806
426
  end
807
427
 
808
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
809
- where_sql
428
+ def select(sql, name = nil, binds = [])
429
+ @connection.query_with_result = true
430
+ rows = exec_query(sql, name, binds).to_a
431
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
432
+ rows
810
433
  end
811
434
 
812
- protected
813
- def quoted_columns_for_index(column_names, options = {})
814
- length = options[:length] if options.is_a?(Hash)
815
-
816
- case length
817
- when Hash
818
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
819
- when Fixnum
820
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
821
- else
822
- column_names.map {|name| quote_column_name(name) }
823
- end
824
- end
825
-
826
- def translate_exception(exception, message)
827
- return super unless exception.respond_to?(:errno)
828
-
829
- case exception.errno
830
- when 1062
831
- RecordNotUnique.new(message, exception)
832
- when 1452
833
- InvalidForeignKey.new(message, exception)
834
- else
835
- super
836
- end
837
- end
838
-
839
- def add_column_sql(table_name, column_name, type, options = {})
840
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
841
- add_column_options!(add_column_sql, options)
842
- add_column_position!(add_column_sql, options)
843
- add_column_sql
844
- end
845
-
846
- def remove_column_sql(table_name, *column_names)
847
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
848
- end
849
- alias :remove_columns_sql :remove_column
850
-
851
- def change_column_sql(table_name, column_name, type, options = {})
852
- column = column_for(table_name, column_name)
853
-
854
- unless options_include_default?(options)
855
- options[:default] = column.default
856
- end
857
-
858
- unless options.has_key?(:null)
859
- options[:null] = column.null
860
- end
861
-
862
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
863
- add_column_options!(change_column_sql, options)
864
- add_column_position!(change_column_sql, options)
865
- change_column_sql
866
- end
867
-
868
- def rename_column_sql(table_name, column_name, new_column_name)
869
- options = {}
870
-
871
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
872
- options[:default] = column.default
873
- options[:null] = column.null
874
- else
875
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
876
- end
877
-
878
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
879
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
880
- add_column_options!(rename_column_sql, options)
881
- rename_column_sql
882
- end
883
-
884
- def add_index_sql(table_name, column_name, options = {})
885
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
886
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
887
- end
888
-
889
- def remove_index_sql(table_name, options = {})
890
- index_name = index_name_for_remove(table_name, options)
891
- "DROP INDEX #{index_name}"
892
- end
893
-
894
- def add_timestamps_sql(table_name)
895
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
896
- end
897
-
898
- def remove_timestamps_sql(table_name)
899
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
900
- end
901
-
902
- private
903
- def exec_stmt(sql, name, binds)
904
- cache = {}
905
- if binds.empty?
906
- stmt = @connection.prepare(sql)
907
- else
908
- cache = @statements[sql] ||= {
909
- :stmt => @connection.prepare(sql)
910
- }
911
- stmt = cache[:stmt]
912
- end
913
-
914
-
915
- begin
916
- stmt.execute(*binds.map { |col, val| type_cast(val, col) })
917
- rescue Mysql::Error => e
918
- # Older versions of MySQL leave the prepared statement in a bad
919
- # place when an error occurs. To support older mysql versions, we
920
- # need to close the statement and delete the statement from the
921
- # cache.
922
- stmt.close
923
- @statements.delete sql
924
- raise e
925
- end
926
-
927
- cols = nil
928
- if metadata = stmt.result_metadata
929
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
930
- field.name
931
- }
932
- end
933
-
934
- result = yield [cols, stmt]
935
-
936
- stmt.result_metadata.free if cols
937
- stmt.free_result
938
- stmt.close if binds.empty?
939
-
940
- result
435
+ # Returns the version of the connected MySQL server.
436
+ def version
437
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
941
438
  end
942
-
943
- def connect
944
- encoding = @config[:encoding]
945
- if encoding
946
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
947
- end
948
-
949
- if @config[:sslca] || @config[:sslkey]
950
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
951
- end
952
-
953
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
954
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
955
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
956
-
957
- @connection.real_connect(*@connection_options)
958
-
959
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
960
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
961
-
962
- configure_connection
963
- end
964
-
965
- def configure_connection
966
- encoding = @config[:encoding]
967
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
968
-
969
- # By default, MySQL 'where id is null' selects the last inserted id.
970
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
971
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
972
- end
973
-
974
- def select(sql, name = nil, binds = [])
975
- @connection.query_with_result = true
976
- rows = exec_query(sql, name, binds).to_a
977
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
978
- rows
979
- end
980
-
981
- def supports_views?
982
- version[0] >= 5
983
- end
984
-
985
- # Returns the version of the connected MySQL server.
986
- def version
987
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
988
- end
989
-
990
- def column_for(table_name, column_name)
991
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
992
- raise "No such column: #{table_name}.#{column_name}"
993
- end
994
- column
995
- end
996
439
  end
997
440
  end
998
441
  end