activerecord 3.2.22.4 → 4.0.13

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,21 +1,43 @@
1
- require 'active_support/core_ext/object/blank'
2
1
  require 'arel/visitors/bind_visitor'
3
2
 
4
3
  module ActiveRecord
5
4
  module ConnectionAdapters
6
5
  class AbstractMysqlAdapter < AbstractAdapter
6
+ class SchemaCreation < AbstractAdapter::SchemaCreation
7
+ private
8
+
9
+ def visit_AddColumn(o)
10
+ add_column_position!(super, o)
11
+ end
12
+
13
+ def add_column_position!(sql, column)
14
+ if column.first
15
+ sql << " FIRST"
16
+ elsif column.after
17
+ sql << " AFTER #{quote_column_name(column.after)}"
18
+ end
19
+ sql
20
+ end
21
+ end
22
+
23
+ def schema_creation
24
+ SchemaCreation.new self
25
+ end
26
+
7
27
  class Column < ConnectionAdapters::Column # :nodoc:
8
- attr_reader :collation
28
+ attr_reader :collation, :strict, :extra
9
29
 
10
- def initialize(name, default, sql_type = nil, null = true, collation = nil)
11
- super(name, default, sql_type, null)
30
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
31
+ @strict = strict
12
32
  @collation = collation
33
+ @extra = extra
34
+ super(name, default, sql_type, null)
13
35
  end
14
36
 
15
37
  def extract_default(default)
16
- if sql_type =~ /blob/i || type == :text
38
+ if blob_or_text_column?
17
39
  if default.blank?
18
- return null ? nil : ''
40
+ null || strict ? nil : ''
19
41
  else
20
42
  raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
21
43
  end
@@ -27,10 +49,14 @@ module ActiveRecord
27
49
  end
28
50
 
29
51
  def has_default?
30
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
52
+ return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
31
53
  super
32
54
  end
33
55
 
56
+ def blob_or_text_column?
57
+ sql_type =~ /blob/i || type == :text
58
+ end
59
+
34
60
  # Must return the relevant concrete adapter
35
61
  def adapter
36
62
  raise NotImplementedError
@@ -56,6 +82,8 @@ module ActiveRecord
56
82
 
57
83
  def extract_limit(sql_type)
58
84
  case sql_type
85
+ when /^enum\((.+)\)/i
86
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
59
87
  when /blob|text/i
60
88
  case sql_type
61
89
  when /tiny/i
@@ -72,8 +100,6 @@ module ActiveRecord
72
100
  when /^mediumint/i; 3
73
101
  when /^smallint/i; 2
74
102
  when /^tinyint/i; 1
75
- when /^enum\((.+)\)/i
76
- $1.split(',').map{|enum| enum.strip.length - 2}.max
77
103
  else
78
104
  super
79
105
  end
@@ -125,6 +151,9 @@ module ActiveRecord
125
151
  :boolean => { :name => "tinyint", :limit => 1 }
126
152
  }
127
153
 
154
+ INDEX_TYPES = [:fulltext, :spatial]
155
+ INDEX_USINGS = [:btree, :hash]
156
+
128
157
  class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
129
158
  include Arel::Visitors::BindVisitor
130
159
  end
@@ -135,10 +164,11 @@ module ActiveRecord
135
164
  @connection_options, @config = connection_options, config
136
165
  @quoted_column_names, @quoted_table_names = {}, {}
137
166
 
138
- if config.fetch(:prepared_statements) { true }
167
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
168
+ @prepared_statements = true
139
169
  @visitor = Arel::Visitors::MySQL.new self
140
170
  else
141
- @visitor = BindSubstitution.new self
171
+ @visitor = unprepared_visitor
142
172
  end
143
173
  end
144
174
 
@@ -170,10 +200,22 @@ module ActiveRecord
170
200
  true
171
201
  end
172
202
 
203
+ # MySQL 4 technically support transaction isolation, but it is affected by a bug
204
+ # where the transaction level gets persisted for the whole session:
205
+ #
206
+ # http://bugs.mysql.com/bug.php?id=39170
207
+ def supports_transaction_isolation?
208
+ version[0] >= 5
209
+ end
210
+
173
211
  def native_database_types
174
212
  NATIVE_DATABASE_TYPES
175
213
  end
176
214
 
215
+ def index_algorithms
216
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
217
+ end
218
+
177
219
  # HELPER METHODS ===========================================
