4 new revisions:

Revision: 81f460c98a20
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 11:09:39 2012
Log:      add marshalling capability
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=81f460c98a20

Revision: ff3c1d6228df
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 11:11:08 2012
Log:      get TApplicationException from thrift, not ttypes...
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=ff3c1d6228df

Revision: f6611bc3ca30
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 11:18:45 2012
Log:      add tests for marshallers
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=f6611bc3ca30

Revision: b71be13e3a41
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 13:52:06 2012
Log:      support for prepared statements
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=b71be13e3a41

==============================================================================
Revision: 81f460c98a20
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 11:09:39 2012
Log:      add marshalling capability

http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=81f460c98a20

Modified:
 /cql/marshal.py

=======================================
--- /cql/marshal.py     Tue Jan 24 09:46:37 2012
+++ /cql/marshal.py     Thu Feb  9 11:09:39 2012
@@ -105,68 +105,122 @@
 def unmarshal_noop(bytestr):
     return bytestr

+marshal_noop = unmarshal_noop
+
 def unmarshal_bool(bytestr):
     if not bytestr:
         return None
     return bool(ord(bytestr[0]))

+def marshal_bool(truth):
+    if truth is None:
+        return ''
+    return chr(bool(truth))
+
 def unmarshal_utf8(bytestr):
     return bytestr.decode("utf8")

+def marshal_utf8(ustr):
+    if ustr is None:
+        return ''
+    return ustr.encode('utf8')
+
 if _have_struct:
     def unmarshal_int32(bytestr):
         if not bytestr:
             return None
         return _int32_packer.unpack(bytestr)[0]
+    def marshal_int32(i):
+        if i is None:
+            return ''
+        return _int32_packer.pack(i)
 else:
     def unmarshal_int32(bytestr):
         if not bytestr:
             return None
         return struct.unpack(">i", bytestr)[0]
+    def marshal_int32(i):
+        if i is None:
+            return ''
+        return struct.pack('>i', i)

 def unmarshal_int(bytestr):
     if not bytestr:
         return None
     return decode_bigint(bytestr)

+def marshal_int(bigint):
+    if bigint is None:
+        return ''
+    return encode_bigint(bigint)
+
 if _have_struct:
     def unmarshal_long(bytestr):
         if not bytestr:
             return None
         return _long_packer.unpack(bytestr)[0]
+    def marshal_long(longint):
+        if longint is None:
+            return ''
+        return _long_packer.pack(longint)
 else:
     def unmarshal_long(bytestr):
         if not bytestr:
             return None
         return struct.unpack(">q", bytestr)[0]
+    def marshal_long(longint):
+        if longint is None:
+            return ''
+        return struct.pack(">q", longint)

 if _have_struct:
     def unmarshal_float(bytestr):
         if not bytestr:
             return None
         return _float_packer.unpack(bytestr)[0]
+    def marshal_float(f):
+        if f is None:
+            return ''
+        return _float_packer.pack(f)
 else:
     def unmarshal_float(bytestr):
         if not bytestr:
             return None
         return struct.unpack(">f", bytestr)[0]
+    def marshal_float(f):
+        if f is None:
+            return ''
+        return struct.pack('>f', f)

 if _have_struct:
     def unmarshal_double(bytestr):
         if not bytestr:
             return None
         return _double_packer.unpack(bytestr)[0]
+    def marshal_double(d):
+        if d is None:
+            return ''
+        return _double_packer.pack(d)
 else:
     def unmarshal_double(bytestr):
         if not bytestr:
             return None
         return struct.unpack(">d", bytestr)[0]
+    def marshal_double(d):
+        if d is None:
+            return ''
+        return struct.pack('>d', d)

 def unmarshal_date(bytestr):
     if not bytestr:
         return None
     return unmarshal_long(bytestr) / 1000.0

+def marshal_date(date):
+    if date is None:
+        return ''
+    return marshal_long(date * 1000)
+
 def unmarshal_decimal(bytestr):
     if not bytestr:
         return None
@@ -174,11 +228,27 @@
     unscaled = decode_bigint(bytestr[4:])
     return Decimal('%de%d' % (unscaled, -scale))

+def marshal_decimal(dec):
+    if dec is None:
+        return ''
+    sign, digits, exponent = dec.as_tuple()
+    unscaled = int(''.join([str(digit) for digit in digits]))
+    if sign:
+        unscaled *= -1
+    scale = marshal_int32(-exponent)
+    unscaled = encode_bigint(unscaled)
+    return scale + unscaled
+
 def unmarshal_uuid(bytestr):
     if not bytestr:
         return None
     return UUID(bytes=bytestr)

