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})