Skip to content

Commit 285fabf

Browse files
author
Richard Jones
committed
added Class.find() unit test, fixed implementations
1 parent 19a133c commit 285fabf

File tree

6 files changed

+177
-46
lines changed

6 files changed

+177
-46
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ are given with the most recent entry first.
3636
- handle stupid mailers that QUOTE their Re; 'Re: "[issue1] bla blah"'
3737
- giving a user a Role that doesn't exist doesn't break stuff any more
3838
- revamped user guide, customisation guide, added maintenance guide
39+
- merge Zope Collector #580 fix from ZPT CVS trunk
3940

4041

4142
2002-09-13 0.5.0 beta2

roundup/backends/back_anydbm.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
#$Id: back_anydbm.py,v 1.85 2002-09-24 01:59:28 richard Exp $
18+
#$Id: back_anydbm.py,v 1.86 2002-09-26 03:04:24 richard Exp $
1919
'''
2020
This module defines a backend that saves the hyperdatabase in a database
2121
chosen by anydbm. It is guaranteed to always be available in python
@@ -524,6 +524,7 @@ def pack(self, pack_before):
524524
if __debug__:
525525
print >>hyperdb.DEBUG, 'packjournal', (self, pack_before)
526526

527+
pack_before = pack_before.serialise()
527528
for classname in self.getclasses():
528529
# get the journal db
529530
db_name = 'journals.%s'%classname
@@ -540,21 +541,10 @@ def pack(self, pack_before):
540541
# unpack the entry
541542
(nodeid, date_stamp, self.journaltag, action,
542543
params) = entry
543-
date_stamp = date.Date(date_stamp)
544544
# if the entry is after the pack date, _or_ the initial
545545
# create entry, then it stays
546546
if date_stamp > pack_before or action == 'create':
547547
l.append(entry)
548-
elif action == 'set':
549-
# grab the last set entry to keep information on
550-
# activity
551-
last_set_entry = entry
552-
if last_set_entry:
553-
date_stamp = last_set_entry[1]
554-
# if the last set entry was made after the pack date
555-
# then it is already in the list
556-
if date_stamp < pack_before:
557-
l.append(last_set_entry)
558548
db[key] = marshal.dumps(l)
559549
if db_type == 'gdbm':
560550
db.reorganize()
@@ -1443,14 +1433,17 @@ def lookup(self, keyvalue):
14431433
def find(self, **propspec):
14441434
'''Get the ids of nodes in this class which link to the given nodes.
14451435
1446-
'propspec' consists of keyword args propname={nodeid:1,}
1447-
'propname' must be the name of a property in this class, or a
1448-
KeyError is raised. That property must be a Link or Multilink
1449-
property, or a TypeError is raised.
1436+
'propspec' consists of keyword args propname=nodeid or
1437+
propname={nodeid:1, }
1438+
'propname' must be the name of a property in this class, or a
1439+
KeyError is raised. That property must be a Link or
1440+
Multilink property, or a TypeError is raised.
14501441
14511442
Any node in this class whose 'propname' property links to any of the
14521443
nodeids will be returned. Used by the full text indexing, which knows
1453-
that "foo" occurs in msg1, msg3 and file7, so we have hits on these issues:
1444+
that "foo" occurs in msg1, msg3 and file7, so we have hits on these
1445+
issues:
1446+
14541447
db.issue.find(messages={'1':1,'3':1}, files={'7':1})
14551448
'''
14561449
propspec = propspec.items()

roundup/backends/back_gadfly.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_gadfly.py,v 1.26 2002-09-24 01:59:28 richard Exp $
1+
# $Id: back_gadfly.py,v 1.27 2002-09-26 03:04:24 richard Exp $
22
__doc__ = '''
33
About Gadfly
44
============
@@ -47,14 +47,23 @@
4747
4848
'''
4949

50-
from roundup.backends.rdbms_common import *
50+
# standard python modules
51+
import sys, os, time, re, errno, weakref, copy
52+
53+
# roundup modules
54+
from roundup import hyperdb, date, password, roundupdb, security
55+
from roundup.hyperdb import String, Password, Date, Interval, Link, \
56+
Multilink, DatabaseError, Boolean, Number
57+
58+
# basic RDBMS backen implementation
59+
from roundup.backends import rdbms_common
5160

5261
# the all-important gadfly :)
5362
import gadfly
5463
import gadfly.client
5564
import gadfly.database
5665

57-
class Database(Database):
66+
class Database(rdbms_common.Database):
5867
# char to use for positional arguments
5968
arg = '?'
6069

@@ -238,10 +247,18 @@ def filter(self, search_matches, filterspec, sort, group):
238247
# return the IDs
239248
return [row[0] for row in l]
240249

241-
class Class(GadflyClass, Class):
250+
def find(self, **propspec):
251+
''' Overload to filter out duplicates in the result
252+
'''
253+
d = {}
254+
for k in rdbms_common.Class.find(self, **propspec):
255+
d[k] = 1
256+
return d.keys()
257+
258+
class Class(GadflyClass, rdbms_common.Class):
242259
pass
243-
class IssueClass(GadflyClass, IssueClass):
260+
class IssueClass(GadflyClass, rdbms_common.IssueClass):
244261
pass
245-
class FileClass(GadflyClass, FileClass):
262+
class FileClass(GadflyClass, rdbms_common.FileClass):
246263
pass
247264

roundup/backends/rdbms_common.py

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: rdbms_common.py,v 1.18 2002-09-25 05:27:29 richard Exp $
1+
# $Id: rdbms_common.py,v 1.19 2002-09-26 03:04:24 richard Exp $
22

33
# standard python modules
44
import sys, os, time, re, errno, weakref, copy
@@ -1458,11 +1458,13 @@ def retire(self, nodeid):
14581458
if self.db.journaltag is None:
14591459
raise DatabaseError, 'Database open read-only'
14601460

1461-
sql = 'update _%s set __retired__=1 where id=%s'%(self.classname,
1462-
self.db.arg)
1461+
# use the arg for __retired__ to cope with any odd database type
1462+
# conversion (hello, sqlite)
1463+
sql = 'update _%s set __retired__=%s where id=%s'%(self.classname,
1464+
self.db.arg, self.db.arg)
14631465
if __debug__:
14641466
print >>hyperdb.DEBUG, 'retire', (self, sql, nodeid)
1465-
self.db.cursor.execute(sql, (nodeid,))
1467+
self.db.cursor.execute(sql, (1, nodeid))
14661468

14671469
def is_retired(self, nodeid):
14681470
'''Return true if the node is rerired
@@ -1570,10 +1572,11 @@ def lookup(self, keyvalue):
15701572
if not self.key:
15711573
raise TypeError, 'No key property set for class %s'%self.classname
15721574