+def marshal_uuid(uuid):
+    if uuid is None:
+        return ''
+    return uuid.bytes
+
 unmarshallers = {BYTES_TYPE:          unmarshal_noop,
                  ASCII_TYPE:          unmarshal_noop,
                  BOOLEAN_TYPE:        unmarshal_bool,
@@ -198,12 +268,53 @@
     short_name = name.split('.')[-1]
     unmarshallers[short_name] = typ

+marshallers =   {BYTES_TYPE:          marshal_noop,
+                 ASCII_TYPE:          marshal_noop,
+                 BOOLEAN_TYPE:        marshal_bool,
+                 DATE_TYPE:           marshal_date,
+                 DECIMAL_TYPE:        marshal_decimal,
+                 UTF8_TYPE:           marshal_utf8,
+                 INT32_TYPE:          marshal_int32,
+                 INTEGER_TYPE:        marshal_int,
+                 LONG_TYPE:           marshal_long,
+                 FLOAT_TYPE:          marshal_float,
+                 DOUBLE_TYPE:         marshal_double,
+                 UUID_TYPE:           marshal_uuid,
+                 LEXICAL_UUID_TYPE:   marshal_uuid,
+                 TIME_UUID_TYPE:      marshal_uuid,
+                 COUNTER_COLUMN_TYPE: marshal_long}
+for name, typ in marshallers.items():
+    short_name = name.split('.')[-1]
+    marshallers[short_name] = typ
+
 def decode_bigint(term):
     val = int(term.encode('hex'), 16)
     if (ord(term[0]) & 128) != 0:
         val = val - (1 << (len(term) * 8))
     return val

+def bitlength(n):
+    bitlen = 0
+    while n > 0:
+        n >>= 1
+        bitlen += 1
+    return bitlen
+
+def encode_bigint(big):
+    pos = True
+    if big < 0:
+        bytelength = bitlength(abs(big) - 1) / 8 + 1
+        big = (1 << bytelength * 8) + big
+        pos = False
+    revbytes = []
+    while big > 0:
+        revbytes.append(chr(big & 0xff))
+        big >>= 8
+    if pos and ord(revbytes[-1]) & 0x80:
+        revbytes.append('\x00')
+    revbytes.reverse()
+    return ''.join(revbytes)
+
 def __escape_quotes(term):
     assert isinstance(term, basestring)
     return term.replace("'", "''")

==============================================================================
Revision: ff3c1d6228df
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 11:11:08 2012
Log:      get TApplicationException from thrift, not ttypes

since it doesn't show up in ttypes anymore for all Thrift versions, and
eventually it's going to disappear. not a great idea to rely on another
module's imports, anyway.

http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=ff3c1d6228df

Modified:
 /cql/cursor.py

=======================================
--- /cql/cursor.py      Fri Oct  7 16:06:33 2011
+++ /cql/cursor.py      Thu Feb  9 11:11:08 2012
@@ -27,8 +27,8 @@
     InvalidRequestException,
     UnavailableException,
     TimedOutException,
-    TApplicationException,
     SchemaDisagreementException)
+from thrift.Thrift import TApplicationException

 _COUNT_DESCRIPTION = (None, None, None, None, None, None, None)
 _VOID_DESCRIPTION = (None)

==============================================================================
Revision: f6611bc3ca30
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 11:18:45 2012
Log:      add tests for marshallers

http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=f6611bc3ca30

Added:
 /test/test_marshalling.py
Deleted:
 /test/test_unmarshalling.py

