Skip to content

Commit 9b87545

Browse files
author
Richard Jones
committed
Added some words to the installation doc about choosing backends.
Added hyperdb Class.filter unit tests - gadfly currently fails substring searching, but I knew it would :( Lots of fixes to the RDBMS backend - it works a treat now! A couple of other cleanups in CGI land...
1 parent 1c48136 commit 9b87545

File tree

8 files changed

+283
-151
lines changed

8 files changed

+283
-151
lines changed

doc/installation.txt

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Installing Roundup
33
==================
44

5-
:Version: $Revision: 1.23 $
5+
:Version: $Revision: 1.24 $
66

77
.. contents::
88

@@ -35,20 +35,40 @@ holds issues which have priorities and statuses. Each issue may also have a
3535
set of messages which are disseminated to the issue's list of nosy users.
3636

3737

38-
Extended Template
39-
-----------------
40-
41-
The extended template adds additional information to issues: product,
42-
platform, version, targetversion and supportcall.
43-
There is an additional class for
44-
handling support calls, which includes a time log, customername, rate and
45-
source.
46-
47-
The priorty class has different default entries too: "fatal-bug", "bug",
48-
"usability" and "feature".
49-
50-
Users of this template will want to change the contents of the product
51-
class as soon as the tracker is created.
38+
Backends
39+
--------
40+
41+
The actual storage of Roundup tracker information is handled by backends.
42+
There's several to choose from, each with benefits and limitations:
43+
44+
**anydbm**
45+
This backend is guaranteed to work on any system that Python runs on. It
46+
will generally choose the best *dbm backend that is available on your system
47+
(from the list dbhash, gdbm, dbm, dumbdbm). It is the least scaleable of all
48+
backends, but performs well enough for a smallish tracker (a couple of
49+
thousand issues, under fifty users, ...).
50+
**bsddb**
51+
This effectively the same as anydbm, but uses the bsddb backend. This allows
52+
it to gain some performance and scaling benefits.
53+
**bsddb3**
54+
Again, this effectively the same as anydbm, but uses the bsddb3 backend.
55+
This allows it to gain some performance and scaling benefits.
56+
**sqlite**
57+
This uses the SQLite embedded RDBMS to provide a fast, scaleable backend.
58+
There are no limitations.
59+
**gadfly**
60+
This is a proof-of-concept relational database backend, not really intended
61+
for actual production use, although it can be. It uses the Gadfly RDBMS
62+
to store data. It is unable to perform string searches due to gadfly not
63+
having a LIKE operation. It should scale well, assuming a client/server
64+
setup is used.
65+
**metakit**
66+
This backend is implemented over the metakit storage system, using Mk4Py as
67+
the interface. It scales much better than the *dbm backends, but has some
68+
missing features:
69+
70+
- you may not unset properties once they are set
71+
- journal retrieval is not implemented
5272

5373

5474
Prerequisites

roundup/backends/back_anydbm.py

Lines changed: 5 additions & 1 deletion
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.80 2002-09-17 23:59:59 richard Exp $
18+
#$Id: back_anydbm.py,v 1.81 2002-09-19 02:37:41 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
@@ -1499,6 +1499,10 @@ def filter(self, search_matches, filterspec, sort, group,
14991499
"sort" and "group" are (dir, prop) where dir is '+', '-' or None
15001500
and prop is a prop name or None
15011501
"search_matches" is {nodeid: marker}
1502+
1503+
The filter must match all properties specificed - but if the
1504+
property value to match is a list, any one of the values in the
1505+
list may match for that property to match.
15021506
'''
15031507
cn = self.classname
15041508

roundup/backends/back_gadfly.py

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_gadfly.py,v 1.22 2002-09-18 05:07:47 richard Exp $
1+
# $Id: back_gadfly.py,v 1.23 2002-09-19 02:37:41 richard Exp $
22
__doc__ = '''
33
About Gadfly
44
============
@@ -136,3 +136,120 @@ def load_journal(self, cursor, classname, cols, nodeid):
136136
res.append((nodeid, date.Date(date_stamp), user, action, params))
137137
return res
138138

139+
class GadflyClass:
140+
def filter(self, search_matches, filterspec, sort, group):
141+
''' Gadfly doesn't have a LIKE predicate :(
142+
'''
143+
cn = self.classname
144+
145+
# figure the WHERE clause from the filterspec
146+
props = self.getprops()
147+
frum = ['_'+cn]
148+
where = []
149+
args = []
150+
a = self.db.arg
151+
for k, v in filterspec.items():
152+
propclass = props[k]
153+
if isinstance(propclass, Multilink):
154+
tn = '%s_%s'%(cn, k)
155+
frum.append(tn)
156+
if isinstance(v, type([])):
157+
s = ','.join([self.arg for x in v])
158+
where.append('id=%s.nodeid and %s.linkid in (%s)'%(tn,tn,s))
159+
args = args + v
160+
else:
161+
where.append('id=%s.nodeid and %s.linkid = %s'%(tn, tn, a))
162+
args.append(v)
163+
else:
164+
if isinstance(v, type([])):
165+
s = ','.join([a for x in v])
166+
where.append('_%s in (%s)'%(k, s))
167+
args = args + v
168+
else:
169+
where.append('_%s=%s'%(k, a))
170+
args.append(v)
171+
172+
# add results of full text search
173+
if search_matches is not None:
174+
v = search_matches.keys()
175+
s = ','.join([a for x in v])
176+
where.append('id in (%s)'%s)
177+
args = args + v
178+
179+
# figure the order by clause
180+
orderby = []
181+
ordercols = []
182+
if sort[0] is not None and sort[1] is not None:
183+
direction, colname = sort
184+
if direction != '-':
185+
if colname == 'activity':
186+
orderby.append('activity')
187+
ordercols.append('max(%s__journal.date) as activity'%cn)
188+
frum.append('%s__journal'%cn)
189+
where.append('%s__journal.nodeid = _%s.id'%(cn, cn))
190+
elif colname == 'id':
191+
orderby.append(colname)
192+
ordercols.append(colname)
193+
else:
194+
orderby.append('_'+colname)
195+
ordercols.append('_'+colname)
196+
else:
197+
if colname == 'activity':
198+
orderby.append('activity desc')
199+
ordercols.append('max(%s__journal.date) as activity'%cn)
200+
frum.append('%s__journal'%cn)
201+
where.append('%s__journal.nodeid = _%s.id'%(cn, cn))
202+
elif colname == 'id':
203+
orderby.append(colname+' desc')
204+
ordercols.append(colname)
205+
else:
206+
orderby.append('_'+colname+' desc')
207+
ordercols.append('_'+colname)
208+
209+
# figure the group by clause
210+
groupby = []
211+
groupcols = []
212+
if group[0] is not None and group[1] is not None:
213+
if group[0] != '-':
214+
groupby.append('_'+group[1])
215+
groupcols.append('_'+group[1])
216+
else:
217+
groupby.append('_'+group[1]+' desc')
218+
groupcols.append('_'+group[1])
219+
220+
# construct the SQL
221+
frum = ','.join(frum)
222+
where = ' and '.join(where)
223+
cols = []
224+
if orderby:
225+
cols = cols + ordercols
226+
order = ' order by %s'%(','.join(orderby))
227+
else:
228+
order = ''
229+
if 0: #groupby:
230+
cols = cols + groupcols
231+
group = ' group by %s'%(','.join(groupby))
232+
else:
233+
group = ''
234+
if 'id' not in cols:
235+
cols.append('id')
236+
cols = ','.join(cols)
237+
sql = 'select %s from %s where %s%s%s'%(cols, frum, where, order,
238+
group)
239+
args = tuple(args)
240+
if __debug__:
241+
print >>hyperdb.DEBUG, 'filter', (self, sql, args)
242+
cursor = self.db.conn.cursor()
243+
cursor.execute(sql, args)
244+
l = cursor.fetchall()
245+
246+
# return the IDs
247+
return [row[0] for row in l]
248+
249+
class Class(GadflyClass, Class):
250+
pass
251+
class IssueClass(GadflyClass, IssueClass):
252+
pass
253+
class FileClass(GadflyClass, FileClass):
254+
pass
255+

roundup/backends/back_sqlite.py

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_sqlite.py,v 1.2 2002-09-18 07:04:37 richard Exp $
1+
# $Id: back_sqlite.py,v 1.3 2002-09-19 02:37:41 richard Exp $
22
__doc__ = '''
33
See https://pysqlite.sourceforge.net/ for pysqlite info
44
'''
@@ -128,80 +128,3 @@ def unserialise(self, classname, node):
128128
d[k] = v
129129
return d
130130

131-
class Class(Class):
132-
_marker = []
133-
def get(self, nodeid, propname, default=_marker, cache=1):
134-
'''Get the value of a property on an existing node of this class.
135-
136-
'nodeid' must be the id of an existing node of this class or an
137-
IndexError is raised. 'propname' must be the name of a property
138-
of this class or a KeyError is raised.
139-
140-
'cache' indicates whether the transaction cache should be queried
141-
for the node. If the node has been modified and you need to
142-
determine what its values prior to modification are, you need to
143-
set cache=0.
144-
'''
145-
if propname == 'id':
146-
return nodeid
147-
148-
if propname == 'creation':
149-
if not self.do_journal:
150-
raise ValueError, 'Journalling is disabled for this class'
151-
journal = self.db.getjournal(self.classname, nodeid)
152-
if journal:
153-
return self.db.getjournal(self.classname, nodeid)[0][1]
154-
else:
155-
# on the strange chance that there's no journal
156-
return date.Date()
157-
if propname == 'activity':
158-
if not self.do_journal:
159-
raise ValueError, 'Journalling is disabled for this class'
160-
journal = self.db.getjournal(self.classname, nodeid)
161-
if journal:
162-
return self.db.getjournal(self.classname, nodeid)[-1][1]
163-
else:
164-
# on the strange chance that there's no journal
165-
return date.Date()
166-
if propname == 'creator':
167-
if not self.do_journal:
168-
raise ValueError, 'Journalling is disabled for this class'
169-
journal = self.db.getjournal(self.classname, nodeid)
170-
if journal:
171-
name = self.db.getjournal(self.classname, nodeid)[0][2]
172-
else:
173-
return None
174-
try:
175-
return self.db.user.lookup(name)
176-
except KeyError:
177-
# the journaltag user doesn't exist any more
178-
return None
179-
180-
# get the property (raises KeyErorr if invalid)
181-
prop = self.properties[propname]
182-
183-
# get the node's dict
184-
d = self.db.getnode(self.classname, nodeid) #, cache=cache)
185-
186-
if not d.has_key(propname):
187-
if default is self._marker:
188-
if isinstance(prop, Multilink):
189-
return []
190-
else:
191-
return None
192-
else:
193-
return default
194-
195-
# special handling for some types
196-
if isinstance(prop, Multilink):
197-
# don't pass our list to other code
198-
return d[propname][:]
199-
elif d[propname] is None:
200-
# always return None right now, no conversion
201-
return None
202-
elif isinstance(prop, Boolean) or isinstance(prop, Number):
203-
# turn Booleans and Numbers into integers
204-
return int(d[propname])
205-
206-
return d[propname]
207-

0 commit comments

Comments
 (0)