julien51-em-mysql 0.3.0

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 (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
+