Merge lp:~jamesh/storm/bug-387840 into lp:storm

Proposed by James Henstridge
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jamesh/storm/bug-387840
Merge into: lp:storm
Diff against target: 139 lines
4 files modified
NEWS (+4/-0)
storm/expr.py (+27/-0)
tests/databases/base.py (+27/-0)
tests/expr.py (+31/-0)
To merge this branch: bzr merge lp:~jamesh/storm/bug-387840
Reviewer Review Type Date Requested Status
John O'Brien (community) Approve
Jamu Kakar (community) Approve
Review via email: mp+13769@code.launchpad.net
To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote :

Add some string manipulation methods to Comparable implemented in terms of the LIKE operator:
 * startswith(): same semantics as unicode.startswith()
 * endswith(): same semantics as unicode.endswith()
 * containsstring(): same semantics as unicode.__contains__()

Each method performs escaping of SQL regexp special characters.

I didn't know whether adding a string-specific __contains__() method to Comparable would be appropriate, which is why the last method has the name containsstring(). I'd be happy to change that if anyone has a suggestion (either a new name, or __contains__ if that is appropriate).

Revision history for this message
Jamu Kakar (jkakar) wrote :

Nice branch! I'm not sure about __contains__ either. I like contains_string anyway, because it's obvious. +1!

review: Approve
Revision history for this message
John O'Brien (jdobrien) wrote :

This works great and it's something i could really use right now!

review: Approve
lp:~jamesh/storm/bug-387840 updated
333. By James Henstridge

merge from trunk

334. By James Henstridge

