1 |
|
# |
2 |
|
# This file is part of GNU Enterprise. |
3 |
|
# |
4 |
|
# GNU Enterprise is free software; you can redistribute it |
5 |
|
# and/or modify it under the terms of the GNU General Public |
6 |
|
# License as published by the Free Software Foundation; either |
7 |
|
# version 2, or (at your option) any later version. |
8 |
|
# |
9 |
|
# GNU Enterprise is distributed in the hope that it will be |
10 |
|
# useful, but WITHOUT ANY WARRANTY; without even the implied |
11 |
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
12 |
|
# PURPOSE. See the GNU General Public License for more details. |
13 |
|
# |
14 |
|
# You should have received a copy of the GNU General Public |
15 |
|
# License along with program; see the file COPYING. If not, |
16 |
|
# write to the Free Software Foundation, Inc., 59 Temple Place |
17 |
|
# - Suite 330, Boston, MA 02111-1307, USA. |
18 |
|
# |
19 |
|
# Copyright 2000-2003 Free Software Foundation |
20 |
|
# |
21 |
|
# FILE: |
22 |
|
# ResultSet.py |
23 |
|
# |
24 |
|
# DESCRIPTION: |
25 |
|
# |
26 |
|
# NOTES: |
27 |
|
# |
28 |
|
|
29 |
|
__all__ = ['ResultSet'] |
30 |
|
|
31 |
|
from gnue.common.apps import GDebug |
32 |
|
from gnue.common.datasources import GConditions, Exceptions |
33 |
|
import string |
34 |
|
|
35 |
|
from RecordSet import RecordSet |
36 |
|
|
37 |
|
########################################################### |
38 |
|
# |
39 |
|
# |
40 |
|
# |
41 |
|
########################################################### |
42 |
|
class ResultSet: |
43 |
|
|
44 |
|
_recordSetClass = RecordSet |
45 |
|
|
46 |
|
def __init__(self, dataObject, cursor=None,defaultValues={},masterRecordSet=None): |
47 |
|
self._dataObject = dataObject |
48 |
|
self._cursor = cursor |
49 |
|
self._cachedRecords = [] |
50 |
|
self._currentRecord = -1 |
51 |
|
self._masterRecordSet = masterRecordSet |
52 |
|
self._readonly = 0 |
53 |
|
self._recordCount = 0 |
54 |
|
self._postingRecord = None |
55 |
|
|
56 |
|
self._defaultValues = {} |
57 |
|
self._defaultValues.update(defaultValues) |
58 |
|
|
59 |
|
self.current = None |
60 |
|
|
61 |
|
if masterRecordSet: |
62 |
|
masterRecordSet.addDetailResultSet(self) |
63 |
|
|
64 |
|
# Since we are overriding __len__ |
65 |
|
def __nonzero__(self): |
66 |
|
return 1 |
67 |
|
|
68 |
|
# Return the # of records |
69 |
|
def __len__(self): |
70 |
|
return self.getRecordCount() |
71 |
|
|
72 |
|
def __getitem__(self, index): |
73 |
|
rs = self.getRecord(index) |
74 |
|
if not rs: |
75 |
|
raise IndexError |
76 |
|
else: |
77 |
|
return rs |
78 |
|
|
79 |
|
|
80 |
|
# Returns whether this result set is read only or not |
81 |
|
def isReadOnly(self): |
82 |
|
return self._readonly |
83 |
|
|
84 |
|
|
85 |
|
# Returns 1=At first record, 0=Not first record |
86 |
|
def isFirstRecord(self): |
87 |
|
return (self._currentRecord == 0) |
88 |
|
|
89 |
|
|
90 |
|
# Returns 1=At last record, 0=Not last record |
91 |
|
def isLastRecord(self): |
92 |
|
if self._currentRecord < len(self._cachedRecords) - 1 or \ |
93 |
|
self._cacheNextRecord(): |
94 |
|
return 0 |
95 |
|
else: |
96 |
|
return 1 |
97 |
|
|
98 |
|
|
99 |
|
# returns -1=No records in memory, #=Current record # |
100 |
|
def getRecordNumber(self): |
101 |
|
return self._currentRecord |
102 |
|
|
103 |
|
|
104 |
|
# returns # of records currently loaded |
105 |
|
def getCacheCount(self): |
106 |
|
return len(self._cachedRecords) |
107 |
|
|
108 |
|
# returns # of records the |
109 |
|
def getRecordCount(self): |
110 |
|
return self._recordCount > 0 and self._recordCount or self.getCacheCount() |
111 |
|
|
112 |
|
# Get a specific record (0=based) |
113 |
|
def getRecord(self, record): |
114 |
|
while (record + 1 > len(self._cachedRecords)) and self._cacheNextRecord(): |
115 |
|
pass |
116 |
|
|
117 |
|
if record + 1 > len(self._cachedRecords): |
118 |
|
return None |
119 |
|
else: |
120 |
|
return self._cachedRecords[record] |
121 |
|
|
122 |
|
|
123 |
|
# move to record #, returns 1=New record loaded, 0=invalid # |
124 |
|
def setRecord(self, record): |
125 |
|
|
126 |
|
while (record > len(self._cachedRecords) -1) and self._cacheNextRecord(): |
127 |
|
pass |
128 |
|
|
129 |
|
if record >= len(self._cachedRecords): |
130 |
|
return None |
131 |
|
else: |
132 |
|
self._currentRecord = record |
133 |
|
self.current = self._cachedRecords[self._currentRecord] |
134 |
|
self.notifyDetailObjects() |
135 |
|
return self.current |
136 |
|
|
137 |
|
# returns 1=New record loaded, 0=No more records |
138 |
|
def nextRecord(self): |
139 |
|
if self._currentRecord + 1 == len(self._cachedRecords): |
140 |
|
if not self._cacheNextRecord(): |
141 |
|
return None |
142 |
|
|
143 |
|
self._currentRecord += 1 |
144 |
|
self.current = self._cachedRecords[self._currentRecord] |
145 |
|
self.notifyDetailObjects() |
146 |
|
return self.current |
147 |
|
|
148 |
|
|
149 |
|
# returns 1=New record loaded, 0=At first record |
150 |
|
def prevRecord(self): |
151 |
|
if self._currentRecord < 1: |
152 |
|
return None |
153 |
|
else: |
154 |
|
self._currentRecord -= 1 |
155 |
|
self.current = self._cachedRecords[self._currentRecord] |
156 |
|
self.notifyDetailObjects() |
157 |
|
return self.current |
158 |
|
|
159 |
|
|
160 |
|
# returns 1=at first record, 0=No records loaded |
161 |
|
def firstRecord(self): |
162 |
|
if self._currentRecord < 0: |
163 |
|
if not self._cacheNextRecord(): |
164 |
|
return None |
165 |
|
|
166 |
|
self._currentRecord = 0 |
167 |
|
self.current = self._cachedRecords[0] |
168 |
|
self.notifyDetailObjects() |
169 |
|
return self.current |
170 |
|
|
171 |
|
|
172 |
|
|
173 |
|
# returns 1=at last record, 0=No records loaded |
174 |
|
def lastRecord(self): |
175 |
|
if self._currentRecord == -1: |
176 |
|
return None |
177 |
|
else: |
178 |
|
while self._cacheNextRecord(): |
179 |
|
pass |
180 |
|
self._currentRecord = len(self._cachedRecords) - 1 |
181 |
|
self.current = self._cachedRecords[self._currentRecord] |
182 |
|
self.notifyDetailObjects() |
183 |
|
return self.current |
184 |
|
|
185 |
|
|
186 |
|
|
187 |
|
# Insert a blank record after the current record |
188 |
|
def insertRecord(self): |
189 |
|
if self.isReadOnly(): |
190 |
|
# Provide better feedback?? |
191 |
|
tmsg = _("Attempted to insert into a read only datasource") |
192 |
|
raise Exceptions.ReadOnlyError, tmsg |
193 |
|
else: |
194 |
|
GDebug.printMesg(7,'Inserting a blank record') |
195 |
|
self._currentRecord += 1 |
196 |
|
self._cachedRecords.insert(self._currentRecord, self._createEmptyRecord()) |
197 |
|
self._recordCount += 1 |
198 |
|
self.current = self._cachedRecords[self._currentRecord] |
199 |
|
|
200 |
|
# Set any dataobject-wide default values |
201 |
|
for field in self._dataObject._defaultValues.keys(): |
202 |
|
self.current.setField(field, self._dataObject._defaultValues[field],0) |
203 |
|
|
204 |
|
# Set any resultset specific values |
205 |
|
for field in self._defaultValues.keys(): |
206 |
|
self.current.setField(field, self._defaultValues[field],0) |
207 |
|
|
208 |
|
# Pull any primary keys from a master record set |
209 |
|
if self._masterRecordSet != None and hasattr(self._dataObject, '_masterfields'): |
210 |
|
i = 0 |
211 |
|
for field in self._dataObject._masterfields: |
212 |
|
self.current.setField(self._dataObject._detailfields[i],self._masterRecordSet.getField(field),0) |
213 |
|
i += 1 |
214 |
|
|
215 |
|
self.notifyDetailObjects() |
216 |
|
return self.current |
217 |
|
|
218 |
|
|
219 |
|
# Returns 1=DataObject, or a detail resultset, has uncommitted changes |
220 |
|
def isPending(self): |
221 |
|
for rec in (self._cachedRecords): |
222 |
|
if rec.isPending(): |
223 |
|
return 1 |
224 |
|
else: |
225 |
|
for detail in rec._cachedDetailResultSets.values(): |
226 |
|
if detail.isPending(): |
227 |
|
return 1 |
228 |
|
return 0 |
229 |
|
|
230 |
|
|
231 |
|
# Returns 1=DataObject has uncommitted changes |
232 |
|
def isRecordPending(self): |
233 |
|
return self.current.isPending() |
234 |
|
|
235 |
|
|
236 |
|
def getPostingRecordset(self): |
237 |
|
global postingRecordset |
238 |
|
return postingRecordset |
239 |
|
|
240 |
|
# Post changes to the database |
241 |
|
def post(self, foreign_keys={}): |
242 |
|
global postingRecordset |
243 |
|
# post our changes |
244 |
|
self._update_cursor = self._dataObject._dataConnection.cursor() |
245 |
|
|
246 |
|
recordPosition = 0 |
247 |
|
while recordPosition < len(self._cachedRecords): |
248 |
|
self._postingRecord = self._cachedRecords[recordPosition] |
249 |
|
postingRecordset = self._postingRecord |
250 |
|
delete = self._postingRecord._emptyFlag or self._postingRecord._deleteFlag |
251 |
|
if not delete: |
252 |
|
# Flip the flag for 'default' values to true so that hidden |
253 |
|
# default fields are included in insert statements |
254 |
|
if self._postingRecord.isPending(): |
255 |
|
for field in self._dataObject._defaultValues.keys(): |
256 |
|
self._postingRecord._modifiedFlags[field] = 1 |
257 |
|
|
258 |
|
for field in foreign_keys.keys(): |
259 |
|
self._postingRecord._fields[field] = foreign_keys[field] |
260 |
|
# Some DBs will throw an exception if you update a Primary Key |
261 |
|
# (even if you are updating to the same value) |
262 |
|
if self._postingRecord._insertFlag: |
263 |
|
self._postingRecord._modifiedFlags[field] = 1 |
264 |
|
|
265 |
|
recordPosition += 1 |
266 |
|
else: |
267 |
|
# Adjust the current record if a preceding record |
268 |
|
# or the current record is deleted |
269 |
|
if recordPosition <= self._currentRecord: |
270 |
|
self._currentRecord -= 1 |
271 |
|
self._cachedRecords.pop(recordPosition) |
272 |
|
self._recordCount -= 1 |
273 |
|
|
274 |
|
self._postingRecord.post() |
275 |
|
|
276 |
|
# Move to record 0 if all preceding records were deleted |
277 |
|
# (or set to -1 if all records were deleted) |
278 |
|
if self._currentRecord < 0: |
279 |
|
if len(self._cachedRecords): |
280 |
|
self._currentRecord = 0 |
281 |
|
else: |
282 |
|
self._currentRecord = -1 |
283 |
|
# TODO: I don't think we need this anymore |
284 |
|
# if self._currentRecord >= self._recordCount: |
285 |
|
# self._currentRecord = self._recordCount - 1 |
286 |
|
|
287 |
|
def notifyDetailObjects(self): |
288 |
|
GDebug.printMesg(5,'Master record changed; Notifying Detail Objects') |
289 |
|
for detail in self._dataObject._detailObjects: |
290 |
|
if detail[1]: |
291 |
|
detail[1].masterResultSetChanged(self, |
292 |
|
detail[0]._masterRecordChanged(self)) |
293 |
|
|
294 |
|
|
295 |
|
# Returns 1=Field is bound to a database field |
296 |
|
def isFieldBound(self, fieldName): |
297 |
|
if self._dataObject._fieldReferences.has_key(fieldName): |
298 |
|
return 1 |
299 |
|
else: |
300 |
|
#TODO: the string.lower() line should never be called but is left |
301 |
|
#TODO: here untill the code is cleaned up |
302 |
|
return self._dataObject._fieldReferences.has_key(string.lower(fieldName)) |
303 |
|
|
304 |
|
|
305 |
|
# Load cacheCount number of new records |
306 |
|
def _cacheNextRecord(self): |
307 |
|
rs = self._loadNextRecord() |
308 |
|
if rs: |
309 |
|
self._dataObject._dataSource._onRecordLoaded(self._cachedRecords[-1]) |
310 |
|
return rs |
311 |
|
|
312 |
|
|
313 |
|
|
314 |
|
### |
315 |
|
### Methods below should be overridden by Vendor Specific functions |
316 |
|
### (_createEmptyRecord may not need to be overridden in all cases) |
317 |
|
### |
318 |
|
|
319 |
|
# Load cacheCount number of new records |
320 |
|
def _loadNextRecord(self): |
321 |
|
return 0 |
322 |
|
|
323 |
|
# Create an empty recordset |
324 |
|
def _createEmptyRecord(self): |
325 |
|
return self._recordSetClass(self) |
326 |
|
|
327 |
|
# Iterator support (Python 2.2+) |
328 |
|
def __iter__(self): |
329 |
|
return _ResultSetIter(self) |
330 |
|
|
331 |
|
|
332 |
|
|
333 |
|
# A simple resultset iterator |
334 |
|
# Lets you use ResultSets as: |
335 |
|
# |
336 |
|
# for record in myResultSet: |
337 |
|
# blah |
338 |
|
# |
339 |
|
# NOTE: Python 2.2+ (but it won't get called in |
340 |
|
# Python 2.1 or below, so not a problem) |
341 |
|
# |
342 |
|
class _ResultSetIter: |
343 |
|
def __init__(self, resultset): |
344 |
|
self.resultset = resultset |
345 |
|
self.used = 0 |
346 |
|
self.done = 0 |
347 |
|
|
348 |
|
def __iter__(self): |
349 |
|
return self |
350 |
|
|
351 |
|
def next(self): |
352 |
|
if self.done: |
353 |
|
raise StopIteration |
354 |
|
if self.used: |
355 |
|
rs = self.resultset.firstRecord() |
356 |
|
else: |
357 |
|
rs = self.resultset.nextRecord() |
358 |
|
|
359 |
|
if not rs: |
360 |
|
raise StopIteration |
361 |
|
else: |
362 |
|
return rs |
363 |
|
|
364 |
|
|
365 |
|
# TODO: wtf? |
366 |
|
postingRecordset = None |
367 |
|
|