3 new revisions:

Revision: e916933631f3
Author:   paul cannon <[email protected]>
Date:     Tue Nov 15 07:21:23 2011
Log:      fix marshallers under python2.4
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=e916933631f3

Revision: 21aab4e89390
Author:   paul cannon <[email protected]>
Date:     Tue Dec 13 14:36:44 2011
Log:      fix overzealous named param regex...
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=21aab4e89390

Revision: 318469bef40d
Author:   paul cannon <[email protected]>
Date:     Tue Dec 13 14:56:06 2011
Log:      fix/add to test_query_preparation for last
http://code.google.com/a/apache-extras.org/p/cassandra-dbapi2/source/detail?r=318469bef40d

==============================================================================
Revision: e916933631f3
Author:   paul cannon <[email protected]>
Date:     Tue Nov 15 07:21:23 2011
Log:      fix marshallers under python2.4

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

Modified:
 /cql/marshal.py

=======================================
--- /cql/marshal.py     Mon Nov  7 12:32:03 2011
+++ /cql/marshal.py     Tue Nov 15 07:21:23 2011
@@ -118,7 +118,7 @@
             return None
         return _float_packer.unpack(bytestr)[0]
 else:
-    def unmarshal_long(bytestr):
+    def unmarshal_float(bytestr):
         if not bytestr:
             return None
         return struct.unpack(">f", bytestr)[0]
@@ -129,7 +129,7 @@
             return None
         return _double_packer.unpack(bytestr)[0]
 else:
-    def unmarshal_long(bytestr):
+    def unmarshal_double(bytestr):
         if not bytestr:
             return None
         return struct.unpack(">d", bytestr)[0]

==============================================================================
Revision: 21aab4e89390
Author:   paul cannon <[email protected]>
Date:     Tue Dec 13 14:36:44 2011
Log:      fix overzealous named param regex

named params immediately preceded by identifiers or string literals will
no longer be recognized. it wouldn't make much sense to be using named
params in that way, anyway.

this solves the problem with new :-delimited columnfamily attributes in
CQL, as explained in issue-11.

remove the notion of backslash-escaping colons, since the colon is not a
valid character in CQL outside of option identifiers anyway (where it
must be immediately preceded and followed by identifier characters).

verified that other DB-API2.0 libs implementing paramstyle='named' treat
substitution markers the same way: ignore markers in string literals,
and markers immediately following identifiers or string literals.

also don't throw an error when more params are provided than requested
in the query string; this situation could occur lots of ways in practice
without it being an error. compare (str % dict) behavior.

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

Modified:
 /cql/marshal.py

=======================================
--- /cql/marshal.py     Tue Nov 15 07:21:23 2011
+++ /cql/marshal.py     Tue Dec 13 14:36:44 2011
@@ -21,7 +21,7 @@

 import cql

-__all__ = ['prepare', 'marshal', 'unmarshal_noop', 'unmarshallers']
+__all__ = ['prepare', 'cql_quote', 'unmarshal_noop', 'unmarshallers']

 if hasattr(struct, 'Struct'): # new in Python 2.5
    _have_struct = True
@@ -39,7 +39,23 @@
         def __init__(self, bytes):
             self.bytes = bytes

-_param_re = re.compile(r"(?<!strategy_options)(?<!\\)(:[a-zA-Z_][a-zA-Z0-9_]*)", re.M)
+_param_re = re.compile(r"""
+    (                           # stuff that is not substitution markers
+      (?:  ' [^']* '            # string literal; ignore colons in here
+        |  [^']                 # eat anything else
+      )*?
+    )
+ (?<! [a-zA-Z0-9_'] ) # no colons immediately preceded by an ident or str literal
+    :
+    ( [a-zA-Z_][a-zA-Z0-9_]* )  # the param name
+""", re.S | re.X)
+
+_comment_re = re.compile(r"""
+    (?:  /\* .*? \*/
+      |  // [^\n]*
+      |  -- [^\n]*
+    )
+""", re.S | re.X)

 BYTES_TYPE = "org.apache.cassandra.db.marshal.BytesType"
 ASCII_TYPE = "org.apache.cassandra.db.marshal.AsciiType"
@@ -58,15 +74,19 @@
 COUNTER_COLUMN_TYPE = "org.apache.cassandra.db.marshal.CounterColumnType"

 def prepare(query, params):
