activerecord-mysql2-adapter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@mysql2 --create
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Matthias Viehweger
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Activerecord::Mysql2::Adapter
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'activerecord-mysql2-adapter'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install activerecord-mysql2-adapter
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,18 @@
1
+ # vim:fileencoding=utf-8
2
+ require File.expand_path('../lib/activerecord-mysql2-adapter/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Matthias Viehweger"]
6
+ gem.email = ["kronn@kronn.de"]
7
+ gem.description = %q{extracted code from mysql2}
8
+ gem.summary = %q{extracted code from mysql2}
9
+ gem.homepage = %q{http://github.com/kronn/activerecord-mysql2-adapter}
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "activerecord-mysql2-adapter"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Activerecord::Mysql2::Adapter::VERSION
16
+
17
+ gem.add_dependency 'mysql2'
18
+ end
@@ -0,0 +1,64 @@
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'
19
+ require '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
+ deferrable = ::EM::DefaultDeferrable.new
47
+ ::EM.watch(self.socket, Watcher, self, deferrable).notify_readable = true
48
+ fiber = Fiber.current
49
+ deferrable.callback do |result|
50
+ fiber.resume(result)
51
+ end
52
+ deferrable.errback do |err|
53
+ fiber.resume(err)
54
+ end
55
+ Fiber.yield.tap do |result|
56
+ raise result if result.is_a?(Exception)
57
+ end
58
+ else
59
+ super(sql, opts)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,619 @@
1
+ # encoding: utf-8
2
+
3
+ require '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
+ private
46
+ def simplified_type(field_type)
47
+ return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
48
+ return :string if field_type =~ /enum/i or field_type =~ /set/i
49
+ return :integer if field_type =~ /year/i
50
+ return :binary if field_type =~ /bit/i
51
+ super
52
+ end
53
+
54
+ def extract_limit(sql_type)
55
+ case sql_type
56
+ when /blob|text/i
57
+ case sql_type
58
+ when /tiny/i
59
+ 255
60
+ when /medium/i
61
+ 16777215
62
+ when /long/i
63
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
64
+ else
65
+ super # we could return 65535 here, but we leave it undecorated by default
66
+ end
67
+ when /^bigint/i; 8
68
+ when /^int/i; 4
69
+ when /^mediumint/i; 3
70
+ when /^smallint/i; 2
71
+ when /^tinyint/i; 1
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ # MySQL misreports NOT NULL column default when none is given.
78
+ # We can't detect this for columns which may have a legitimate ''
79
+ # default (string) but we can for others (integer, datetime, boolean,
80
+ # and the rest).
81
+ #
82
+ # Test whether the column has default '', is not null, and is not
83
+ # a type allowing default ''.
84
+ def missing_default_forged_as_empty_string?(default)
85
+ type != :string && !null && default == ''
86
+ end
87
+ end
88
+
89
+ class Mysql2Adapter < AbstractAdapter
90
+ cattr_accessor :emulate_booleans
91
+ self.emulate_booleans = true
92
+
93
+ ADAPTER_NAME = 'Mysql2'
94
+ PRIMARY = "PRIMARY"
95
+
96
+ LOST_CONNECTION_ERROR_MESSAGES = [
97
+ "Server shutdown in progress",
98
+ "Broken pipe",
99
+ "Lost connection to MySQL server during query",
100
+ "MySQL server has gone away" ]
101
+
102
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
103
+
104
+ NATIVE_DATABASE_TYPES = {
105
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
106
+ :string => { :name => "varchar", :limit => 255 },
107
+ :text => { :name => "text" },
108
+ :integer => { :name => "int", :limit => 4 },
109
+ :float => { :name => "float" },
110
+ :decimal => { :name => "decimal" },
111
+ :datetime => { :name => "datetime" },
112
+ :timestamp => { :name => "datetime" },
113
+ :time => { :name => "time" },
114
+ :date => { :name => "date" },
115
+ :binary => { :name => "blob" },
116
+ :boolean => { :name => "tinyint", :limit => 1 }
117
+ }
118
+
119
+ def initialize(connection, logger, connection_options, config)
120
+ super(connection, logger)
121
+ @connection_options, @config = connection_options, config
122
+ @quoted_column_names, @quoted_table_names = {}, {}
123
+ configure_connection
124
+ end
125
+
126
+ def adapter_name
127
+ ADAPTER_NAME
128
+ end
129
+
130
+ def supports_migrations?
131
+ true
132
+ end
133
+
134
+ def supports_primary_key?
135
+ true
136
+ end
137
+
138
+ def supports_savepoints?
139
+ true
140
+ end
141
+
142
+ def native_database_types
143
+ NATIVE_DATABASE_TYPES
144
+ end
145
+
146
+ # QUOTING ==================================================
147
+
148
+ def quote(value, column = nil)
149
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
150
+ s = column.class.string_to_binary(value).unpack("H*")[0]
151
+ "x'#{s}'"
152
+ elsif value.kind_of?(BigDecimal)
153
+ value.to_s("F")
154
+ else
155
+ super
156
+ end
157
+ end
158
+
159
+ def quote_column_name(name) #:nodoc:
160
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
161
+ end
162
+
163
+ def quote_table_name(name) #:nodoc:
164
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
165
+ end
166
+
167
+ def quote_string(string)
168
+ @connection.escape(string)
169
+ end
170
+
171
+ def quoted_true
172
+ QUOTED_TRUE
173
+ end
174
+
175
+ def quoted_false
176
+ QUOTED_FALSE
177
+ end
178
+
179
+ # REFERENTIAL INTEGRITY ====================================
180
+
181
+ def disable_referential_integrity(&block) #:nodoc:
182
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
183
+
184
+ begin
185
+ update("SET FOREIGN_KEY_CHECKS = 0")
186
+ yield
187
+ ensure
188
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
189
+ end
190
+ end
191
+
192
+ # CONNECTION MANAGEMENT ====================================
193
+
194
+ def active?
195
+ return false unless @connection
196
+ @connection.ping
197
+ end
198
+
199
+ def reconnect!
200
+ disconnect!
201
+ connect
202
+ end
203
+
204
+ # this is set to true in 2.3, but we don't want it to be
205
+ def requires_reloading?
206
+ false
207
+ end
208
+
209
+ def disconnect!
210
+ unless @connection.nil?
211
+ @connection.close
212
+ @connection = nil
213
+ end
214
+ end
215
+
216
+ def reset!
217
+ disconnect!
218
+ connect
219
+ end
220
+
221
+ # DATABASE STATEMENTS ======================================
222
+
223
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
224
+ #
225
+ # The overrides below perform much better than the originals in AbstractAdapter
226
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
227
+ #
228
+ # # Returns a record hash with the column names as keys and column values
229
+ # # as values.
230
+ # def select_one(sql, name = nil)
231
+ # result = execute(sql, name)
232
+ # result.each(:as => :hash) do |r|
233
+ # return r
234
+ # end
235
+ # end
236
+ #
237
+ # # Returns a single value from a record
238
+ # def select_value(sql, name = nil)
239
+ # result = execute(sql, name)
240
+ # if first = result.first
241
+ # first.first
242
+ # end
243
+ # end
244
+ #
245
+ # # Returns an array of the values of the first column in a select:
246
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
247
+ # def select_values(sql, name = nil)
248
+ # execute(sql, name).map { |row| row.first }
249
+ # end
250
+
251
+ # Returns an array of arrays containing the field values.
252
+ # Order is the same as that returned by +columns+.
253
+ def select_rows(sql, name = nil)
254
+ execute(sql, name).to_a
255
+ end
256
+
257
+ # Executes the SQL statement in the context of this connection.
258
+ def execute(sql, name = nil)
259
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
260
+ # made since we established the connection
261
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
262
+ if name == :skip_logging
263
+ @connection.query(sql)
264
+ else
265
+ log(sql, name) { @connection.query(sql) }
266
+ end
267
+ rescue ActiveRecord::StatementInvalid => exception
268
+ if exception.message.split(":").first =~ /Packets out of order/
269
+ 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."
270
+ else
271
+ raise
272
+ end
273
+ end
274
+
275
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
276
+ super
277
+ id_value || @connection.last_id
278
+ end
279
+ alias :create :insert_sql
280
+
281
+ def update_sql(sql, name = nil)
282
+ super
283
+ @connection.affected_rows
284
+ end
285
+
286
+ def begin_db_transaction
287
+ execute "BEGIN"
288
+ rescue Exception
289
+ # Transactions aren't supported
290
+ end
291
+
292
+ def commit_db_transaction
293
+ execute "COMMIT"
294
+ rescue Exception
295
+ # Transactions aren't supported
296
+ end
297
+
298
+ def rollback_db_transaction
299
+ execute "ROLLBACK"
300
+ rescue Exception
301
+ # Transactions aren't supported
302
+ end
303
+
304
+ def create_savepoint
305
+ execute("SAVEPOINT #{current_savepoint_name}")
306
+ end
307
+
308
+ def rollback_to_savepoint
309
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
310
+ end
311
+
312
+ def release_savepoint
313
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
314
+ end
315
+
316
+ def add_limit_offset!(sql, options)
317
+ limit, offset = options[:limit], options[:offset]
318
+ if limit && offset
319
+ sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
320
+ elsif limit
321
+ sql << " LIMIT #{sanitize_limit(limit)}"
322
+ elsif offset
323
+ sql << " OFFSET #{offset.to_i}"
324
+ end
325
+ sql
326
+ end
327
+
328
+ # SCHEMA STATEMENTS ========================================
329
+
330
+ def structure_dump
331
+ if supports_views?
332
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
333
+ else
334
+ sql = "SHOW TABLES"
335
+ end
336
+
337
+ select_all(sql).inject("") do |structure, table|
338
+ table.delete('Table_type')
339
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
340
+ end
341
+ end
342
+
343
+ def recreate_database(name, options = {})
344
+ drop_database(name)
345
+ create_database(name, options)
346
+ end
347
+
348
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
349
+ # Charset defaults to utf8.
350
+ #
351
+ # Example:
352
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
353
+ # create_database 'matt_development'
354
+ # create_database 'matt_development', :charset => :big5
355
+ def create_database(name, options = {})
356
+ if options[:collation]
357
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
358
+ else
359
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
360
+ end
361
+ end
362
+
363
+ def drop_database(name) #:nodoc:
364
+ execute "DROP DATABASE IF EXISTS `#{name}`"
365
+ end
366
+
367
+ def current_database
368
+ select_value 'SELECT DATABASE() as db'
369
+ end
370
+
371
+ # Returns the database character set.
372
+ def charset
373
+ show_variable 'character_set_database'
374
+ end
375
+
376
+ # Returns the database collation strategy.
377
+ def collation
378
+ show_variable 'collation_database'
379
+ end
380
+
381
+ def tables(name = nil)
382
+ tables = []
383
+ execute("SHOW TABLES", name).each do |field|
384
+ tables << field.first
385
+ end
386
+ tables
387
+ end
388
+
389
+ def table_exists?(name)
390
+ return true if super
391
+
392
+ name = name.to_s
393
+ schema, table = name.split('.', 2)
394
+
395
+ unless table # A table was provided without a schema
396
+ table = schema
397
+ schema = nil
398
+ end
399
+
400
+ tables(nil, schema).include? table
401
+ end
402
+
403
+ def drop_table(table_name, options = {})
404
+ super(table_name, options)
405
+ end
406
+
407
+ def indexes(table_name, name = nil)
408
+ indexes = []
409
+ current_index = nil
410
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
411
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
412
+ if current_index != row[:Key_name]
413
+ next if row[:Key_name] == PRIMARY # skip the primary key
414
+ current_index = row[:Key_name]
415
+ indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
416
+ end
417
+
418
+ indexes.last.columns << row[:Column_name]
419
+ indexes.last.lengths << row[:Sub_part]
420
+ end
421
+ indexes
422
+ end
423
+
424
+ def columns(table_name, name = nil)
425
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
426
+ columns = []
427
+ result = execute(sql, :skip_logging)
428
+ result.each(:symbolize_keys => true, :as => :hash) { |field|
429
+ columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
430
+ }
431
+ columns
432
+ end
433
+
434
+ def create_table(table_name, options = {})
435
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
436
+ end
437
+
438
+ def rename_table(table_name, new_name)
439
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
440
+ end
441
+
442
+ def add_column(table_name, column_name, type, options = {})
443
+ 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])}"
444
+ add_column_options!(add_column_sql, options)
445
+ add_column_position!(add_column_sql, options)
446
+ execute(add_column_sql)
447
+ end
448
+
449
+ def change_column_default(table_name, column_name, default)
450
+ column = column_for(table_name, column_name)
451
+ change_column table_name, column_name, column.sql_type, :default => default
452
+ end
453
+
454
+ def change_column_null(table_name, column_name, null, default = nil)
455
+ column = column_for(table_name, column_name)
456
+
457
+ unless null || default.nil?
458
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
459
+ end
460
+
461
+ change_column table_name, column_name, column.sql_type, :null => null
462
+ end
463
+
464
+ def change_column(table_name, column_name, type, options = {})
465
+ column = column_for(table_name, column_name)
466
+
467
+ unless options_include_default?(options)
468
+ options[:default] = column.default
469
+ end
470
+
471
+ unless options.has_key?(:null)
472
+ options[:null] = column.null
473
+ end
474
+
475
+ 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])}"
476
+ add_column_options!(change_column_sql, options)
477
+ add_column_position!(change_column_sql, options)
478
+ execute(change_column_sql)
479
+ end
480
+
481
+ def rename_column(table_name, column_name, new_column_name)
482
+ options = {}
483
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
484
+ options[:default] = column.default
485
+ options[:null] = column.null
486
+ else
487
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
488
+ end
489
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
490
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
491
+ add_column_options!(rename_column_sql, options)
492
+ execute(rename_column_sql)
493
+ end
494
+
495
+ # Maps logical Rails types to MySQL-specific data types.
496
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
497
+ return super unless type.to_s == 'integer'
498
+
499
+ case limit
500
+ when 1; 'tinyint'
501
+ when 2; 'smallint'
502
+ when 3; 'mediumint'
503
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
504
+ when 5..8; 'bigint'
505
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
506
+ end
507
+ end
508
+
509
+ def add_column_position!(sql, options)
510
+ if options[:first]
511
+ sql << " FIRST"
512
+ elsif options[:after]
513
+ sql << " AFTER #{quote_column_name(options[:after])}"
514
+ end
515
+ end
516
+
517
+ def show_variable(name)
518
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
519
+ variables.first['Value'] unless variables.empty?
520
+ end
521
+
522
+ def pk_and_sequence_for(table)
523
+ keys = []
524
+ result = execute("describe #{quote_table_name(table)}")
525
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
526
+ keys << row[:Field] if row[:Key] == "PRI"
527
+ end
528
+ keys.length == 1 ? [keys.first, nil] : nil
529
+ end
530
+
531
+ # Returns just a table's primary key
532
+ def primary_key(table)
533
+ pk_and_sequence = pk_and_sequence_for(table)
534
+ pk_and_sequence && pk_and_sequence.first
535
+ end
536
+
537
+ def case_sensitive_equality_operator
538
+ "= BINARY"
539
+ end
540
+
541
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
542
+ where_sql
543
+ end
544
+
545
+ protected
546
+ def quoted_columns_for_index(column_names, options = {})
547
+ length = options[:length] if options.is_a?(Hash)
548
+
549
+ quoted_column_names = case length
550
+ when Hash
551
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
552
+ when Fixnum
553
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
554
+ else
555
+ column_names.map {|name| quote_column_name(name) }
556
+ end
557
+ end
558
+
559
+ def translate_exception(exception, message)
560
+ return super unless exception.respond_to?(:error_number)
561
+
562
+ case exception.error_number
563
+ when 1062
564
+ RecordNotUnique.new(message, exception)
565
+ when 1452
566
+ InvalidForeignKey.new(message, exception)
567
+ else
568
+ super
569
+ end
570
+ end
571
+
572
+ private
573
+ def connect
574
+ @connection = Mysql2::Client.new(@config)
575
+ configure_connection
576
+ end
577
+
578
+ def configure_connection
579
+ @connection.query_options.merge!(:as => :array)
580
+
581
+ # By default, MySQL 'where id is null' selects the last inserted id.
582
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
583
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
584
+ encoding = @config[:encoding]
585
+
586
+ # make sure we set the encoding
587
+ variable_assignments << "NAMES '#{encoding}'" if encoding
588
+
589
+ # increase timeout so mysql server doesn't disconnect us
590
+ wait_timeout = @config[:wait_timeout]
591
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
592
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
593
+
594
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
595
+ end
596
+
597
+ # Returns an array of record hashes with the column names as keys and
598
+ # column values as values.
599
+ def select(sql, name = nil)
600
+ execute(sql, name).each(:as => :hash)
601
+ end
602
+
603
+ def supports_views?
604
+ version[0] >= 5
605
+ end
606
+
607
+ def version
608
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
609
+ end
610
+
611
+ def column_for(table_name, column_name)
612
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
613
+ raise "No such column: #{table_name}.#{column_name}"
614
+ end
615
+ column
616
+ end
617
+ end
618
+ end
619
+ end
@@ -0,0 +1,132 @@
1
+ # Necessary monkeypatching to make AR fiber-friendly.
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+
6
+ def self.fiber_pools
7
+ @fiber_pools ||= []
8
+ end
9
+ def self.register_fiber_pool(fp)
10
+ fiber_pools << fp
11
+ end
12
+
13
+ class FiberedMonitor
14
+ class Queue
15
+ def initialize
16
+ @queue = []
17
+ end
18
+
19
+ def wait(timeout)
20
+ t = timeout || 5
21
+ fiber = Fiber.current
22
+ x = EM::Timer.new(t) do
23
+ @queue.delete(fiber)
24
+ fiber.resume(false)
25
+ end
26
+ @queue << fiber
27
+ Fiber.yield.tap do
28
+ x.cancel
29
+ end
30
+ end
31
+
32
+ def signal
33
+ fiber = @queue.pop
34
+ fiber.resume(true) if fiber
35
+ end
36
+ end
37
+
38
+ def synchronize
39
+ yield
40
+ end
41
+
42
+ def new_cond
43
+ Queue.new
44
+ end
45
+ end
46
+
47
+ # ActiveRecord's connection pool is based on threads. Since we are working
48
+ # with EM and a single thread, multiple fiber design, we need to provide
49
+ # our own connection pool that keys off of Fiber.current so that different
50
+ # fibers running in the same thread don't try to use the same connection.
51
+ class ConnectionPool
52
+ def initialize(spec)
53
+ @spec = spec
54
+
55
+ # The cache of reserved connections mapped to threads
56
+ @reserved_connections = {}
57
+
58
+ # The mutex used to synchronize pool access
59
+ @connection_mutex = FiberedMonitor.new
60
+ @queue = @connection_mutex.new_cond
61
+
62
+ # default 5 second timeout unless on ruby 1.9
63
+ @timeout = spec.config[:wait_timeout] || 5
64
+
65
+ # default max pool size to 5
66
+ @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
67
+
68
+ @connections = []
69
+ @checked_out = []
70
+ @automatic_reconnect = true
71
+ @tables = {}
72
+
73
+ @columns = Hash.new do |h, table_name|
74
+ h[table_name] = with_connection do |conn|
75
+
76
+ # Fetch a list of columns
77
+ conn.columns(table_name, "#{table_name} Columns").tap do |columns|
78
+
79
+ # set primary key information
80
+ columns.each do |column|
81
+ column.primary = column.name == primary_keys[table_name]
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ @columns_hash = Hash.new do |h, table_name|
88
+ h[table_name] = Hash[columns[table_name].map { |col|
89
+ [col.name, col]
90
+ }]
91
+ end
92
+
93
+ @primary_keys = Hash.new do |h, table_name|
94
+ h[table_name] = with_connection do |conn|
95
+ table_exists?(table_name) ? conn.primary_key(table_name) : 'id'
96
+ end
97
+ end
98
+ end
99
+
100
+ def clear_stale_cached_connections!
101
+ cache = @reserved_connections
102
+ keys = Set.new(cache.keys)
103
+
104
+ ActiveRecord::ConnectionAdapters.fiber_pools.each do |pool|
105
+ pool.busy_fibers.each_pair do |object_id, fiber|
106
+ keys.delete(object_id)
107
+ end
108
+ end
109
+
110
+ keys.each do |key|
111
+ next unless cache.has_key?(key)
112
+ checkin cache[key]
113
+ cache.delete(key)
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def current_connection_id #:nodoc:
120
+ Fiber.current.object_id
121
+ end
122
+
123
+ def checkout_and_verify(c)
124
+ @checked_out << c
125
+ c.run_callbacks :checkout
126
+ c.verify!
127
+ c
128
+ end
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,8 @@
1
+ require "activerecord-mysql2-adapter/version"
2
+
3
+ module Activerecord
4
+ module Mysql2
5
+ module Adapter
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Activerecord
2
+ module Mysql2
3
+ module Adapter
4
+ VERSION = "0.0.2"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Arel
2
+ module SqlCompiler
3
+ class Mysql2Compiler < GenericCompiler
4
+ def limited_update_conditions(conditions, taken)
5
+ conditions << " LIMIT #{taken}"
6
+ conditions
7
+ end
8
+ end
9
+ end
10
+ end
11
+
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-mysql2-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthias Viehweger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mysql2
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: extracted code from mysql2
31
+ email:
32
+ - kronn@kronn.de
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rvmrc
39
+ - Gemfile
40
+ - LICENSE
41
+ - README.md
42
+ - Rakefile
43
+ - activerecord-mysql2-adapter.gemspec
44
+ - lib/active_record/connection_adapters/em_mysql2_adapter.rb
45
+ - lib/active_record/connection_adapters/mysql2_adapter.rb
46
+ - lib/active_record/fiber_patches.rb
47
+ - lib/activerecord-mysql2-adapter.rb
48
+ - lib/activerecord-mysql2-adapter/version.rb
49
+ - lib/arel/engines/sql/compilers/mysql2_compiler.rb
50
+ homepage: http://github.com/kronn/activerecord-mysql2-adapter
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.24
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: extracted code from mysql2
74
+ test_files: []