=======================================
--- /dev/null
+++ /test/test_marshalling.py   Thu Feb  9 11:18:45 2012
@@ -0,0 +1,88 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+from decimal import Decimal
+from uuid import UUID
+import cql
+from cql.marshal import unmarshallers, marshallers, unmarshal_noop, marshal_noop
+
+marshalled_value_pairs = (
+ ('lorem ipsum dolor sit amet', 'AsciiType', 'lorem ipsum dolor sit amet'),
+    ('', 'AsciiType', ''),
+    ('\x01', 'BooleanType', True),
+    ('\x00', 'BooleanType', False),
+    ('', 'BooleanType', None),
+    ('\xff\xfe\xfd\xfc\xfb', 'BytesType', '\xff\xfe\xfd\xfc\xfb'),
+    ('', 'BytesType', ''),
+ ('\x7f\xff\xff\xff\xff\xff\xff\xff', 'CounterColumnType', 9223372036854775807), + ('\x80\x00\x00\x00\x00\x00\x00\x00', 'CounterColumnType', -9223372036854775808),
+    ('', 'CounterColumnType', None),
+    ('\x00\x00\x013\x7fb\xeey', 'DateType', 1320692149.881),
+    ('', 'DateType', None),
+ ('\x00\x00\x00\r\nJ\x04"^\x91\x04\x8a\xb1\x18\xfe', 'DecimalType', Decimal('1243878957943.1234124191998')), + ('\x00\x00\x00\x06\xe5\xde]\x98Y', 'DecimalType', Decimal('-112233.441191')), + ('\x00\x00\x00\x14\x00\xfa\xce', 'DecimalType', Decimal('0.00000000000000064206')), + ('\x00\x00\x00\x14\xff\x052', 'DecimalType', Decimal('-0.00000000000000064206')),
+    ('\xff\xff\xff\x9c\x00\xfa\xce', 'DecimalType', Decimal('64206e100')),
+    ('', 'DecimalType', None),
+    ('@\xd2\xfa\x08\x00\x00\x00\x00', 'DoubleType', 19432.125),
+    ('\xc0\xd2\xfa\x08\x00\x00\x00\x00', 'DoubleType', -19432.125),
+ ('\x7f\xef\x00\x00\x00\x00\x00\x00', 'DoubleType', 1.7415152243978685e+308),
+    ('', 'DoubleType', None),
+    ('F\x97\xd0@', 'FloatType', 19432.125),
+    ('\xc6\x97\xd0@', 'FloatType', -19432.125),
+    ('\xc6\x97\xd0@', 'FloatType', -19432.125),
+ ('\x7f\x7f\x00\x00', 'FloatType', 338953138925153547590470800371487866880.0),
+    ('', 'FloatType', None),
+    ('\x7f\x50\x00\x00', 'Int32Type', 2135949312),
+    ('\xff\xfd\xcb\x91', 'Int32Type', -144495),
+    ('', 'Int32Type', None),
+ ('f\x1e\xfd\xf2\xe3\xb1\x9f|\x04_\x15', 'IntegerType', 123456789123456789123456789),
+    ('', 'IntegerType', None),
+    ('\x7f\xff\xff\xff\xff\xff\xff\xff', 'LongType',  9223372036854775807),
+    ('\x80\x00\x00\x00\x00\x00\x00\x00', 'LongType', -9223372036854775808),
+    ('', 'LongType', None),
+ ('\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6', 'UTF8Type', u'\u307e\u3057\u3066'), + ('\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6' * 1000, 'UTF8Type', u'\u307e\u3057\u3066' * 1000),
+    ('', 'UTF8Type', u''),
+ ('\xff' * 16, 'UUIDType', UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')), + ('I\x15~\xfc\xef<\x9d\xe3\x16\x98\xaf\x80\x1f\xb4\x0b*', 'UUIDType', UUID('49157efc-ef3c-9de3-1698-af801fb40b2a')),
+    ('', 'UUIDType', None),
+)
+
+class TestUnmarshal(unittest.TestCase):
+    def test_unmarshalling(self):
+        for serializedval, valtype, nativeval in marshalled_value_pairs:
+            unmarshaller = unmarshallers.get(valtype, unmarshal_noop)
+            whatwegot = unmarshaller(serializedval)
+            self.assertEqual(whatwegot, nativeval,
+ msg='Unmarshaller for %s (%s) failed: unmarshal(%r) got %r instead of %r' + % (valtype, unmarshaller, serializedval, whatwegot, nativeval))
+            self.assertEqual(type(whatwegot), type(nativeval),
+ msg='Unmarshaller for %s (%s) gave wrong type (%s instead of %s)' + % (valtype, unmarshaller, type(whatwegot), type(nativeval)))
+
+    def test_marshalling(self):
+        for serializedval, valtype, nativeval in marshalled_value_pairs:
+            marshaller = marshallers.get(valtype, marshal_noop)
+            whatwegot = marshaller(nativeval)
+            self.assertEqual(whatwegot, serializedval,
+ msg='Marshaller for %s (%s) failed: marshal(%r) got %r instead of %r' + % (valtype, marshaller, nativeval, whatwegot, serializedval))
+            self.assertEqual(type(whatwegot), type(serializedval),
+ msg='Marshaller for %s (%s) gave wrong type (%s instead of %s)' + % (valtype, marshaller, type(whatwegot), type(serializedval)))
=======================================
--- /test/test_unmarshalling.py Mon Nov  7 12:32:57 2011
+++ /dev/null
@@ -1,77 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import unittest
-from decimal import Decimal
-from uuid import UUID
-import cql
-from cql.marshal import unmarshallers, unmarshal_noop
-
-demarshal_me = (
- ('lorem ipsum dolor sit amet', 'AsciiType', 'lorem ipsum dolor sit amet'),
-    ('', 'AsciiType', ''),
-    ('\x01', 'BooleanType', True),
-    ('\x00', 'BooleanType', False),
-    ('', 'BooleanType', None),
-    ('\xff\xfe\xfd\xfc\xfb', 'BytesType', '\xff\xfe\xfd\xfc\xfb'),
-    ('', 'BytesType', ''),
- ('\x7f\xff\xff\xff\xff\xff\xff\xff', 'CounterColumnType', 9223372036854775807), - ('\x80\x00\x00\x00\x00\x00\x00\x00', 'CounterColumnType', -9223372036854775808),
-    ('', 'CounterColumnType', None),
-    ('\x00\x00\x013\x7fb\xeey', 'DateType', 1320692149.881),
-    ('', 'DateType', None),
- ('\x00\x00\x00\r\nJ\x04"^\x91\x04\x8a\xb1\x18\xfe', 'DecimalType', Decimal('1243878957943.1234124191998')), - ('\x00\x00\x00\x06\xe5\xde]\x98Y', 'DecimalType', Decimal('-112233.441191')), - ('\x00\x00\x00\x14\x00\xfa\xce', 'DecimalType', Decimal('0.00000000000000064206')), - ('\x00\x00\x00\x14\xff\x052', 'DecimalType', Decimal('-0.00000000000000064206')),
-    ('\xff\xff\xff\x9c\x00\xfa\xce', 'DecimalType', Decimal('64206e100')),
-    ('', 'DecimalType', None),
-    ('@\xd2\xfa\x08\x00\x00\x00\x00', 'DoubleType', 19432.125),
-    ('\xc0\xd2\xfa\x08\x00\x00\x00\x00', 'DoubleType', -19432.125),
- ('\x7f\xef\x00\x00\x00\x00\x00\x00', 'DoubleType', 1.7415152243978685e+308),
-    ('', 'DoubleType', None),
-    ('F\x97\xd0@', 'FloatType', 19432.125),
-    ('\xc6\x97\xd0@', 'FloatType', -19432.125),
-    ('\xc6\x97\xd0@', 'FloatType', -19432.125),
- ('\x7f\x7f\x00\x00', 'FloatType', 338953138925153547590470800371487866880.0),
-    ('', 'FloatType', None),
-    ('\x7f\x50\x00\x00', 'Int32Type', 2135949312),
-    ('\xff\xfd\xcb\x91', 'Int32Type', -144495),
-    ('', 'Int32Type', None),
- ('f\x1e\xfd\xf2\xe3\xb1\x9f|\x04_\x15', 'IntegerType', 123456789123456789123456789),
-    ('', 'IntegerType', None),
-    ('\x7f\xff\xff\xff\xff\xff\xff\xff', 'LongType',  9223372036854775807),
-    ('\x80\x00\x00\x00\x00\x00\x00\x00', 'LongType', -9223372036854775808),
-    ('', 'LongType', None),
- ('\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6', 'UTF8Type', u'\u307e\u3057\u3066'), - ('\xe3\x81\xbe\xe3\x81\x97\xe3\x81\xa6' * 1000, 'UTF8Type', u'\u307e\u3057\u3066' * 1000),
-    ('', 'UTF8Type', u''),
- ('\xff' * 16, 'UUIDType', UUID('ffffffff-ffff-ffff-ffff-ffffffffffff')), - ('I\x15~\xfc\xef<\x9d\xe3\x16\x98\xaf\x80\x1f\xb4\x0b*', 'UUIDType', UUID('49157efc-ef3c-9de3-1698-af801fb40b2a')),
-    ('', 'UUIDType', None),
-)
-
-class TestUnmarshal(unittest.TestCase):
-    def test_unmarshalling(self):
-        for serializedval, valtype, marshaledval in demarshal_me:
-            unmarshaller = unmarshallers.get(valtype, unmarshal_noop)
-            whatwegot = unmarshaller(serializedval)
-            self.assertEqual(whatwegot, marshaledval,
- msg='Unmarshaller for %s (%s) failed: unmarshal(%r) got %r instead of %r' - % (valtype, unmarshaller, serializedval, whatwegot, marshaledval))
-            self.assertEqual(type(whatwegot), type(marshaledval),
- msg='Unmarshaller for %s (%s) gave wrong type (%s instead of %s)' - % (valtype, unmarshaller, type(whatwegot), type(marshaledval)))

==============================================================================
Revision: b71be13e3a41
Author:   paul cannon <[email protected]>
Date:     Thu Feb  9 13:52:06 2012
Log:      support for prepared statements

http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=b71be13e3a41

Modified:
 /cql/connection.py
 /cql/cursor.py
 /cql/marshal.py

=======================================
--- /cql/connection.py  Wed Jan 25 04:36:19 2012
+++ /cql/connection.py  Thu Feb  9 13:52:06 2012
@@ -49,6 +49,8 @@
             credentials = {"username": user, "password": password}
self.client.login(AuthenticationRequest(credentials=credentials))

+ self.remote_thrift_version = tuple(map(int, self.client.describe_version().split('.')))
+
         if cql_version:
             self.client.set_cql_version(cql_version)

=======================================
--- /cql/cursor.py      Thu Feb  9 11:11:08 2012
+++ /cql/cursor.py      Thu Feb  9 13:52:06 2012
@@ -19,7 +19,7 @@
 import zlib

 import cql
-from cql.marshal import prepare
+from cql.marshal import prepare_inline, prepare_query, PreparedQuery
 from cql.decoders import SchemaDecoder
 from cql.cassandra.ttypes import (
     Compression,
@@ -33,6 +33,8 @@
 _COUNT_DESCRIPTION = (None, None, None, None, None, None, None)
 _VOID_DESCRIPTION = (None)

+MIN_THRIFT_FOR_PREPARED_QUERIES = (19, 27, 0)
+
 class Cursor:
     _keyspace_re = re.compile("USE (\w+);?",
                               re.IGNORECASE | re.MULTILINE)
@@ -40,6 +42,7 @@
                              re.IGNORECASE | re.MULTILINE | re.DOTALL)
     _ddl_re = re.compile("\s*(CREATE|ALTER|DROP)\s+",
                          re.IGNORECASE | re.MULTILINE)
+    supports_prepared_queries = False

     def __init__(self, parent_connection):
         self.open_socket = True
@@ -55,6 +58,10 @@
         self.compression = 'GZIP'
         self.decoder = None

+ if hasattr(parent_connection.client, 'execute_prepared_cql_query') \ + and parent_connection.remote_thrift_version >= MIN_THRIFT_FOR_PREPARED_QUERIES:
+            self.supports_prepared_queries = True
+
     ###
     # Cursor API
     ###
@@ -62,29 +69,60 @@
     def close(self):
         self.open_socket = False

-    def prepare(self, query, params):
-        return prepare(query, params)
-
-    def execute(self, cql_query, params={}, decoder=None):
+    def compress_query_text(self, querytext):
+        if self.compression == 'GZIP':
+            compressed_q = zlib.compress(querytext)
+        else:
+            compressed_q = querytext
+        req_compression = getattr(Compression, self.compression)
+        return compressed_q, req_compression
+
+    def prepare_inline(self, query, params):
+        try:
+            prepared_q_text = prepare_inline(query, params)
+        except KeyError, e:
+            raise cql.ProgrammingError("Unmatched named substitution: " +
+                                       "%s not given for %r" % (e, query))
+        return self.compress_query_text(prepared_q_text)
+
+    def prepare_query(self, query, paramtypes=None):
+        prepared_q_text, paramnames = prepare_query(query)
+ compressed_q, compression = self.compress_query_text(prepared_q_text) + presult = self._connection.client.prepare_cql_query(compressed_q, compression)
+        assert presult.count == len(paramnames)
+        if presult.variable_types is None and presult.count > 0:
+ raise cql.ProgrammingError("Cassandra did not provide types for bound" + " parameters. Prepared statements are only"
+                                       " supported with cql3.")
+ return PreparedQuery(query, presult.itemId, presult.variable_types, paramnames)
+
+    def pre_execution_setup(self):
         self.__checksock()
         self.rs_idx = 0
         self.rowcount = 0
         self.description = None
-        try:
-            prepared_q = self.prepare(cql_query, params)
-        except KeyError, e:
-            raise cql.ProgrammingError("Unmatched named substitution: " +
- "%s not given for %s" % (e, cql_query))
-
-        if self.compression == 'GZIP':
-            compressed_q = zlib.compress(prepared_q)
-        else:
-            compressed_q = prepared_q
-        request_compression = getattr(Compression, self.compression)
-
+
+    def execute(self, cql_query, params={}, decoder=None):
+        self.pre_execution_setup()
+
+        prepared_q, compress = self.prepare_inline(cql_query, params)
+        doquery = self._connection.client.execute_cql_query
+ response = self.handle_cql_execution_errors(doquery, prepared_q, compress)
+
+        return self.process_execution_results(response, decoder=decoder)
+
+    def execute_prepared(self, prepared_query, params={}, decoder=None):
+        self.pre_execution_setup()
+
+        doquery = self._connection.client.execute_prepared_cql_query
+        paramvals = prepared_query.encode_params()
+ response = self.handle_cql_execution_errors(doquery, prepared_query.itemid, paramvals)
+
+        return self.process_execution_results(response, decoder=decoder)
+
+    def handle_cql_execution_errors(self, executor, *args, **kwargs):
         try:
-            client = self._connection.client
- response = client.execute_cql_query(compressed_q, request_compression)
+            return executor(*args, **kwargs)
         except InvalidRequestException, ire:
             raise cql.ProgrammingError("Bad Request: %s" % ire.why)
         except SchemaDisagreementException, sde:
@@ -97,6 +135,7 @@
         except TApplicationException, tapp:
             raise cql.InternalError("Internal application error")

+    def process_execution_results(self, response, decoder=None):
         if response.type == CqlResultType.ROWS:
             self.decoder = (decoder or SchemaDecoder)(response.schema)
             self.result = response.rows
=======================================
--- /cql/marshal.py     Thu Feb  9 11:09:39 2012
+++ /cql/marshal.py     Thu Feb  9 13:52:06 2012
@@ -21,7 +21,8 @@

 import cql

-__all__ = ['prepare', 'cql_quote', 'unmarshal_noop', 'unmarshallers']
+__all__ = ['prepare_inline', 'prepare_query', 'cql_quote', 'unmarshal_noop', 'unmarshallers',
+           'cql_marshal', 'PreparedQuery']

 if hasattr(struct, 'Struct'): # new in Python 2.5
    _have_struct = True
@@ -73,6 +74,19 @@
 TIME_UUID_TYPE = "org.apache.cassandra.db.marshal.TimeUUIDType"
 COUNTER_COLUMN_TYPE = "org.apache.cassandra.db.marshal.CounterColumnType"

+class PreparedQuery(object):
+    def __init__(self, querytext, itemid, vartypes, paramnames):
+        self.querytext = querytext
+        self.itemid = itemid
+        self.vartypes = vartypes
+        self.paramnames = paramnames
+        if len(self.vartypes) != len(self.paramnames):
+ raise cql.ProgrammingError("Length of variable types list is not the same" + " length as the list of parameter names")
+
+    def encode_params(self, params):
+ return [cql_marshal(params[n], t) for (n, t) in zip(self.paramnames, self.vartypes)]
+
 def blank_comments(query):
     def teh_blanker(match):
         m = match.group(0)
@@ -81,7 +95,7 @@
         return ' ' * len(m)
     return _comment_re.sub(teh_blanker, query)

-def prepare(query, params):
+def prepare_inline(query, params):
     """
     For every match of the form ":param_name", call cql_quote
     on kwargs['param_name'] and replace that section of the query
@@ -94,6 +108,18 @@
     query = blank_comments(query)
return _param_re.sub(lambda m: m.group(1) + cql_quote(params[m.group(2)]), query)

+def prepare_query(querytext):
+    querytext = blank_comments(querytext)
+    paramnames = []
+    def found_param(match):
+        pre_param_text = match.group(1)
+        paramname = match.group(2)
+        paramnames.append(paramname)
+        return pre_param_text + '?'
+    transformed_query = _param_re.sub(found_param, querytext)
+    return transformed_query, paramnames
+
+
 def cql_quote(term):
     if isinstance(term, unicode):
         return "'%s'" % __escape_quotes(term.encode('utf8'))

Reply via email to