-    # For every match of the form ":param_name", call marshal
-    # on kwargs['param_name'] and replace that section of the query
-    # with the result
- new, count = re.subn(_param_re, lambda m: marshal(params[m.group(1)[1:]]), query)
-    if len(params) > count:
- raise cql.ProgrammingError("More keywords were provided than parameters")
-    return new.replace("\:", ":")
-
-def marshal(term):
+    """
+    For every match of the form ":param_name", call cql_quote
+    on kwargs['param_name'] and replace that section of the query
+    with the result
+    """
+
+ # kill comments first, so that we don't have to try to parse around them.
+    # but keep the character count the same, so that location-tagged error
+    # messages still work
+    query = _comment_re.sub(lambda m: ' ' * len(m.group(0)), query)
+ return _param_re.sub(lambda m: m.group(1) + cql_quote(params[m.group(2)]), query)
+
+def cql_quote(term):
     if isinstance(term, unicode):
         return "'%s'" % __escape_quotes(term.encode('utf8'))
     elif isinstance(term, str):
@@ -177,5 +197,5 @@
     return val

 def __escape_quotes(term):
-    assert isinstance(term, (str, unicode))
-    return term.replace("\'", "''")
+    assert isinstance(term, basestring)
+    return term.replace("'", "''")

==============================================================================
Revision: 318469bef40d
Author:   paul cannon <[email protected]>
Date:     Tue Dec 13 14:56:06 2011
Log:      fix/add to test_query_preparation for last

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

Modified:
 /test/test_query_preparation.py

=======================================
--- /test/test_query_preparation.py     Wed Aug 24 11:18:52 2011
+++ /test/test_query_preparation.py     Tue Dec 13 14:56:06 2011
@@ -30,12 +30,24 @@
 """
 SELECT :a..:b FROM ColumnFamily;
 """,
+"""
+CREATE KEYSPACE foo WITH strategy_class='SimpleStrategy' AND strategy_options:replication_factor = :_a_;
+""",
+"""
+CREATE COLUMNFAMILY blah WITH somearg:another=:opt AND foo='bar':baz AND option=:value:suffix;
+""",
+"""
+SELECT :lo..:hi FROM ColumnFamily WHERE KEY=':dontsubstthis' AND col > /* ignore :this */ :colval23;
+""",
 )

 ARGUMENTS = (
{'a': 1, 'b': 3, 'c': long(1000), 'd': long(3000), 'e': "key", 'f': unicode("val")},
     {},
     {'a': "a'b", 'b': "c'd'e"},
+    {'_a_': 12},
+    {'opt': "abc'", 'unused': 'thatsok', 'value': '\n'},
+    {'lo': ' ', 'hi': ':hi', 'colval23': 0.2},
 )

 STANDARDS = (
@@ -48,18 +60,25 @@
 """
 SELECT 'a''b'..'c''d''e' FROM ColumnFamily;
 """,
+"""
+CREATE KEYSPACE foo WITH strategy_class='SimpleStrategy' AND strategy_options:replication_factor = 12;
+""",
+"""
+CREATE COLUMNFAMILY blah WITH somearg:another='abc''' AND foo='bar':baz AND option='
+':suffix;
+""",
+"""
+SELECT ' '..':hi' FROM ColumnFamily WHERE KEY=':dontsubstthis' AND col
                   0.2;
+""",
 )

 class TestPrepare(unittest.TestCase):
     def test_prepares(self):
         "test prepared queries against known standards"
-        for (i, test) in enumerate(TESTS):
-            a = prepare(test, ARGUMENTS[i])
-            b = STANDARDS[i]
-            assert a == b, "\n%s !=\n%s" % (a, b)
+        for test, args, standard in zip(TESTS, ARGUMENTS, STANDARDS):
+            prepared = prepare(test, args)
+            self.assertEqual(prepared, standard)

     def test_bad(self):
         "ensure bad calls raise exceptions"
         self.assertRaises(KeyError, prepare, ":a :b", {'a': 1})
- self.assertRaises(cql.ProgrammingError, prepare, ":a :b", {'a': 1, 'b': 2, 'c': 3})
-        self.assertRaises(cql.ProgrammingError, prepare, "none", {'a': 1})

Reply via email to