activerecord 3.0.20 → 3.1.0.beta1

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 (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -3,6 +3,21 @@ require 'active_support/core_ext/kernel/requires'
3
3
  require 'active_support/core_ext/object/blank'
4
4
  require 'set'
5
5
 
6
+ gem 'mysql', '~> 2.8.1'
7
+ require 'mysql'
8
+
9
+ class Mysql
10
+ class Time
11
+ ###
12
+ # This monkey patch is for test_additional_columns_from_join_table
13
+ def to_date
14
+ Date.new(year, month, day)
15
+ end
16
+ end
17
+ class Stmt; include Enumerable end
18
+ class Result; include Enumerable end
19
+ end
20
+
6
21
  module ActiveRecord
7
22
  class Base
8
23
  # Establishes a connection to the database that's used by all Active Record objects.
@@ -15,18 +30,6 @@ module ActiveRecord
15
30
  password = config[:password].to_s
16
31
  database = config[:database]
17
32
 
18
- unless defined? Mysql
19
- begin
20
- require 'mysql'
21
- rescue LoadError
22
- raise "!!! Missing the mysql2 gem. Add it to your Gemfile: gem 'mysql2'"
23
- end
24
-
25
- unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash)
26
- raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'"
27
- end
28
- end
29
-
30
33
  mysql = Mysql.init
31
34
  mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
32
35
 
@@ -39,6 +42,30 @@ module ActiveRecord
39
42
 
40
43
  module ConnectionAdapters
41
44
  class MysqlColumn < Column #:nodoc:
45
+ class << self
46
+ def string_to_time(value)
47
+ return super unless Mysql::Time === value
48
+ new_time(
49
+ value.year,
50
+ value.month,
51
+ value.day,
52
+ value.hour,
53
+ value.minute,
54
+ value.second,
55
+ value.second_part)
56
+ end
57
+
58
+ def string_to_dummy_time(v)
59
+ return super unless Mysql::Time === v
60
+ new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
61
+ end
62
+
63
+ def string_to_date(v)
64
+ return super unless Mysql::Time === v
65
+ new_date(v.year, v.month, v.day)
66
+ end
67
+ end
68
+
42
69
  def extract_default(default)
43
70
  if sql_type =~ /blob/i || type == :text
44
71
  if default.blank?
@@ -132,7 +159,7 @@ module ActiveRecord
132
159
  cattr_accessor :emulate_booleans
133
160
  self.emulate_booleans = true
134
161
 
135
- ADAPTER_NAME = 'MySQL'.freeze
162
+ ADAPTER_NAME = 'MySQL'
136
163
 
137
164
  LOST_CONNECTION_ERROR_MESSAGES = [
138
165
  "Server shutdown in progress",
@@ -140,10 +167,10 @@ module ActiveRecord
140
167
  "Lost connection to MySQL server during query",
141
168
  "MySQL server has gone away" ]
142
169
 
143
- QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
170
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
144
171
 
