activerecord 3.1.9 → 3.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +317 -336
  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 +3 -42
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +64 -31
  16. data/lib/active_record/associations/collection_proxy.rb +2 -37
  17. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  18. data/lib/active_record/associations/has_many_association.rb +5 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +28 -9
  20. data/lib/active_record/associations/has_one_association.rb +15 -13
  21. data/lib/active_record/associations/join_dependency.rb +2 -2
  22. data/lib/active_record/associations/preloader.rb +14 -10
  23. data/lib/active_record/associations/through_association.rb +7 -3
  24. data/lib/active_record/associations.rb +92 -76
  25. data/lib/active_record/attribute_assignment.rb +221 -0
  26. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  27. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  28. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  29. data/lib/active_record/attribute_methods/read.rb +73 -83
  30. data/lib/active_record/attribute_methods/serialization.rb +102 -0
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
  32. data/lib/active_record/attribute_methods/write.rb +31 -6
  33. data/lib/active_record/attribute_methods.rb +231 -30
  34. data/lib/active_record/autosave_association.rb +43 -22
  35. data/lib/active_record/base.rb +227 -1708
  36. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  37. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  38. data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
  39. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  40. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
  45. data/lib/active_record/connection_adapters/column.rb +37 -11
  46. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
  47. data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
  49. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  50. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  51. data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
  52. data/lib/active_record/counter_cache.rb +9 -4
  53. data/lib/active_record/dynamic_finder_match.rb +12 -0
  54. data/lib/active_record/dynamic_matchers.rb +84 -0
  55. data/lib/active_record/errors.rb +11 -1
  56. data/lib/active_record/explain.rb +85 -0
  57. data/lib/active_record/explain_subscriber.rb +25 -0
  58. data/lib/active_record/fixtures/file.rb +65 -0
  59. data/lib/active_record/fixtures.rb +56 -85
  60. data/lib/active_record/identity_map.rb +3 -4
  61. data/lib/active_record/inheritance.rb +174 -0
  62. data/lib/active_record/integration.rb +49 -0
  63. data/lib/active_record/locking/optimistic.rb +30 -25
  64. data/lib/active_record/locking/pessimistic.rb +23 -1
  65. data/lib/active_record/log_subscriber.rb +3 -3
  66. data/lib/active_record/migration/command_recorder.rb +8 -8
  67. data/lib/active_record/migration.rb +68 -35
  68. data/lib/active_record/model_schema.rb +366 -0
  69. data/lib/active_record/nested_attributes.rb +3 -2
  70. data/lib/active_record/persistence.rb +57 -11
  71. data/lib/active_record/querying.rb +58 -0
  72. data/lib/active_record/railtie.rb +31 -29
  73. data/lib/active_record/railties/controller_runtime.rb +3 -1
  74. data/lib/active_record/railties/databases.rake +191 -110
  75. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  76. data/lib/active_record/readonly_attributes.rb +26 -0
  77. data/lib/active_record/reflection.rb +7 -15
  78. data/lib/active_record/relation/batches.rb +5 -2
  79. data/lib/active_record/relation/calculations.rb +47 -15
  80. data/lib/active_record/relation/delegation.rb +49 -0
  81. data/lib/active_record/relation/finder_methods.rb +9 -7
  82. data/lib/active_record/relation/predicate_builder.rb +18 -7
  83. data/lib/active_record/relation/query_methods.rb +75 -9
  84. data/lib/active_record/relation/spawn_methods.rb +11 -2
  85. data/lib/active_record/relation.rb +78 -32
  86. data/lib/active_record/result.rb +1 -1
  87. data/lib/active_record/sanitization.rb +194 -0
  88. data/lib/active_record/schema_dumper.rb +12 -5
  89. data/lib/active_record/scoping/default.rb +142 -0
  90. data/lib/active_record/scoping/named.rb +202 -0
  91. data/lib/active_record/scoping.rb +152 -0
  92. data/lib/active_record/serialization.rb +1 -43
  93. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  94. data/lib/active_record/session_store.rb +17 -15
  95. data/lib/active_record/store.rb +52 -0
  96. data/lib/active_record/test_case.rb +11 -7
  97. data/lib/active_record/timestamp.rb +17 -3
  98. data/lib/active_record/transactions.rb +27 -6
  99. data/lib/active_record/translation.rb +22 -0
  100. data/lib/active_record/validations/associated.rb +5 -4
  101. data/lib/active_record/validations/uniqueness.rb +7 -7
  102. data/lib/active_record/validations.rb +1 -1
  103. data/lib/active_record/version.rb +2 -2
  104. data/lib/active_record.rb +38 -3
  105. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  106. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  107. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  108. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  109. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  110. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  111. metadata +30 -10
  112. data/lib/active_record/named_scope.rb +0 -200