Add NEWS file entry.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-10-23 19:19:16 +0000
3+++ NEWS 2009-11-02 11:51:52 +0000
4@@ -7,6 +7,10 @@
5 if it is of the same type. The resulting expression tree uses less
6 stack when compiling so reduces the chance of hitting Python's
7 recursion limit (bug #242813).
8+ - Add startswith(), endswith() and contains_string() methods to
9+ Comparable. These methods perform prefix, suffix and substring
10+ checks respectively using the LIKE operator, taking care of escaping
11+ for you (bug #387840).
12
13 Bug fixes
14 ---------
15
16=== modified file 'storm/expr.py'
17--- storm/expr.py 2009-10-21 09:44:32 +0000
18+++ storm/expr.py 2009-11-02 11:51:52 +0000
19@@ -395,6 +395,15 @@
20 raise CompileError("Can't compile python expressions with %r" % type(expr))
21
22
23+# A translation table that can escape a unicode string for use in a
24+# Like() expression that uses "!" as the escape character.
25+like_escape = {
26+ ord(u"!"): u"!!",
27+ ord(u"_"): u"!_",
28+ ord(u"%"): u"!%"
29+ }
30+
31+
32 class Comparable(object):
33 __slots__ = ()
34
35@@ -498,6 +507,24 @@
36 def upper(self):
37 return Upper(self)
38
39+ def startswith(self, prefix):
40+ if not isinstance(prefix, unicode):
41+ raise ExprError("Expected unicode argument, got %r" % type(prefix))
42+ pattern = prefix.translate(like_escape) + u"%"
43+ return Like(self, pattern, u"!")
44+
45+ def endswith(self, suffix):
46+ if not isinstance(suffix, unicode):
47+ raise ExprError("Expected unicode argument, got %r" % type(suffix))
48+ pattern = u"%" + suffix.translate(like_escape)
49+ return Like(self, pattern, u"!")
50+
51+ def contains_string(self, substring):
52+ if not isinstance(substring, unicode):
53+ raise ExprError("Expected unicode argument, got %r" % type(substring))
54+ pattern = u"%" + substring.translate(like_escape) + u"%"
55+ return Like(self, pattern, u"!")
56+
57
58 class ComparableExpr(Expr, Comparable):
59 __slots__ = ()
60
61=== modified file 'tests/databases/base.py'
62--- tests/databases/base.py 2009-07-30 06:19:27 +0000
63+++ tests/databases/base.py 2009-11-02 11:51:52 +0000
64@@ -395,6 +395,33 @@
65 "UPDATE test SET title='whatever'")
66 self.assertEquals(result.rowcount, 2)
67
68+ def test_expr_startswith(self):
69+ self.connection.execute("INSERT INTO test VALUES (30, '!!_%blah')")
70+ self.connection.execute("INSERT INTO test VALUES (40, '!!blah')")
71+ id = Column("id", SQLToken("test"))
72+ title = Column("title", SQLToken("test"))
73+ expr = Select(id, title.startswith(u"!!_%"))
74+ result = list(self.connection.execute(expr))
75+ self.assertEquals(result, [(30,)])
76+
77+ def test_expr_endswith(self):
78+ self.connection.execute("INSERT INTO test VALUES (30, 'blah_%!!')")
79+ self.connection.execute("INSERT INTO test VALUES (40, 'blah!!')")
80+ id = Column("id", SQLToken("test"))
81+ title = Column("title", SQLToken("test"))
82+ expr = Select(id, title.endswith(u"_%!!"))
83+ result = list(self.connection.execute(expr))
84+ self.assertEquals(result, [(30,)])
85+
86+ def test_expr_contains_string(self):
87+ self.connection.execute("INSERT INTO test VALUES (30, 'blah_%!!x')")
88+ self.connection.execute("INSERT INTO test VALUES (40, 'blah!!x')")
89+ id = Column("id", SQLToken("test"))
90+ title = Column("title", SQLToken("test"))
91+ expr = Select(id, title.contains_string(u"_%!!"))
92+ result = list(self.connection.execute(expr))
93+ self.assertEquals(result, [(30,)])
94+
95
96 class UnsupportedDatabaseTest(object):
97
98
99=== modified file 'tests/expr.py'
100--- tests/expr.py 2009-10-21 09:44:32 +0000
101+++ tests/expr.py 2009-11-02 11:51:52 +0000
102@@ -184,6 +184,37 @@
103 expr = Like(elem1, elem2, elem3, False)
104 self.assertEquals(expr.case_sensitive, False)
105
106+ def test_startswith(self):
107+ expr = Func1()
108+ self.assertRaises(ExprError, expr.startswith, "not a unicode string")
109+
110+ like_expr = expr.startswith(u"abc!!_%")
111+ self.assertTrue(isinstance(like_expr, Like))
112+ self.assertTrue(like_expr.expr1 is expr)
113+ self.assertEquals(like_expr.expr2, u"abc!!!!!_!%%")
114+ self.assertEquals(like_expr.escape, u"!")
115+
116+ def test_endswith(self):
117+ expr = Func1()
118+ self.assertRaises(ExprError, expr.startswith, "not a unicode string")
119+
120+ like_expr = expr.endswith(u"abc!!_%")
121+ self.assertTrue(isinstance(like_expr, Like))
122+ self.assertTrue(like_expr.expr1 is expr)
123+ self.assertEquals(like_expr.expr2, u"%abc!!!!!_!%")
124+ self.assertEquals(like_expr.escape, u"!")
125+
126+ def test_contains_string(self):
127+ expr = Func1()
128+ self.assertRaises(
129+ ExprError, expr.contains_string, "not a unicode string")
130+
131+ like_expr = expr.contains_string(u"abc!!_%")
132+ self.assertTrue(isinstance(like_expr, Like))
133+ self.assertTrue(like_expr.expr1 is expr)
134+ self.assertEquals(like_expr.expr2, u"%abc!!!!!_!%%")
135+ self.assertEquals(like_expr.escape, u"!")
136+
137 def test_eq(self):
138 expr = Eq(elem1, elem2)
139 self.assertEquals(expr.expr1, elem1)

Subscribers

People subscribed via source and target branches

to status/vote changes: