activerecord-mysql2-adapter 0.0.2

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.
@@ -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: []