1573-
sql = '''select id from _%s where _%s=%s
1574-
and __retired__ != '1' '''%(self.classname, self.key,
1575-
self.db.arg)
1576-
self.db.sql(sql, (keyvalue,))
1575+
# use the arg to handle any odd database type conversion (hello,
1576+
# sqlite)
1577+
sql = "select id from _%s where _%s=%s and __retired__ <> %s"%(
1578+
self.classname, self.key, self.db.arg, self.db.arg)
1579+
self.db.sql(sql, (keyvalue, 1))
15771580

15781581
# see if there was a result that's not retired
15791582
row = self.db.sql_fetchone()
@@ -1587,7 +1590,8 @@ def lookup(self, keyvalue):
15871590
def find(self, **propspec):
15881591
'''Get the ids of nodes in this class which link to the given nodes.
15891592
1590-
'propspec' consists of keyword args propname={nodeid:1,}
1593+
'propspec' consists of keyword args propname=nodeid or
1594+
propname={nodeid:1, }
15911595
'propname' must be the name of a property in this class, or a
15921596
KeyError is raised. That property must be a Link or Multilink
15931597
property, or a TypeError is raised.
@@ -1601,17 +1605,51 @@ def find(self, **propspec):
16011605
'''
16021606
if __debug__:
16031607
print >>hyperdb.DEBUG, 'find', (self, propspec)
1608+
1609+
# shortcut
16041610
if not propspec:
16051611
return []
1606-
queries = []
1607-
tables = []
1612+
1613+
# validate the args
1614+
props = self.getprops()
1615+
propspec = propspec.items()
1616+
for propname, nodeids in propspec:
1617+
# check the prop is OK
1618+
prop = props[propname]
1619+
if not isinstance(prop, Link) and not isinstance(prop, Multilink):
1620+
raise TypeError, "'%s' not a Link/Multilink property"%propname
1621+
1622+
# first, links
1623+
where = []
16081624
allvalues = ()
1609-
for prop, values in propspec.items():
1610-
allvalues += tuple(values.keys())
1611-
a = self.db.arg
1625+
a = self.db.arg
1626+
for prop, values in propspec:
1627+
if not isinstance(props[prop], hyperdb.Link):
1628+
continue
1629+
if type(values) is type(''):
1630+
allvalues += (values,)
1631+
where.append('_%s = %s'%(prop, a))
1632+
else:
1633+
allvalues += tuple(values.keys())
1634+
where.append('_%s in (%s)'%(prop, ','.join([a]*len(values))))
1635+
tables = []
1636+
if where:
1637+
tables.append('select id as nodeid from _%s where %s'%(
1638+
self.classname, ' and '.join(where)))
1639+
1640+
# now multilinks
1641+
for prop, values in propspec:
1642+
if not isinstance(props[prop], hyperdb.Multilink):
1643+
continue
1644+
if type(values) is type(''):
1645+
allvalues += (values,)
1646+
s = a
1647+
else:
1648+
allvalues += tuple(values.keys())
1649+
s = ','.join([a]*len(values))
16121650
tables.append('select nodeid from %s_%s where linkid in (%s)'%(
1613-
self.classname, prop, ','.join([a for x in values.keys()])))
1614-
sql = '\nintersect\n'.join(tables)
1651+
self.classname, prop, s))
1652+
sql = '\nunion\n'.join(tables)
16151653
self.db.sql(sql, allvalues)
16161654
l = [x[0] for x in self.db.sql_fetchall()]
16171655
if __debug__:

test/test_db.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: test_db.py,v 1.55 2002-09-24 01:59:44 richard Exp $
18+
# $Id: test_db.py,v 1.56 2002-09-26 03:04:24 richard Exp $
1919

2020
import unittest, os, shutil, time
2121

@@ -429,20 +429,20 @@ def testPack(self):
429429
self.db.commit()
430430

431431
# sleep for at least a second, then get a date to pack at
432-
time.sleep(2)
432+
time.sleep(1)
433433
pack_before = date.Date('.')
434-
time.sleep(2)
435434

436-
# one more entry
435+
# wait another second and add one more entry
436+
time.sleep(1)
437437
self.db.issue.set(id, status='3')
438438
self.db.commit()
439+
jlen = len(self.db.getjournal('issue', id))
439440

440441
# pack
441442
self.db.pack(pack_before)
442-
journal = self.db.getjournal('issue', id)
443443

444444
# we should have the create and last set entries now
445-
self.assertEqual(2, len(journal))
445+
self.assertEqual(jlen-1, len(self.db.getjournal('issue', id)))
446446

447447
def testIDGeneration(self):
448448
id1 = self.db.issue.create(title="spam", status='1')
@@ -487,6 +487,36 @@ def testForcedReindexing(self):
487487
self.assertEquals(self.db.indexer.search(['flebble'], self.db.issue),
488488
{'1': {}})
489489