@@ -1,11 +1,8 @@
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
- gem 'mysql', '~> 2.8'
5
+ gem 'mysql', '~> 2.8.1'
9
6
  require 'mysql'
10
7
 
11
8
  class 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,116 +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
- elsif value.kind_of?(BigDecimal)
287
- value.to_s("F")
288
- else
289
- super
290
- end
291
- end
292
-
293
163
  def type_cast(value, column)
294
164
  return super unless value == true || value == false
295
165
 
296
166
  value ? 1 : 0
297
167
  end
298
168
 
299
- def quote_column_name(name) #:nodoc:
300
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
301
- end
302
-
303
- def quote_table_name(name) #:nodoc:
304
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
305
- end
306
-
307
169
  def quote_string(string) #:nodoc:
308
170
  @connection.quote(string)
309
171
  end
310
172
 
311
- def quoted_true
312
- QUOTED_TRUE
313
- end
314
-
315
- def quoted_false
316
- QUOTED_FALSE
317
- end
318
-
319
- # REFERENTIAL INTEGRITY ====================================
320
-
321
- def disable_referential_integrity #:nodoc:
322
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
323
-
324
- begin
325
- update("SET FOREIGN_KEY_CHECKS = 0")
326
- yield
327
- ensure
328
- update("SET FOREIGN_KEY_CHECKS = #{old}")
329
- end
330
- end
331
-
332
173
  # CONNECTION MANAGEMENT ====================================
333
174
 
334
175
  def active?
@@ -373,7 +214,7 @@ module ActiveRecord
373
214
 
374
215
  def select_rows(sql, name = nil)
375
216
  @connection.query_with_result = true
376
- rows = exec_without_stmt(sql, name).rows
217
+ rows = exec_query(sql, name).rows
377
218
  @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
378
219
  rows
379
220
  end
@@ -441,11 +282,19 @@ module ActiveRecord
441
282
  end
442
283
 
443
284
  def exec_query(sql, name = 'SQL', binds = [])
444
- log(sql, name, binds) do
445
- exec_stmt(sql, name, binds) do |cols, stmt|
446
- ActiveRecord::Result.new(cols, stmt.to_a) if cols
447
- 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)
448
293
  end
294
+
295
+ yield affected_rows if block_given?
296
+
297
+ result_set
449
298
  end
450
299
 
451
300
  def last_inserted_id(result)
@@ -454,35 +303,28 @@ module ActiveRecord
454
303
 
455
304
  def exec_without_stmt(sql, name = 'SQL') # :nodoc:
456
305
  # Some queries, like SHOW CREATE TABLE don't work through the prepared
457
- # statement API. For those queries, we need to use this method. :'(
306
+ # statement API. For those queries, we need to use this method. :'(
458
307
  log(sql, name) do
459
308
  result = @connection.query(sql)
460
- cols = []
461
- rows = []
309
+ affected_rows = @connection.affected_rows
462
310
 
463
311
  if result
464
312
  cols = result.fetch_fields.map { |field| field.name }