145
172
  NATIVE_DATABASE_TYPES = {
146
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
173
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
147
174
  :string => { :name => "varchar", :limit => 255 },
148
175
  :text => { :name => "text" },
149
176
  :integer => { :name => "int", :limit => 4 },
@@ -161,6 +188,8 @@ module ActiveRecord
161
188
  super(connection, logger)
162
189
  @connection_options, @config = connection_options, config
163
190
  @quoted_column_names, @quoted_table_names = {}, {}
191
+ @statements = {}
192
+ @client_encoding = nil
164
193
  connect
165
194
  end
166
195
 
@@ -168,14 +197,27 @@ module ActiveRecord
168
197
  ADAPTER_NAME
169
198
  end
170
199
 
200
+ def supports_bulk_alter? #:nodoc:
201
+ true
202
+ end
203
+
204
+ # Returns true, since this connection adapter supports prepared statement
205
+ # caching.
206
+ def supports_statement_cache?
207
+ true
208
+ end
209
+
210
+ # Returns true, since this connection adapter supports migrations.
171
211
  def supports_migrations? #:nodoc:
172
212
  true
173
213
  end
174
214
 
215
+ # Returns true.
175
216
  def supports_primary_key? #:nodoc:
176
217
  true
177
218
  end
178
219
 
220
+ # Returns true, since this connection adapter supports savepoints.
179
221
  def supports_savepoints? #:nodoc:
180
222
  true
181
223
  end
@@ -198,8 +240,14 @@ module ActiveRecord
198
240
  end
199
241
  end
200
242
 
243
+ def type_cast(value, column)
244
+ return super unless value == true || value == false
245
+
246
+ value ? 1 : 0
247
+ end
248
+
201
249
  def quote_column_name(name) #:nodoc:
202
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
250
+ @quoted_column_names[name] ||= "`#{name}`"
203
251
  end
204
252
 
205
253
  def quote_table_name(name) #:nodoc:
@@ -252,9 +300,12 @@ module ActiveRecord
252
300
 
253
301
  def reconnect!
254
302
  disconnect!
303
+ clear_cache!
255
304
  connect
256
305
  end
257
306
 
307
+ # Disconnects from the database if already connected. Otherwise, this
308
+ # method does nothing.
258
309
  def disconnect!
259
310
  @connection.close rescue nil
260
311
  end
@@ -272,14 +323,105 @@ module ActiveRecord
272
323
 
273
324
  def select_rows(sql, name = nil)
274
325
  @connection.query_with_result = true
275
- result = execute(sql, name)
276
- rows = []
277
- result.each { |row| rows << row }
278
- result.free
326
+ rows = exec_without_stmt(sql, name).rows
279
327
  @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
280
328
  rows
281
329
  end
282
330
 
331
+ # Clears the prepared statements cache.
332
+ def clear_cache!
333
+ @statements.values.each do |cache|
334
+ cache[:stmt].close
335
+ end
336
+ @statements.clear
337
+ end
338
+
339
+ if "<3".respond_to?(:encode)
340
+ # Taken from here:
341
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
342
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
343
+ ENCODINGS = {
344
+ "armscii8" => nil,
345
+ "ascii" => Encoding::US_ASCII,
346
+ "big5" => Encoding::Big5,
347
+ "binary" => Encoding::ASCII_8BIT,
348
+ "cp1250" => Encoding::Windows_1250,
349
+ "cp1251" => Encoding::Windows_1251,
350
+ "cp1256" => Encoding::Windows_1256,
351
+ "cp1257" => Encoding::Windows_1257,
352
+ "cp850" => Encoding::CP850,
353
+ "cp852" => Encoding::CP852,
354
+ "cp866" => Encoding::IBM866,
355
+ "cp932" => Encoding::Windows_31J,
356
+ "dec8" => nil,
357
+ "eucjpms" => Encoding::EucJP_ms,
358
+ "euckr" => Encoding::EUC_KR,
359
+ "gb2312" => Encoding::EUC_CN,
360
+ "gbk" => Encoding::GBK,
361
+ "geostd8" => nil,
362
+ "greek" => Encoding::ISO_8859_7,
363
+ "hebrew" => Encoding::ISO_8859_8,
364
+ "hp8" => nil,
365
+ "keybcs2" => nil,
366
+ "koi8r" => Encoding::KOI8_R,
367
+ "koi8u" => Encoding::KOI8_U,
368
+ "latin1" => Encoding::ISO_8859_1,
369
+ "latin2" => Encoding::ISO_8859_2,
370
+ "latin5" => Encoding::ISO_8859_9,
371
+ "latin7" => Encoding::ISO_8859_13,
372
+ "macce" => Encoding::MacCentEuro,
373
+ "macroman" => Encoding::MacRoman,
374
+ "sjis" => Encoding::SHIFT_JIS,
375
+ "swe7" => nil,
376
+ "tis620" => Encoding::TIS_620,
377
+ "ucs2" => Encoding::UTF_16BE,
378
+ "ujis" => Encoding::EucJP_ms,
379
+ "utf8" => Encoding::UTF_8,
380
+ "utf8mb4" => Encoding::UTF_8,
381
+ }
382
+ else
383
+ ENCODINGS = Hash.new { |h,k| h[k] = k }
384
+ end
385
+
386
+ # Get the client encoding for this database
387
+ def client_encoding
388
+ return @client_encoding if @client_encoding
389
+
390
+ result = exec_query(
391
+ "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
392
+ 'SCHEMA')
393
+ @client_encoding = ENCODINGS[result.rows.last.last]
394
+ end
395
+
396
+ def exec_query(sql, name = 'SQL', binds = [])
397
+ log(sql, name, binds) do
398
+ exec_stmt(sql, name, binds) do |cols, stmt|
399
+ ActiveRecord::Result.new(cols, stmt.to_a) if cols
400
+ end
401
+ end
402
+ end
403
+
404
+ def last_inserted_id(result)
405
+ @connection.insert_id
406
+ end
407
+
408
+ def exec_without_stmt(sql, name = 'SQL') # :nodoc:
409
+ # Some queries, like SHOW CREATE TABLE don't work through the prepared
410
+ # statement API. For those queries, we need to use this method. :'(
411
+ log(sql, name) do
412
+ result = @connection.query(sql)
413
+ cols = []
414
+ rows = []
415
+
416
+ if result
417
+ cols = result.fetch_fields.map { |field| field.name }
418
+ rows = result.to_a
419
+ result.free
420
+ end
421
+ ActiveRecord::Result.new(cols, rows)
422
+ end
423
+ end
424
+
283
425
  # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
284
426
  # the Result object after you're done using it.
285
427
  def execute(sql, name = nil) #:nodoc:
@@ -307,9 +449,18 @@ module ActiveRecord
307
449
  @connection.affected_rows
308
450
  end
309
451
 
452
+ def exec_delete(sql, name, binds)
453
+ log(sql, name, binds) do
454
+ exec_stmt(sql, name, binds) do |cols, stmt|
455
+ stmt.affected_rows
456
+ end
457
+ end
458
+ end
459
+ alias :exec_update :exec_delete
460
+
310
461
  def begin_db_transaction #:nodoc:
311
- execute "BEGIN"
312
- rescue Exception
462
+ exec_without_stmt "BEGIN"
463
+ rescue Mysql::Error
313
464
  # Transactions aren't supported
314
465
  end
315
466
 
@@ -348,6 +499,7 @@ module ActiveRecord
348
499
  end
349
500
  sql
350
501
  end
502
+ deprecate :add_limit_offset!
351
503
 
352
504
  # SCHEMA STATEMENTS ========================================
353
505
 
@@ -360,10 +512,13 @@ module ActiveRecord
360
512
 
361
513
  select_all(sql).map do |table|
362
514
  table.delete('Table_type')
363
- select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
515
+ sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
516
+ exec_without_stmt(sql).first['Create Table'] + ";\n\n"
364
517
  end.join("")
365
518
  end
366
519
 
520
+ # Drops the database specified on the +name+ attribute
521
+ # and creates it again using the provided +options+.
367
522
  def recreate_database(name, options = {}) #:nodoc:
368
523
  drop_database(name)
369
524
  create_database(name, options)
@@ -384,6 +539,10 @@ module ActiveRecord
384
539
  end
385
540
  end
386
541
 
542
+ # Drops a MySQL database.
543
+ #
544
+ # Example:
545
+ # drop_database 'sebastian_development'
387
546
  def drop_database(name) #:nodoc:
388
547
  execute "DROP DATABASE IF EXISTS `#{name}`"
389
548
  end
@@ -403,13 +562,8 @@ module ActiveRecord
403
562
  end
404
563
 
405
564
  def tables(name = nil, database = nil) #:nodoc:
406
- tables = []
407
-
408
- sql = "SHOW TABLES "
409
- sql << "IN #{quote_table_name(database)} " if database
410
-
411
- result = execute(sql, 'SCHEMA')
412
- result.each { |field| tables << field[0] }
565
+ result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
566
+ tables = result.collect { |field| field[0] }
413
567
  result.free
414
568
  tables
415
569
  end
@@ -432,6 +586,7 @@ module ActiveRecord
432
586
  super(table_name, options)
433
587
  end
434
588
 
589
+ # Returns an array of indexes for the given table.
435
590
  def indexes(table_name, name = nil)#:nodoc:
436
591
  indexes = []
437
592
  current_index = nil
@@ -450,11 +605,11 @@ module ActiveRecord
450
605
  indexes
451
606
  end
452
607
 
608
+ # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
453
609
  def columns(table_name, name = nil)#:nodoc:
454
610
  sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
455
- columns = []
456
- result = execute(sql, :skip_logging)
457
- result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
611
+ result = execute(sql, 'SCHEMA')
612
+ columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
458
613
  result.free
459
614
  columns
460
615
  end
@@ -463,15 +618,31 @@ module ActiveRecord
463
618
  super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
464
619
  end
465
620
 
621
+ # Renames a table.
622
+ #
623
+ # Example:
624
+ # rename_table('octopuses', 'octopi')
466
625
  def rename_table(table_name, new_name)
467
626
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
468
627
  end
469
628
 
629
+ def bulk_change_table(table_name, operations) #:nodoc:
630
+ sqls = operations.map do |command, args|
631
+ table, arguments = args.shift, args
632
+ method = :"#{command}_sql"
633
+
634
+ if respond_to?(method)
635
+ send(method, table, *arguments)
636
+ else
637
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
638
+ end
639
+ end.flatten.join(", ")
640
+
641
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
642
+ end
643
+
470
644
  def add_column(table_name, column_name, type, options = {})
471
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
472
- add_column_options!(add_column_sql, options)
473
- add_column_position!(add_column_sql, options)
474
- execute(add_column_sql)
645
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
475
646
  end
476
647
 
477
648
  def change_column_default(table_name, column_name, default) #:nodoc:
@@ -490,58 +661,24 @@ module ActiveRecord
490
661
  end
491
662
 
492
663
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
493
- column = column_for(table_name, column_name)
494
-
495
- unless options_include_default?(options)
496
- options[:default] = column.default
497
- end
498
-
499
- unless options.has_key?(:null)
500
- options[:null] = column.null
501
- end
502
-
503
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
504
- add_column_options!(change_column_sql, options)
505
- add_column_position!(change_column_sql, options)
506
- execute(change_column_sql)
664
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
507
665
  end
508
666
 
509
667
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
510
- options = {}
511
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
512
- options[:default] = column.default
513
- options[:null] = column.null
514
- else
515
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
516
- end
517
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
518
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
519
- add_column_options!(rename_column_sql, options)
520
- execute(rename_column_sql)
668
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
521
669
  end
522
670
 
523
671
  # Maps logical Rails types to MySQL-specific data types.
524
672
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
525
- case type.to_s
526
- when 'integer'
527
- case limit
528
- when 1; 'tinyint'
529
- when 2; 'smallint'
530
- when 3; 'mediumint'
531
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
532
- when 5..8; 'bigint'
533
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
534
- end
535
- when 'text'
536
- case limit
537
- when 0..0xff; 'tinytext'
538
- when nil, 0x100..0xffff; 'text'
539
- when 0x10000..0xffffff; 'mediumtext'
540
- when 0x1000000..0xffffffff; 'longtext'
541
- else raise(ActiveRecordError, "No text type has character length #{limit}")
542
- end
543
- else
544
- super
673
+ return super unless type.to_s == 'integer'
674
+
675
+ case limit
676
+ when 1; 'tinyint'
677
+ when 2; 'smallint'
678
+ when 3; 'mediumint'
679
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
680
+ when 5..8; 'bigint'
681
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
545
682
  end
546
683
  end
547
684
 
@@ -562,7 +699,7 @@ module ActiveRecord
562
699
  # Returns a table's primary key and belonging sequence.
563
700
  def pk_and_sequence_for(table) #:nodoc:
564
701
  keys = []
565
- result = execute("describe #{quote_table_name(table)}")
702
+ result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
566
703
  result.each_hash do |h|
567
704
  keys << h["Field"]if h["Key"] == "PRI"
568
705
  end
@@ -579,6 +716,11 @@ module ActiveRecord
579
716
  def case_sensitive_equality_operator
580
717
  "= BINARY"
581
718
  end
719
+ deprecate :case_sensitive_equality_operator
720
+
721
+ def case_sensitive_modifier(node)
722
+ Arel::Nodes::Bin.new(node)
723
+ end
582
724
 
583
725
  def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
584
726
  where_sql
@@ -611,7 +753,110 @@ module ActiveRecord
611
753
  end
612
754
  end
613
755
 
756
+ def add_column_sql(table_name, column_name, type, options = {})
757
+ add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
758
+ add_column_options!(add_column_sql, options)
759
+ add_column_position!(add_column_sql, options)
760
+ add_column_sql
761
+ end
762
+
763
+ def remove_column_sql(table_name, *column_names)
764
+ columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
765
+ end
766
+ alias :remove_columns_sql :remove_column
767
+
768
+ def change_column_sql(table_name, column_name, type, options = {})
769
+ column = column_for(table_name, column_name)
770
+
771
+ unless options_include_default?(options)
772
+ options[:default] = column.default
773
+ end
774
+
775
+ unless options.has_key?(:null)
776
+ options[:null] = column.null
777
+ end
778
+
779
+ change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
780
+ add_column_options!(change_column_sql, options)
781
+ add_column_position!(change_column_sql, options)
782
+ change_column_sql
783
+ end
784
+
785
+ def rename_column_sql(table_name, column_name, new_column_name)
786
+ options = {}
787
+
788
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
789
+ options[:default] = column.default
790
+ options[:null] = column.null
791
+ else
792
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
793
+ end
794
+
795
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
796
+ rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
797
+ add_column_options!(rename_column_sql, options)
798
+ rename_column_sql
799
+ end
800
+
801
+ def add_index_sql(table_name, column_name, options = {})
802
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
803
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
804
+ end
805
+
806
+ def remove_index_sql(table_name, options = {})
807
+ index_name = index_name_for_remove(table_name, options)
808
+ "DROP INDEX #{index_name}"
809
+ end
810
+
811
+ def add_timestamps_sql(table_name)
812
+ [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
813
+ end
814
+
815
+ def remove_timestamps_sql(table_name)
816
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
817
+ end
818
+
614
819
  private
820
+ def exec_stmt(sql, name, binds)
821
+ cache = {}
822
+ if binds.empty?
823
+ stmt = @connection.prepare(sql)
824
+ else
825
+ cache = @statements[sql] ||= {
826
+ :stmt => @connection.prepare(sql)
827
+ }
828
+ stmt = cache[:stmt]
829
+ end
830
+
831
+
832
+ begin
833
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
834
+ rescue Mysql::Error => e
835
+ # Older versions of MySQL leave the prepared statement in a bad
836
+ # place when an error occurs. To support older mysql versions, we
837
+ # need to close the statement and delete the statement from the
838
+ # cache.
839
+ stmt.close
840
+ @statements.delete sql
841
+ raise e
842
+ end
843
+
844
+ cols = nil
845
+ if metadata = stmt.result_metadata
846
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
847
+ field.name
848
+ }
849
+ end
850
+
851
+ result = yield [cols, stmt]
852
+
853
+ stmt.result_metadata.free if cols
854
+ stmt.free_result
855
+ stmt.close if binds.empty?
856
+
857
+ result
858
+ end
859
+
615
860
  def connect
616
861
  encoding = @config[:encoding]
617
862
  if encoding
@@ -643,12 +888,9 @@ module ActiveRecord
643
888
  execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
644
889
  end
645
890
 
646
- def select(sql, name = nil)
891
+ def select(sql, name = nil, binds = [])
647
892
  @connection.query_with_result = true
648
- result = execute(sql, name)
649
- rows = []
650
- result.each_hash { |row| rows << row }
651
- result.free
893
+ rows = exec_query(sql, name, binds).to_a
652
894
  @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
653
895
  rows
654
896
  end
@@ -657,6 +899,7 @@ module ActiveRecord
657
899
  version[0] >= 5
658
900
  end
659
901
 
902
+ # Returns the version of the connected MySQL server.
660
903
  def version
661
904
  @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
662
905
  end