Skip to content

Commit

Permalink
Merge pull request #61 from ddormer/56-deprecate-like-blob
Browse files Browse the repository at this point in the history
Deprecate startswith/endswith/like/notLike on `attributes.bytes`.

LIKE comparisons on SQLite BLOB values do not work as you might expect, and don't work at all if SQLite was compiled with `SQLITE_LIKE_DOESNT_MATCH_BLOBS`.
  • Loading branch information
mithrandi committed Jan 30, 2016
2 parents 55782bc + 17d518f commit a86c0e7
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 36 deletions.
25 changes: 25 additions & 0 deletions axiom/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from zope.interface import implements

from twisted.python import filepath
from twisted.python.deprecate import deprecated
from twisted.python.components import registerAdapter
from twisted.python.versions import Version

from epsilon.extime import Time

Expand Down Expand Up @@ -915,11 +917,34 @@ def infilter(self, pyval, oself, store):
raise ConstraintError(self, "str or other byte buffer", pyval)
return buffer(pyval)


def outfilter(self, dbval, oself):
if dbval is None:
return None
return str(dbval)


@deprecated(Version("Axiom", 0, 7, 5))
def like(self, *others):
return super(SQLAttribute, self).like(*others)


@deprecated(Version("Axiom", 0, 7, 5))
def notLike(self, *others):
return super(SQLAttribute, self).notLike(*others)


@deprecated(Version("Axiom", 0, 7, 5))
def startswith(self, other):
return super(SQLAttribute, self).startswith(other)


@deprecated(Version("Axiom", 0, 7, 5))
def endswith(self, other):
return super(SQLAttribute, self).endswith(other)



class InvalidPathError(ValueError):
"""
A path that could not be used with the database was attempted to be used
Expand Down
85 changes: 49 additions & 36 deletions axiom/test/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -1218,8 +1218,8 @@ def testOneString(self):
D.one.notLike('foobar%'),
'(%s NOT LIKE (?))' % (D.one.getColumnName(self.store),),
['foobar%'])
self.assertEquals(self.query(D, D.one.like('d1.one')), [self.d1])
self.assertEquals(self.query(D, D.one.notLike('d%.one')), [])
self.assertEquals(self.query(D, D.four.like(u'd1.four')), [self.d1])
self.assertEquals(self.query(D, D.four.notLike(u'd%.four')), [])

def testOneColumn(self):
self.assertQuery(
Expand All @@ -1230,11 +1230,11 @@ def testOneColumn(self):

def testOneColumnAndStrings(self):
self.assertQuery(
D.one.like('%', D.id, '%one'),
'(%s LIKE (? || %s || ?))' % (D.one.getColumnName(self.store),
D.four.like(u'%', D.id, u'%four'),
'(%s LIKE (? || %s || ?))' % (D.four.getColumnName(self.store),
D.id.getColumnName(self.store)),
['%', '%one'])
q = self.query(D, D.one.like('%', D.id, '%one'))
[u'%', u'%four'])
q = self.query(D, D.four.like(u'%', D.id, u'%four'))
e = [self.d1, self.d2, self.d3]
self.assertEquals(sorted(q), sorted(e))

Expand All @@ -1251,27 +1251,17 @@ def testMultipleColumns(self):

def testStartsEndsWith(self):
self.assertQuery(
D.one.startswith('foo'),
'(%s LIKE (?))' % (D.one.getColumnName(self.store),),
D.four.startswith(u'foo'),
'(%s LIKE (?))' % (D.four.getColumnName(self.store),),
['foo%'])
self.assertQuery(
D.one.endswith('foo'),
'(%s LIKE (?))' % (D.one.getColumnName(self.store),),
D.four.endswith(u'foo'),
'(%s LIKE (?))' % (D.four.getColumnName(self.store),),
['%foo'])
self.assertEquals(
self.query(D, D.one.startswith('d1')), [self.d1])
self.query(D, D.four.startswith(u'd1')), [self.d1])
self.assertEquals(
self.query(D, D.one.endswith('3.one')), [self.d3])


def testStartsEndsWithColumn(self):
self.assertQuery(
D.one.startswith(D.two),
'(%s LIKE (%s || ?))' % (D.one.getColumnName(self.store),
D.two.getColumnName(self.store)),
['%'])
self.assertEquals(
self.query(D, D.one.startswith(D.two)), [])
self.query(D, D.four.endswith(u'3.four')), [self.d3])


def testStartsEndsWithText(self):
Expand All @@ -1283,20 +1273,6 @@ def testStartsEndsWithText(self):
[self.d2])


def testOtherTable(self):
self.assertQuery(
D.one.startswith(A.type),
'(%s LIKE (%s || ?))' % (D.one.getColumnName(self.store),
A.type.getColumnName(self.store)),
['%'])

C(store=self.store, name=u'd1.')
C(store=self.store, name=u'2.one')
self.assertEquals(
self.query(D, D.one.startswith(C.name)), [self.d1])
self.assertEquals(
self.query(D, D.one.endswith(C.name)), [self.d2])


class UniqueTest(TestCase):

Expand Down Expand Up @@ -1796,3 +1772,40 @@ def test_placeholderColumnNamesInQueryTarget(self):
expectedSQL = "placeholder_0.oid, placeholder_0.[attr], placeholder_0.[characters], placeholder_0.[other]"

self.assertEquals(query._queryTarget, expectedSQL)



class BytesDeprecatedLikeTests(TestCase):
"""
Deprecated tests for LIKE queries on L{axiom.attributes.bytes}.
"""
def test_startsWith(self):
self.assertWarns(
DeprecationWarning,
'axiom.attributes.startswith was deprecated in Axiom 0.7.5',
__file__,
lambda: D.one.startswith('string'))


def test_endsWith(self):
self.assertWarns(
DeprecationWarning,
'axiom.attributes.endswith was deprecated in Axiom 0.7.5',
__file__,
lambda: D.one.endswith('string'))


def test_like(self):
self.assertWarns(
DeprecationWarning,
'axiom.attributes.like was deprecated in Axiom 0.7.5',
__file__,
lambda: D.one.like('string'))


def test_notLike(self):
self.assertWarns(
DeprecationWarning,
'axiom.attributes.notLike was deprecated in Axiom 0.7.5',
__file__,
lambda: D.one.notLike('string'))

0 comments on commit a86c0e7

Please sign in to comment.