178
220
 
179
221
  # The two drivers have slightly different ways of yielding hashes of results, so
@@ -183,8 +225,8 @@ module ActiveRecord
183
225
  end
184
226
 
185
227
  # Overridden by the adapters to instantiate their specific Column type.
186
- def new_column(field, default, type, null, collation) # :nodoc:
187
- Column.new(field, default, type, null, collation)
228
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
229
+ Column.new(field, default, type, null, collation, extra)
188
230
  end
189
231
 
190
232
  # Must return the Mysql error number from the exception, if the exception has an
@@ -246,14 +288,14 @@ module ActiveRecord
246
288
  end
247
289
  rescue ActiveRecord::StatementInvalid => exception
248
290
  if exception.message.split(":").first =~ /Packets out of order/
249
- 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."
291
+ raise ActiveRecord::StatementInvalid.new("'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.", exception.original_exception)
250
292
  else
251
293
  raise
252
294
  end
253
295
  end
254
296
 
255
297
  # MysqlAdapter has to free a result after using it, so we use this method to write
256
- # stuff in a abstract way without concerning ourselves about whether it needs to be
298
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
257
299
  # explicitly freed or not.
258
300
  def execute_and_free(sql, name = nil) #:nodoc:
259
301
  yield execute(sql, name)
@@ -266,19 +308,26 @@ module ActiveRecord
266
308
 
267
309
  def begin_db_transaction
268
310
  execute "BEGIN"
269
- rescue Exception
311
+ rescue
312
+ # Transactions aren't supported
313
+ end
314
+
315
+ def begin_isolated_db_transaction(isolation)
316
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
317
+ begin_db_transaction
318
+ rescue
270
319
  # Transactions aren't supported
271
320
  end
272
321
 
273
322
  def commit_db_transaction #:nodoc:
274
323
  execute "COMMIT"
275
- rescue Exception
324
+ rescue
276
325
  # Transactions aren't supported
277
326
  end
278
327
 
279
328
  def rollback_db_transaction #:nodoc:
280
329
  execute "ROLLBACK"
281
- rescue Exception
330
+ rescue
282
331
  # Transactions aren't supported
283
332
  end
284
333
 
@@ -296,55 +345,38 @@ module ActiveRecord
296
345
 
297
346
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
298
347
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
299
- # these, we must use a subquery. However, MySQL is too stupid to create a
300
- # temporary table for this automatically, so we have to give it some prompting
301
- # in the form of a subsubquery. Ugh!
348
+ # these, we must use a subquery.
302
349
  def join_to_update(update, select) #:nodoc:
303
350
  if select.limit || select.offset || select.orders.any?
304
- subsubselect = select.clone
305
- subsubselect.projections = [update.key]
306
-
307
- subselect = Arel::SelectManager.new(select.engine)
308
- subselect.project Arel.sql(update.key.name)
309
- subselect.from subsubselect.as('__active_record_temp')
310
-
311
- update.where update.key.in(subselect)
351
+ super
312
352
  else
313
353
  update.table select.source
314
354
  update.wheres = select.constraints
315
355
  end
316
356
  end
317
357
 
318
- # SCHEMA STATEMENTS ========================================
319
-
320
- def structure_dump #:nodoc:
321
- if supports_views?
322
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
323
- else
324
- sql = "SHOW TABLES"
325
- end
326
-
327
- select_all(sql).map { |table|
328
- table.delete('Table_type')
329
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
330
- exec_query(sql).first['Create Table'] + ";\n\n"
331
- }.join
358
+ def empty_insert_statement_value
359
+ "VALUES ()"
332
360
  end
333
361
 
362
+ # SCHEMA STATEMENTS ========================================
363
+
334
364
  # Drops the database specified on the +name+ attribute
335
365
  # and creates it again using the provided +options+.
336
366
  def recreate_database(name, options = {})
337
367
  drop_database(name)
338
- create_database(name, options)
368
+ sql = create_database(name, options)
369
+ reconnect!
370
+ sql
339
371
  end
340
372
 
341
373
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
342
374
  # Charset defaults to utf8.
343
375
  #
344
376
  # Example:
345
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
377
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
346
378
  # create_database 'matt_development'
347
- # create_database 'matt_development', :charset => :big5
379
+ # create_database 'matt_development', charset: :big5
348
380
  def create_database(name, options = {})
349
381
  if options[:collation]
