<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>prepared_statement.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,6 +1,8 @@
 %w[util
    constants
    messages
+   prepared_statement
    connection].each {|f| require &quot;#{ File.dirname(__FILE__) }/#{ f }&quot; }
 
+require 'stringio'
 </diff>
      <filename>asymy.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,53 +1,5 @@
 require File.dirname(__FILE__) + '/asymy'
 
-require 'stringio'
-
-# XXX for debugging --- ditch both these methods when we're done
-
-class Fixnum; def printable?; self &gt;= 0x20 &amp;&amp; self &lt;= 0x7e; end; end
-
-class String
-    if RUBY_VERSION[0..2] != '1.9'
-        def hexdump(capture=false)
-            sio = StringIO.new
-            rem = size - 1
-            off = 0
-
-            while rem &gt; 0
-                pbuf = &quot;&quot;
-                pad = (15 - rem) if rem &lt; 16
-                pad ||= 0
-
-                sio.write((&quot;0&quot; * (8 - (x = off.to_s(16)).size)) + x + &quot;  &quot;)
-
-                0.upto(15-pad) do |i|
-                    c = self[off]
-                    x = c.to_s(16)
-                    sio.write((&quot;0&quot; * (2 - x.size)) + x + &quot; &quot;)
-                    if c.printable?
-                        pbuf &lt;&lt; c
-                    else
-                        pbuf &lt;&lt; &quot;.&quot;
-                    end
-                    off += 1
-                    rem -= 1
-                    sio.write(&quot; &quot;) if i == 7
-                end
-
-                sio.write(&quot;-- &quot; * pad) if pad &gt; 0
-                sio.write(&quot; |#{ pbuf }|\n&quot;)
-            end
-
-            sio.rewind()
-            if capture
-                sio.read()
-            else
-                puts sio.read()
-            end
-        end
-    end
-end
-
 module Asymy
 
     # I'm thinking, one per connection
@@ -82,8 +34,18 @@ module Asymy
         # block argument that receives the results, in two arguments, fields (an array of hashes)
         # and rows (an array of strings)
         def exec(cmd, &amp;block)
-            @queue &lt;&lt; [cmd.extend(StringX), block]
-            inject if ready?
+            @queue &lt;&lt; [cmd.extend(StringX), block, Commands::QUERY]
+            @fp.inject
+        end
+
+        def prepare(cmd, &amp;block)
+            @queue &lt;&lt; [cmd.extend(StringX), block, Commands::STMT_PREPARE]
+            @fp.inject
+        end
+
+        def execute_prepared(args, &amp;block)
+            @queue &lt;&lt; [args, block, Commands::STMT_EXECUTE]
+            @fp.inject
         end
 
         # no user-servicable parts below (attrs used to communicate with module)
@@ -125,6 +87,9 @@ module Asymy
                     self.state = :error
                 end
 
+                def packet.eof?; self[0].ord == 0xfe; end
+                def packet.ok?; self[0].ord == 0x00; end
+
                 case self.state
                 when :preauth
                     handle_preauth(num, Packets::Greeting.new(packet))
@@ -145,23 +110,47 @@ module Asymy
                     # XXX just ignore for now
                     self.state = :awaiting_fields
                 when :awaiting_fields
-                    if packet[0].ord == 0xfe
+                    if packet.eof?
                         self.state = :awaiting_rows
                     else
                         handle_field(num, Packets::Field.new(packet))
                     end
                 when :awaiting_rows
-                    if packet[0].ord == 0xfe
+                    if packet.eof?
                         @cb.call(@fields, @rows)
                         @fields = nil
                         @rows = nil
-                        self.state = :ready
-                        inject
+                        ready!
                     else
                         # rows have a variable number of variable-length strings, and no other
                         # structure, so just hand the raw data off.
                         handle_row(num, packet)
                     end
+                when :awaiting_statement_handle
+                    if packet.ok?
+                        handle_statement_handle(num, Packets::PrepareOk.new(packet))
+                    else
+                        # XXX handle this case
+                        @state = :error
+                    end
+                when :awaiting_prepared_params
+                    if packet.eof?
+                        @state = :waiting_prepared_fields
+                    else
+                        # I cannot for the life of me figure out what I'm supposed
+                        # to do with these --- using mysql-ruby, I can't get them
+                        # to change based on their type. Why does MySQL send them?
+                        # I figured it'd be to let me enforce types on the params.
+                    end
+                when :awaiting_prepared_fields
+                    if packet.eof?
+                        @cb.call(@stmt)
+                        @cb, @stmt, @expect_params, @expect_columns = nil, nil, nil, nil
+                        ready!
+                    else
+                        # I guess I could cache these? But why bother? MySQL is just
+                        # going to send them again. This protocol confuses and infuriates us!
+                    end
                 when :error
                     pp self.error
                 else
@@ -169,14 +158,32 @@ module Asymy
                 end
             end
 
+            def ready!; self.state = :ready; inject; end
+
             def inject
+                return if not ready?
+
+                # this is going to get untidy real fast XXX
+
                 if(now = self.queue.slice!(0))
                     @cb = now[1]
-                    self.state = :awaiting_result_set
-                    p = Packets::Command.new
-                    p.command = Commands::QUERY
-                    p.arg = now[0]
-                    send_data(p.marshall)
+                    case now[2]
+                    when Commands::QUERY
+                        self.state = :awaiting_result_set
+                        p = Packets::Command.new
+                        p.command = Commands::QUERY
+                        p.arg = now[0]
+                        send_data(p.marshall)
+                    when Commands::STMT_PREPARE
+                        self.state = :awaiting_statement_handle
+                        p = Packets::Prepare.new
+                        p.query = now[0]
+                        send_data(p.marshall)
+                    when Commands::STMT_EXECUTE
+
+                    else
+                        raise &quot;wtf?&quot;
+                    end
                 end
             end
 
@@ -206,8 +213,7 @@ module Asymy
             end
 
             def handle_postauth(num, ok)
-                self.state = :ready
-                inject
+                ready!
             end
 
             def handle_field(num, field)
@@ -233,11 +239,23 @@ module Asymy
                 @rows &lt;&lt; rv
             end
 
+            def handle_statement_handle(num, ok)
+                @stmt = PreparedStatement.new(:backpointer =&gt; @bp,
+                                              :handle =&gt; ok.stmt_id)
+                @expect_params = @stmt.parameters
+                @expect_columns = @stmt.columns
+                if @expect_params &gt; 0
+                    @state = :awaiting_prepared_params
+                else
+                    @state = :awaiting_prepared_columns
+                end
+            end
+
             def method_missing(meth, *args); @bp.send(meth, *args); end
         end
 
         def reco
-            EventMachine::connect(@target, @port, Session) {|c| c.bp = self}
+            EventMachine::connect(@target, @port, Session) {|c| c.bp = self; @fp = c;}
         end
 
         public</diff>
      <filename>connection.rb</filename>
    </modified>
    <modified>
      <diff>@@ -80,6 +80,7 @@ module Asymy
         STRING = 0xfe
         GEOMETRY = 0xff
     end
+    FieldTypes.extend(ModuleX)
 
     module FieldFlags
         NOT_NULL = 0x0001</diff>
      <filename>constants.rb</filename>
    </modified>
    <modified>
      <diff>@@ -145,6 +145,18 @@ module Asymy
             field :command, :l8
             field :arg, :asciiz
         end
+
+        class PrepareStatement &lt; Packet
+            field :command, :l8, Commands::STMT_PREPARE
+            field :query, :asciiz
+        end
+
+        class PrepareOk &lt; Packet
+            field :field_count, :l8
+            field :stmt_id, :l32
+            field :columns, :l16
+            field :parameters, :l16
+        end
     end
 
 end</diff>
      <filename>messages.rb</filename>
    </modified>
    <modified>
      <diff>@@ -9,9 +9,9 @@ EventMachine::run {
     c = Asymy::Connection.new(:target =&gt; &quot;localhost&quot;,
                               :port =&gt; 13306,
                               :username =&gt; &quot;dummy&quot;,
-                              :password =&gt; &quot;pass&quot;,
-                              :database =&gt; &quot;test&quot;)
-    c.exec(&quot;select * from edges&quot;) do |x, y|
+                              :password =&gt; &quot;helu&quot;,
+                              :database =&gt; &quot;mysql&quot;)
+    c.exec(&quot;select * from user&quot;) do |x, y|
         pp x
         pp y
     end</diff>
      <filename>test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,7 +23,63 @@ class Numeric
     def ord; self; end unless RUBY_VERSION[0..2] == '1.9'
 end
 
+# XXX for debugging --- ditch both these methods when we're done
+
+class Fixnum; def printable?; self &gt;= 0x20 &amp;&amp; self &lt;= 0x7e; end; end
+
+class String
+    if RUBY_VERSION[0..2] != '1.9'
+        def hexdump(capture=false)
+            sio = StringIO.new
+            rem = size - 1
+            off = 0
+
+            while rem &gt; 0
+                pbuf = &quot;&quot;
+                pad = (15 - rem) if rem &lt; 16
+                pad ||= 0
+
+                sio.write((&quot;0&quot; * (8 - (x = off.to_s(16)).size)) + x + &quot;  &quot;)
+
+                0.upto(15-pad) do |i|
+                    c = self[off]
+                    x = c.to_s(16)
+                    sio.write((&quot;0&quot; * (2 - x.size)) + x + &quot; &quot;)
+                    if c.printable?
+                        pbuf &lt;&lt; c
+                    else
+                        pbuf &lt;&lt; &quot;.&quot;
+                    end
+                    off += 1
+                    rem -= 1
+                    sio.write(&quot; &quot;) if i == 7
+                end
+
+                sio.write(&quot;-- &quot; * pad) if pad &gt; 0
+                sio.write(&quot; |#{ pbuf }|\n&quot;)
+            end
+
+            sio.rewind()
+            if capture
+                sio.read()
+            else
+                puts sio.read()
+            end
+        end
+    end
+end
+
 module Asymy
+    module ModuleX
+        def to_name_hash
+            @name_hash ||= constants.map {|k| [k.intern, const_get(k.intern)]}.to_hash
+        end
+
+        def to_key_hash
+            @key_hash ||= constants.map {|k| [const_get(k.intern), k.intern]}.to_hash
+        end
+    end
+
     module StringX
         def crypt(nonce)
             sha = lambda {|k| OpenSSL::Digest::SHA1.new(k).digest }</diff>
      <filename>util.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>ef447a762996c83b4b6e83dd63fddb8810d3ef96</id>
    </parent>
  </parents>
  <author>
    <name>Thomas Ptacek</name>
    <email>tqbf@matasano.com</email>
  </author>
  <url>http://github.com/tqbf/asymy/commit/687ee9dd2058485e082fde63d338a578d32ea299</url>
  <id>687ee9dd2058485e082fde63d338a578d32ea299</id>
  <committed-date>2008-06-13T00:19:40-07:00</committed-date>
  <authored-date>2008-06-13T00:19:40-07:00</authored-date>
  <message>a lazy swipe at prepared statements --- about 20% of the way there, maybe?</message>
  <tree>6f7911e30b56bcc2c25b003575991e2ad97ee6a8</tree>
  <committer>
    <name>Thomas Ptacek</name>
    <email>tqbf@matasano.com</email>
  </committer>
</commit>
