ghazel-mysql2 0.2.6.1

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