activerecord 3.1.12 → 3.2.0.rc1

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 (99) hide show
  1. data/CHANGELOG.md +6263 -103
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record.rb +28 -2
  5. data/lib/active_record/aggregations.rb +2 -2
  6. data/lib/active_record/associations.rb +82 -69
  7. data/lib/active_record/associations/association.rb +2 -37
  8. data/lib/active_record/associations/association_scope.rb +3 -30
  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 +55 -28
  16. data/lib/active_record/associations/collection_proxy.rb +1 -35
  17. data/lib/active_record/associations/has_many_association.rb +5 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  19. data/lib/active_record/associations/join_dependency.rb +1 -1
  20. data/lib/active_record/associations/preloader/association.rb +3 -1
  21. data/lib/active_record/attribute_assignment.rb +221 -0
  22. data/lib/active_record/attribute_methods.rb +212 -32
  23. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  24. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  25. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  26. data/lib/active_record/attribute_methods/read.rb +69 -80
  27. data/lib/active_record/attribute_methods/serialization.rb +89 -0
  28. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  29. data/lib/active_record/attribute_methods/write.rb +27 -5
  30. data/lib/active_record/autosave_association.rb +23 -8
  31. data/lib/active_record/base.rb +223 -1712
  32. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  33. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  40. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +1 -1
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures.rb +31 -76
  53. data/lib/active_record/fixtures/file.rb +65 -0
  54. data/lib/active_record/identity_map.rb +1 -7
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +19 -11
  58. data/lib/active_record/locking/pessimistic.rb +1 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration.rb +38 -29
  61. data/lib/active_record/migration/command_recorder.rb +7 -7
  62. data/lib/active_record/model_schema.rb +362 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -1
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +133 -77
  69. data/lib/active_record/readonly_attributes.rb +26 -0
  70. data/lib/active_record/reflection.rb +7 -15
  71. data/lib/active_record/relation.rb +78 -35
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +5 -4
  76. data/lib/active_record/relation/predicate_builder.rb +13 -16
  77. data/lib/active_record/relation/query_methods.rb +59 -4
  78. data/lib/active_record/result.rb +1 -1
  79. data/lib/active_record/sanitization.rb +194 -0
  80. data/lib/active_record/schema_dumper.rb +5 -2
  81. data/lib/active_record/scoping.rb +152 -0
  82. data/lib/active_record/scoping/default.rb +140 -0
  83. data/lib/active_record/scoping/named.rb +202 -0
  84. data/lib/active_record/serialization.rb +1 -43
  85. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  86. data/lib/active_record/session_store.rb +11 -11
  87. data/lib/active_record/store.rb +50 -0
  88. data/lib/active_record/test_case.rb +11 -7
  89. data/lib/active_record/timestamp.rb +16 -3
  90. data/lib/active_record/transactions.rb +5 -5
  91. data/lib/active_record/translation.rb +22 -0
  92. data/lib/active_record/validations.rb +1 -1
  93. data/lib/active_record/validations/associated.rb +5 -4
  94. data/lib/active_record/validations/uniqueness.rb +4 -4
  95. data/lib/active_record/version.rb +3 -3
  96. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  97. metadata +48 -38
  98. checksums.yaml +0 -7
  99. data/lib/active_record/named_scope.rb +0 -200