350
382
  execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
@@ -409,7 +441,11 @@ module ActiveRecord
409
441
  if current_index != row[:Key_name]
410
442
  next if row[:Key_name] == 'PRIMARY' # skip the primary key
411
443
  current_index = row[:Key_name]
412
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
444
+
445
+ mysql_index_type = row[:Index_type].downcase.to_sym
446
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
447
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
448
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
413
449
  end
414
450
 
415
451
  indexes.last.columns << row[:Column_name]
@@ -421,11 +457,12 @@ module ActiveRecord
421
457
  end
422
458
 
423
459
  # Returns an array of +Column+ objects for the table specified by +table_name+.
424
- def columns(table_name, name = nil)#:nodoc:
460
+ def columns(table_name)#:nodoc:
425
461
  sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
426
462
  execute_and_free(sql, 'SCHEMA') do |result|
427
463
  each_hash(result).map do |field|
428
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
464
+ field_name = set_field_encoding(field[:Field])
465
+ new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
429
466
  end
430
467
  end
431
468
  end
@@ -455,10 +492,7 @@ module ActiveRecord
455
492
  # rename_table('octopuses', 'octopi')
456
493
  def rename_table(table_name, new_name)
457
494
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
458
- end
459
-
460
- def add_column(table_name, column_name, type, options = {})
461
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
495
+ rename_table_indexes(table_name, new_name)
462
496
  end
463
497
 
464
498
  def change_column_default(table_name, column_name, default)
@@ -482,11 +516,24 @@ module ActiveRecord
482
516
 
483
517
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
484
518
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
519
+ rename_column_indexes(table_name, column_name, new_column_name)
520
+ end
521
+
522
+ def add_index(table_name, column_name, options = {}) #:nodoc:
523
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
524
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
485
525
  end
486
526
 
487
527
  # Maps logical Rails types to MySQL-specific data types.
488
528
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
489
529
  case type.to_s
530
+ when 'binary'
531
+ case limit
532
+ when 0..0xfff; "varbinary(#{limit})"
533
+ when nil; "blob"
534
+ when 0x1000..0xffffffff; "blob(#{limit})"
535
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
536
+ end
490
537
  when 'integer'
491
538
  case limit
492
539
  when 1; 'tinyint'
@@ -519,7 +566,7 @@ module ActiveRecord
519
566
 
520
567
  # SHOW VARIABLES LIKE 'name'
521
568
  def show_variable(name)
522
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
569
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
523
570
  variables.first['Value'] unless variables.empty?
524
571
  end
525
572
 
@@ -528,7 +575,7 @@ module ActiveRecord
528
575
  execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
529
576
  create_table = each_hash(result).first[:"Create Table"]
530
577
  if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
531
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
578
+ keys = $1.split(",").map { |key| key.delete('`"') }
532
579
  keys.length == 1 ? [keys.first, nil] : nil
533
580
  else
534
581
  nil
@@ -558,8 +605,27 @@ module ActiveRecord
558
605
  where_sql
559
606
  end
560
607
 
608
+ def strict_mode?
609
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
610
+ end
611
+
612
+ def valid_type?(type)
613
+ !native_database_types[type].nil?
614
+ end
615
+
561
616
  protected
562
617
 
618
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
619
+ # to give it some prompting in the form of a subsubquery. Ugh!
620
+ def subquery_for(key, select)
621
+ subsubselect = select.clone
622
+ subsubselect.projections = [key]
623
+
624
+ subselect = Arel::SelectManager.new(select.engine)
625
+ subselect.project Arel.sql(key.name)
626
+ subselect.from subsubselect.as('__active_record_temp')
627
+ end
628
+
563
629
  def add_index_length(option_strings, column_names, options = {})
564
630
  if options.is_a?(Hash) && length = options[:length]
565
631
  case length
@@ -626,20 +692,24 @@ module ActiveRecord
626
692
  if column = columns(table_name).find { |c| c.name == column_name.to_s }
627
693
  options[:default] = column.default
628
694
  options[:null] = column.null
695
+ options[:auto_increment] = (column.extra == "auto_increment")
629
696
  else
630
697
  raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
631
698
  end
632
699
 
633
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
700
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
634
701
  rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
635
702
  add_column_options!(rename_column_sql, options)
636
703
  rename_column_sql
637
704
  end
638
705
 