465
- rows = result.to_a
313
+ result_set = ActiveRecord::Result.new(cols, result.to_a)
466
314
  result.free
315
+ else
316
+ result_set = ActiveRecord::Result.new([], [])
467
317
  end
468
- ActiveRecord::Result.new(cols, rows)
318
+
319
+ [result_set, affected_rows]
469
320
  end
470
321
  end
471
322
 
472
- # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
473
- # the Result object after you're done using it.
474
- def execute(sql, name = nil) #:nodoc:
475
- if name == :skip_logging
476
- @connection.query(sql)
477
- else
478
- log(sql, name) { @connection.query(sql) }
479
- end
480
- rescue ActiveRecord::StatementInvalid => exception
481
- if exception.message.split(":").first =~ /Packets out of order/
482
- 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."
483
- else
484
- raise
485
- end
323
+ def execute_and_free(sql, name = nil)
324
+ result = execute(sql, name)
325
+ ret = yield result
326
+ result.free
327
+ ret
486
328
  end
487
329
 
488
330
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
@@ -491,510 +333,109 @@ module ActiveRecord
491
333
  end
492
334
  alias :create :insert_sql
493
335
 
494
- def update_sql(sql, name = nil) #:nodoc:
495
- super
496
- @connection.affected_rows
497
- end
498
-
499
336
  def exec_delete(sql, name, binds)
500
- log(sql, name, binds) do
501
- exec_stmt(sql, name, binds) do |cols, stmt|
502
- stmt.affected_rows
503
- end
337
+ affected_rows = 0
338
+
339
+ exec_query(sql, name, binds) do |n|
340
+ affected_rows = n
504
341
  end
342
+
343
+ affected_rows
505
344
  end
506
345
  alias :exec_update :exec_delete
507
346
 
508
347
  def begin_db_transaction #:nodoc:
509
- exec_without_stmt "BEGIN"
348
+ exec_query "BEGIN"
510
349
  rescue Mysql::Error
511
350
  # Transactions aren't supported
512
351
  end
513
352
 
