mysql2 0.2.6-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/CHANGELOG.md +117 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +240 -0
  6. data/Rakefile +5 -0
  7. data/VERSION +1 -0
  8. data/benchmark/active_record.rb +53 -0
  9. data/benchmark/allocations.rb +33 -0
  10. data/benchmark/escape.rb +39 -0
  11. data/benchmark/query_with_mysql_casting.rb +83 -0
  12. data/benchmark/query_without_mysql_casting.rb +50 -0
  13. data/benchmark/sequel.rb +39 -0
  14. data/benchmark/setup_db.rb +115 -0
  15. data/examples/eventmachine.rb +21 -0
  16. data/examples/threaded.rb +20 -0
  17. data/ext/mysql2/client.c +659 -0
  18. data/ext/mysql2/client.h +41 -0
  19. data/ext/mysql2/extconf.rb +65 -0
  20. data/ext/mysql2/mysql2_ext.c +12 -0
  21. data/ext/mysql2/mysql2_ext.h +32 -0
  22. data/ext/mysql2/result.c +475 -0
  23. data/ext/mysql2/result.h +20 -0
  24. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +63 -0
  25. data/lib/active_record/connection_adapters/mysql2_adapter.rb +654 -0
  26. data/lib/active_record/fiber_patches.rb +104 -0
  27. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +11 -0
  28. data/lib/mysql2.rb +16 -0
  29. data/lib/mysql2/client.rb +235 -0
  30. data/lib/mysql2/em.rb +33 -0
  31. data/lib/mysql2/error.rb +15 -0
  32. data/lib/mysql2/result.rb +5 -0
  33. data/mysql2.gemspec +89 -0
  34. data/spec/em/em_spec.rb +49 -0
  35. data/spec/mysql2/client_spec.rb +348 -0
  36. data/spec/mysql2/error_spec.rb +25 -0
  37. data/spec/mysql2/result_spec.rb +318 -0
  38. data/spec/rcov.opts +3 -0
  39. data/spec/spec_helper.rb +67 -0
  40. data/tasks/benchmarks.rake +8 -0
  41. data/tasks/compile.rake +54 -0
  42. data/tasks/jeweler.rake +17 -0
  43. data/tasks/rspec.rake +16 -0
  44. data/tasks/vendor_mysql.rake +41 -0
  45. metadata +120 -0