639
- def remove_column_sql(table_name, *column_names)
640
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
706
+ def remove_column_sql(table_name, column_name, type = nil, options = {})
707
+ "DROP #{quote_column_name(column_name)}"
708
+ end
709
+
710
+ def remove_columns_sql(table_name, *column_names)
711
+ column_names.map {|column_name| remove_column_sql(table_name, column_name) }
641
712
  end
642
- alias :remove_columns_sql :remove_column
643
713
 
644
714
  def add_index_sql(table_name, column_name, options = {})
645
715
  index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
@@ -651,8 +721,8 @@ module ActiveRecord
651
721
  "DROP INDEX #{index_name}"
652
722
  end
653
723
 
654
- def add_timestamps_sql(table_name)
655
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
724
+ def add_timestamps_sql(table_name, options = {})
725
+ [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
656
726
  end
657
727
 
658
728
  def remove_timestamps_sql(table_name)
@@ -671,6 +741,44 @@ module ActiveRecord
671
741
  end
672
742
  column
673
743
  end
744
+
745
+ def configure_connection
746
+ variables = @config.fetch(:variables, {}).stringify_keys
747
+
748
+ # By default, MySQL 'where id is null' selects the last inserted id.
749
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
750
+ variables['sql_auto_is_null'] = 0
751
+
752
+ # Increase timeout so the server doesn't disconnect us.
753
+ wait_timeout = @config[:wait_timeout]
754
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
755
+ variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
756
+
757
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
758
+ # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
759
+ # If the user has provided another value for sql_mode, don't replace it.
760
+ unless variables.has_key?('sql_mode')
761
+ variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
762
+ end
763
+
764
+ # NAMES does not have an equals sign, see
765
+ # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
766
+ # (trailing comma because variable_assignments will always have content)
767
+ encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
768
+
769
+ # Gather up all of the SET variables...
770
+ variable_assignments = variables.map do |k, v|
771
+ if v == ':default' || v == :default
772
+ "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
773
+ elsif !v.nil?
774
+ "@@SESSION.#{k.to_s} = #{quote(v)}"
775
+ end
776
+ # or else nil; compact to clear nils out
777
+ end.compact.join(', ')
778
+
779
+ # ...and send them all in one query
780
+ execute("SET #{encoding} #{variable_assignments}", :skip_logging)
781
+ end
674
782
  end
675
783
  end
676
784
  end
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
14
14
  end
15
15
 
16
- attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
16
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function
17
17
  attr_accessor :primary, :coder
18
18
 
19
19
  alias :encoded? :coder
@@ -27,16 +27,17 @@ module ActiveRecord
27
27
  # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
28
28
  # +null+ determines if this column allows +NULL+ values.
29
29
  def initialize(name, default, sql_type = nil, null = true)
30
- @name = name
31
- @sql_type = sql_type
32
- @null = null
33
- @limit = extract_limit(sql_type)
34
- @precision = extract_precision(sql_type)
35
- @scale = extract_scale(sql_type)
36
- @type = simplified_type(sql_type)
37
- @default = extract_default(default)
38
- @primary = nil
39
- @coder = nil
30
+ @name = name
31
+ @sql_type = sql_type
32
+ @null = null
33
+ @limit = extract_limit(sql_type)
34
+ @precision = extract_precision(sql_type)
35
+ @scale = extract_scale(sql_type)
36
+ @type = simplified_type(sql_type)
37
+ @default = extract_default(default)
38
+ @default_function = nil
39
+ @primary = nil
40
+ @coder = nil
40
41
  end
41
42
 
42
43
  # Returns +true+ if the column is either of type string or text.
@@ -66,6 +67,26 @@ module ActiveRecord
66
67
  end
67
68
  end
68
69
 
70
+ def binary?
71
+ type == :binary
72
+ end
73
+
74
+ # Casts a Ruby value to something appropriate for writing to the database.
75
+ def type_cast_for_write(value)
76
+ return value unless number?
77
+
78
+ case value
79
+ when FalseClass
80
+ 0
81
+ when TrueClass
82
+ 1
83
+ when String
84
+ value.presence
85
+ else
86
+ value
87
+ end
88
+ end
89
+
69
90
  # Casts value (which is a String) to an appropriate instance.
70
91
  def type_cast(value)
71
92
  return nil if value.nil?
@@ -80,7 +101,7 @@ module ActiveRecord
80
101
  when :decimal then klass.value_to_decimal(value)
81
102
  when :datetime, :timestamp then klass.string_to_time(value)
82
103
  when :time then klass.string_to_dummy_time(value)
83
- when :date then klass.string_to_date(value)
104
+ when :date then klass.value_to_date(value)
84
105
  when :binary then klass.binary_to_string(value)
85
106
  when :boolean then klass.value_to_boolean(value)
86
107
  else value
@@ -88,6 +109,10 @@ module ActiveRecord
88
109
  end
89
110
 
90
111
  def type_cast_code(var_name)
112
+ message = "Column#type_cast_code is deprecated in favor of using Column#type_cast only, " \
113
+ "and it is going to be removed in future Rails versions."
114
+ ActiveSupport::Deprecation.warn message
115
+
91
116
  klass = self.class.name
92
117
 
93
118
  case type
@@ -97,9 +122,12 @@ module ActiveRecord
97
122
  when :decimal then "#{klass}.value_to_decimal(#{var_name})"
98
123
  when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
99
124
  when :time then "#{klass}.string_to_dummy_time(#{var_name})"
100
- when :date then "#{klass}.string_to_date(#{var_name})"
125
+ when :date then "#{klass}.value_to_date(#{var_name})"
101
126
  when :binary then "#{klass}.binary_to_string(#{var_name})"
102
127
  when :boolean then "#{klass}.value_to_boolean(#{var_name})"
128
+ when :hstore then "#{klass}.string_to_hstore(#{var_name})"
129
+ when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
130
+ when :json then "#{klass}.string_to_json(#{var_name})"
103
131
  else var_name
104
132
  end
105
133
  end
@@ -132,11 +160,15 @@ module ActiveRecord
132
160
  value
133
161
  end
134
162
 
135
- def string_to_date(string)
136
- return string unless string.is_a?(String)
137
- return nil if string.empty?
138
-
139
- fast_string_to_date(string) || fallback_string_to_date(string)
163
+ def value_to_date(value)
164
+ if value.is_a?(String)
165
+ return nil if value.empty?
166
+ fast_string_to_date(value) || fallback_string_to_date(value)
167
+ elsif value.respond_to?(:to_date)
168
+ value.to_date
169
+ else
170
+ value
171
+ end
140
172
  end
141
173
 
142
174
  def string_to_time(string)
@@ -161,7 +193,7 @@ module ActiveRecord
161
193
 
162
194
  # convert something to a boolean
163
195
  def value_to_boolean(value)
164
- if value.is_a?(String) && value.blank?
196
+ if value.is_a?(String) && value.empty?
165
197
  nil
166
198
  else
167
199
  TRUE_VALUES.include?(value)
@@ -206,11 +238,19 @@ module ActiveRecord
206
238
  end
207
239
  end
208
240
 
209
- def new_time(year, mon, mday, hour, min, sec, microsec)
241
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
210
242
  # Treat 0000-00-00 00:00:00 as nil.
211
243
  return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
212
244
 
213
- Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
245
+ if offset
246
+ time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
247
+ return nil unless time
248
+
249
+ time -= offset
250
+ Base.default_timezone == :utc ? time : time.getlocal
251
+ else
252
+ Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
253
+ end
214
254
  end
215
255
 
216
256
  def fast_string_to_date(string)
@@ -219,20 +259,11 @@ module ActiveRecord
219
259
  end
220
260
  end
221
261
 
222
- if RUBY_VERSION >= '1.9'
223
- # Doesn't handle time zones.
224
- def fast_string_to_time(string)
225
- if string =~ Format::ISO_DATETIME
226
- microsec = ($7.to_r * 1_000_000).to_i
227
- new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
228
- end
229
- end
230
- else
231
- def fast_string_to_time(string)
232
- if string =~ Format::ISO_DATETIME
233
- microsec = ($7.to_f * 1_000_000).round.to_i
234
- new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
235
- end
262
+ # Doesn't handle time zones.
263
+ def fast_string_to_time(string)
264
+ if string =~ Format::ISO_DATETIME
265
+ microsec = ($7.to_r * 1_000_000).to_i
266
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
236
267
  end
237
268
  end
238
269
 
@@ -244,7 +275,7 @@ module ActiveRecord
244
275
  time_hash = Date._parse(string)
245
276
  time_hash[:sec_fraction] = microseconds(time_hash)
246
277
 
247
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
278
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
248
279
  end
249
280
  end
250
281