julien51-em-mysql 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README +40 -0
  2. data/em-mysql.gemspec +21 -0
  3. data/lib/em/mysql.rb +492 -0
  4. data/lib/sequel/async.rb +184 -0
  5. data/test.rb +59 -0
  6. metadata +67 -0
data/README ADDED
@@ -0,0 +1,40 @@
1
+ Async MySQL driver for Ruby/EventMachine
2
+ (c) 2008 Aman Gupta (tmm1)
3
+
4
+
5
+ Requires mysqlplus.
6
+
7
+ require 'em/mysql'
8
+
9
+ # alias SQL for simpler syntax
10
+
11
+ SQL = EventedMysql
12
+ def SQL(query, &blk) SQL.select(query, &blk) end
13
+
14
+
15
+ # setup connection details and allow 4 connections to the server
16
+
17
+ SQL.settings.update :host => 'localhost',
18
+ :port => 3306,
19
+ :database => 'test',
20
+ :connections => 4
21
+
22
+
23
+ # use 4 connections to execute queries in parallel
24
+
25
+ SQL('select sleep(0.25)'){ p 'done' }
26
+ SQL('select sleep(0.25)'){ p 'done' }
27
+ SQL('select sleep(0.25)'){ p 'done' }
28
+ SQL('select sleep(0.25)'){ p 'done' }
29
+
30
+ Also includes a sequel async wrapper
31
+
32
+ require 'sequel'
33
+ require 'sequel/async'
34
+
35
+ DB = Sequel.connect(:adapter => 'mysql', :user => 'root', :database => 'test', ...)
36
+ EventedMysql.settings.update(..., :on_error => proc{|e| log 'error', e })
37
+
38
+ DB[:table].where(:field => 'value').async_update(:field => 'new value')
39
+
40
+ For more info, see the comments in lib/sequel/async.rb
data/em-mysql.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = 'em-mysql'
3
+ s.version = '0.3.0'
4
+ s.date = '2009-06-23'
5
+ s.summary = 'Async MySQL client API for Ruby/EventMachine'
6
+ s.email = "em-mysql@tmm1.net"
7
+ s.homepage = "http://github.com/tmm1/em-mysql"
8
+ s.description = 'Async MySQL client API for Ruby/EventMachine'
9
+ s.has_rdoc = false
10
+ s.authors = ["Aman Gupta"]
11
+ s.add_dependency('eventmachine', '>= 0.12.8')
12
+
13
+ # git ls-files
14
+ s.files = %w[
15
+ README
16
+ em-mysql.gemspec
17
+ lib/em/mysql.rb
18
+ lib/sequel/async.rb
19
+ test.rb
20
+ ]
21
+ end
data/lib/em/mysql.rb ADDED
@@ -0,0 +1,492 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'mysqlplus'
4
+ require 'fcntl'
5
+
6
+ class Mysql
7
+ def result
8
+ @cur_result
9
+ end
10
+ end
11
+
12
+ class EventedMysql < EM::Connection
13
+ def initialize mysql, opts
14
+ @mysql = mysql
15
+ @fd = mysql.socket
16
+ @opts = opts
17
+ @current = nil
18
+ @@queue ||= []
19
+ @processing = false
20
+ @connected = true
21
+
22
+ log 'mysql connected'
23
+ make_socket_blocking
24
+ EM.add_timer(0){ next_query }
25
+ end
26
+ attr_reader :processing, :connected, :opts
27
+ alias :settings :opts
28
+
29
+ DisconnectErrors = [
30
+ 'query: not connected',
31
+ 'MySQL server has gone away',
32
+ 'Lost connection to MySQL server during query'
33
+ ] unless defined? DisconnectErrors
34
+
35
+ def notify_readable
36
+ log 'readable'
37
+ if item = @current
38
+ @current = nil
39
+ start, response, sql, cblk, eblk = item
40
+ log 'mysql response', Time.now-start, sql
41
+ arg = case response
42
+ when :raw
43
+ result = @mysql.get_result
44
+ @mysql.instance_variable_set('@cur_result', result)
45
+ @mysql
46
+ when :select
47
+ ret = []
48
+ result = @mysql.get_result
49
+ result.each_hash{|h| ret << h }
50
+ log 'mysql result', ret
51
+ ret
52
+ when :update
53
+ result = @mysql.get_result
54
+ @mysql.affected_rows
55
+ when :insert
56
+ result = @mysql.get_result
57
+ @mysql.insert_id
58
+ else
59
+ result = @mysql.get_result
60
+ log 'got a result??', result if result
61
+ nil
62
+ end
63
+
64
+ @processing = false
65
+ # result.free if result.is_a? Mysql::Result
66
+ next_query
67
+ cblk.call(arg) if cblk
68
+ else
69
+ log 'readable, but nothing queued?! probably an ERROR state'
70
+ return close
71
+ end
72
+ rescue Mysql::Error => e
73
+ log 'mysql error', e.message
74
+ if e.message =~ /Deadlock/
75
+ @@queue << [response, sql, cblk, eblk]
76
+ @processing = false
77
+ next_query
78
+ elsif DisconnectErrors.include? e.message
79
+ @@queue << [response, sql, cblk, eblk]
80
+ return close
81
+ elsif cb = (eblk || @opts[:on_error])
82
+ cb.call(e)
83
+ @processing = false
84
+ next_query
85
+ else
86
+ raise e
87
+ end
88
+ # ensure
89
+ # res.free if res.is_a? Mysql::Result
90
+ # @processing = false
91
+ # next_query
92
+ end
93
+
94
+ def unbind
95
+ log 'mysql disconnect', $!, *($! ? $!.backtrace[0..5] : [])
96
+ # cp = EventedMysql.instance_variable_get('@connection_pool') and cp.delete(self)
97
+ @connected = false
98
+
99
+ # XXX wait for the next tick until the current fd is removed completely from the reactor
100
+ #
101
+ # XXX in certain cases the new FD# (@mysql.socket) is the same as the old, since FDs are re-used
102
+ # XXX without next_tick in these cases, unbind will get fired on the newly attached signature as well
103
+ #
104
+ # XXX do _NOT_ use EM.next_tick here. if a bunch of sockets disconnect at the same time, we want
105
+ # XXX reconnects to happen after all the unbinds have been processed
106
+ EM.add_timer(0) do
107
+ log 'mysql reconnecting'
108
+ @processing = false
109
+ @mysql = EventedMysql._connect @opts
110
+ @fd = @mysql.socket
111
+
112
+ @signature = EM.attach_fd @mysql.socket, true, false
113
+ log 'mysql connected'
114
+ EM.instance_variable_get('@conns')[@signature] = self
115
+ @connected = true
116
+ make_socket_blocking
117
+ next_query
118
+ end
119
+ end
120
+
121
+ def execute sql, response = nil, cblk = nil, eblk = nil, &blk
122
+ cblk ||= blk
123
+
124
+ begin
125
+ unless @processing or !@connected
126
+ # begin
127
+ # log 'mysql ping', @mysql.ping
128
+ # # log 'mysql stat', @mysql.stat
129
+ # # log 'mysql errno', @mysql.errno
130
+ # rescue
131
+ # log 'mysql ping failed'
132
+ # @@queue << [response, sql, blk]
133
+ # return close
134
+ # end
135
+
136
+ @processing = true
137
+
138
+ log 'mysql sending', sql
139
+ @mysql.send_query(sql)
140
+ else
141
+ @@queue << [response, sql, cblk, eblk]
142
+ return
143
+ end
144
+ rescue Mysql::Error => e
145
+ log 'mysql error', e.message
146
+ if DisconnectErrors.include? e.message
147
+ @@queue << [response, sql, cblk, eblk]
148
+ return close
149
+ else
150
+ raise e
151
+ end
152
+ end
153
+
154
+ log 'queuing', response, sql
155
+ @current = [Time.now, response, sql, cblk, eblk]
156
+ end
157
+
158
+ def close
159
+ @connected = false
160
+ @mysql.close
161
+ # IO.pipe
162
+ # EM.add_timer(0){ close_connection }
163
+ # close_connection
164
+ fd = detach
165
+ @io.close if @io
166
+ @io = nil
167
+ log 'detached fd', fd
168
+ end
169
+
170
+ private
171
+
172
+ def make_socket_blocking
173
+ if defined?(Fcntl::F_GETFL)
174
+ @io = IO.for_fd(@mysql.socket)
175
+ m = @io.fcntl(Fcntl::F_GETFL, 0)
176
+ @io.fcntl(Fcntl::F_SETFL, ~Fcntl::O_NONBLOCK & m)
177
+ end
178
+ end
179
+
180
+ def next_query
181
+ if @connected and !@processing and pending = @@queue.shift
182
+ response, sql, cblk, eblk = pending
183
+ execute(sql, response, cblk, eblk)
184
+ end
185
+ end
186
+
187
+ def log *args
188
+ return unless @opts[:logging]
189
+ p [Time.now, @fd, (@signature[-4..-1] if @signature), *args]
190
+ end
191
+
192
+ public
193
+
194
+ def self.connect opts
195
+ unless EM.respond_to?(:attach) and Mysql.method_defined?(:socket)
196
+ raise RuntimeError, 'mysqlplus and EM.attach are required for EventedMysql'
197
+ end
198
+
199
+ if conn = _connect(opts)
200
+ EM.attach conn.socket, self, conn, opts
201
+ else
202
+ EM.add_timer(5){ connect opts }
203
+ end
204
+ end
205
+
206
+ self::Mysql = ::Mysql unless defined? self::Mysql
207
+
208
+ # stolen from sequel
209
+ def self._connect opts
210
+ opts = settings.merge(opts)
211
+
212
+ conn = Mysql.init
213
+
214
+ # set encoding _before_ connecting
215
+ if charset = opts[:charset] || opts[:encoding]
216
+ conn.options(Mysql::SET_CHARSET_NAME, charset)
217
+ end
218
+
219
+ conn.options(Mysql::OPT_LOCAL_INFILE, 'client')
220
+
221
+ conn.real_connect(
222
+ opts[:host] || 'localhost',
223
+ opts[:user] || 'root',
224
+ opts[:password],
225
+ opts[:database],
226
+ opts[:port],
227
+ opts[:socket],
228
+ 0 +
229
+ # XXX multi results require multiple callbacks to parse
230
+ # Mysql::CLIENT_MULTI_RESULTS +
231
+ # Mysql::CLIENT_MULTI_STATEMENTS +
232
+ (opts[:compress] == false ? 0 : Mysql::CLIENT_COMPRESS)
233
+ )
234
+
235
+ # increase timeout so mysql server doesn't disconnect us
236
+ # this is especially bad if we're disconnected while EM.attach is
237
+ # still in progress, because by the time it gets to EM, the FD is
238
+ # no longer valid, and it throws a c++ 'bad file descriptor' error
239
+ # (do not use a timeout of -1 for unlimited, it does not work on mysqld > 5.0.60)
240
+ conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
241
+
242
+ # we handle reconnecting (and reattaching the new fd to EM)
243
+ conn.reconnect = false
244
+
245
+ # By default, MySQL 'where id is null' selects the last inserted id
246
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
247
+ conn.query("set SQL_AUTO_IS_NULL=0")
248
+
249
+ # get results for queries
250
+ conn.query_with_result = true
251
+
252
+ conn
253
+ rescue Mysql::Error => e
254
+ if cb = opts[:on_error]
255
+ cb.call(e)
256
+ nil
257
+ else
258
+ raise e
259
+ end
260
+ end
261
+ end
262
+
263
+ class EventedMysql
264
+ def self.settings
265
+ @settings ||= { :connections => 4, :logging => false }
266
+ end
267
+
268
+ def self.execute query, type = nil, cblk = nil, eblk = nil, &blk
269
+ unless nil#connection = connection_pool.find{|c| not c.processing and c.connected }
270
+ @n ||= 0
271
+ connection = connection_pool[@n]
272
+ @n = 0 if (@n+=1) >= connection_pool.size
273
+ end
274
+
275
+ connection.execute(query, type, cblk, eblk, &blk)
276
+ end
277
+
278
+ %w[ select insert update raw ].each do |type| class_eval %[
279
+
280
+ def self.#{type} query, cblk = nil, eblk = nil, &blk
281
+ execute query, :#{type}, cblk, eblk, &blk
282
+ end
283
+
284
+ ] end
285
+
286
+ def self.all query, type = nil, &blk
287
+ responses = 0
288
+ connection_pool.each do |c|
289
+ c.execute(query, type) do
290
+ responses += 1
291
+ blk.call if blk and responses == @connection_pool.size
292
+ end
293
+ end
294
+ end
295
+
296
+ def self.connection_pool
297
+ @connection_pool ||= (1..settings[:connections]).map{ EventedMysql.connect(settings) }
298
+ # p ['connpool', settings[:connections], @connection_pool.size]
299
+ # (1..(settings[:connections]-@connection_pool.size)).each do
300
+ # @connection_pool << EventedMysql.connect(settings)
301
+ # end unless settings[:connections] == @connection_pool.size
302
+ # @connection_pool
303
+ end
304
+
305
+ def self.reset!
306
+ @connection_pool.each do |c|
307
+ c.close
308
+ end
309
+ @connection_pool = nil
310
+ end
311
+ end
312
+
313
+ if __FILE__ == $0 and require 'em/spec'
314
+
315
+ EM.describe EventedMysql, 'individual connections' do
316
+
317
+ should 'create a new connection' do
318
+ @mysql = EventedMysql.connect :host => '127.0.0.1',
319
+ :port => 3306,
320
+ :database => 'test',
321
+ :logging => false
322
+
323
+ @mysql.class.should == EventedMysql
324
+ done
325
+ end
326
+
327
+ should 'execute sql' do
328
+ start = Time.now
329
+
330
+ @mysql.execute('select sleep(0.2)'){
331
+ (Time.now-start).should.be.close 0.2, 0.1
332
+ done
333
+ }
334
+ end
335
+
336
+ should 'reconnect when disconnected' do
337
+ @mysql.close
338
+ @mysql.execute('select 1+2'){
339
+ :connected.should == :connected
340
+ done
341
+ }
342
+ end
343
+
344
+ # to test, run:
345
+ # mysqladmin5 -u root kill `mysqladmin5 -u root processlist | grep "select sleep(5)+1" | cut -d'|' -f2`
346
+ #
347
+ # should 're-run query if disconnected during query' do
348
+ # @mysql.execute('select sleep(5)+1', :select){ |res|
349
+ # res.first['sleep(5)+1'].should == '1'
350
+ # done
351
+ # }
352
+ # end
353
+
354
+ should 'run select queries and return results' do
355
+ @mysql.execute('select 1+2', :select){ |res|
356
+ res.size.should == 1
357
+ res.first['1+2'].should == '3'
358
+ done
359
+ }
360
+ end
361
+
362
+ should 'queue up queries and execute them in order' do
363
+ @mysql.execute('select 1+2', :select)
364
+ @mysql.execute('select 2+3', :select)
365
+ @mysql.execute('select 3+4', :select){ |res|
366
+ res.first['3+4'].should == '7'
367
+ done
368
+ }
369
+ end
370
+
371
+ should 'continue processing queries after hitting an error' do
372
+ @mysql.settings.update :on_error => proc{|e|}
373
+
374
+ @mysql.execute('select 1+ from table'){}
375
+ @mysql.execute('select 1+1 as num', :select){ |res|
376
+ res[0]['num'].should == '2'
377
+ done
378
+ }
379
+ end
380
+
381
+ should 'have raw mode which yields the mysql object' do
382
+ @mysql.execute('select 1+2 as num', :raw){ |mysql|
383
+ mysql.should.is_a? Mysql
384
+ mysql.result.all_hashes.should == [{'num' => '3'}]
385
+ done
386
+ }
387
+ end
388
+
389
+ should 'allow custom error callbacks for each query' do
390
+ @mysql.settings.update :on_error => proc{ should.flunk('default errback invoked') }
391
+
392
+ @mysql.execute('select 1+ from table', :select, proc{
393
+ should.flunk('callback invoked')
394
+ }, proc{ |e|
395
+ done
396
+ })
397
+ end
398
+
399
+ end
400
+
401
+ EM.describe EventedMysql, 'connection pools' do
402
+
403
+ EventedMysql.settings.update :connections => 3
404
+
405
+ should 'run queries in parallel' do
406
+ n = 0
407
+ EventedMysql.select('select sleep(0.25)'){ n+=1 }
408
+ EventedMysql.select('select sleep(0.25)'){ n+=1 }
409
+ EventedMysql.select('select sleep(0.25)'){ n+=1 }
410
+
411
+ EM.add_timer(0.30){
412
+ n.should == 3
413
+ done
414
+ }
415
+ end
416
+
417
+ end
418
+
419
+ SQL = EventedMysql
420
+ def SQL(query, &blk) SQL.select(query, &blk) end
421
+
422
+ # XXX this should get cleaned up automatically after reactor stops
423
+ SQL.reset!
424
+
425
+
426
+ EM.describe SQL, 'sql api' do
427
+
428
+ should 'run a query on all connections' do
429
+ SQL.all('use test'){
430
+ :done.should == :done
431
+ done
432
+ }
433
+ end
434
+
435
+ should 'execute queries with no results' do
436
+ SQL.execute('drop table if exists evented_mysql_test'){
437
+ :table_dropped.should == :table_dropped
438
+ SQL.execute('create table evented_mysql_test (id int primary key auto_increment, num int not null)'){
439
+ :table_created.should == :table_created
440
+ done
441
+ }
442
+ }
443
+ end
444
+
445
+ should 'insert rows and return inserted id' do
446
+ SQL.insert('insert into evented_mysql_test (num) values (10),(11),(12)'){ |id|
447
+ id.should == 1
448
+ done
449
+ }
450
+ end
451
+
452
+ should 'select rows from the database' do
453
+ SQL.select('select * from evented_mysql_test'){ |res|
454
+ res.size.should == 3
455
+ res.first.should == { 'id' => '1', 'num' => '10' }
456
+ res.last.should == { 'id' => '3', 'num' => '12' }
457
+ done
458
+ }
459
+ end
460
+
461
+ should 'update rows and return affected rows' do
462
+ SQL.update('update evented_mysql_test set num = num + 10'){ |changed|
463
+ changed.should == 3
464
+ done
465
+ }
466
+ end
467
+
468
+ should 'allow access to insert_id in raw mode' do
469
+ SQL.raw('insert into evented_mysql_test (num) values (20), (21), (22)'){ |mysql|
470
+ mysql.insert_id.should == 4
471
+ done
472
+ }
473
+ end
474
+
475
+ should 'allow access to affected_rows in raw mode' do
476
+ SQL.raw('update evented_mysql_test set num = num + 10'){ |mysql|
477
+ mysql.affected_rows.should == 6
478
+ done
479
+ }
480
+ end
481
+
482
+ should 'fire error callback with exceptions' do
483
+ SQL.settings.update :on_error => proc{ |e|
484
+ e.class.should == Mysql::Error
485
+ done
486
+ }
487
+ SQL.select('select 1+ from table'){}
488
+ end
489
+
490
+ end
491
+
492
+ end
@@ -0,0 +1,184 @@
1
+ # async sequel extensions, for use with em-mysql
2
+ #
3
+ # require 'em/mysql'
4
+ # DB = Sequel.connect(:adapter => 'mysql', :user => 'root', :database => 'test', ...)
5
+ # EventedMysql.settings.update(..., :on_error => proc{|e| log 'error', e })
6
+ #
7
+ # def log *args
8
+ # p [Time.now, *args]
9
+ # end
10
+ #
11
+ # DB[:table].where(:id < 100).async_update(:field => 'new value') do |num_updated|
12
+ # log "done updating #{num_updated} rows"
13
+ # end
14
+ #
15
+ # DB[:table].async_insert(:field => 'value') do |insert_id|
16
+ # log "inserted row #{insert_id}"
17
+ # end
18
+ #
19
+ # DB[:table].async_multi_insert([:field], [ ['one'], ['two'], ['three'] ]) do
20
+ # log "done inserting 3 rows"
21
+ # end
22
+ #
23
+ # DB[:table].limit(10).async_each do |row|
24
+ # log "got a row", row
25
+ # end; log "this will be printed before the query returns"
26
+ #
27
+ # DB[:table].async_all do |rows|
28
+ # DB[:table].async_multi_insert([:field], rows.map{|r| "new_#{r[:field]}" })
29
+ # end
30
+ #
31
+ # DB[:table].async_all do |rows|
32
+ # num = rows.size
33
+ #
34
+ # rows.each{ |r|
35
+ # DB[:table].where(:id => r[:id]).async_update(:field => rand(10000).to_s) do
36
+ # num = num-1
37
+ # if num == 0
38
+ # log "last update completed"
39
+ # end
40
+ # end
41
+ # }
42
+ # end
43
+ #
44
+ # DB[:table].async_count do |num_rows|
45
+ # log "table has #{num_rows} rows"
46
+ # end
47
+
48
+ require 'sequel'
49
+ raise 'need Sequel >= 3.2.0' unless Sequel::MAJOR == 3 and Sequel::MINOR >= 2
50
+
51
+ module Sequel
52
+ class Dataset
53
+ def async_insert *args, &cb
54
+ EventedMysql.insert insert_sql(*args), &cb
55
+ nil
56
+ end
57
+
58
+ def async_insert_ignore *args, &cb
59
+ EventedMysql.insert insert_ignore.insert_sql(*args), &cb
60
+ nil
61
+ end
62
+
63
+ def async_update *args, &cb
64
+ EventedMysql.update update_sql(*args), &cb
65
+ nil
66
+ end
67
+
68
+ def async_delete &cb
69
+ EventedMysql.execute delete_sql, &cb
70
+ nil
71
+ end
72
+
73
+ def async_multi_insert *args, &cb
74
+ EventedMysql.execute multi_insert_sql(*args).first, &cb
75
+ nil
76
+ end
77
+
78
+ def async_multi_insert_ignore *args, &cb
79
+ EventedMysql.execute insert_ignore.multi_insert_sql(*args).first, &cb
80
+ nil
81
+ end
82
+
83
+ def async_fetch_rows sql, iter = :each
84
+ EventedMysql.raw(sql) do |m|
85
+ r = m.result
86
+
87
+ i = -1
88
+ cols = r.fetch_fields.map{|f| [output_identifier(f.name), Sequel::MySQL::MYSQL_TYPES[f.type], i+=1]}
89
+ @columns = cols.map{|c| c.first}
90
+ rows = []
91
+ while row = r.fetch_row
92
+ h = {}
93
+ cols.each{|n, p, i| v = row[i]; h[n] = (v && p) ? p.call(v) : v}
94
+ if iter == :each
95
+ yield h
96
+ else
97
+ rows << h
98
+ end
99
+ end
100
+ yield rows if iter == :all
101
+ end
102
+ nil
103
+ end
104
+
105
+ def async_each
106
+ async_fetch_rows(select_sql, :each) do |r|
107
+ if row_proc = @row_proc
108
+ yield row_proc.call(r)
109
+ else
110
+ yield r
111
+ end
112
+ end
113
+ nil
114
+ end
115
+
116
+ def async_all
117
+ async_fetch_rows(sql, :all) do |rows|
118
+ if row_proc = @row_proc
119
+ yield(rows.map{|r| row_proc.call(r) })
120
+ else
121
+ yield(rows)
122
+ end
123
+ end
124
+ nil
125
+ end
126
+
127
+ def async_count &cb
128
+ if options_overlap(COUNT_FROM_SELF_OPTS)
129
+ from_self.async_count(&cb)
130
+ else
131
+ clone(STOCK_COUNT_OPTS).async_each{|r|
132
+ yield r.is_a?(Hash) ? r.values.first.to_i : r.values.values.first.to_i
133
+ }
134
+ end
135
+ nil
136
+ end
137
+ end
138
+
139
+ class Model
140
+ def async_update *args, &cb
141
+ this.async_update(*args, &cb)
142
+ set(*args)
143
+ self
144
+ end
145
+
146
+ def async_delete &cb
147
+ this.async_delete(&cb)
148
+ nil
149
+ end
150
+
151
+ class << self
152
+ [ :async_insert,
153
+ :async_insert_ignore,
154
+ :async_multi_insert,
155
+ :async_multi_insert_ignore,
156
+ :async_each,
157
+ :async_all,
158
+ :async_update,
159
+ :async_count ].each do |method|
160
+ class_eval %[
161
+ def #{method} *args, &cb
162
+ dataset.#{method}(*args, &cb)
163
+ end
164
+ ]
165
+ end
166
+
167
+ # async version of Model#[]
168
+ def async_lookup args
169
+ unless Hash === args
170
+ args = primary_key_hash(args)
171
+ end
172
+
173
+ dataset.where(args).limit(1).async_all{ |rows|
174
+ if rows.any?
175
+ yield rows.first
176
+ else
177
+ yield nil
178
+ end
179
+ }
180
+ nil
181
+ end
182
+ end
183
+ end
184
+ end
data/test.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'lib/em/mysql'
2
+
3
+ # EM.kqueue
4
+ # EM.epoll
5
+ EM.run{
6
+ EM.start_server '127.0.0.1', 12345 do |c|
7
+ def c.receive_data data
8
+ p 'sending http response'
9
+ send_data "hello"
10
+ close_connection_after_writing
11
+ end
12
+ end
13
+
14
+ SQL = EventedMysql
15
+ def SQL(query, &blk) SQL.select(query, &blk) end
16
+
17
+ if true
18
+
19
+ SQL.settings.update :logging => true,
20
+ :database => 'test',
21
+ :connections => 1
22
+
23
+ SQL.execute('select 1+2')
24
+
25
+ EM.add_timer(1){
26
+ 3.times do SQL.select('select sleep(0.5)+1'){|r| p(r) } end
27
+ }
28
+
29
+ elsif false
30
+
31
+ SQL.settings.update :logging => true,
32
+ :database => 'test',
33
+ :connections => 10
34
+
35
+ EM.add_timer(2.5){ SQL.all('use test') }
36
+
37
+ else
38
+
39
+ SQL.settings.update :logging => true,
40
+ :database => 'test',
41
+ :connections => 10,
42
+ :timeout => 1
43
+
44
+ n = 0
45
+
46
+ SQL.execute('drop table if exists testingabc'){
47
+ SQL.execute('create table testingabc (a int, b int, c int)'){
48
+ EM.add_periodic_timer(0.2) do
49
+ cur_num = n+=1
50
+ SQL.execute("insert into testingabc values (1,2,#{cur_num})"){
51
+ SQL("select * from testingabc where c = #{cur_num} limit 1"){ |res| puts;puts }
52
+ }
53
+ end
54
+ }
55
+ }
56
+
57
+ end
58
+
59
+ }
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: julien51-em-mysql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Aman Gupta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-23 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.12.8
24
+ version:
25
+ description: Async MySQL client API for Ruby/EventMachine
26
+ email: em-mysql@tmm1.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - README
35
+ - em-mysql.gemspec
36
+ - lib/em/mysql.rb
37
+ - lib/sequel/async.rb
38
+ - test.rb
39
+ has_rdoc: false
40
+ homepage: http://github.com/tmm1/em-mysql
41
+ licenses:
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: Async MySQL client API for Ruby/EventMachine
66
+ test_files: []
67
+