GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Description: Trivial async MySQL driver for Ruby EventMachine
Clone URL: git://github.com/tqbf/asymy.git
a lazy swipe at prepared statements --- about 20% of the way there, maybe?
Thomas Ptacek (author)
Fri Jun 13 00:19:40 -0700 2008
commit  687ee9dd2058485e082fde63d338a578d32ea299
tree    6f7911e30b56bcc2c25b003575991e2ad97ee6a8
parent  ef447a762996c83b4b6e83dd63fddb8810d3ef96
...
1
2
3
 
4
5
 
6
...
1
2
3
4
5
6
7
8
0
@@ -1,6 +1,8 @@
0
 %w[util
0
    constants
0
    messages
0
+ prepared_statement
0
    connection].each {|f| require "#{ File.dirname(__FILE__) }/#{ f }" }
0
 
0
+require 'stringio'
0
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
...
82
83
84
85
86
 
 
 
 
 
 
 
 
 
 
 
 
87
88
89
...
125
126
127
 
 
 
128
129
130
...
145
146
147
148
 
149
150
151
152
153
154
 
155
156
157
158
159
 
160
161
162
163
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
166
167
...
169
170
171
 
 
172
 
 
 
 
173
174
175
176
177
178
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
181
182
...
206
207
208
209
210
 
211
212
213
...
233
234
235
 
 
 
 
 
 
 
 
 
 
 
 
236
237
238
239
240
 
241
242
243
...
1
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
4
5
...
34
35
36
 
 
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
...
87
88
89
90
91
92
93
94
95
...
110
111
112
 
113
114
115
116
117
118
 
119
120
121
122
 
 
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
...
158
159
160
161
162
163
164
165
166
167
168
169
 
 
 
 
 
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
...
213
214
215
 
 
216
217
218
219
...
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
 
258
259
260
261
0
@@ -1,53 +1,5 @@
0
 require File.dirname(__FILE__) + '/asymy'
0
 
0
-require 'stringio'
0
-
0
-# XXX for debugging --- ditch both these methods when we're done
0
-
0
-class Fixnum; def printable?; self >= 0x20 && self <= 0x7e; end; end
0
-
0
-class String
0
- if RUBY_VERSION[0..2] != '1.9'
0
- def hexdump(capture=false)
0
- sio = StringIO.new
0
- rem = size - 1
0
- off = 0
0
-
0
- while rem > 0
0
- pbuf = ""
0
- pad = (15 - rem) if rem < 16
0
- pad ||= 0
0
-
0
- sio.write(("0" * (8 - (x = off.to_s(16)).size)) + x + " ")
0
-
0
- 0.upto(15-pad) do |i|
0
- c = self[off]
0
- x = c.to_s(16)
0
- sio.write(("0" * (2 - x.size)) + x + " ")
0
- if c.printable?
0
- pbuf << c
0
- else
0
- pbuf << "."
0
- end
0
- off += 1
0
- rem -= 1
0
- sio.write(" ") if i == 7
0
- end
0
-
0
- sio.write("-- " * pad) if pad > 0
0
- sio.write(" |#{ pbuf }|\n")
0
- end
0
-
0
- sio.rewind()
0
- if capture
0
- sio.read()
0
- else
0
- puts sio.read()
0
- end
0
- end
0
- end
0
-end
0
-
0
 module Asymy
0
 
0
     # I'm thinking, one per connection
0
@@ -82,8 +34,18 @@ module Asymy
0
         # block argument that receives the results, in two arguments, fields (an array of hashes)
0
         # and rows (an array of strings)
0
         def exec(cmd, &block)
0
- @queue << [cmd.extend(StringX), block]
0
- inject if ready?
0
+ @queue << [cmd.extend(StringX), block, Commands::QUERY]
0
+ @fp.inject
0
+ end
0
+
0
+ def prepare(cmd, &block)
0
+ @queue << [cmd.extend(StringX), block, Commands::STMT_PREPARE]
0
+ @fp.inject
0
+ end
0
+
0
+ def execute_prepared(args, &block)
0
+ @queue << [args, block, Commands::STMT_EXECUTE]
0
+ @fp.inject
0
         end
0
 
0
         # no user-servicable parts below (attrs used to communicate with module)