@@ -0,0 +1,653 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class AbstractMysqlAdapter < AbstractAdapter
6
+ class Column < ConnectionAdapters::Column # :nodoc:
7
+ attr_reader :collation
8
+
9
+ def initialize(name, default, sql_type = nil, null = true, collation = nil)
10
+ super(name, default, sql_type, null)
11
+ @collation = collation
12
+ end
13
+
14
+ def extract_default(default)
15
+ if sql_type =~ /blob/i || type == :text
16
+ if default.blank?
17
+ return null ? nil : ''
18
+ else
19
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
20
+ end
21
+ elsif missing_default_forged_as_empty_string?(default)
22
+ nil
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def has_default?
29
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
30
+ super
31
+ end
32
+
33
+ # Must return the relevant concrete adapter
34
+ def adapter
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def case_sensitive?
39
+ collation && !collation.match(/_ci$/)
40
+ end
41
+
42
+ private
43
+
44
+ def simplified_type(field_type)
45
+ return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
46
+
47
+ case field_type
48
+ when /enum/i, /set/i then :string
49
+ when /year/i then :integer
50
+ when /bit/i then :binary
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def extract_limit(sql_type)
57
+ case sql_type
58
+ when /blob|text/i
59
+ case sql_type
60
+ when /tiny/i
61
+ 255
62
+ when /medium/i
63
+ 16777215
64
+ when /long/i
65
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
66
+ else
67
+ super # we could return 65535 here, but we leave it undecorated by default
68
+ end
69
+ when /^bigint/i; 8
70
+ when /^int/i; 4
71
+ when /^mediumint/i; 3
72
+ when /^smallint/i; 2
73
+ when /^tinyint/i; 1
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # MySQL misreports NOT NULL column default when none is given.
80
+ # We can't detect this for columns which may have a legitimate ''
81
+ # default (string) but we can for others (integer, datetime, boolean,
82
+ # and the rest).
83
+ #
84
+ # Test whether the column has default '', is not null, and is not
85
+ # a type allowing default ''.
86
+ def missing_default_forged_as_empty_string?(default)
87
+ type != :string && !null && default == ''
88
+ end
89
+ end
90
+
91
+ ##
92
+ # :singleton-method:
93
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
94
+ # as boolean. If you wish to disable this emulation (which was the default
95
+ # behavior in versions 0.13.1 and earlier) you can add the following line
96
+ # to your application.rb file:
97
+ #
98
+ # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
99
+ class_attribute :emulate_booleans
100
+ self.emulate_booleans = true
101
+
102
+ LOST_CONNECTION_ERROR_MESSAGES = [
103
+ "Server shutdown in progress",
104
+ "Broken pipe",
105
+ "Lost connection to MySQL server during query",
106
+ "MySQL server has gone away" ]
107
+
108
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
109
+
110
+ NATIVE_DATABASE_TYPES = {
111
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
112
+ :string => { :name => "varchar", :limit => 255 },
113
+ :text => { :name => "text" },
114
+ :integer => { :name => "int", :limit => 4 },
115
+ :float => { :name => "float" },
116
+ :decimal => { :name => "decimal" },
117
+ :datetime => { :name => "datetime" },
118
+ :timestamp => { :name => "datetime" },
119
+ :time => { :name => "time" },
120
+ :date => { :name => "date" },
121
+ :binary => { :name => "blob" },
122
+ :boolean => { :name => "tinyint", :limit => 1 }
123
+ }
124
+
125
+ # FIXME: Make the first parameter more similar for the two adapters
126
+ def initialize(connection, logger, connection_options, config)
127
+ super(connection, logger)
128
+ @connection_options, @config = connection_options, config
129
+ @quoted_column_names, @quoted_table_names = {}, {}
130
+ @visitor = Arel::Visitors::MySQL.new self
131
+ end
132
+
133
+ def adapter_name #:nodoc:
134
+ self.class::ADAPTER_NAME
135
+ end
136
+
137
+ # Returns true, since this connection adapter supports migrations.
138
+ def supports_migrations?
139
+ true
140
+ end
141
+
142
+ def supports_primary_key?
143
+ true
144
+ end
145
+
146
+ # Returns true, since this connection adapter supports savepoints.
147
+ def supports_savepoints?
148
+ true
149
+ end
150
+
151
+ def supports_bulk_alter? #:nodoc:
152
+ true
153
+ end
154
+
155
+ # Technically MySQL allows to create indexes with the sort order syntax
156
+ # but at the moment (5.5) it doesn't yet implement them
157
+ def supports_index_sort_order?
158
+ true
159
+ end
160
+
161
+ def native_database_types
162
+ NATIVE_DATABASE_TYPES
163
+ end
164
+
165
+ # HELPER METHODS ===========================================
166
+
167
+ # The two drivers have slightly different ways of yielding hashes of results, so
168
+ # this method must be implemented to provide a uniform interface.
169
+ def each_hash(result) # :nodoc:
170
+ raise NotImplementedError
171
+ end
172
+
173
+ # Overridden by the adapters to instantiate their specific Column type.
174
+ def new_column(field, default, type, null, collation) # :nodoc:
175
+ Column.new(field, default, type, null, collation)
176
+ end
177
+
178
+ # Must return the Mysql error number from the exception, if the exception has an
179
+ # error number.
180
+ def error_number(exception) # :nodoc:
181
+ raise NotImplementedError
182
+ end
183
+
184
+ # QUOTING ==================================================
185
+
186
+ def quote(value, column = nil)
187
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
188
+ s = column.class.string_to_binary(value).unpack("H*")[0]
189
+ "x'#{s}'"
190
+ elsif value.kind_of?(BigDecimal)
191
+ value.to_s("F")
192
+ else
193
+ super
194
+ end
195
+ end
196
+
197
+ def quote_column_name(name) #:nodoc:
198
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
199
+ end
200
+
201
+ def quote_table_name(name) #:nodoc:
202
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
203
+ end
204
+
205
+ def quoted_true
206
+ QUOTED_TRUE
207
+ end
208
+
209
+ def quoted_false
210
+ QUOTED_FALSE
211
+ end
212
+
213
+ # REFERENTIAL INTEGRITY ====================================
214
+
215
+ def disable_referential_integrity(&block) #:nodoc:
216
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
217
+
218
+ begin
219
+ update("SET FOREIGN_KEY_CHECKS = 0")
220
+ yield
221
+ ensure
222
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
223
+ end
224
+ end
225
+
226
+ # DATABASE STATEMENTS ======================================
227
+
228
+ # Executes the SQL statement in the context of this connection.
229
+ def execute(sql, name = nil)
230
+ if name == :skip_logging
231
+ @connection.query(sql)
232
+ else
233
+ log(sql, name) { @connection.query(sql) }
234
+ end
235
+ rescue ActiveRecord::StatementInvalid => exception
236
+ if exception.message.split(":").first =~ /Packets out of order/
237
+ 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."
238
+ else
239
+ raise
240
+ end
241
+ end
242
+
243
+ # MysqlAdapter has to free a result after using it, so we use this method to write
244
+ # stuff in a abstract way without concerning ourselves about whether it needs to be
245
+ # explicitly freed or not.
246
+ def execute_and_free(sql, name = nil) #:nodoc:
247
+ yield execute(sql, name)
248
+ end
249
+
250
+ def update_sql(sql, name = nil) #:nodoc:
251
+ super
252
+ @connection.affected_rows
253
+ end
254
+
255
+ def begin_db_transaction
256
+ execute "BEGIN"
257
+ rescue Exception
258
+ # Transactions aren't supported
259
+ end
260
+
261
+ def commit_db_transaction #:nodoc:
262
+ execute "COMMIT"
263
+ rescue Exception
264
+ # Transactions aren't supported
265
+ end
266
+
267
+ def rollback_db_transaction #:nodoc:
268
+ execute "ROLLBACK"
269
+ rescue Exception
270
+ # Transactions aren't supported
271
+ end
272
+
273
+ def create_savepoint
274
+ execute("SAVEPOINT #{current_savepoint_name}")
275
+ end
276
+
277
+ def rollback_to_savepoint
278
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
279
+ end
280
+
281
+ def release_savepoint
282
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
283
+ end
284
+
285
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
286
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
287
+ # these, we must use a subquery. However, MySQL is too stupid to create a
288
+ # temporary table for this automatically, so we have to give it some prompting
289
+ # in the form of a subsubquery. Ugh!
290
+ def join_to_update(update, select) #:nodoc:
291
+ if select.limit || select.offset || select.orders.any?
292
+ subsubselect = select.clone
293
+ subsubselect.projections = [update.key]
294
+
295
+ subselect = Arel::SelectManager.new(select.engine)
296
+ subselect.project Arel.sql(update.key.name)
297
+ subselect.from subsubselect.as('__active_record_temp')
298
+
299
+ update.where update.key.in(subselect)
300
+ else
301
+ update.table select.source
302
+ update.wheres = select.constraints
303
+ end
304
+ end
305
+
306
+ # SCHEMA STATEMENTS ========================================
307
+
308
+ def structure_dump #:nodoc:
309
+ if supports_views?
310
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
311
+ else
312
+ sql = "SHOW TABLES"
313
+ end
314
+
315
+ select_all(sql).map { |table|
316
+ table.delete('Table_type')
317
+ sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
318
+ exec_without_stmt(sql).first['Create Table'] + ";\n\n"
319
+ }.join
320
+ end
321
+
322
+ # Drops the database specified on the +name+ attribute
323
+ # and creates it again using the provided +options+.
324
+ def recreate_database(name, options = {})
325
+ drop_database(name)
326
+ create_database(name, options)
327
+ end
328
+
329
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
330
+ # Charset defaults to utf8.
331
+ #
332
+ # Example:
333
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
334
+ # create_database 'matt_development'
335
+ # create_database 'matt_development', :charset => :big5
336
+ def create_database(name, options = {})
337
+ if options[:collation]
338
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
339
+ else
340
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
341
+ end
342
+ end
343
+
344
+ # Drops a MySQL database.
345
+ #
346
+ # Example:
347
+ # drop_database('sebastian_development')
348
+ def drop_database(name) #:nodoc:
349
+ execute "DROP DATABASE IF EXISTS `#{name}`"
350
+ end
351
+
352
+ def current_database
353
+ select_value 'SELECT DATABASE() as db'
354
+ end
355
+
356
+ # Returns the database character set.
357
+ def charset
358
+ show_variable 'character_set_database'
359
+ end
360
+
361
+ # Returns the database collation strategy.
362
+ def collation
363
+ show_variable 'collation_database'
364
+ end
365
+
366
+ def tables(name = nil, database = nil, like = nil) #:nodoc:
367
+ sql = "SHOW TABLES "
368
+ sql << "IN #{database} " if database
369
+ sql << "LIKE #{quote(like)}" if like
370
+
371
+ execute_and_free(sql, 'SCHEMA') do |result|
372
+ result.collect { |field| field.first }
373
+ end
374
+ end
375
+
376
+ def table_exists?(name)
377
+ return false unless name
378
+ return true if tables(nil, nil, name).any?
379
+
380
+ name = name.to_s
381
+ schema, table = name.split('.', 2)
382
+
383
+ unless table # A table was provided without a schema
384
+ table = schema
385
+ schema = nil
386
+ end
387
+
388
+ tables(nil, schema, table).any?
389
+ end
390
+
391
+ # Returns an array of indexes for the given table.
392
+ def indexes(table_name, name = nil) #:nodoc:
393
+ indexes = []
394
+ current_index = nil
395
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
396
+ each_hash(result) do |row|
397
+ if current_index != row[:Key_name]
398
+ next if row[:Key_name] == 'PRIMARY' # skip the primary key
399
+ current_index = row[:Key_name]
400
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
401
+ end
402
+
403
+ indexes.last.columns << row[:Column_name]
404
+ indexes.last.lengths << row[:Sub_part]
405
+ end
406
+ end
407
+
408
+ indexes
409
+ end
410
+
411
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
412
+ def columns(table_name, name = nil)#:nodoc:
413
+ sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
414
+ execute_and_free(sql, 'SCHEMA') do |result|
415
+ each_hash(result).map do |field|
416
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
417
+ end
418
+ end
419
+ end
420
+
421
+ def create_table(table_name, options = {}) #:nodoc:
422
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
423
+ end
424
+
425
+ def bulk_change_table(table_name, operations) #:nodoc:
426
+ sqls = operations.map do |command, args|
427
+ table, arguments = args.shift, args
428
+ method = :"#{command}_sql"
429
+
430
+ if respond_to?(method)
431
+ send(method, table, *arguments)
432
+ else
433
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
434
+ end
435
+ end.flatten.join(", ")
436
+
437
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
438
+ end
439
+
440
+ # Renames a table.
441
+ #
442
+ # Example:
443
+ # rename_table('octopuses', 'octopi')
444
+ def rename_table(table_name, new_name)
445
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
446
+ end
447
+
448
+ def add_column(table_name, column_name, type, options = {})
449
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
450
+ end
451
+
452
+ def change_column_default(table_name, column_name, default)
453
+ column = column_for(table_name, column_name)
454
+ change_column table_name, column_name, column.sql_type, :default => default
455
+ end
456
+
457
+ def change_column_null(table_name, column_name, null, default = nil)
458
+ column = column_for(table_name, column_name)
459
+
460
+ unless null || default.nil?
461
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
462
+ end
463
+
464
+ change_column table_name, column_name, column.sql_type, :null => null
465
+ end
466
+
467
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
468
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
469
+ end
470
+
471
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
472
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
473
+ end
474
+
475
+ # Maps logical Rails types to MySQL-specific data types.
476
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
477
+ return super unless type.to_s == 'integer'
478
+
479
+ case limit
480
+ when 1; 'tinyint'
481
+ when 2; 'smallint'
482
+ when 3; 'mediumint'
483
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
484
+ when 5..8; 'bigint'
485
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
486
+ end
487
+ end
488
+
489
+ def add_column_position!(sql, options)
490
+ if options[:first]
491
+ sql << " FIRST"
492
+ elsif options[:after]
493
+ sql << " AFTER #{quote_column_name(options[:after])}"
494
+ end
495
+ end
496
+
497
+ # SHOW VARIABLES LIKE 'name'
498
+ def show_variable(name)
499
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
500
+ variables.first['Value'] unless variables.empty?
501
+ end
502
+
503
+ # Returns a table's primary key and belonging sequence.
504
+ def pk_and_sequence_for(table)
505
+ execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
506
+ create_table = each_hash(result).first[:"Create Table"]
507
+ if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
508
+ keys = $1.split(",").map { |key| key.gsub(/`/, "") }
509
+ keys.length == 1 ? [keys.first, nil] : nil
510
+ else
511
+ nil
512
+ end
513
+ end
514
+ end
515
+
516
+ # Returns just a table's primary key
517
+ def primary_key(table)
518
+ pk_and_sequence = pk_and_sequence_for(table)
519
+ pk_and_sequence && pk_and_sequence.first
520
+ end
521
+
522
+ def case_sensitive_modifier(node)
523
+ Arel::Nodes::Bin.new(node)
524
+ end
525
+
526
+ def case_insensitive_comparison(table, attribute, column, value)
527
+ if column.case_sensitive?
528
+ super
529
+ else
530
+ table[attribute].eq(value)
531
+ end
532
+ end
533
+
534
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
535
+ where_sql
536
+ end
537
+
538
+ protected
539
+
540
+ def add_index_length(option_strings, column_names, options = {})
541
+ if options.is_a?(Hash) && length = options[:length]
542
+ case length
543
+ when Hash
544
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)}
545
+ when Fixnum
546
+ column_names.each {|name| option_strings[name] += "(#{length})"}
547
+ end
548
+ end
549
+
550
+ return option_strings
551
+ end
552
+
553
+ def quoted_columns_for_index(column_names, options = {})
554
+ option_strings = Hash[column_names.map {|name| [name, '']}]
555
+
556
+ # add index length
557
+ option_strings = add_index_length(option_strings, column_names, options)
558
+
559
+ # add index sort order
560
+ option_strings = add_index_sort_order(option_strings, column_names, options)
561
+
562
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
563
+ end
564
+
565
+ def translate_exception(exception, message)
566
+ case error_number(exception)
567
+ when 1062
568
+ RecordNotUnique.new(message, exception)
569
+ when 1452
570
+ InvalidForeignKey.new(message, exception)
571
+ else
572
+ super
573
+ end
574
+ end
575
+
576
+ def add_column_sql(table_name, column_name, type, options = {})
577
+ add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
578
+ add_column_options!(add_column_sql, options)
579
+ add_column_position!(add_column_sql, options)
580
+ add_column_sql
581
+ end
582
+
583
+ def change_column_sql(table_name, column_name, type, options = {})
584
+ column = column_for(table_name, column_name)
585
+
586
+ unless options_include_default?(options)
587
+ options[:default] = column.default
588
+ end
589
+
590
+ unless options.has_key?(:null)
591
+ options[:null] = column.null
592
+ end
593
+
594
+ change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
595
+ add_column_options!(change_column_sql, options)
596
+ add_column_position!(change_column_sql, options)
597
+ change_column_sql
598
+ end
599
+
600
+ def rename_column_sql(table_name, column_name, new_column_name)
601
+ options = {}
602
+
603
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
604
+ options[:default] = column.default
605
+ options[:null] = column.null
606
+ else
607
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
608
+ end
609
+
610
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
611
+ rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
612
+ add_column_options!(rename_column_sql, options)
613
+ rename_column_sql
614
+ end
615
+
616
+ def remove_column_sql(table_name, *column_names)
617
+ columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
618
+ end
619
+ alias :remove_columns_sql :remove_column
620
+
621
+ def add_index_sql(table_name, column_name, options = {})
622
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
623
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
624
+ end
625
+
626
+ def remove_index_sql(table_name, options = {})
627
+ index_name = index_name_for_remove(table_name, options)
628
+ "DROP INDEX #{index_name}"
629
+ end
630
+
631
+ def add_timestamps_sql(table_name)
632
+ [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
633
+ end
634
+
635
+ def remove_timestamps_sql(table_name)
636
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
637
+ end
638
+
639
+ private
640
+
641
+ def supports_views?
642
+ version[0] >= 5
643
+ end
644
+
645
+ def column_for(table_name, column_name)
646
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
647
+ raise "No such column: #{table_name}.#{column_name}"
648
+ end
649
+ column
650
+ end
651
+ end
652
+ end
653
+ end