490+
#
491+
# searching tests follow
492+
#
493+
def testFind(self):
494+
self.db.user.create(username='test')
495+
ids = []
496+
ids.append(self.db.issue.create(status="1", nosy=['1']))
497+
oddid = self.db.issue.create(status="2", nosy=['2'])
498+
ids.append(self.db.issue.create(status="1", nosy=['1','2']))
499+
self.db.issue.create(status="3", nosy=['1'])
500+
ids.sort()
501+
502+
# should match first and third
503+
got = self.db.issue.find(status='1')
504+
got.sort()
505+
self.assertEqual(got, ids)
506+
507+
# none
508+
self.assertEqual(self.db.issue.find(status='4'), [])
509+
510+
# should match first three
511+
got = self.db.issue.find(status='1', nosy='2')
512+
got.sort()
513+
ids.append(oddid)
514+
ids.sort()
515+
self.assertEqual(got, ids)
516+
517+
# none
518+
self.assertEqual(self.db.issue.find(status='4', nosy='3'), [])
519+
490520
def testStringFind(self):
491521
ids = []
492522
ids.append(self.db.issue.create(title="spam"))
@@ -629,6 +659,13 @@ def testIDGeneration(self):
629659
id2 = self.db.issue.create(title="eggs", status='2')
630660
self.assertNotEqual(id1, id2)
631661

662+
def testFilteringString(self):
663+
ae, filt = self.filteringSetup()
664+
ae(filt(None, {'title': 'issue one'}, ('+','id'), (None,None)), ['1'])
665+
# XXX gadfly can't do substring LIKE searches
666+
#ae(filt(None, {'title': 'issue'}, ('+','id'), (None,None)),
667+
# ['1','2','3'])
668+
632669
class gadflyReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
633670
def setUp(self):
634671
from roundup.backends import gadfly

test/test_mailgw.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# but WITHOUT ANY WARRANTY; without even the implied warranty of
99
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1010
#
11-
# $Id: test_mailgw.py,v 1.31 2002-09-20 05:08:00 richard Exp $
11+
# $Id: test_mailgw.py,v 1.32 2002-09-26 03:04:24 richard Exp $
1212

1313
import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
1414

@@ -765,6 +765,51 @@ def testMultipartEnc01(self):
765765
766766
A message with first part encoded (encoded oe =F6)
767767
768+
----------
769+
status: unread -> chatting
770+
_________________________________________________________________________
771+
"Roundup issue tracker" <[email protected]>
772+
http://your.tracker.url.example/issue1
773+
_________________________________________________________________________
774+
''')
775+
776+
def testFollowupStupidQuoting(self):
777+
self.doNewIssue()
778+
779+
message = cStringIO.StringIO('''Content-Type: text/plain;
780+
charset="iso-8859-1"
781+
From: richard <richard@test>
782+
783+
Message-Id: <followup_dummy_id>
784+
In-Reply-To: <dummy_test_message_id>
785+
Subject: Re: "[issue1] Testing... "
786+
787+
This is a followup
788+
''')
789+
handler = self.instance.MailGW(self.instance, self.db)
790+
handler.trapExceptions = 0
791+
handler.main(message)
792+
793+
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
794+
795+
796+
Content-Type: text/plain
797+
Subject: [issue1] Testing...
798+
799+
From: "richard" <[email protected]>
800+
Reply-To: "Roundup issue tracker" <[email protected]>
801+
MIME-Version: 1.0
802+
Message-Id: <followup_dummy_id>
803+
In-Reply-To: <dummy_test_message_id>
804+
X-Roundup-Name: Roundup issue tracker
805+
Content-Transfer-Encoding: quoted-printable
806+
807+
808+
richard <richard@test> added the comment:
809+
810+
This is a followup
811+
812+
768813
----------
769814
status: unread -> chatting
770815
_________________________________________________________________________

0 commit comments

Comments
 (0)