514
- def commit_db_transaction #:nodoc:
515
- execute "COMMIT"
516
- rescue Exception
517
- # Transactions aren't supported
518
- end
519
-
520
- def rollback_db_transaction #:nodoc:
521
- execute "ROLLBACK"
522
- rescue Exception
523
- # Transactions aren't supported
524
- end
525
-
526
- def create_savepoint
527
- execute("SAVEPOINT #{current_savepoint_name}")
528
- end
529
-
530
- def rollback_to_savepoint
531
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
532
- end
533
-
534
- def release_savepoint
535
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
536
- end
537
-
538
- def add_limit_offset!(sql, options) #:nodoc:
539
- limit, offset = options[:limit], options[:offset]
540
- if limit && offset
541
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
542
- elsif limit
543
- sql << " LIMIT #{sanitize_limit(limit)}"
544
- elsif offset
545
- sql << " OFFSET #{offset.to_i}"
546
- end
547
- sql
548
- end
549
- deprecate :add_limit_offset!
550
-
551
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
552
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
553
- # these, we must use a subquery. However, MySQL is too stupid to create a
554
- # temporary table for this automatically, so we have to give it some prompting
555
- # in the form of a subsubquery. Ugh!
556
- def join_to_update(update, select) #:nodoc:
557
- if select.limit || select.offset || select.orders.any?
558
- subsubselect = select.clone
559
- subsubselect.projections = [update.key]
560
-
561
- subselect = Arel::SelectManager.new(select.engine)
562
- subselect.project Arel.sql(update.key.name)
563
- subselect.from subsubselect.as('__active_record_temp')
564
-
565
- update.where update.key.in(subselect)
566
- else
567
- update.table select.source
568
- update.wheres = select.constraints
569
- end
570
- end
571
-
572
- # SCHEMA STATEMENTS ========================================
573
-
574
- def structure_dump #:nodoc:
575
- if supports_views?
576
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
577
- else
578
- sql = "SHOW TABLES"
579
- end
580
-
581
- select_all(sql).map do |table|
582
- table.delete('Table_type')
583
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
584
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
585
- end.join("")
586
- end
587
-
588
- # Drops the database specified on the +name+ attribute
589
- # and creates it again using the provided +options+.
590
- def recreate_database(name, options = {}) #:nodoc:
591
- drop_database(name)
592
- create_database(name, options)
593
- end
594
-
595
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
596
- # Charset defaults to utf8.
597
- #
598
- # Example:
599
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
600
- # create_database 'matt_development'
601
- # create_database 'matt_development', :charset => :big5
602
- def create_database(name, options = {})
603
- if options[:collation]
604
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
605
- else
606
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
607
- end
608
- end
609
-
610
- # Drops a MySQL database.
611
- #
612
- # Example:
613
- # drop_database 'sebastian_development'
614
- def drop_database(name) #:nodoc:
615
- execute "DROP DATABASE IF EXISTS `#{name}`"
616
- end
617
-
618
- def current_database
619
- select_value 'SELECT DATABASE() as db'
620
- end
621
-
622
- # Returns the database character set.
623
- def charset
624
- show_variable 'character_set_database'
625
- end
626
-
627
- # Returns the database collation strategy.
628
- def collation
629
- show_variable 'collation_database'
630
- end
631
-
632
- def tables(name = nil, database = nil) #:nodoc:
633
- sql = "SHOW TABLES "
634
- sql << "IN #{quote_table_name(database)} " if database
635
-
636
- result = execute(sql, 'SCHEMA')
637
- tables = result.collect { |field| field[0] }
638
- result.free
639
- tables
640
- end
641
-
642
- def table_exists?(name)
643
- return true if super
644
-
645
- name = name.to_s
646
- schema, table = name.split('.', 2)
647
-
648
- unless table # A table was provided without a schema
649
- table = schema
650
- schema = nil
651
- end
652
-
653
- tables(nil, schema).include? table
654
- end
655
-
656
- def drop_table(table_name, options = {})
657
- super(table_name, options)
658
- end
659
-
660
- # Returns an array of indexes for the given table.
661
- def indexes(table_name, name = nil)#:nodoc:
662
- indexes = []
663
- current_index = nil
664
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
665
- result.each do |row|
666
- if current_index != row[2]
667
- next if row[2] == "PRIMARY" # skip the primary key
668
- current_index = row[2]
669
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
670
- end
671
-
672
- indexes.last.columns << row[4]
673
- indexes.last.lengths << row[7]
674
- end
675
- result.free
676
- indexes
677
- end
678
-
679
- # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
680
- def columns(table_name, name = nil)#:nodoc:
681
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
682
- result = execute(sql, 'SCHEMA')
683
- columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
684
- result.free
685
- columns
686
- end
687
-
688
- def create_table(table_name, options = {}) #:nodoc:
689
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
690
- end
691
-
692
- # Renames a table.
693
- #
694
- # Example:
695
- # rename_table('octopuses', 'octopi')
696
- def rename_table(table_name, new_name)
697
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
698
- end
699
-
700
- def bulk_change_table(table_name, operations) #:nodoc:
701
- sqls = operations.map do |command, args|
702
- table, arguments = args.shift, args
703
- method = :"#{command}_sql"
353
+ private
704
354
 
705
- if respond_to?(method, true)
706
- 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)
707
360
  else
708
- raise "Unknown method called : #{method}(#{arguments.inspect})"
361
+ cache = @statements[sql] ||= {
362
+ :stmt => @connection.prepare(sql)
363
+ }
364
+ stmt = cache[:stmt]
709
365
  end
710
- end.flatten.join(", ")
711
366
 
712
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
713
- 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
714
378
 
