em-mysql 0.4.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 +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
+