@@ -0,0 +1,20 @@
1
+ #ifndef MYSQL2_RESULT_H
2
+ #define MYSQL2_RESULT_H
3
+
4
+ void init_mysql2_result();
5
+ VALUE rb_mysql_result_to_obj(MYSQL_RES * r);
6
+
7
+ typedef struct {
8
+ VALUE fields;
9
+ VALUE rows;
10
+ VALUE encoding;
11
+ long numberOfFields;
12
+ unsigned long numberOfRows;
13
+ unsigned long lastRowProcessed;
14
+ short int resultFreed;
15
+ MYSQL_RES *result;
16
+ } mysql2_result_wrapper;
17
+
18
+ #define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
19
+
20
+ #endif
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ # AR adapter for using a fibered mysql2 connection with EM
4
+ # This adapter should be used within Thin or Unicorn with the rack-fiber_pool middleware.
5
+ # Just update your database.yml's adapter to be 'em_mysql2'
6
+
7
+ module ActiveRecord
8
+ class Base
9
+ def self.em_mysql2_connection(config)
10
+ client = ::Mysql2::Fibered::Client.new(config.symbolize_keys)
11
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
12
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'fiber'
18
+ require 'eventmachine' unless defined? EventMachine
19
+ require 'mysql2' unless defined? Mysql2
20
+ require 'active_record/connection_adapters/mysql2_adapter'
21
+ require 'active_record/fiber_patches'
22
+
23
+ module Mysql2
24
+ module Fibered
25
+ class Client < ::Mysql2::Client
26
+ module Watcher
27
+ def initialize(client, deferable)
28
+ @client = client
29
+ @deferable = deferable
30
+ end
31
+
32
+ def notify_readable
33
+ begin
34
+ detach
35
+ results = @client.async_result
36
+ @deferable.succeed(results)
37
+ rescue Exception => e
38
+ puts e.backtrace.join("\n\t")
39
+ @deferable.fail(e)
40
+ end
41
+ end
42
+ end
43
+
44
+ def query(sql, opts={})
45
+ if EM.reactor_running?
46
+ super(sql, opts.merge(:async => true))
47
+ deferable = ::EM::DefaultDeferrable.new
48
+ ::EM.watch(self.socket, Watcher, self, deferable).notify_readable = true
49
+ fiber = Fiber.current
50
+ deferable.callback do |result|
51
+ fiber.resume(result)
52
+ end
53
+ deferable.errback do |err|
54
+ fiber.resume(err)
55
+ end
56
+ Fiber.yield
57
+ else
58
+ super(sql, opts)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,654 @@
1
+ # encoding: utf-8
2
+
3
+ require 'mysql2' unless defined? Mysql2
4
+
5
+ module ActiveRecord
6
+ class Base
7
+ def self.mysql2_connection(config)
8
+ config[:username] = 'root' if config[:username].nil?
9
+
10
+ if Mysql2::Client.const_defined? :FOUND_ROWS
11
+ config[:flags] = Mysql2::Client::FOUND_ROWS
12
+ end
13
+
14
+ client = Mysql2::Client.new(config.symbolize_keys)
15
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
16
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
17
+ end
18
+ end
19
+
20
+ module ConnectionAdapters
21
+ class Mysql2Column < Column
22
+ BOOL = "tinyint(1)"
23
+ def extract_default(default)
24
+ if sql_type =~ /blob/i || type == :text
25
+ if default.blank?
26
+ return null ? nil : ''
27
+ else
28
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
29
+ end
30
+ elsif missing_default_forged_as_empty_string?(default)
31
+ nil
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def has_default?
38
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
39
+ super
40
+ end
41
+
42
+ # Returns the Ruby class that corresponds to the abstract data type.
43
+ def klass
44
+ case type
45
+ when :integer then Fixnum
46
+ when :float then Float
47
+ when :decimal then BigDecimal
48
+ when :datetime then Time
49
+ when :date then Date
50
+ when :timestamp then Time
51
+ when :time then Time
52
+ when :text, :string then String
53
+ when :binary then String
54
+ when :boolean then Object
55
+ end
56
+ end
57
+
58
+ def type_cast(value)
59
+ return nil if value.nil?
60
+ case type
61
+ when :string then value
62
+ when :text then value
63
+ when :integer then value.to_i rescue value ? 1 : 0
64
+ when :float then value.to_f # returns self if it's already a Float
65
+ when :decimal then self.class.value_to_decimal(value)
66
+ when :datetime, :timestamp then value.class == Time ? value : self.class.string_to_time(value)
67
+ when :time then value.class == Time ? value : self.class.string_to_dummy_time(value)
68
+ when :date then value.class == Date ? value : self.class.string_to_date(value)
69
+ when :binary then value
70
+ when :boolean then self.class.value_to_boolean(value)
71
+ else value
72
+ end
73
+ end
74
+
75
+ def type_cast_code(var_name)
76
+ case type
77
+ when :string then nil
78
+ when :text then nil
79
+ when :integer then "#{var_name}.to_i rescue #{var_name} ? 1 : 0"
80
+ when :float then "#{var_name}.to_f"
81
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
82
+ when :datetime, :timestamp then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_time(#{var_name})"
83
+ when :time then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_dummy_time(#{var_name})"
84
+ when :date then "#{var_name}.class == Date ? #{var_name} : #{self.class.name}.string_to_date(#{var_name})"
85
+ when :binary then nil
86
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
87
+ else nil
88
+ end
89
+ end
90
+
91
+ private
92
+ def simplified_type(field_type)
93
+ return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
94
+ return :string if field_type =~ /enum/i or field_type =~ /set/i
95
+ return :integer if field_type =~ /year/i
96
+ return :binary if field_type =~ /bit/i
97
+ super
98
+ end
99
+
100
+ def extract_limit(sql_type)
101
+ case sql_type
102
+ when /blob|text/i
103
+ case sql_type
104
+ when /tiny/i
105
+ 255
106
+ when /medium/i
107
+ 16777215
108
+ when /long/i
109
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
110
+ else
111
+ super # we could return 65535 here, but we leave it undecorated by default
112
+ end
113
+ when /^bigint/i; 8
114
+ when /^int/i; 4
115
+ when /^mediumint/i; 3
116
+ when /^smallint/i; 2
117
+ when /^tinyint/i; 1
118
+ else
119
+ super
120
+ end
121
+ end
122
+
123
+ # MySQL misreports NOT NULL column default when none is given.
124
+ # We can't detect this for columns which may have a legitimate ''
125
+ # default (string) but we can for others (integer, datetime, boolean,
126
+ # and the rest).
127
+ #
128
+ # Test whether the column has default '', is not null, and is not
129
+ # a type allowing default ''.
130
+ def missing_default_forged_as_empty_string?(default)
131
+ type != :string && !null && default == ''
132
+ end
133
+ end
134
+
135
+ class Mysql2Adapter < AbstractAdapter
136
+ cattr_accessor :emulate_booleans
137
+ self.emulate_booleans = true
138
+
139
+ ADAPTER_NAME = 'Mysql2'
140
+ PRIMARY = "PRIMARY"
141
+
142
+ LOST_CONNECTION_ERROR_MESSAGES = [
143
+ "Server shutdown in progress",
144
+ "Broken pipe",
145
+ "Lost connection to MySQL server during query",
146
+ "MySQL server has gone away" ]
147
+
148
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
149
+
150
+ NATIVE_DATABASE_TYPES = {
151
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
152
+ :string => { :name => "varchar", :limit => 255 },
153
+ :text => { :name => "text" },
154
+ :integer => { :name => "int", :limit => 4 },
155
+ :float => { :name => "float" },
156
+ :decimal => { :name => "decimal" },
157
+ :datetime => { :name => "datetime" },
158
+ :timestamp => { :name => "datetime" },
159
+ :time => { :name => "time" },
160
+ :date => { :name => "date" },
161
+ :binary => { :name => "blob" },
162
+ :boolean => { :name => "tinyint", :limit => 1 }
163
+ }
164
+
165
+ def initialize(connection, logger, connection_options, config)
166
+ super(connection, logger)
167
+ @connection_options, @config = connection_options, config
168
+ @quoted_column_names, @quoted_table_names = {}, {}
169
+ configure_connection
170
+ end
171
+
172
+ def adapter_name
173
+ ADAPTER_NAME
174
+ end
175
+
176
+ def supports_migrations?
177
+ true
178
+ end
179
+
180
+ def supports_primary_key?
181
+ true
182
+ end
183
+
184
+ def supports_savepoints?
185
+ true
186
+ end
187
+
188
+ def native_database_types
189
+ NATIVE_DATABASE_TYPES
190
+ end
191
+
192
+ # QUOTING ==================================================
193
+
194
+ def quote(value, column = nil)
195
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
196
+ s = column.class.string_to_binary(value).unpack("H*")[0]
197
+ "x'#{s}'"
198
+ elsif value.kind_of?(BigDecimal)
199
+ value.to_s("F")
200
+ else
201
+ super
202
+ end
203
+ end
204
+
205
+ def quote_column_name(name) #:nodoc:
206
+ @quoted_column_names[name] ||= "`#{name}`"
207
+ end
208
+
209
+ def quote_table_name(name) #:nodoc:
210
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
211
+ end
212
+
213
+ def quote_string(string)
214
+ @connection.escape(string)
215
+ end
216
+
217
+ def quoted_true
218
+ QUOTED_TRUE
219
+ end
220
+
221
+ def quoted_false
222
+ QUOTED_FALSE
223
+ end
224
+
225
+ # REFERENTIAL INTEGRITY ====================================
226
+
227
+ def disable_referential_integrity(&block) #:nodoc:
228
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
229
+
230
+ begin
231
+ update("SET FOREIGN_KEY_CHECKS = 0")
232
+ yield
233
+ ensure
234
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
235
+ end
236
+ end
237
+
238
+ # CONNECTION MANAGEMENT ====================================
239
+
240
+ def active?
241
+ return false unless @connection
242
+ @connection.query 'select 1'
243
+ true
244
+ rescue Mysql2::Error
245
+ false
246
+ end
247
+
248
+ def reconnect!
249
+ disconnect!
250
+ connect
251
+ end
252
+
253
+ # this is set to true in 2.3, but we don't want it to be
254
+ def requires_reloading?
255
+ false
256
+ end
257
+
258
+ def disconnect!
259
+ unless @connection.nil?
260
+ @connection.close
261
+ @connection = nil
262
+ end
263
+ end
264
+
265
+ def reset!
266
+ disconnect!
267
+ connect
268
+ end
269
+
270
+ # DATABASE STATEMENTS ======================================
271
+
272
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
273
+ #
274
+ # The overrides below perform much better than the originals in AbstractAdapter
275
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
276
+ #
277
+ # # Returns a record hash with the column names as keys and column values
278
+ # # as values.
279
+ # def select_one(sql, name = nil)
280
+ # result = execute(sql, name)
281
+ # result.each(:as => :hash) do |r|
282
+ # return r
283
+ # end
284
+ # end
285
+ #
286
+ # # Returns a single value from a record
287
+ # def select_value(sql, name = nil)
288
+ # result = execute(sql, name)
289
+ # if first = result.first
290
+ # first.first
291
+ # end
292
+ # end
293
+ #
294
+ # # Returns an array of the values of the first column in a select:
295
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
296
+ # def select_values(sql, name = nil)
297
+ # execute(sql, name).map { |row| row.first }
298
+ # end
299
+
300
+ # Returns an array of arrays containing the field values.
301
+ # Order is the same as that returned by +columns+.
302
+ def select_rows(sql, name = nil)
303
+ execute(sql, name).to_a
304
+ end
305
+
306
+ # Executes the SQL statement in the context of this connection.
307
+ def execute(sql, name = nil)
308
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
309
+ # made since we established the connection
310
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
311
+ if name == :skip_logging
312
+ @connection.query(sql)
313
+ else
314
+ log(sql, name) { @connection.query(sql) }
315
+ end
316
+ rescue ActiveRecord::StatementInvalid => exception
317
+ if exception.message.split(":").first =~ /Packets out of order/
318
+ 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."
319
+ else
320
+ raise
321
+ end
322
+ end
323
+
324
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
325
+ super
326
+ id_value || @connection.last_id
327
+ end
328
+ alias :create :insert_sql
329
+
330
+ def update_sql(sql, name = nil)
331
+ super
332
+ @connection.affected_rows
333
+ end
334
+
335
+ def begin_db_transaction
336
+ execute "BEGIN"
337
+ rescue Exception
338
+ # Transactions aren't supported
339
+ end
340
+
341
+ def commit_db_transaction
342
+ execute "COMMIT"
343
+ rescue Exception
344
+ # Transactions aren't supported
345
+ end
346
+
347
+ def rollback_db_transaction
348
+ execute "ROLLBACK"
349
+ rescue Exception
350
+ # Transactions aren't supported
351
+ end
352
+
353
+ def create_savepoint
354
+ execute("SAVEPOINT #{current_savepoint_name}")
355
+ end
356
+
357
+ def rollback_to_savepoint
358
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
359
+ end
360
+
361
+ def release_savepoint
362
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
363
+ end
364
+
365
+ def add_limit_offset!(sql, options)
366
+ limit, offset = options[:limit], options[:offset]
367
+ if limit && offset
368
+ sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
369
+ elsif limit
370
+ sql << " LIMIT #{sanitize_limit(limit)}"
371
+ elsif offset
372
+ sql << " OFFSET #{offset.to_i}"
373
+ end
374
+ sql
375
+ end
376
+
377
+ # SCHEMA STATEMENTS ========================================
378
+
379
+ def structure_dump
380
+ if supports_views?
381
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
382
+ else
383
+ sql = "SHOW TABLES"
384
+ end
385
+
386
+ select_all(sql).inject("") do |structure, table|
387
+ table.delete('Table_type')
388
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
389
+ end
390
+ end
391
+
392
+ def recreate_database(name, options = {})
393
+ drop_database(name)
394
+ create_database(name, options)
395
+ end
396
+
397
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
398
+ # Charset defaults to utf8.
399
+ #
400
+ # Example:
401
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
402
+ # create_database 'matt_development'
403
+ # create_database 'matt_development', :charset => :big5
404
+ def create_database(name, options = {})
405
+ if options[:collation]
406
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
407
+ else
408
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
409
+ end
410
+ end
411
+
412
+ def drop_database(name) #:nodoc:
413
+ execute "DROP DATABASE IF EXISTS `#{name}`"
414
+ end
415
+
416
+ def current_database
417
+ select_value 'SELECT DATABASE() as db'
418
+ end
419
+
420
+ # Returns the database character set.
421
+ def charset
422
+ show_variable 'character_set_database'
423
+ end
424
+
425
+ # Returns the database collation strategy.
426
+ def collation
427
+ show_variable 'collation_database'
428
+ end
429
+
430
+ def tables(name = nil)
431
+ tables = []
432
+ execute("SHOW TABLES", name).each do |field|
433
+ tables << field.first
434
+ end
435
+ tables
436
+ end
437
+
438
+ def drop_table(table_name, options = {})
439
+ super(table_name, options)
440
+ end
441
+
442
+ def indexes(table_name, name = nil)
443
+ indexes = []
444
+ current_index = nil
445
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
446
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
447
+ if current_index != row[:Key_name]
448
+ next if row[:Key_name] == PRIMARY # skip the primary key
449
+ current_index = row[:Key_name]
450
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
451
+ end
452
+
453
+ indexes.last.columns << row[:Column_name]
454
+ indexes.last.lengths << row[:Sub_part]
455
+ end
456
+ indexes
457
+ end
458
+
459
+ def columns(table_name, name = nil)
460
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
461
+ columns = []
462
+ result = execute(sql, :skip_logging)
463
+ result.each(:symbolize_keys => true, :as => :hash) { |field|
464
+ columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
465
+ }
466
+ columns
467
+ end
468
+
469
+ def create_table(table_name, options = {})
470
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
471
+ end
472
+
473
+ def rename_table(table_name, new_name)
474
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
475
+ end
476
+
477
+ def add_column(table_name, column_name, type, options = {})
478
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
479
+ add_column_options!(add_column_sql, options)
480
+ add_column_position!(add_column_sql, options)
481
+ execute(add_column_sql)
482
+ end
483
+
484
+ def change_column_default(table_name, column_name, default)
485
+ column = column_for(table_name, column_name)
486
+ change_column table_name, column_name, column.sql_type, :default => default
487
+ end
488
+
489
+ def change_column_null(table_name, column_name, null, default = nil)
490
+ column = column_for(table_name, column_name)
491
+
492
+ unless null || default.nil?
493
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
494
+ end
495
+
496
+ change_column table_name, column_name, column.sql_type, :null => null
497
+ end
498
+
499
+ def change_column(table_name, column_name, type, options = {})
500
+ column = column_for(table_name, column_name)
501
+
502
+ unless options_include_default?(options)
503
+ options[:default] = column.default
504
+ end
505
+
506
+ unless options.has_key?(:null)
507
+ options[:null] = column.null
508
+ end
509
+
510
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
511
+ add_column_options!(change_column_sql, options)
512
+ add_column_position!(change_column_sql, options)
513
+ execute(change_column_sql)
514
+ end
515
+
516
+ def rename_column(table_name, column_name, new_column_name)
517
+ options = {}
518
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
519
+ options[:default] = column.default
520
+ options[:null] = column.null
521
+ else
522
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
523
+ end
524
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
525
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
526
+ add_column_options!(rename_column_sql, options)
527
+ execute(rename_column_sql)
528
+ end
529
+
530
+ # Maps logical Rails types to MySQL-specific data types.
531
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
532
+ return super unless type.to_s == 'integer'
533
+
534
+ case limit
535
+ when 1; 'tinyint'
536
+ when 2; 'smallint'
537
+ when 3; 'mediumint'
538
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
539
+ when 5..8; 'bigint'
540
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
541
+ end
542
+ end
543
+
544
+ def add_column_position!(sql, options)
545
+ if options[:first]
546
+ sql << " FIRST"
547
+ elsif options[:after]
548
+ sql << " AFTER #{quote_column_name(options[:after])}"
549
+ end
550
+ end
551
+
552
+ def show_variable(name)
553
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
554
+ variables.first['Value'] unless variables.empty?
555
+ end
556
+
557
+ def pk_and_sequence_for(table)
558
+ keys = []
559
+ result = execute("describe #{quote_table_name(table)}")
560
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
561
+ keys << row[:Field] if row[:Key] == "PRI"
562
+ end
563
+ keys.length == 1 ? [keys.first, nil] : nil
564
+ end
565
+
566
+ # Returns just a table's primary key
567
+ def primary_key(table)
568
+ pk_and_sequence = pk_and_sequence_for(table)
569
+ pk_and_sequence && pk_and_sequence.first
570
+ end
571
+
572
+ def case_sensitive_equality_operator
573
+ "= BINARY"
574
+ end
575
+
576
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
577
+ where_sql
578
+ end
579
+
580
+ protected
581
+ def quoted_columns_for_index(column_names, options = {})
582
+ length = options[:length] if options.is_a?(Hash)
583
+
584
+ quoted_column_names = case length
585
+ when Hash
586
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
587
+ when Fixnum
588
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
589
+ else
590
+ column_names.map {|name| quote_column_name(name) }
591
+ end
592
+ end
593
+
594
+ def translate_exception(exception, message)
595
+ return super unless exception.respond_to?(:error_number)
596
+
597
+ case exception.error_number
598
+ when 1062
599
+ RecordNotUnique.new(message, exception)
600
+ when 1452
601
+ InvalidForeignKey.new(message, exception)
602
+ else
603
+ super
604
+ end
605
+ end
606
+
607
+ private
608
+ def connect
609
+ @connection = Mysql2::Client.new(@config)
610
+ configure_connection
611
+ end
612
+
613
+ def configure_connection
614
+ @connection.query_options.merge!(:as => :array)
615
+
616
+ # By default, MySQL 'where id is null' selects the last inserted id.
617
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
618
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
619
+ encoding = @config[:encoding]
620
+
621
+ # make sure we set the encoding
622
+ variable_assignments << "NAMES '#{encoding}'" if encoding
623
+
624
+ # increase timeout so mysql server doesn't disconnect us
625
+ wait_timeout = @config[:wait_timeout]
626
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
627
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
628
+
629
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
630
+ end
631
+
632
+ # Returns an array of record hashes with the column names as keys and
633
+ # column values as values.
634
+ def select(sql, name = nil)
635
+ execute(sql, name).each(:as => :hash)
636
+ end
637
+
638
+ def supports_views?
639
+ version[0] >= 5
640
+ end
641
+
642
+ def version
643
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
644
+ end
645
+
646
+ def column_for(table_name, column_name)
647
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
648
+ raise "No such column: #{table_name}.#{column_name}"
649
+ end
650
+ column
651
+ end
652
+ end
653
+ end
654
+ end