Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 678 lines (563 sloc) 22.949 kb
fd39847 @tenderlove prepared statements can be disabled
tenderlove authored
1 require 'arel/visitors/bind_visitor'
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
2
3 module ActiveRecord
4 module ConnectionAdapters
5 class AbstractMysqlAdapter < AbstractAdapter
4fcd847 @jonleighton Extract simplified_type into the abstract class
jonleighton authored
6 class Column < ConnectionAdapters::Column # :nodoc:
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when column h...
Joseph Palermo authored
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
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
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
4fcd847 @jonleighton Extract simplified_type into the abstract class
jonleighton authored
33 # Must return the relevant concrete adapter
34 def adapter
35 raise NotImplementedError
36 end
37
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when column h...
Joseph Palermo authored
38 def case_sensitive?
39 collation && !collation.match(/_ci$/)
40 end
41
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
42 private
43
4fcd847 @jonleighton Extract simplified_type into the abstract class
jonleighton authored
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
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
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
f498000 @masarakki fix: limit of enum columns of mysql
masarakki authored
74 when /^enum\((.+)\)/i
75 $1.split(',').map{|enum| enum.strip.length - 2}.max
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
76 else
77 super
78 end
79 end
80
81 # MySQL misreports NOT NULL column default when none is given.
82 # We can't detect this for columns which may have a legitimate ''
83 # default (string) but we can for others (integer, datetime, boolean,
84 # and the rest).
85 #
86 # Test whether the column has default '', is not null, and is not
87 # a type allowing default ''.
88 def missing_default_forged_as_empty_string?(default)
89 type != :string && !null && default == ''
90 end
91 end
92
93 ##
94 # :singleton-method:
95 # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
96 # as boolean. If you wish to disable this emulation (which was the default
97 # behavior in versions 0.13.1 and earlier) you can add the following line
98 # to your application.rb file:
99 #
100 # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
101 class_attribute :emulate_booleans
102 self.emulate_booleans = true
103
104 LOST_CONNECTION_ERROR_MESSAGES = [
105 "Server shutdown in progress",
106 "Broken pipe",
107 "Lost connection to MySQL server during query",
108 "MySQL server has gone away" ]
109
110 QUOTED_TRUE, QUOTED_FALSE = '1', '0'
111
112 NATIVE_DATABASE_TYPES = {
113 :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
114 :string => { :name => "varchar", :limit => 255 },
115 :text => { :name => "text" },
116 :integer => { :name => "int", :limit => 4 },
117 :float => { :name => "float" },
118 :decimal => { :name => "decimal" },
119 :datetime => { :name => "datetime" },
120 :timestamp => { :name => "datetime" },
121 :time => { :name => "time" },
122 :date => { :name => "date" },
123 :binary => { :name => "blob" },
124 :boolean => { :name => "tinyint", :limit => 1 }
125 }
126
fd39847 @tenderlove prepared statements can be disabled
tenderlove authored
127 class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
128 include Arel::Visitors::BindVisitor
129 end
130
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
131 # FIXME: Make the first parameter more similar for the two adapters
132 def initialize(connection, logger, connection_options, config)
133 super(connection, logger)
134 @connection_options, @config = connection_options, config
135 @quoted_column_names, @quoted_table_names = {}, {}
fd39847 @tenderlove prepared statements can be disabled
tenderlove authored
136
137 if config.fetch(:prepared_statements) { true }
138 @visitor = Arel::Visitors::MySQL.new self
139 else
140 @visitor = BindSubstitution.new self
141 end
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
142 end
143
144 def adapter_name #:nodoc:
145 self.class::ADAPTER_NAME
146 end
147
148 # Returns true, since this connection adapter supports migrations.
149 def supports_migrations?
150 true
151 end
152
153 def supports_primary_key?
154 true
155 end
156
157 # Returns true, since this connection adapter supports savepoints.
158 def supports_savepoints?
159 true
160 end
161
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence it...
jonleighton authored
162 def supports_bulk_alter? #:nodoc:
163 true
164 end
165
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
166 # Technically MySQL allows to create indexes with the sort order syntax
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
167 # but at the moment (5.5) it doesn't yet implement them
168 def supports_index_sort_order?
169 true
170 end
171
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
172 def native_database_types
173 NATIVE_DATABASE_TYPES
174 end
175
176 # HELPER METHODS ===========================================
177
178 # The two drivers have slightly different ways of yielding hashes of results, so
179 # this method must be implemented to provide a uniform interface.
180 def each_hash(result) # :nodoc:
181 raise NotImplementedError
182 end
183
184 # Overridden by the adapters to instantiate their specific Column type.
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when column h...
Joseph Palermo authored
185 def new_column(field, default, type, null, collation) # :nodoc:
186 Column.new(field, default, type, null, collation)
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
187 end
188
189 # Must return the Mysql error number from the exception, if the exception has an
190 # error number.
191 def error_number(exception) # :nodoc:
192 raise NotImplementedError
193 end
194
195 # QUOTING ==================================================
196
197 def quote(value, column = nil)
198 if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
199 s = column.class.string_to_binary(value).unpack("H*")[0]
200 "x'#{s}'"
201 elsif value.kind_of?(BigDecimal)
202 value.to_s("F")
203 else
204 super
205 end
206 end
207
208 def quote_column_name(name) #:nodoc:
209 @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
210 end
211
212 def quote_table_name(name) #:nodoc:
213 @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
214 end
215
216 def quoted_true
217 QUOTED_TRUE
218 end
219
220 def quoted_false
221 QUOTED_FALSE
222 end
223
224 # REFERENTIAL INTEGRITY ====================================
225
226 def disable_referential_integrity(&block) #:nodoc:
227 old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
228
229 begin
230 update("SET FOREIGN_KEY_CHECKS = 0")
231 yield
232 ensure
233 update("SET FOREIGN_KEY_CHECKS = #{old}")
234 end
235 end
236
237 # DATABASE STATEMENTS ======================================
238
239 # Executes the SQL statement in the context of this connection.
240 def execute(sql, name = nil)
241 if name == :skip_logging
242 @connection.query(sql)
243 else
244 log(sql, name) { @connection.query(sql) }
245 end
246 rescue ActiveRecord::StatementInvalid => exception
247 if exception.message.split(":").first =~ /Packets out of order/
248 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."
249 else
250 raise
251 end
252 end
253
254 # MysqlAdapter has to free a result after using it, so we use this method to write
984bc75 missplelling error in abstract_mysql_adapter
Angelo Capilleri authored
255 # stuff in an abstract way without concerning ourselves about whether it needs to be
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
256 # explicitly freed or not.
257 def execute_and_free(sql, name = nil) #:nodoc:
258 yield execute(sql, name)
259 end
260
261 def update_sql(sql, name = nil) #:nodoc:
262 super
263 @connection.affected_rows
264 end
265
266 def begin_db_transaction
267 execute "BEGIN"
c989160 @dylanahsmith Avoid unnecessary catching of Exception instead of StandardError.
dylanahsmith authored
268 rescue
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
269 # Transactions aren't supported
270 end
271
272 def commit_db_transaction #:nodoc:
273 execute "COMMIT"
c989160 @dylanahsmith Avoid unnecessary catching of Exception instead of StandardError.
dylanahsmith authored
274 rescue
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
275 # Transactions aren't supported
276 end
277
278 def rollback_db_transaction #:nodoc:
279 execute "ROLLBACK"
c989160 @dylanahsmith Avoid unnecessary catching of Exception instead of StandardError.
dylanahsmith authored
280 rescue
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
281 # Transactions aren't supported
282 end
283
284 def create_savepoint
285 execute("SAVEPOINT #{current_savepoint_name}")
286 end
287
288 def rollback_to_savepoint
289 execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
290 end
291
292 def release_savepoint
293 execute("RELEASE SAVEPOINT #{current_savepoint_name}")
294 end
295
296 # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
297 # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
6f6c290 @rafaelfranca Fix delete_all when chained with joins.
rafaelfranca authored
298 # these, we must use a subquery.
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
299 def join_to_update(update, select) #:nodoc:
300 if select.limit || select.offset || select.orders.any?
6f6c290 @rafaelfranca Fix delete_all when chained with joins.
rafaelfranca authored
301 super
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
302 else
303 update.table select.source
304 update.wheres = select.constraints
305 end
306 end
307
308 # SCHEMA STATEMENTS ========================================
309
310 def structure_dump #:nodoc:
311 if supports_views?
312 sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
313 else
314 sql = "SHOW TABLES"
315 end
316
576d700 @kennyj Fix logs name consistency.
kennyj authored
317 select_all(sql, 'SCHEMA').map { |table|
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
318 table.delete('Table_type')
319 sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
220864e @jeremycole Fixing texts; down to three failing tests.
jeremycole authored
320 exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
97ca635 @tenderlove Join method uses empty string by default, so remove it
tenderlove authored
321 }.join
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
322 end
323
324 # Drops the database specified on the +name+ attribute
325 # and creates it again using the provided +options+.
326 def recreate_database(name, options = {})
327 drop_database(name)
328 create_database(name, options)
329 end
330
331 # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
332 # Charset defaults to utf8.
333 #
334 # Example:
335 # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
336 # create_database 'matt_development'
337 # create_database 'matt_development', :charset => :big5
338 def create_database(name, options = {})
339 if options[:collation]
340 execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
341 else
342 execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
343 end
344 end
345
346 # Drops a MySQL database.
347 #
348 # Example:
349 # drop_database('sebastian_development')
350 def drop_database(name) #:nodoc:
351 execute "DROP DATABASE IF EXISTS `#{name}`"
352 end
353
354 def current_database
355 select_value 'SELECT DATABASE() as db'
356 end
357
358 # Returns the database character set.
359 def charset
360 show_variable 'character_set_database'
361 end
362
363 # Returns the database collation strategy.
364 def collation
365 show_variable 'collation_database'
366 end
367
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
368 def tables(name = nil, database = nil, like = nil) #:nodoc:
369 sql = "SHOW TABLES "
69c7f02 @kennyj Fix GH #3163. Should quote database on mysql/mysql2.
kennyj authored
370 sql << "IN #{quote_table_name(database)} " if database
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
371 sql << "LIKE #{quote(like)}" if like
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
372
373 execute_and_free(sql, 'SCHEMA') do |result|
374 result.collect { |field| field.first }
375 end
376 end
377
378 def table_exists?(name)
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
379 return false unless name
380 return true if tables(nil, nil, name).any?
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
381
382 name = name.to_s
383 schema, table = name.split('.', 2)
384
385 unless table # A table was provided without a schema
386 table = schema
387 schema = nil
388 end
389
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
390 tables(nil, schema, table).any?
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
391 end
392
393 # Returns an array of indexes for the given table.
394 def indexes(table_name, name = nil) #:nodoc:
395 indexes = []
396 current_index = nil
397 execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
398 each_hash(result) do |row|
399 if current_index != row[:Key_name]
400 next if row[:Key_name] == 'PRIMARY' # skip the primary key
401 current_index = row[:Key_name]
402 indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
403 end
404
405 indexes.last.columns << row[:Column_name]
406 indexes.last.lengths << row[:Sub_part]
407 end
408 end
409
410 indexes
411 end
412
413 # Returns an array of +Column+ objects for the table specified by +table_name+.
dcd74eb @smartinez87 Remove useless argument in #columns.
smartinez87 authored
414 def columns(table_name)#:nodoc:
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when column h...
Joseph Palermo authored
415 sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
416 execute_and_free(sql, 'SCHEMA') do |result|
417 each_hash(result).map do |field|
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when column h...
Joseph Palermo authored
418 new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
419 end
420 end
421 end
422
423 def create_table(table_name, options = {}) #:nodoc:
424 super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
425 end
426
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence it...
jonleighton authored
427 def bulk_change_table(table_name, operations) #:nodoc:
428 sqls = operations.map do |command, args|
429 table, arguments = args.shift, args
430 method = :"#{command}_sql"
431
19c7124 @tenderlove more ruby 2.0 respond_to? changes
tenderlove authored
432 if respond_to?(method, true)
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence it...
jonleighton authored
433 send(method, table, *arguments)
434 else
435 raise "Unknown method called : #{method}(#{arguments.inspect})"
436 end
437 end.flatten.join(", ")
438
439 execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
440 end
441
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
442 # Renames a table.
443 #
444 # Example:
445 # rename_table('octopuses', 'octopi')
446 def rename_table(table_name, new_name)
447 execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
448 end
449
450 def add_column(table_name, column_name, type, options = {})
451 execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
452 end
453
454 def change_column_default(table_name, column_name, default)
455 column = column_for(table_name, column_name)
456 change_column table_name, column_name, column.sql_type, :default => default
457 end
458
459 def change_column_null(table_name, column_name, null, default = nil)
460 column = column_for(table_name, column_name)
461
462 unless null || default.nil?
463 execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
464 end
465
466 change_column table_name, column_name, column.sql_type, :null => null
467 end
468
469 def change_column(table_name, column_name, type, options = {}) #:nodoc:
470 execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
471 end
472
473 def rename_column(table_name, column_name, new_column_name) #:nodoc:
474 execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
475 end
476
477 # Maps logical Rails types to MySQL-specific data types.
478 def type_to_sql(type, limit = nil, precision = nil, scale = nil)
fe7cacb @kennyj Fix type_to_sql with text and limit on mysql/mysql2. Fix GH #3931.
kennyj authored
479 case type.to_s
480 when 'integer'
481 case limit
482 when 1; 'tinyint'
483 when 2; 'smallint'
484 when 3; 'mediumint'
485 when nil, 4, 11; 'int(11)' # compatibility with MySQL default
486 when 5..8; 'bigint'
487 else raise(ActiveRecordError, "No integer type has byte size #{limit}")
488 end
489 when 'text'
490 case limit
491 when 0..0xff; 'tinytext'
492 when nil, 0x100..0xffff; 'text'
493 when 0x10000..0xffffff; 'mediumtext'
494 when 0x1000000..0xffffffff; 'longtext'
495 else raise(ActiveRecordError, "No text type has character length #{limit}")
496 end
497 else
498 super
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
499 end
500 end
501
502 def add_column_position!(sql, options)
503 if options[:first]
504 sql << " FIRST"
505 elsif options[:after]
506 sql << " AFTER #{quote_column_name(options[:after])}"
507 end
508 end
509
510 # SHOW VARIABLES LIKE 'name'
511 def show_variable(name)
576d700 @kennyj Fix logs name consistency.
kennyj authored
512 variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
513 variables.first['Value'] unless variables.empty?
514 end
515
516 # Returns a table's primary key and belonging sequence.
517 def pk_and_sequence_for(table)
f3470b0 @kennyj Use show create table.
kennyj authored
518 execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
519 create_table = each_hash(result).first[:"Create Table"]
851b816 @amatsuda be sure to currectly fetch PK name from MySQL even if the PK has some cu...
amatsuda authored
520 if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
602e038 @seamusabshere thanks to @jurriaan
seamusabshere authored
521 keys = $1.split(",").map { |key| key.delete('`"') }
f3470b0 @kennyj Use show create table.
kennyj authored
522 keys.length == 1 ? [keys.first, nil] : nil
523 else
524 nil
525 end
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
526 end
527 end
528
529 # Returns just a table's primary key
530 def primary_key(table)
531 pk_and_sequence = pk_and_sequence_for(table)
532 pk_and_sequence && pk_and_sequence.first
533 end
534
535 def case_sensitive_modifier(node)
536 Arel::Nodes::Bin.new(node)
537 end
538
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when column h...
Joseph Palermo authored
539 def case_insensitive_comparison(table, attribute, column, value)
540 if column.case_sensitive?
541 super
542 else
543 table[attribute].eq(value)
544 end
545 end
546
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
547 def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
548 where_sql
549 end
550
551 protected
552
6f6c290 @rafaelfranca Fix delete_all when chained with joins.
rafaelfranca authored
553 # MySQL is too stupid to create a temporary table for use subquery, so we have
554 # to give it some prompting in the form of a subsubquery. Ugh!
555 def subquery_for(key, select)
556 subsubselect = select.clone
557 subsubselect.projections = [key]
558
559 subselect = Arel::SelectManager.new(select.engine)
560 subselect.project Arel.sql(key.name)
561 subselect.from subsubselect.as('__active_record_temp')
562 end
563
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
564 def add_index_length(option_strings, column_names, options = {})
565 if options.is_a?(Hash) && length = options[:length]
566 case length
567 when Hash
6cde635 @paul Handle nil in add_index :length option in MySQL
paul authored
568 column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
569 when Fixnum
570 column_names.each {|name| option_strings[name] += "(#{length})"}
571 end
572 end
573
574 return option_strings
575 end
576
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
577 def quoted_columns_for_index(column_names, options = {})
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
578 option_strings = Hash[column_names.map {|name| [name, '']}]
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
579
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
580 # add index length
581 option_strings = add_index_length(option_strings, column_names, options)
582
583 # add index sort order
584 option_strings = add_index_sort_order(option_strings, column_names, options)
585
586 column_names.map {|name| quote_column_name(name) + option_strings[name]}
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
587 end
588
589 def translate_exception(exception, message)
590 case error_number(exception)
591 when 1062
592 RecordNotUnique.new(message, exception)
593 when 1452
594 InvalidForeignKey.new(message, exception)
595 else
596 super
597 end
598 end
599
600 def add_column_sql(table_name, column_name, type, options = {})
601 add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
602 add_column_options!(add_column_sql, options)
603 add_column_position!(add_column_sql, options)
604 add_column_sql
605 end
606
607 def change_column_sql(table_name, column_name, type, options = {})
608 column = column_for(table_name, column_name)
609
610 unless options_include_default?(options)
611 options[:default] = column.default
612 end
613
614 unless options.has_key?(:null)
615 options[:null] = column.null
616 end
617
618 change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
619 add_column_options!(change_column_sql, options)
620 add_column_position!(change_column_sql, options)
621 change_column_sql
622 end
623
624 def rename_column_sql(table_name, column_name, new_column_name)
625 options = {}
626
627 if column = columns(table_name).find { |c| c.name == column_name.to_s }
628 options[:default] = column.default
629 options[:null] = column.null
630 else
631 raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
632 end
633
576d700 @kennyj Fix logs name consistency.
kennyj authored
634 current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
635 rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
636 add_column_options!(rename_column_sql, options)
637 rename_column_sql
638 end
639
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence it...
jonleighton authored
640 def remove_column_sql(table_name, *column_names)
641 columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
642 end
643 alias :remove_columns_sql :remove_column
644
645 def add_index_sql(table_name, column_name, options = {})
646 index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
647 "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
648 end
649
650 def remove_index_sql(table_name, options = {})
651 index_name = index_name_for_remove(table_name, options)
652 "DROP INDEX #{index_name}"
653 end
654
655 def add_timestamps_sql(table_name)
656 [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
657 end
658
659 def remove_timestamps_sql(table_name)
660 [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
661 end
662
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between Mysql...
jonleighton authored
663 private
664
665 def supports_views?
666 version[0] >= 5
667 end
668
669 def column_for(table_name, column_name)
670 unless column = columns(table_name).find { |c| c.name == column_name.to_s }
671 raise "No such column: #{table_name}.#{column_name}"
672 end
673 column
674 end
675 end
676 end
677 end
Something went wrong with that request. Please try again.