Skip to content

Commit 14d6f18

Browse files
author
Ralf Schlatterbeck
committed
Fixed bug in filter_iter refactoring (lazy multilinks)...
...in rare cases this would result in duplicate multilinks to the same node. We're now going the safe route and doing lazy evaluation only for read-only access, whenever updates are done we fetch everything.
1 parent 584a134 commit 14d6f18

File tree

3 files changed

+55
-10
lines changed

3 files changed

+55
-10
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ Fixed:
9696
parameter for binding to all interfaces now (still left in for
9797
compatibility). Thanks to Toni Mueller for providing the first version
9898
of this patch and discussing implementations.
99+
- Fixed bug in filter_iter refactoring (lazy multilinks), in rare cases
100+
this would result in duplicate multilinks to the same node. We're now
101+
going the safe route and doing lazy evaluation only for read-only
102+
access, whenever updates are done we fetch everything.
99103

100104
2010-10-08 1.4.16 (r4541)
101105

roundup/backends/rdbms_common.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,8 +1080,34 @@ def to_hyperdb_value(self, propklass):
10801080

10811081
raise ValueError('%r is not a hyperdb property class' % propklass)
10821082

1083-
def getnode(self, classname, nodeid):
1083+
def _materialize_multilink(self, classname, nodeid, node, propname):
1084+
""" evaluation of single Multilink (lazy eval may have skipped this)
1085+
"""
1086+
if propname not in node:
1087+
sql = 'select linkid from %s_%s where nodeid=%s'%(classname,
1088+
propname, self.arg)
1089+
self.sql(sql, (nodeid,))
1090+
# extract the first column from the result
1091+
# XXX numeric ids
1092+
items = [int(x[0]) for x in self.cursor.fetchall()]
1093+
items.sort ()
1094+
node[propname] = [str(x) for x in items]
1095+
1096+
def _materialize_multilinks(self, classname, nodeid, node, props=None):
1097+
""" get all Multilinks of a node (lazy eval may have skipped this)
1098+
"""
1099+
cl = self.classes[classname]
1100+
props = props or [pn for (pn, p) in cl.properties.iteritems()
1101+
if isinstance(p, Multilink)]
1102+
for propname in props:
1103+
if propname not in node:
1104+
self._materialize_multilink(classname, nodeid, node, propname)
1105+
1106+
def getnode(self, classname, nodeid, fetch_multilinks=True):
10841107
""" Get a node from the database.
1108+
For optimisation optionally we don't fetch multilinks
1109+
(lazy Multilinks).
1110+
But for internal database operations we need them.
10851111
"""
10861112
# see if we have this node cached
10871113
key = (classname, nodeid)
@@ -1091,6 +1117,8 @@ def getnode(self, classname, nodeid):
10911117
if __debug__:
10921118
self.stats['cache_hits'] += 1
10931119
# return the cached information
1120+
if fetch_multilinks:
1121+
self._materialize_multilinks(classname, nodeid, self.cache[key])
10941122
return self.cache[key]
10951123

10961124
if __debug__:
@@ -1124,6 +1152,9 @@ def getnode(self, classname, nodeid):
11241152
value = self.to_hyperdb_value(props[name].__class__)(value)
11251153
node[name] = value
11261154

1155+
if fetch_multilinks and mls:
1156+
self._materialize_multilinks(classname, nodeid, node, mls)
1157+
11271158
# save off in the cache
11281159
key = (classname, nodeid)
11291160
self._cache_save(key, node)
@@ -1616,7 +1647,7 @@ def get(self, nodeid, propname, default=_marker, cache=1):
16161647
return nodeid
16171648

16181649
# get the node's dict
1619-
d = self.db.getnode(self.classname, nodeid)
1650+
d = self.db.getnode(self.classname, nodeid, fetch_multilinks=False)
16201651
# handle common case -- that property is in dict -- first
16211652
# if None and one of creator/creation actor/activity return None
16221653
if propname in d:
@@ -1640,14 +1671,7 @@ def get(self, nodeid, propname, default=_marker, cache=1):
16401671

16411672
# lazy evaluation of Multilink
16421673
if propname not in d and isinstance(prop, Multilink):
1643-
sql = 'select linkid from %s_%s where nodeid=%s'%(self.classname,
1644-
propname, self.db.arg)
1645-
self.db.sql(sql, (nodeid,))
1646-
# extract the first column from the result
1647-
# XXX numeric ids
1648-
items = [int(x[0]) for x in self.db.cursor.fetchall()]
1649-
items.sort ()
1650-
d[propname] = [str(x) for x in items]
1674+
self.db._materialize_multilink(self.classname, nodeid, d, propname)
16511675

16521676
# handle there being no value in the table for the property
16531677
if propname not in d or d[propname] is None:

test/db_test_base.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,23 @@ def testMultilinkChange(self):
321321
if commit: self.db.commit()
322322
self.assertEqual(self.db.issue.get(nid, "nosy"), [])
323323

324+
def testMakeSeveralMultilinkedNodes(self):
325+
for commit in (0,1):
326+
u1 = self.db.user.create(username='foo%s'%commit)
327+
u2 = self.db.user.create(username='bar%s'%commit)
328+
u3 = self.db.user.create(username='baz%s'%commit)
329+
nid = self.db.issue.create(title="spam", nosy=[u1])
330+
if commit: self.db.commit()
331+
self.assertEqual(self.db.issue.get(nid, "nosy"), [u1])
332+
self.db.issue.set(nid, deadline=date.Date('.'))
333+
self.db.issue.set(nid, nosy=[u1,u2], title='ta%s'%commit)
334+
if commit: self.db.commit()
335+
self.assertEqual(self.db.issue.get(nid, "nosy"), [u1,u2])
336+
self.db.issue.set(nid, deadline=date.Date('.'))
337+
self.db.issue.set(nid, nosy=[u1,u2,u3], title='tb%s'%commit)
338+
if commit: self.db.commit()
339+
self.assertEqual(self.db.issue.get(nid, "nosy"), [u1,u2,u3])
340+
324341
def testMultilinkChangeIterable(self):
325342
for commit in (0,1):
326343
# invalid nosy value assertion

0 commit comments

Comments
 (0)