0
@@ -125,6 +87,9 @@ module Asymy
0
                     self.state = :error
0
                 end
0
 
0
+ def packet.eof?; self[0].ord == 0xfe; end
0
+ def packet.ok?; self[0].ord == 0x00; end
0
+
0
                 case self.state
0
                 when :preauth
0
                     handle_preauth(num, Packets::Greeting.new(packet))
0
@@ -145,23 +110,47 @@ module Asymy
0
                     # XXX just ignore for now
0
                     self.state = :awaiting_fields
0
                 when :awaiting_fields
0
- if packet[0].ord == 0xfe
0
+ if packet.eof?
0
                         self.state = :awaiting_rows
0
                     else
0
                         handle_field(num, Packets::Field.new(packet))
0
                     end
0
                 when :awaiting_rows
0
- if packet[0].ord == 0xfe
0
+ if packet.eof?
0
                         @cb.call(@fields, @rows)
0
                         @fields = nil
0
                         @rows = nil
0
- self.state = :ready
0
- inject
0
+ ready!
0
                     else
0
                         # rows have a variable number of variable-length strings, and no other
0
                         # structure, so just hand the raw data off.
0
                         handle_row(num, packet)
0
                     end
0
+ when :awaiting_statement_handle
0
+ if packet.ok?
0
+ handle_statement_handle(num, Packets::PrepareOk.new(packet))
0
+ else
0
+ # XXX handle this case
0
+ @state = :error
0
+ end
0
+ when :awaiting_prepared_params
0
+ if packet.eof?
0
+ @state = :waiting_prepared_fields
0
+ else
0
+ # I cannot for the life of me figure out what I'm supposed
0
+ # to do with these --- using mysql-ruby, I can't get them
0
+ # to change based on their type. Why does MySQL send them?
0
+ # I figured it'd be to let me enforce types on the params.
0
+ end
0
+ when :awaiting_prepared_fields
0
+ if packet.eof?
0
+ @cb.call(@stmt)
0
+ @cb, @stmt, @expect_params, @expect_columns = nil, nil, nil, nil
0
+ ready!
0
+ else
0
+ # I guess I could cache these? But why bother? MySQL is just
0
+ # going to send them again. This protocol confuses and infuriates us!
0
+ end
0
                 when :error
0
                     pp self.error
0
                 else
0
@@ -169,14 +158,32 @@ module Asymy
0
                 end
0
             end
0
 
0
+ def ready!; self.state = :ready; inject; end
0
+
0
             def inject
0
+ return if not ready?
0
+
0
+ # this is going to get untidy real fast XXX
0
+
0
                 if(now = self.queue.slice!(0))
0
                     @cb = now[1]
0
- self.state = :awaiting_result_set
0
- p = Packets::Command.new
0
- p.command = Commands::QUERY
0
- p.arg = now[0]
0
- send_data(p.marshall)
0
+ case now[2]
0
+ when Commands::QUERY
0
+ self.state = :awaiting_result_set
0
+ p = Packets::Command.new
0
+ p.command = Commands::QUERY
0
+ p.arg = now[0]
0
+ send_data(p.marshall)
0
+ when Commands::STMT_PREPARE
0
+ self.state = :awaiting_statement_handle
0
+ p = Packets::Prepare.new
0
+ p.query = now[0]
0
+ send_data(p.marshall)
0
+ when Commands::STMT_EXECUTE
0
+
0
+ else
0
+ raise "wtf?"
0
+ end
0
                 end
0
             end
0
 
0
@@ -206,8 +213,7 @@ module Asymy
0
             end
0
 
0
             def handle_postauth(num, ok)
0
- self.state = :ready
0
- inject
0
+ ready!
0
             end
0
 
0
             def handle_field(num, field)
0
@@ -233,11 +239,23 @@ module Asymy
0
                 @rows << rv
0
             end
0
 
0
+ def handle_statement_handle(num, ok)
0
+ @stmt = PreparedStatement.new(:backpointer => @bp,
0
+ :handle => ok.stmt_id)
0
+ @expect_params = @stmt.parameters
0
+ @expect_columns = @stmt.columns
0
+ if @expect_params > 0
0
+ @state = :awaiting_prepared_params
0
+ else
0
+ @state = :awaiting_prepared_columns
0
+ end
0
+ end
0
+
0
             def method_missing(meth, *args); @bp.send(meth, *args); end
