tmm1-em-mysql 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +28 -0
- data/lib/em/mysql.rb +459 -0
- data/lib/sequel/async.rb +157 -0
- data/test.rb +59 -0
- metadata +65 -0
data/README
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Async MySQL driver for Ruby/EventMachine
|
2
|
+
(c) 2008 Aman Gupta (tmm1)
|
3
|
+
|
4
|
+
|
5
|
+
Requires mysqlplus and the EM.attach patch.
|
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' }
|
data/lib/em/mysql.rb
ADDED
@@ -0,0 +1,459 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'mysqlplus'
|
4
|
+
|
5
|
+
class EventedMysql < EM::Connection
|
6
|
+
def initialize mysql, opts
|
7
|
+
@mysql = mysql
|
8
|
+
@fd = mysql.socket
|
9
|
+
@opts = opts
|
10
|
+
@queue = []
|
11
|
+
@pending = []
|
12
|
+
@processing = false
|
13
|
+
@connected = true
|
14
|
+
|
15
|
+
log 'mysql connected'
|
16
|
+
end
|
17
|
+
attr_reader :processing, :connected, :opts
|
18
|
+
alias :settings :opts
|
19
|
+
|
20
|
+
def connection_completed
|
21
|
+
@connected = true
|
22
|
+
next_query
|
23
|
+
end
|
24
|
+
|
25
|
+
DisconnectErrors = [
|
26
|
+
'query: not connected',
|
27
|
+
'MySQL server has gone away',
|
28
|
+
'Lost connection to MySQL server during query'
|
29
|
+
] unless defined? DisconnectErrors
|
30
|
+
|
31
|
+
def notify_readable
|
32
|
+
log 'readable'
|
33
|
+
if item = @queue.shift
|
34
|
+
start, response, sql, cblk, eblk = item
|
35
|
+
log 'mysql response', Time.now-start, sql
|
36
|
+
arg = case response
|
37
|
+
when :raw
|
38
|
+
@mysql
|
39
|
+
when :select
|
40
|
+
ret = []
|
41
|
+
result = @mysql.get_result
|
42
|
+
result.each_hash{|h| ret << h }
|
43
|
+
log 'mysql result', ret
|
44
|
+
ret
|
45
|
+
when :update
|
46
|
+
result = @mysql.get_result
|
47
|
+
@mysql.affected_rows
|
48
|
+
when :insert
|
49
|
+
result = @mysql.get_result
|
50
|
+
@mysql.insert_id
|
51
|
+
else
|
52
|
+
result = @mysql.get_result
|
53
|
+
log 'got a result??', result if result
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
@processing = false
|
58
|
+
# result.free if result.is_a? Mysql::Result
|
59
|
+
next_query
|
60
|
+
cblk.call(arg) if cblk
|
61
|
+
else
|
62
|
+
log 'readable, but nothing queued?! probably an ERROR state'
|
63
|
+
return close
|
64
|
+
end
|
65
|
+
rescue Mysql::Error => e
|
66
|
+
log 'mysql error', e.message
|
67
|
+
if DisconnectErrors.include? e.message
|
68
|
+
@pending << [response, sql, cblk, eblk]
|
69
|
+
return close
|
70
|
+
elsif cb = (eblk || @opts[:on_error])
|
71
|
+
cb.call(e)
|
72
|
+
@processing = false
|
73
|
+
next_query
|
74
|
+
else
|
75
|
+
raise e
|
76
|
+
end
|
77
|
+
# ensure
|
78
|
+
# res.free if res.is_a? Mysql::Result
|
79
|
+
# @processing = false
|
80
|
+
# next_query
|
81
|
+
end
|
82
|
+
|
83
|
+
def unbind
|
84
|
+
log 'mysql disconnect', $!
|
85
|
+
# cp = EventedMysql.instance_variable_get('@connection_pool') and cp.delete(self)
|
86
|
+
@connected = false
|
87
|
+
|
88
|
+
# XXX wait for the next tick until the current fd is removed completely from the reactor
|
89
|
+
#
|
90
|
+
# XXX in certain cases the new FD# (@mysql.socket) is the same as the old, since FDs are re-used
|
91
|
+
# XXX without next_tick in these cases, unbind will get fired on the newly attached signature as well
|
92
|
+
#
|
93
|
+
# XXX do _NOT_ use EM.next_tick here. if a bunch of sockets disconnect at the same time, we want
|
94
|
+
# XXX reconnects to happen after all the unbinds have been processed
|
95
|
+
EM.add_timer(0) do
|
96
|
+
log 'mysql reconnecting'
|
97
|
+
@processing = false
|
98
|
+
@mysql = EventedMysql._connect @opts
|
99
|
+
@fd = @mysql.socket
|
100
|
+
|
101
|
+
@signature = EM.attach_fd @mysql.socket, true, false
|
102
|
+
log 'mysql connected'
|
103
|
+
EM.instance_variable_get('@conns')[@signature] = self
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def execute sql, response = nil, cblk = nil, eblk = nil, &blk
|
108
|
+
cblk ||= blk
|
109
|
+
|
110
|
+
begin
|
111
|
+
unless @processing or !@connected
|
112
|
+
# begin
|
113
|
+
# log 'mysql ping', @mysql.ping
|
114
|
+
# # log 'mysql stat', @mysql.stat
|
115
|
+
# # log 'mysql errno', @mysql.errno
|
116
|
+
# rescue
|
117
|
+
# log 'mysql ping failed'
|
118
|
+
# @pending << [response, sql, blk]
|
119
|
+
# return close
|
120
|
+
# end
|
121
|
+
|
122
|
+
@processing = true
|
123
|
+
|
124
|
+
log 'mysql sending', sql
|
125
|
+
@mysql.send_query(sql)
|
126
|
+
else
|
127
|
+
@pending << [response, sql, cblk, eblk]
|
128
|
+
return
|
129
|
+
end
|
130
|
+
rescue Mysql::Error => e
|
131
|
+
log 'mysql error', e.message
|
132
|
+
if DisconnectErrors.include? e.message
|
133
|
+
@pending << [response, sql, cblk, eblk]
|
134
|
+
return close
|
135
|
+
else
|
136
|
+
raise e
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
log 'queuing', response, sql
|
141
|
+
@queue << [Time.now, response, sql, cblk, eblk]
|
142
|
+
end
|
143
|
+
|
144
|
+
def close
|
145
|
+
@connected = false
|
146
|
+
# @mysql.close
|
147
|
+
# IO.pipe
|
148
|
+
# EM.add_timer(0){ close_connection }
|
149
|
+
# close_connection
|
150
|
+
fd = detach
|
151
|
+
log 'detached fd', fd
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def next_query
|
157
|
+
if @connected and !@processing and pending = @pending.shift
|
158
|
+
response, sql, cblk, eblk = pending
|
159
|
+
execute(sql, response, cblk, eblk)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def log *args
|
164
|
+
return unless @opts[:logging]
|
165
|
+
p [Time.now, @fd, (@signature[-4..-1] if @signature), *args]
|
166
|
+
end
|
167
|
+
|
168
|
+
public
|
169
|
+
|
170
|
+
def self.connect opts
|
171
|
+
unless EM.respond_to?(:attach) and Mysql.method_defined?(:socket)
|
172
|
+
raise RuntimeError, 'mysqlplus and EM.attach are required for EventedMysql'
|
173
|
+
end
|
174
|
+
|
175
|
+
if conn = _connect(opts)
|
176
|
+
EM.attach conn.socket, self, conn, opts
|
177
|
+
else
|
178
|
+
EM.add_timer(5){ connect opts }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
self::Mysql = ::Mysql unless defined? self::Mysql
|
183
|
+
|
184
|
+
# stolen from sequel
|
185
|
+
def self._connect opts
|
186
|
+
opts = settings.merge(opts)
|
187
|
+
|
188
|
+
conn = Mysql.init
|
189
|
+
|
190
|
+
# set encoding _before_ connecting
|
191
|
+
if charset = opts[:charset] || opts[:encoding]
|
192
|
+
conn.options(Mysql::SET_CHARSET_NAME, charset)
|
193
|
+
end
|
194
|
+
|
195
|
+
conn.options(Mysql::OPT_LOCAL_INFILE, 'client')
|
196
|
+
|
197
|
+
conn.real_connect(
|
198
|
+
opts[:host] || 'localhost',
|
199
|
+
opts[:user] || 'root',
|
200
|
+
opts[:password],
|
201
|
+
opts[:database],
|
202
|
+
opts[:port],
|
203
|
+
opts[:socket],
|
204
|
+
0 +
|
205
|
+
# XXX multi results require multiple callbacks to parse
|
206
|
+
# Mysql::CLIENT_MULTI_RESULTS +
|
207
|
+
# Mysql::CLIENT_MULTI_STATEMENTS +
|
208
|
+
|
209
|
+
# XXX this should check for opts[:compression]
|
210
|
+
Mysql::CLIENT_COMPRESS
|
211
|
+
)
|
212
|
+
|
213
|
+
# increase timeout so mysql server doesn't disconnect us
|
214
|
+
# this is especially bad if we're disconnected while EM.attach is
|
215
|
+
# still in progress, because by the time it gets to EM, the FD is
|
216
|
+
# no longer valid, and it throws a c++ 'bad file descriptor' error
|
217
|
+
# (do not use a timeout of -1 for unlimited, it does not work on mysqld > 5.0.60)
|
218
|
+
conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
|
219
|
+
|
220
|
+
# we handle reconnecting (and reattaching the new fd to EM)
|
221
|
+
conn.reconnect = false
|
222
|
+
|
223
|
+
# By default, MySQL 'where id is null' selects the last inserted id
|
224
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
225
|
+
conn.query("set SQL_AUTO_IS_NULL=0")
|
226
|
+
|
227
|
+
# get results for queries
|
228
|
+
conn.query_with_result = true
|
229
|
+
|
230
|
+
# if encoding = opts[:encoding] || opts[:charset]
|
231
|
+
# conn.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
232
|
+
# conn.query("set names '#{encoding}'")
|
233
|
+
# conn.query("set character_set_connection = '#{encoding}'")
|
234
|
+
# conn.query("set character_set_client = '#{encoding}'")
|
235
|
+
# conn.query("set character_set_database = '#{encoding}'")
|
236
|
+
# conn.query("set character_set_server = '#{encoding}'")
|
237
|
+
# conn.query("set character_set_results = '#{encoding}'")
|
238
|
+
# end
|
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.get_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 'fire error callback with exceptions' do
|
450
|
+
SQL.settings.update :on_error => proc{ |e|
|
451
|
+
e.class.should == Mysql::Error
|
452
|
+
done
|
453
|
+
}
|
454
|
+
SQL.select('select 1+ from table'){}
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
|
459
|
+
end
|
data/lib/sequel/async.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# async sequel extensions, for use with em-mysql
|
2
|
+
#
|
3
|
+
# require 'em/mysql'
|
4
|
+
# DB = Sequel.connect(...)
|
5
|
+
# ADB = EventedMysql.connect(..., :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 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
|
+
module Sequel
|
49
|
+
class Dataset
|
50
|
+
def async_insert *args, &cb
|
51
|
+
ADB.insert insert_sql(*args), &cb
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def async_update *args, &cb
|
56
|
+
ADB.update update_sql(*args), &cb
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def async_delete &cb
|
61
|
+
ADB.execute delete_sql, &cb
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def async_multi_insert *args, &cb
|
66
|
+
ADB.execute multi_insert_sql(*args).first, &cb
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def async_multi_insert_ignore *args, &cb
|
71
|
+
ADB.execute multi_insert_sql(*args).first.sub(/insert/i, "INSERT IGNORE"), &cb
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def async_each *args
|
76
|
+
ADB.select(select_sql(*args)) do |rows|
|
77
|
+
rows.each{|r|
|
78
|
+
r = transform_load(r) if @transform
|
79
|
+
r = row_proc[r] if row_proc
|
80
|
+
yield r
|
81
|
+
}
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def async_all
|
87
|
+
ADB.select(sql) do |rows|
|
88
|
+
if row_proc or transform
|
89
|
+
yield(rows.map{|r|
|
90
|
+
r = transform_load(r) if @transform
|
91
|
+
r = row_proc[r] if row_proc
|
92
|
+
r
|
93
|
+
})
|
94
|
+
else
|
95
|
+
yield(rows)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def async_count &cb
|
102
|
+
if options_overlap(COUNT_FROM_SELF_OPTS)
|
103
|
+
from_self.async_count(&cb)
|
104
|
+
else
|
105
|
+
naked.async_each(STOCK_COUNT_OPTS){|r|
|
106
|
+
yield r.values.first.to_i
|
107
|
+
}
|
108
|
+
end
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Model
|
114
|
+
def async_update *args, &cb
|
115
|
+
this.async_update(*args, &cb)
|
116
|
+
set(*args)
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def async_delete &cb
|
121
|
+
this.async_delete(&cb)
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
class << self
|
126
|
+
[ :async_insert,
|
127
|
+
:async_multi_insert,
|
128
|
+
:async_multi_insert_ignore,
|
129
|
+
:async_each,
|
130
|
+
:async_all,
|
131
|
+
:async_update,
|
132
|
+
:async_count ].each do |method|
|
133
|
+
class_eval %[
|
134
|
+
def #{method} *args, &cb
|
135
|
+
dataset.#{method}(*args, &cb)
|
136
|
+
end
|
137
|
+
]
|
138
|
+
end
|
139
|
+
|
140
|
+
# async version of Model#[]
|
141
|
+
def async_lookup args
|
142
|
+
unless Hash === args
|
143
|
+
args = primary_key_hash(args)
|
144
|
+
end
|
145
|
+
|
146
|
+
dataset.where(args).limit(1).async_all{ |rows|
|
147
|
+
if rows.any?
|
148
|
+
yield rows.first
|
149
|
+
else
|
150
|
+
yield nil
|
151
|
+
end
|
152
|
+
}
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
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,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tmm1-em-mysql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aman Gupta
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-18 00:00:00 -08: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.4
|
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
|
+
- lib/em/mysql.rb
|
36
|
+
- lib/sequel/async.rb
|
37
|
+
- test.rb
|
38
|
+
has_rdoc: false
|
39
|
+
homepage: http://github.com/tmm1/em-mysql
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.2.0
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Async MySQL client API for Ruby/EventMachine
|
64
|
+
test_files: []
|
65
|
+
|