Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 757 lines (628 sloc) 26.054 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 My…
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:
af8c8b4 @jonleighton The default value of a text/blob in mysql strict mode should be nil
jonleighton authored
7 attr_reader :collation, :strict
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
8
af8c8b4 @jonleighton The default value of a text/blob in mysql strict mode should be nil
jonleighton authored
9 def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false)
10 @strict = strict
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
11 @collation = collation
af8c8b4 @jonleighton The default value of a text/blob in mysql strict mode should be nil
jonleighton authored
12
13 super(name, default, sql_type, null)
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
14 end
15
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
16 def extract_default(default)
6415d3a @acapilleri small refactoring, added blob_or_text_colum? in AbstractMysqlAdapter
acapilleri authored
17 if blob_or_text_column?
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
18 if default.blank?
af8c8b4 @jonleighton The default value of a text/blob in mysql strict mode should be nil
jonleighton authored
19 null || strict ? nil : ''
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
20 else
21 raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
22 end
23 elsif missing_default_forged_as_empty_string?(default)
24 nil
25 else
26 super
27 end
28 end
29
30 def has_default?
6415d3a @acapilleri small refactoring, added blob_or_text_colum? in AbstractMysqlAdapter
acapilleri authored
31 return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
32 super
33 end
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
34
6415d3a @acapilleri small refactoring, added blob_or_text_colum? in AbstractMysqlAdapter
acapilleri authored
35 def blob_or_text_column?
36 sql_type =~ /blob/i || type == :text
37 end
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
38
4fcd847 @jonleighton Extract simplified_type into the abstract class
jonleighton authored
39 # Must return the relevant concrete adapter
40 def adapter
41 raise NotImplementedError
42 end
43
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
44 def case_sensitive?
45 collation && !collation.match(/_ci$/)
46 end
47
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
48 private
49
4fcd847 @jonleighton Extract simplified_type into the abstract class
jonleighton authored
50 def simplified_type(field_type)
51 return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
52
53 case field_type
54 when /enum/i, /set/i then :string
55 when /year/i then :integer
56 when /bit/i then :binary
57 else
58 super
59 end
60 end
61
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
62 def extract_limit(sql_type)
63 case sql_type
64 when /blob|text/i
65 case sql_type
66 when /tiny/i
67 255
68 when /medium/i
69 16777215
70 when /long/i
71 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
72 else
73 super # we could return 65535 here, but we leave it undecorated by default
74 end
75 when /^bigint/i; 8
76 when /^int/i; 4
77 when /^mediumint/i; 3
78 when /^smallint/i; 2
79 when /^tinyint/i; 1
f498000 @masarakki fix: limit of enum columns of mysql
masarakki authored
80 when /^enum\((.+)\)/i
81 $1.split(',').map{|enum| enum.strip.length - 2}.max
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
82 else
83 super
84 end
85 end
86
87 # MySQL misreports NOT NULL column default when none is given.
88 # We can't detect this for columns which may have a legitimate ''
89 # default (string) but we can for others (integer, datetime, boolean,
90 # and the rest).
91 #
92 # Test whether the column has default '', is not null, and is not
93 # a type allowing default ''.
94 def missing_default_forged_as_empty_string?(default)
95 type != :string && !null && default == ''
96 end
97 end
98
99 ##
100 # :singleton-method:
101 # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
102 # as boolean. If you wish to disable this emulation (which was the default
103 # behavior in versions 0.13.1 and earlier) you can add the following line
104 # to your application.rb file:
105 #
106 # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
107 class_attribute :emulate_booleans
108 self.emulate_booleans = true
109
110 LOST_CONNECTION_ERROR_MESSAGES = [
111 "Server shutdown in progress",
112 "Broken pipe",
113 "Lost connection to MySQL server during query",
114 "MySQL server has gone away" ]
115
116 QUOTED_TRUE, QUOTED_FALSE = '1', '0'
117
118 NATIVE_DATABASE_TYPES = {
119 :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
120 :string => { :name => "varchar", :limit => 255 },
121 :text => { :name => "text" },
122 :integer => { :name => "int", :limit => 4 },
123 :float => { :name => "float" },
124 :decimal => { :name => "decimal" },
125 :datetime => { :name => "datetime" },
126 :timestamp => { :name => "datetime" },
127 :time => { :name => "time" },
128 :date => { :name => "date" },
129 :binary => { :name => "blob" },
130 :boolean => { :name => "tinyint", :limit => 1 }
131 }
132
fd39847 @tenderlove prepared statements can be disabled
tenderlove authored
133 class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
134 include Arel::Visitors::BindVisitor
135 end
136
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
137 # FIXME: Make the first parameter more similar for the two adapters
138 def initialize(connection, logger, connection_options, config)
139 super(connection, logger)
140 @connection_options, @config = connection_options, config
141 @quoted_column_names, @quoted_table_names = {}, {}
fd39847 @tenderlove prepared statements can be disabled
tenderlove authored
142
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
143 if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
fd39847 @tenderlove prepared statements can be disabled
tenderlove authored
144 @visitor = Arel::Visitors::MySQL.new self
145 else
146 @visitor = BindSubstitution.new self
147 end
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
148 end
149
150 def adapter_name #:nodoc:
151 self.class::ADAPTER_NAME
152 end
153
154 # Returns true, since this connection adapter supports migrations.
155 def supports_migrations?
156 true
157 end
158
159 def supports_primary_key?
160 true
161 end
162
163 # Returns true, since this connection adapter supports savepoints.
164 def supports_savepoints?
165 true
166 end
167
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence…
jonleighton authored
168 def supports_bulk_alter? #:nodoc:
169 true
170 end
171
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
172 # Technically MySQL allows to create indexes with the sort order syntax
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
173 # but at the moment (5.5) it doesn't yet implement them
174 def supports_index_sort_order?
175 true
176 end
177
392eeec @jonleighton Support for specifying transaction isolation level
jonleighton authored
178 # MySQL 4 technically support transaction isolation, but it is affected by a bug
179 # where the transaction level gets persisted for the whole session:
180 #
181 # http://bugs.mysql.com/bug.php?id=39170
182 def supports_transaction_isolation?
183 version[0] >= 5
184 end
185
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
186 def native_database_types
187 NATIVE_DATABASE_TYPES
188 end
189
190 # HELPER METHODS ===========================================
191
192 # The two drivers have slightly different ways of yielding hashes of results, so
193 # this method must be implemented to provide a uniform interface.
194 def each_hash(result) # :nodoc:
195 raise NotImplementedError
196 end
197
198 # Overridden by the adapters to instantiate their specific Column type.
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
199 def new_column(field, default, type, null, collation) # :nodoc:
200 Column.new(field, default, type, null, collation)
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
201 end
202
203 # Must return the Mysql error number from the exception, if the exception has an
204 # error number.
205 def error_number(exception) # :nodoc:
206 raise NotImplementedError
207 end
208
209 # QUOTING ==================================================
210
211 def quote(value, column = nil)
212 if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
213 s = column.class.string_to_binary(value).unpack("H*")[0]
214 "x'#{s}'"
afd4a14 @steveklabnik Revert "Merge pull request #9207 from dylanahsmith/mysql-quote-numeric"
steveklabnik authored
215 elsif value.kind_of?(BigDecimal)
216 value.to_s("F")
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
217 else
218 super
219 end
220 end
221
222 def quote_column_name(name) #:nodoc:
223 @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
224 end
225
226 def quote_table_name(name) #:nodoc:
227 @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
228 end
229
230 def quoted_true
231 QUOTED_TRUE
232 end
233
234 def quoted_false
235 QUOTED_FALSE
236 end
237
238 # REFERENTIAL INTEGRITY ====================================
239
240 def disable_referential_integrity(&block) #:nodoc:
241 old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
242
243 begin
244 update("SET FOREIGN_KEY_CHECKS = 0")
245 yield
246 ensure
247 update("SET FOREIGN_KEY_CHECKS = #{old}")
248 end
249 end
250
251 # DATABASE STATEMENTS ======================================
252
253 # Executes the SQL statement in the context of this connection.
254 def execute(sql, name = nil)
255 if name == :skip_logging
256 @connection.query(sql)
257 else
258 log(sql, name) { @connection.query(sql) }
259 end
260 rescue ActiveRecord::StatementInvalid => exception
261 if exception.message.split(":").first =~ /Packets out of order/
262 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."
263 else
264 raise
265 end
266 end
267
268 # 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
269 # 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 My…
jonleighton authored
270 # explicitly freed or not.
271 def execute_and_free(sql, name = nil) #:nodoc:
272 yield execute(sql, name)
273 end
274
275 def update_sql(sql, name = nil) #:nodoc:
276 super
277 @connection.affected_rows
278 end
279
280 def begin_db_transaction
281 execute "BEGIN"
c989160 @dylanahsmith Avoid unnecessary catching of Exception instead of StandardError.
dylanahsmith authored
282 rescue
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
283 # Transactions aren't supported
284 end
285
392eeec @jonleighton Support for specifying transaction isolation level
jonleighton authored
286 def begin_isolated_db_transaction(isolation)
287 execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
288 begin_db_transaction
289 rescue
290 # Transactions aren't supported
291 end
292
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
293 def commit_db_transaction #:nodoc:
294 execute "COMMIT"
c989160 @dylanahsmith Avoid unnecessary catching of Exception instead of StandardError.
dylanahsmith authored
295 rescue
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
296 # Transactions aren't supported
297 end
298
299 def rollback_db_transaction #:nodoc:
300 execute "ROLLBACK"
c989160 @dylanahsmith Avoid unnecessary catching of Exception instead of StandardError.
dylanahsmith authored
301 rescue
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
302 # Transactions aren't supported
303 end
304
305 def create_savepoint
306 execute("SAVEPOINT #{current_savepoint_name}")
307 end
308
309 def rollback_to_savepoint
310 execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
311 end
312
313 def release_savepoint
314 execute("RELEASE SAVEPOINT #{current_savepoint_name}")
315 end
316
317 # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
318 # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
6f6c290 @rafaelfranca Fix delete_all when chained with joins.
rafaelfranca authored
319 # these, we must use a subquery.
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
320 def join_to_update(update, select) #:nodoc:
321 if select.limit || select.offset || select.orders.any?
6f6c290 @rafaelfranca Fix delete_all when chained with joins.
rafaelfranca authored
322 super
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
323 else
324 update.table select.source
325 update.wheres = select.constraints
326 end
327 end
328
144e869 @jonleighton Support for partial inserts.
jonleighton authored
329 def empty_insert_statement_value
330 "VALUES ()"
331 end
332
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
333 # SCHEMA STATEMENTS ========================================
334
335 def structure_dump #:nodoc:
336 if supports_views?
337 sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
338 else
339 sql = "SHOW TABLES"
340 end
341
576d700 @kennyj Fix logs name consistency.
kennyj authored
342 select_all(sql, 'SCHEMA').map { |table|
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
343 table.delete('Table_type')
344 sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
220864e @jeremycole Fixing texts; down to three failing tests.
jeremycole authored
345 exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
97ca635 @tenderlove Join method uses empty string by default, so remove it
tenderlove authored
346 }.join
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
347 end
348
349 # Drops the database specified on the +name+ attribute
350 # and creates it again using the provided +options+.
351 def recreate_database(name, options = {})
352 drop_database(name)
353 create_database(name, options)
354 end
355
356 # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
357 # Charset defaults to utf8.
358 #
359 # Example:
9edd4e1 @AvnerCohen Migration of docs to 1.9 hash syntax
AvnerCohen authored
360 # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
361 # create_database 'matt_development'
9edd4e1 @AvnerCohen Migration of docs to 1.9 hash syntax
AvnerCohen authored
362 # create_database 'matt_development', charset: :big5
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
363 def create_database(name, options = {})
364 if options[:collation]
365 execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
366 else
367 execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
368 end
369 end
370
371 # Drops a MySQL database.
372 #
373 # Example:
374 # drop_database('sebastian_development')
375 def drop_database(name) #:nodoc:
376 execute "DROP DATABASE IF EXISTS `#{name}`"
377 end
378
379 def current_database
380 select_value 'SELECT DATABASE() as db'
381 end
382
383 # Returns the database character set.
384 def charset
385 show_variable 'character_set_database'
386 end
387
388 # Returns the database collation strategy.
389 def collation
390 show_variable 'collation_database'
391 end
392
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
393 def tables(name = nil, database = nil, like = nil) #:nodoc:
394 sql = "SHOW TABLES "
69c7f02 @kennyj Fix GH #3163. Should quote database on mysql/mysql2.
kennyj authored
395 sql << "IN #{quote_table_name(database)} " if database
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
396 sql << "LIKE #{quote(like)}" if like
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
397
398 execute_and_free(sql, 'SCHEMA') do |result|
399 result.collect { |field| field.first }
400 end
401 end
402
403 def table_exists?(name)
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
404 return false unless name
405 return true if tables(nil, nil, name).any?
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
406
407 name = name.to_s
408 schema, table = name.split('.', 2)
409
410 unless table # A table was provided without a schema
411 table = schema
412 schema = nil
413 end
414
38703ac @jonleighton Revert naive O(1) table_exists? implementation.
jonleighton authored
415 tables(nil, schema, table).any?
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
416 end
417
418 # Returns an array of indexes for the given table.
419 def indexes(table_name, name = nil) #:nodoc:
420 indexes = []
421 current_index = nil
422 execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
423 each_hash(result) do |row|
424 if current_index != row[:Key_name]
425 next if row[:Key_name] == 'PRIMARY' # skip the primary key
426 current_index = row[:Key_name]
427 indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
428 end
429
430 indexes.last.columns << row[:Column_name]
431 indexes.last.lengths << row[:Sub_part]
432 end
433 end
434
435 indexes
436 end
437
438 # Returns an array of +Column+ objects for the table specified by +table_name+.
dcd74eb @smartinez87 Remove useless argument in #columns.
smartinez87 authored
439 def columns(table_name)#:nodoc:
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
440 sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
441 execute_and_free(sql, 'SCHEMA') do |result|
442 each_hash(result).map do |field|
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
443 new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
444 end
445 end
446 end
447
448 def create_table(table_name, options = {}) #:nodoc:
449 super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
450 end
451
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence…
jonleighton authored
452 def bulk_change_table(table_name, operations) #:nodoc:
a0613ad @spastorino Revert "Use flat_map { } instead of map {}.flatten"
spastorino authored
453 sqls = operations.map do |command, args|
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence…
jonleighton authored
454 table, arguments = args.shift, args
455 method = :"#{command}_sql"
456
19c7124 @tenderlove more ruby 2.0 respond_to? changes
tenderlove authored
457 if respond_to?(method, true)
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence…
jonleighton authored
458 send(method, table, *arguments)
459 else
460 raise "Unknown method called : #{method}(#{arguments.inspect})"
461 end
a0613ad @spastorino Revert "Use flat_map { } instead of map {}.flatten"
spastorino authored
462 end.flatten.join(", ")
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence…
jonleighton authored
463
464 execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
465 end
466
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
467 # Renames a table.
468 #
469 # Example:
470 # rename_table('octopuses', 'octopi')
471 def rename_table(table_name, new_name)
472 execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
39eef1a @senny also rename indexes when a table or column is renamed
senny authored
473 rename_table_indexes(table_name, new_name)
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
474 end
475
476 def add_column(table_name, column_name, type, options = {})
477 execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
478 end
479
480 def change_column_default(table_name, column_name, default)
481 column = column_for(table_name, column_name)
482 change_column table_name, column_name, column.sql_type, :default => default
483 end
484
485 def change_column_null(table_name, column_name, null, default = nil)
486 column = column_for(table_name, column_name)
487
488 unless null || default.nil?
489 execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
490 end
491
492 change_column table_name, column_name, column.sql_type, :null => null
493 end
494
495 def change_column(table_name, column_name, type, options = {}) #:nodoc:
496 execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
497 end
498
499 def rename_column(table_name, column_name, new_column_name) #:nodoc:
500 execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
39eef1a @senny also rename indexes when a table or column is renamed
senny authored
501 rename_column_indexes(table_name, column_name, new_column_name)
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
502 end
503
504 # Maps logical Rails types to MySQL-specific data types.
505 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
506 case type.to_s
5d30e44 @pwnall Use the MySQL varbinary type when appropriate in migrations.
pwnall authored
507 when 'binary'
508 case limit
509 when 0..0xfff; "varbinary(#{limit})"
510 when nil; "blob"
511 when 0x1000..0xffffffff; "blob(#{limit})"
512 else raise(ActiveRecordError, "No binary type has character length #{limit}")
513 end
fe7cacb @kennyj Fix type_to_sql with text and limit on mysql/mysql2. Fix GH #3931.
kennyj authored
514 when 'integer'
515 case limit
516 when 1; 'tinyint'
517 when 2; 'smallint'
518 when 3; 'mediumint'
519 when nil, 4, 11; 'int(11)' # compatibility with MySQL default
520 when 5..8; 'bigint'
521 else raise(ActiveRecordError, "No integer type has byte size #{limit}")
522 end
523 when 'text'
524 case limit
525 when 0..0xff; 'tinytext'
526 when nil, 0x100..0xffff; 'text'
527 when 0x10000..0xffffff; 'mediumtext'
528 when 0x1000000..0xffffffff; 'longtext'
529 else raise(ActiveRecordError, "No text type has character length #{limit}")
530 end
531 else
532 super
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
533 end
534 end
535
536 def add_column_position!(sql, options)
537 if options[:first]
538 sql << " FIRST"
539 elsif options[:after]
540 sql << " AFTER #{quote_column_name(options[:after])}"
541 end
542 end
543
544 # SHOW VARIABLES LIKE 'name'
545 def show_variable(name)
576d700 @kennyj Fix logs name consistency.
kennyj authored
546 variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
547 variables.first['Value'] unless variables.empty?
548 end
549
550 # Returns a table's primary key and belonging sequence.
551 def pk_and_sequence_for(table)
f3470b0 @kennyj Use show create table.
kennyj authored
552 execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
553 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…
amatsuda authored
554 if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
602e038 @seamusabshere thanks to @jurriaan
seamusabshere authored
555 keys = $1.split(",").map { |key| key.delete('`"') }
f3470b0 @kennyj Use show create table.
kennyj authored
556 keys.length == 1 ? [keys.first, nil] : nil
557 else
558 nil
559 end
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
560 end
561 end
562
563 # Returns just a table's primary key
564 def primary_key(table)
565 pk_and_sequence = pk_and_sequence_for(table)
566 pk_and_sequence && pk_and_sequence.first
567 end
568
569 def case_sensitive_modifier(node)
570 Arel::Nodes::Bin.new(node)
571 end
572
c90e5ce Only use LOWER for mysql case insensitive uniqueness check when colum…
Joseph Palermo authored
573 def case_insensitive_comparison(table, attribute, column, value)
574 if column.case_sensitive?
575 super
576 else
577 table[attribute].eq(value)
578 end
579 end
580
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
581 def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
582 where_sql
583 end
584
af8c8b4 @jonleighton The default value of a text/blob in mysql strict mode should be nil
jonleighton authored
585 def strict_mode?
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
586 self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
af8c8b4 @jonleighton The default value of a text/blob in mysql strict mode should be nil
jonleighton authored
587 end
588
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
589 protected
590
6f6c290 @rafaelfranca Fix delete_all when chained with joins.
rafaelfranca authored
591 # MySQL is too stupid to create a temporary table for use subquery, so we have
592 # to give it some prompting in the form of a subsubquery. Ugh!
593 def subquery_for(key, select)
594 subsubselect = select.clone
595 subsubselect.projections = [key]
596
597 subselect = Arel::SelectManager.new(select.engine)
598 subselect.project Arel.sql(key.name)
599 subselect.from subsubselect.as('__active_record_temp')
600 end
601
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
602 def add_index_length(option_strings, column_names, options = {})
603 if options.is_a?(Hash) && length = options[:length]
604 case length
605 when Hash
6cde635 @paul Handle nil in add_index :length option in MySQL
paul authored
606 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
607 when Fixnum
608 column_names.each {|name| option_strings[name] += "(#{length})"}
609 end
610 end
611
612 return option_strings
613 end
614
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
615 def quoted_columns_for_index(column_names, options = {})
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
616 option_strings = Hash[column_names.map {|name| [name, '']}]
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
617
69dcd45 @vjebelev AR changes to support creating ordered (asc, desc) indexes
vjebelev authored
618 # add index length
619 option_strings = add_index_length(option_strings, column_names, options)
620
621 # add index sort order
622 option_strings = add_index_sort_order(option_strings, column_names, options)
623
624 column_names.map {|name| quote_column_name(name) + option_strings[name]}
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
625 end
626
627 def translate_exception(exception, message)
628 case error_number(exception)
629 when 1062
630 RecordNotUnique.new(message, exception)
631 when 1452
632 InvalidForeignKey.new(message, exception)
633 else
634 super
635 end
636 end
637
638 def add_column_sql(table_name, column_name, type, options = {})
639 add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
640 add_column_options!(add_column_sql, options)
641 add_column_position!(add_column_sql, options)
642 add_column_sql
643 end
644
645 def change_column_sql(table_name, column_name, type, options = {})
646 column = column_for(table_name, column_name)
647
648 unless options_include_default?(options)
649 options[:default] = column.default
650 end
651
652 unless options.has_key?(:null)
653 options[:null] = column.null
654 end
655
656 change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
657 add_column_options!(change_column_sql, options)
658 add_column_position!(change_column_sql, options)
659 change_column_sql
660 end
661
662 def rename_column_sql(table_name, column_name, new_column_name)
663 options = {}
664
665 if column = columns(table_name).find { |c| c.name == column_name.to_s }
666 options[:default] = column.default
667 options[:null] = column.null
668 else
669 raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
670 end
671
576d700 @kennyj Fix logs name consistency.
kennyj authored
672 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 My…
jonleighton authored
673 rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
674 add_column_options!(rename_column_sql, options)
675 rename_column_sql
676 end
677
a4932d6 @marcandre Fixes for PR [#8267]
marcandre authored
678 def remove_column_sql(table_name, column_name, type = nil, options = {})
679 "DROP #{quote_column_name(column_name)}"
680 end
681
682 def remove_columns_sql(table_name, *column_names)
683 column_names.map {|column_name| remove_column_sql(table_name, column_name) }
fd22d04 @jonleighton Move the bulk alter table code into the abstract mysql adapter, hence…
jonleighton authored
684 end
685
686 def add_index_sql(table_name, column_name, options = {})
687 index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
688 "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
689 end
690
691 def remove_index_sql(table_name, options = {})
692 index_name = index_name_for_remove(table_name, options)
693 "DROP INDEX #{index_name}"
694 end
695
696 def add_timestamps_sql(table_name)
697 [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
698 end
699
700 def remove_timestamps_sql(table_name)
701 [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
702 end
703
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
704 private
705
706 def supports_views?
707 version[0] >= 5
708 end
709
710 def column_for(table_name, column_name)
711 unless column = columns(table_name).find { |c| c.name == column_name.to_s }
712 raise "No such column: #{table_name}.#{column_name}"
713 end
714 column
715 end
97d06e8 @sodabrew Session variables for mysql, mysql2, and postgresql adapters can be set
sodabrew authored
716
717 def configure_connection
718 variables = @config[:variables] || {}
719
720 # By default, MySQL 'where id is null' selects the last inserted id.
721 # Turn this off. http://dev.rubyonrails.org/ticket/6778
722 variables[:sql_auto_is_null] = 0
723
724 # Increase timeout so the server doesn't disconnect us.
725 wait_timeout = @config[:wait_timeout]
726 wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
e54acf1 @rafaelfranca Do not type cast all the database url values.
rafaelfranca authored
727 variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout)
97d06e8 @sodabrew Session variables for mysql, mysql2, and postgresql adapters can be set
sodabrew authored
728
729 # Make MySQL reject illegal values rather than truncating or blanking them, see
730 # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
731 # If the user has provided another value for sql_mode, don't replace it.
732 if strict_mode? && !variables.has_key?(:sql_mode)
733 variables[:sql_mode] = 'STRICT_ALL_TABLES'
734 end
735
736 # NAMES does not have an equals sign, see
737 # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
738 # (trailing comma because variable_assignments will always have content)
739 encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
740
741 # Gather up all of the SET variables...
742 variable_assignments = variables.map do |k, v|
743 if v == ':default' || v == :default
744 "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
745 elsif !v.nil?
746 "@@SESSION.#{k.to_s} = #{quote(v)}"
747 end
748 # or else nil; compact to clear nils out
749 end.compact.join(', ')
750
751 # ...and send them all in one query
752 execute("SET #{encoding} #{variable_assignments}", :skip_logging)
753 end
5766539 @jonleighton Create an AbstractMysqlAdapter to abstract the common code between My…
jonleighton authored
754 end
755 end
756 end
Something went wrong with that request. Please try again.