0
         end
0
 
0
         def reco
0
- EventMachine::connect(@target, @port, Session) {|c| c.bp = self}
0
+ EventMachine::connect(@target, @port, Session) {|c| c.bp = self; @fp = c;}
0
         end
0
 
0
         public
...
80
81
82
 
83
84
85
...
80
81
82
83
84
85
86
0
@@ -80,6 +80,7 @@ module Asymy
0
         STRING = 0xfe
0
         GEOMETRY = 0xff
0
     end
0
+ FieldTypes.extend(ModuleX)
0
 
0
     module FieldFlags
0
         NOT_NULL = 0x0001
...
145
146
147
 
 
 
 
 
 
 
 
 
 
 
 
148
149
150
...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
0
@@ -145,6 +145,18 @@ module Asymy
0
             field :command, :l8
0
             field :arg, :asciiz
0
         end
0
+
0
+ class PrepareStatement < Packet
0
+ field :command, :l8, Commands::STMT_PREPARE
0
+ field :query, :asciiz
0
+ end
0
+
0
+ class PrepareOk < Packet
0
+ field :field_count, :l8
0
+ field :stmt_id, :l32
0
+ field :columns, :l16
0
+ field :parameters, :l16
0
+ end
0
     end
0
 
0
 end
...
9
10
11
12
13
14
 
 
 
15
16
17
...
9
10
11
 
 
 
12
13
14
15
16
17
0
@@ -9,9 +9,9 @@ EventMachine::run {
0
     c = Asymy::Connection.new(:target => "localhost",
0
                               :port => 13306,
0
                               :username => "dummy",
0
- :password => "pass",
0
- :database => "test")
0
- c.exec("select * from edges") do |x, y|
0
+ :password => "helu",
0
+ :database => "mysql")
0
+ c.exec("select * from user") do |x, y|
0
         pp x
0
         pp y
0
     end
...
23
24
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
 
 
 
 
 
 
 
 
 
27
28
29
...
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
0
@@ -23,7 +23,63 @@ class Numeric
0
     def ord; self; end unless RUBY_VERSION[0..2] == '1.9'
0
 end
0
 
0
+# XXX for debugging --- ditch both these methods when we're done
0
+
0
+class Fixnum; def printable?; self >= 0x20 && self <= 0x7e; end; end
0
+
0
+class String
0
+ if RUBY_VERSION[0..2] != '1.9'
0
+ def hexdump(capture=false)
0
+ sio = StringIO.new
0
+ rem = size - 1
0
+ off = 0
0
+
0
+ while rem > 0
0
+ pbuf = ""
0
+ pad = (15 - rem) if rem < 16
0
+ pad ||= 0
0
+
0
+ sio.write(("0" * (8 - (x = off.to_s(16)).size)) + x + " ")
0
+
0
+ 0.upto(15-pad) do |i|
0
+ c = self[off]
0
+ x = c.to_s(16)
0
+ sio.write(("0" * (2 - x.size)) + x + " ")
0
+ if c.printable?
0
+ pbuf << c
0
+ else
0
+ pbuf << "."
0
+ end
0
+ off += 1
0
+ rem -= 1
0
+ sio.write(" ") if i == 7
0
+ end
0
+
0
+ sio.write("-- " * pad) if pad > 0
0
+ sio.write(" |#{ pbuf }|\n")
0
+ end
0
+
0
+ sio.rewind()
0
+ if capture
0
+ sio.read()
0
+ else
0
+ puts sio.read()
0
+ end
0
+ end
0
+ end
0
+end
0
+
0
 module Asymy
0
+ module ModuleX
0
+ def to_name_hash
0
+ @name_hash ||= constants.map {|k| [k.intern, const_get(k.intern)]}.to_hash
0
+ end
0
+
0
+ def to_key_hash
0
+ @key_hash ||= constants.map {|k| [const_get(k.intern), k.intern]}.to_hash
0
+ end
0
+ end
0
+
0
     module StringX
0
         def crypt(nonce)
0
             sha = lambda {|k| OpenSSL::Digest::SHA1.new(k).digest }

Comments

    No one has commented yet.