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