Code

Some speedups - both of the SQL backends can handle using only one cursor.
[roundup.git] / roundup / backends / back_gadfly.py
1 # $Id: back_gadfly.py,v 1.25 2002-09-23 06:48:34 richard Exp $
2 __doc__ = '''
3 About Gadfly
4 ============
6 Gadfly  is  a  collection  of  python modules that provides relational
7 database  functionality  entirely implemented in Python. It supports a
8 subset  of  the intergalactic standard RDBMS Structured Query Language
9 SQL.
12 Basic Structure
13 ===============
15 We map roundup classes to relational tables. Automatically detect schema
16 changes and modify the gadfly table schemas appropriately. Multilinks
17 (which represent a many-to-many relationship) are handled through
18 intermediate tables.
20 Journals are stored adjunct to the per-class tables.
22 Table names and columns have "_" prepended so the names can't
23 clash with restricted names (like "order"). Retirement is determined by the
24 __retired__ column being true.
26 All columns are defined as VARCHAR, since it really doesn't matter what
27 type they're defined as. We stuff all kinds of data in there ;) [as long as
28 it's marshallable, gadfly doesn't care]
31 Additional Instance Requirements
32 ================================
34 The instance configuration must specify where the database is. It does this
35 with GADFLY_DATABASE, which is used as the arguments to the gadfly.gadfly()
36 method:
38 Using an on-disk database directly (not a good idea):
39   GADFLY_DATABASE = (database name, directory)
41 Using a network database (much better idea):
42   GADFLY_DATABASE = (policy, password, address, port)
44 Because multiple accesses directly to a gadfly database aren't handled, but
45 multiple network accesses are, it's strongly advised that the latter setup be
46 used.
48 '''
50 from roundup.backends.rdbms_common import *
52 # the all-important gadfly :)
53 import gadfly
54 import gadfly.client
55 import gadfly.database
57 class Database(Database):
58     # char to use for positional arguments
59     arg = '?'
61     def open_connection(self):
62         db = getattr(self.config, 'GADFLY_DATABASE', ('database', self.dir))
63         if len(db) == 2:
64             # ensure files are group readable and writable
65             os.umask(0002)
66             try:
67                 self.conn = gadfly.gadfly(*db)
68             except IOError, error:
69                 if error.errno != errno.ENOENT:
70                     raise
71                 self.database_schema = {}
72                 self.conn = gadfly.gadfly()
73                 self.conn.startup(*db)
74                 self.cursor = self.conn.cursor()
75                 self.cursor.execute('create table schema (schema varchar)')
76                 self.cursor.execute('create table ids (name varchar, num integer)')
77             else:
78                 self.cursor = self.conn.cursor()
79                 self.cursor.execute('select schema from schema')
80                 self.database_schema = self.cursor.fetchone()[0]
81         else:
82             self.conn = gadfly.client.gfclient(*db)
83             self.database_schema = self.load_dbschema()
85     def __repr__(self):
86         return '<roundfly 0x%x>'%id(self)
88     def sql_fetchone(self):
89         ''' Fetch a single row. If there's nothing to fetch, return None.
90         '''
91         try:
92             return self.cursor.fetchone()
93         except gadfly.database.error, message:
94             if message == 'no more results':
95                 return None
96             raise
98     def save_dbschema(self, schema):
99         ''' Save the schema definition that the database currently implements
100         '''
101         self.sql('insert into schema values (?)', (self.database_schema,))
103     def load_dbschema(self):
104         ''' Load the schema definition that the database currently implements
105         '''
106         self.cursor.execute('select schema from schema')
107         return self.cursor.fetchone()[0]
109     def save_journal(self, classname, cols, nodeid, journaldate,
110             journaltag, action, params):
111         ''' Save the journal entry to the database
112         '''
113         # nothing special to do
114         entry = (nodeid, journaldate, journaltag, action, params)
116         # do the insert
117         a = self.arg
118         sql = 'insert into %s__journal (%s) values (?,?,?,?,?)'%(classname,
119             cols)
120         if __debug__:
121             print >>hyperdb.DEBUG, 'addjournal', (self, sql, entry)
122         self.cursor.execute(sql, entry)
124     def load_journal(self, classname, cols, nodeid):
125         ''' Load the journal from the database
126         '''
127         # now get the journal entries
128         sql = 'select %s from %s__journal where nodeid=%s'%(cols, classname,
129             self.arg)
130         if __debug__:
131             print >>hyperdb.DEBUG, 'getjournal', (self, sql, nodeid)
132         self.cursor.execute(sql, (nodeid,))
133         res = []
134         for nodeid, date_stamp, user, action, params in self.cursor.fetchall():
135             res.append((nodeid, date.Date(date_stamp), user, action, params))
136         return res
138 class GadflyClass:
139     def filter(self, search_matches, filterspec, sort, group):
140         ''' Gadfly doesn't have a LIKE predicate :(
141         '''
142         cn = self.classname
144         # figure the WHERE clause from the filterspec
145         props = self.getprops()
146         frum = ['_'+cn]
147         where = []
148         args = []
149         a = self.db.arg
150         for k, v in filterspec.items():
151             propclass = props[k]
152             if isinstance(propclass, Multilink):
153                 tn = '%s_%s'%(cn, k)
154                 frum.append(tn)
155                 if isinstance(v, type([])):
156                     s = ','.join([a for x in v])
157                     where.append('id=%s.nodeid and %s.linkid in (%s)'%(tn,tn,s))
158                     args = args + v
159                 else:
160                     where.append('id=%s.nodeid and %s.linkid = %s'%(tn, tn, a))
161                     args.append(v)
162             else:
163                 if isinstance(v, type([])):
164                     s = ','.join([a for x in v])
165                     where.append('_%s in (%s)'%(k, s))
166                     args = args + v
167                 else:
168                     where.append('_%s=%s'%(k, a))
169                     args.append(v)
171         # add results of full text search
172         if search_matches is not None:
173             v = search_matches.keys()
174             s = ','.join([a for x in v])
175             where.append('id in (%s)'%s)
176             args = args + v
178         # "grouping" is just the first-order sorting in the SQL fetch
179         # can modify it...)
180         orderby = []
181         ordercols = []
182         if group[0] is not None and group[1] is not None:
183             if group[0] != '-':
184                 orderby.append('_'+group[1])
185                 ordercols.append('_'+group[1])
186             else:
187                 orderby.append('_'+group[1]+' desc')
188                 ordercols.append('_'+group[1])
190         # now add in the sorting
191         group = ''
192         if sort[0] is not None and sort[1] is not None:
193             direction, colname = sort
194             if direction != '-':
195                 if colname == 'id':
196                     orderby.append(colname)
197                 else:
198                     orderby.append('_'+colname)
199                     ordercols.append('_'+colname)
200             else:
201                 if colname == 'id':
202                     orderby.append(colname+' desc')
203                     ordercols.append(colname)
204                 else:
205                     orderby.append('_'+colname+' desc')
206                     ordercols.append('_'+colname)
208         # construct the SQL
209         frum = ','.join(frum)
210         if where:
211             where = ' where ' + (' and '.join(where))
212         else:
213             where = ''
214         cols = ['id']
215         if orderby:
216             cols = cols + ordercols
217             order = ' order by %s'%(','.join(orderby))
218         else:
219             order = ''
220         cols = ','.join(cols)
221         sql = 'select %s from %s %s%s%s'%(cols, frum, where, group, order)
222         args = tuple(args)
223         if __debug__:
224             print >>hyperdb.DEBUG, 'filter', (self, sql, args)
225         self.db.cursor.execute(sql, args)
226         l = self.db.cursor.fetchall()
228         # return the IDs
229         return [row[0] for row in l]
231 class Class(GadflyClass, Class):
232     pass
233 class IssueClass(GadflyClass, IssueClass):
234     pass
235 class FileClass(GadflyClass, FileClass):
236     pass