715
- def add_column(table_name, column_name, type, options = {})
716
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
717
- 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
718
385
 
719
- def change_column_default(table_name, column_name, default) #:nodoc:
720
- column = column_for(table_name, column_name)
721
- change_column table_name, column_name, column.sql_type, :default => default
722
- end
386
+ result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
387
+ affected_rows = stmt.affected_rows
723
388
 
724
- def change_column_null(table_name, column_name, null, default = nil)
725
- column = column_for(table_name, column_name)
389
+ stmt.result_metadata.free if cols
390
+ stmt.free_result
391
+ stmt.close if binds.empty?
726
392
 
727
- unless null || default.nil?
728
- 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]
729
394
  end
730
-
731
- change_column table_name, column_name, column.sql_type, :null => null
732
- end
733
-
734
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
735
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
736
395
  end
737
396
 
738
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
739
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
740
- end
741
-
742
- # Maps logical Rails types to MySQL-specific data types.
743
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
744
- case type.to_s
745
- when 'integer'
746
- case limit
747
- when 1; 'tinyint'
748
- when 2; 'smallint'
749
- when 3; 'mediumint'
750
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
751
- when 5..8; 'bigint'
752
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
753
- end
754
- when 'text'
755
- case limit
756
- when 0..0xff; 'tinytext'
757
- when nil, 0x100..0xffff; 'text'
758
- when 0x10000..0xffffff; 'mediumtext'
759
- when 0x1000000..0xffffffff; 'longtext'
760
- else raise(ActiveRecordError, "No text type has character length #{limit}")
761
- end
762
- else
763
- super
397
+ def connect
398
+ encoding = @config[:encoding]
399
+ if encoding
400
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
764
401
  end
765
- end
766
402
 
767
- def add_column_position!(sql, options)
768
- if options[:first]
769
- sql << " FIRST"
770
- elsif options[:after]
771
- 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])
772
405
  end
773
- end
774
406
 
775
- # SHOW VARIABLES LIKE 'name'
776
- def show_variable(name)
777
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
778
- variables.first['Value'] unless variables.empty?
779
- 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]
780
410
 
781
- # Returns a table's primary key and belonging sequence.
782
- def pk_and_sequence_for(table) #:nodoc:
783
- result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
784
- create_table = result.fetch_hash["Create Table"]
785
- result.free
411
+ @connection.real_connect(*@connection_options)
786
412
 
787
- if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
788
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
789
- keys.length == 1 ? [keys.first, nil] : nil
790
- else
791
- nil
792
- end
793
- 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=)
794
415
 
795
- # Returns just a table's primary key
796
- def primary_key(table)
797
- pk_and_sequence = pk_and_sequence_for(table)
798
- pk_and_sequence && pk_and_sequence.first
416
+ configure_connection
799
417
  end
800
418
 
801
- def case_sensitive_equality_operator
802
- "= BINARY"
803
- end
804
- deprecate :case_sensitive_equality_operator
419
+ def configure_connection
420
+ encoding = @config[:encoding]
421
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
805
422
 
806
- def case_sensitive_modifier(node)
807
- 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)
808
426
  end
809
427
 
810
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
811
- 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
812
433
  end
813
434
 
814
- protected
815
- def quoted_columns_for_index(column_names, options = {})
816
- length = options[:length] if options.is_a?(Hash)
817
-
818
- case length
819
- when Hash
820
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
821
- when Fixnum
822
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
823
- else
824
- column_names.map {|name| quote_column_name(name) }
825
- end
826
- end
827
-
828
- def translate_exception(exception, message)
829
- return super unless exception.respond_to?(:errno)
830
-
831
- case exception.errno
832
- when 1062
833
- RecordNotUnique.new(message, exception)
834
- when 1452
835
- InvalidForeignKey.new(message, exception)
836
- else
837
- super
838
- end
839
- end
840
-
841
- def add_column_sql(table_name, column_name, type, options = {})
842
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
843
- add_column_options!(add_column_sql, options)
844
- add_column_position!(add_column_sql, options)
845
- add_column_sql
846
- end
847
-
848
- def remove_column_sql(table_name, *column_names)
849
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
850
- end
851
- alias :remove_columns_sql :remove_column
852
-
853
- def change_column_sql(table_name, column_name, type, options = {})
854
- column = column_for(table_name, column_name)
855
-
856
- unless options_include_default?(options)
857
- options[:default] = column.default
858
- end
859
-
860
- unless options.has_key?(:null)
861
- options[:null] = column.null
862
- end
863
-
864
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
865
- add_column_options!(change_column_sql, options)
866
- add_column_position!(change_column_sql, options)
867
- change_column_sql
868
- end
869
-
870
- def rename_column_sql(table_name, column_name, new_column_name)
871
- options = {}
872
-
873
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
874
- options[:default] = column.default
875
- options[:null] = column.null
876
- else
877
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
878
- end
879
-
880
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
881
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
882
- add_column_options!(rename_column_sql, options)
883
- rename_column_sql
884
- end
885
-
886
- def add_index_sql(table_name, column_name, options = {})
887
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
888
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
889
- end
890
-
891
- def remove_index_sql(table_name, options = {})
892
- index_name = index_name_for_remove(table_name, options)
893
- "DROP INDEX #{index_name}"
894
- end
895
-
896
- def add_timestamps_sql(table_name)
897
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
898
- end
899
-
900
- def remove_timestamps_sql(table_name)
901
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
902
- end
903
-
904
- private
905
- def exec_stmt(sql, name, binds)
906
- cache = {}
907
- if binds.empty?
908
- stmt = @connection.prepare(sql)
909
- else
910
- cache = @statements[sql] ||= {
911
- :stmt => @connection.prepare(sql)
912
- }
913
- stmt = cache[:stmt]
914
- end
915
-
916
-
917
- begin
918
- stmt.execute(*binds.map { |col, val| type_cast(val, col) })
919
- rescue Mysql::Error => e
920
- # Older versions of MySQL leave the prepared statement in a bad
921
- # place when an error occurs. To support older mysql versions, we
922
- # need to close the statement and delete the statement from the
923
- # cache.
924
- stmt.close
925
- @statements.delete sql
926
- raise e
927
- end
928
-
929
- cols = nil
930
- if metadata = stmt.result_metadata
931
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
932
- field.name
933
- }
934
- end
935
-
936
- result = yield [cols, stmt]
937
-
938
- stmt.result_metadata.free if cols
939
- stmt.free_result
940
- stmt.close if binds.empty?
941
-
942
- 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 }
943
438
  end
944
-
945
- def connect
946
- encoding = @config[:encoding]
947
- if encoding
948
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
949
- end
950
-
951
- if @config[:sslca] || @config[:sslkey]
952
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
953
- end
954
-
955
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
956
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
957
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
958
-
959
- @connection.real_connect(*@connection_options)
960
-
961
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
962
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
963
-
964
- configure_connection
965
- end
966
-
967
- def configure_connection
968
- encoding = @config[:encoding]
969
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
970
-
971
- # By default, MySQL 'where id is null' selects the last inserted id.
972
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
973
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
974
- end
975
-
976
- def select(sql, name = nil, binds = [])
977
- @connection.query_with_result = true
978
- rows = exec_query(sql, name, binds).to_a
979
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
980
- rows
981
- end
982
-
983
- def supports_views?
984
- version[0] >= 5
985
- end
986
-
987
- # Returns the version of the connected MySQL server.
988
- def version
989
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
990
- end
991
-
992
- def column_for(table_name, column_name)
993
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
994
- raise "No such column: #{table_name}.#{column_name}"
995
- end
996
- column
997
- end
998
439
  end
999
440
  end
1000
441
  end