| 1 |
# -*- coding: utf-8 -*- |
|---|
| 2 |
# dabo/db/dCursorMixin |
|---|
| 3 |
|
|---|
| 4 |
import datetime |
|---|
| 5 |
import time |
|---|
| 6 |
import re |
|---|
| 7 |
from decimal import Decimal |
|---|
| 8 |
import dabo |
|---|
| 9 |
import dabo.dConstants as kons |
|---|
| 10 |
from dabo.dLocalize import _ |
|---|
| 11 |
import dabo.dException as dException |
|---|
| 12 |
from dabo.dObject import dObject |
|---|
| 13 |
from dNoEscQuoteStr import dNoEscQuoteStr |
|---|
| 14 |
from dabo.db.dDataSet import dDataSet |
|---|
| 15 |
from dabo.lib import dates |
|---|
| 16 |
from dabo.lib.utils import noneSortKey, caseInsensitiveSortKey |
|---|
| 17 |
from dabo.lib.utils import ustr |
|---|
| 18 |
|
|---|
| 19 |
|
|---|
| 20 |
class dCursorMixin(dObject): |
|---|
| 21 |
"""Dabo's cursor class, representing the lowest tier.""" |
|---|
| 22 |
_call_initProperties = False |
|---|
| 23 |
# Make these class attributes, so that they are shared among all instances |
|---|
| 24 |
_fieldStructure = {} |
|---|
| 25 |
_fieldsToAlwaysCorrectType = [] |
|---|
| 26 |
|
|---|
| 27 |
def __init__(self, sql="", *args, **kwargs): |
|---|
| 28 |
self._convertStrToUnicode = True |
|---|
| 29 |
self._initProperties() |
|---|
| 30 |
if sql and isinstance(sql, basestring) and len(sql) > 0: |
|---|
| 31 |
self.UserSQL = sql |
|---|
| 32 |
|
|---|
| 33 |
#self.super() |
|---|
| 34 |
#super(dCursorMixin, self).__init__() |
|---|
| 35 |
## pkm: Neither of the above are correct. We need to explicitly |
|---|
| 36 |
## call dObject's __init__, otherwise the cursor object with |
|---|
| 37 |
## which we are mixed-in will take the __init__. |
|---|
| 38 |
dObject.__init__(self, *args, **kwargs) |
|---|
| 39 |
|
|---|
| 40 |
# Just in case this is used outside of the context of a bizobj |
|---|
| 41 |
if not hasattr(self, "superCursor") or self.superCursor is None: |
|---|
| 42 |
myBases = self.__class__.__bases__ |
|---|
| 43 |
for base in myBases: |
|---|
| 44 |
# Find the first base class that doesn't have the 'autoPopulatePK' |
|---|
| 45 |
# attribute. Designate that class as the superCursor class. |
|---|
| 46 |
if hasattr(base, "fetchall"): |
|---|
| 47 |
self.superCursor = base |
|---|
| 48 |
break |
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 |
def _initProperties(self): |
|---|
| 52 |
# Holds the dict used for adding new blank records |
|---|
| 53 |
self._blank = {} |
|---|
| 54 |
# Flag for indicating NULL default values were set |
|---|
| 55 |
self._nullDefaults = False |
|---|
| 56 |
# Writable version of the dbapi 'description' attribute |
|---|
| 57 |
self.descriptionClean = None |
|---|
| 58 |
# Last executed sql params |
|---|
| 59 |
self.lastParams = None |
|---|
| 60 |
# Column on which the result set is sorted |
|---|
| 61 |
self.sortColumn = "" |
|---|
| 62 |
# Order of the sorting. Should be either ASC, DESC or empty for no sort |
|---|
| 63 |
self.sortOrder = "" |
|---|
| 64 |
# Is the sort case-sensitive? |
|---|
| 65 |
self.sortCase = True |
|---|
| 66 |
# Holds the last SQL run in a requery() call. |
|---|
| 67 |
self._lastSQL = "" |
|---|
| 68 |
# Hold the time that this cursor was last requeried. |
|---|
| 69 |
self.lastRequeryTime = 0 |
|---|
| 70 |
# These are used to determine if the field list of successive select statements |
|---|
| 71 |
# are identical. |
|---|
| 72 |
self.__lastExecute = "" |
|---|
| 73 |
self.__lastFieldList = "" |
|---|
| 74 |
self._whitespacePat = re.compile(r"(\s+)") |
|---|
| 75 |
self._selectStatementPat = re.compile(r"\bselect\b(.+)\bfrom\b", re.I | re.M | re.S) |
|---|
| 76 |
# Holds the keys in the original, unsorted order for unsorting the dataset |
|---|
| 77 |
self.__unsortedRows = [] |
|---|
| 78 |
# Holds the name of fields to be skipped when updating the backend, such |
|---|
| 79 |
# as calculated or derived fields, or fields that are otherwise not to be updated. |
|---|
| 80 |
self.__nonUpdateFields = None |
|---|
| 81 |
# User-editable list of non-updated fields |
|---|
| 82 |
self.nonUpdateFields = [] |
|---|
| 83 |
# Flag that is set when the user explicitly sets the Key Field |
|---|
| 84 |
self._keyFieldSet = False |
|---|
| 85 |
# Cursor that manages this cursor's SQL. Default to self; |
|---|
| 86 |
# in some cases, such as a single bizobj managing several cursors, |
|---|
| 87 |
# it will be a separate object. |
|---|
| 88 |
self.sqlManager = self |
|---|
| 89 |
# Attribute that holds the data of the cursor |
|---|
| 90 |
self._records = dDataSet() |
|---|
| 91 |
# Attribute that holds the current row number |
|---|
| 92 |
self.__rownumber = -1 |
|---|
| 93 |
# Data structure info |
|---|
| 94 |
self._dataStructure = None |
|---|
| 95 |
self._table = "" |
|---|
| 96 |
self._keyField = "" |
|---|
| 97 |
self._userSQL = None |
|---|
| 98 |
self._virtualFields = {} |
|---|
| 99 |
|
|---|
| 100 |
self._autoPopulatePK = True |
|---|
| 101 |
self._autoQuoteNames = True |
|---|
| 102 |
|
|---|
| 103 |
self.__tmpPK = -1 # temp PK value for new records. |
|---|
| 104 |
# Holds the data types for each field |
|---|
| 105 |
self._types = {} |
|---|
| 106 |
|
|---|
| 107 |
# Holds reference to auxiliary cursor that handles queries that |
|---|
| 108 |
# are not supposed to affect the record set. |
|---|
| 109 |
self.__auxCursor = None |
|---|
| 110 |
# Marks the cursor as an auxiliary cursor |
|---|
| 111 |
self._isAuxiliary = False |
|---|
| 112 |
|
|---|
| 113 |
# Reference to the object with backend-specific behaviors |
|---|
| 114 |
self.__backend = None |
|---|
| 115 |
|
|---|
| 116 |
# Reference to the bizobj that 'owns' this cursor, if any, |
|---|
| 117 |
self._bizobj = None |
|---|
| 118 |
|
|---|
| 119 |
# set properties for the SQL Builder functions |
|---|
| 120 |
self.clearSQL() |
|---|
| 121 |
self.hasSqlBuilder = True |
|---|
| 122 |
|
|---|
| 123 |
# props for building the auxiliary cursor |
|---|
| 124 |
self._cursorFactoryFunc = None |
|---|
| 125 |
self._cursorFactoryClass = None |
|---|
| 126 |
|
|---|
| 127 |
# mementos and new records, keyed on record object ids: |
|---|
| 128 |
self._mementos = {} |
|---|
| 129 |
self._newRecords = {} |
|---|
| 130 |
|
|---|
| 131 |
# Flag preference cursors so that they don't fill up the logs |
|---|
| 132 |
self._isPrefCursor = False |
|---|
| 133 |
|
|---|
| 134 |
# Get the parameter for the backend type |
|---|
| 135 |
self._paramPlaceholder = None |
|---|
| 136 |
|
|---|
| 137 |
self.initProperties() |
|---|
| 138 |
|
|---|
| 139 |
|
|---|
| 140 |
def setCursorFactory(self, func, cls): |
|---|
| 141 |
self._cursorFactoryFunc = func |
|---|
| 142 |
self._cursorFactoryClass = cls |
|---|
| 143 |
|
|---|
| 144 |
|
|---|
| 145 |
def clearSQL(self): |
|---|
| 146 |
self._fieldClause = "" |
|---|
| 147 |
self._fromClause = "" |
|---|
| 148 |
self._joinClause = "" |
|---|
| 149 |
self._whereClause = "" |
|---|
| 150 |
self._childFilterClause = "" |
|---|
| 151 |
self._groupByClause = "" |
|---|
| 152 |
self._orderByClause = "" |
|---|
| 153 |
self._limitClause = "" |
|---|
| 154 |
self._defaultLimit = 1000 |
|---|
| 155 |
|
|---|
| 156 |
|
|---|
| 157 |
def getSortColumn(self): |
|---|
| 158 |
return self.sortColumn |
|---|
| 159 |
|
|---|
| 160 |
|
|---|
| 161 |
def getSortOrder(self): |
|---|
| 162 |
return self.sortOrder |
|---|
| 163 |
|
|---|
| 164 |
|
|---|
| 165 |
def getSortCase(self): |
|---|
| 166 |
return self.sortCase |
|---|
| 167 |
|
|---|
| 168 |
|
|---|
| 169 |
def pkExpression(self, rec=None): |
|---|
| 170 |
"""Returns the PK expression for the passed record.""" |
|---|
| 171 |
if rec is None: |
|---|
| 172 |
try: |
|---|
| 173 |
rec = self._records[self.RowNumber] |
|---|
| 174 |
except IndexError: |
|---|
| 175 |
rec = {} |
|---|
| 176 |
if isinstance(self.KeyField, tuple): |
|---|
| 177 |
if rec: |
|---|
| 178 |
pk = tuple([rec[kk] for kk in self.KeyField]) |
|---|
| 179 |
else: |
|---|
| 180 |
pk = tuple([None for kk in self.KeyField]) |
|---|
| 181 |
else: |
|---|
| 182 |
pk = rec.get(self.KeyField, None) |
|---|
| 183 |
return pk |
|---|
| 184 |
|
|---|
| 185 |
|
|---|
| 186 |
def pkFieldExpression(self): |
|---|
| 187 |
"""Returns the field or comma-separated list of field names |
|---|
| 188 |
for the PK for this cursor's table. |
|---|
| 189 |
""" |
|---|
| 190 |
if isinstance(self.KeyField, tuple): |
|---|
| 191 |
pkField = ", ".join([kk for kk in self.KeyField]) |
|---|
| 192 |
else: |
|---|
| 193 |
pkField = self.KeyField |
|---|
| 194 |
return pkField |
|---|
| 195 |
|
|---|
| 196 |
|
|---|
| 197 |
def _correctFieldType(self, field_val, field_name, _newQuery=False): |
|---|
| 198 |
"""Correct the type of the passed field_val, based on self.DataStructure. |
|---|
| 199 |
|
|---|
| 200 |
This is called by self.execute(), and contains code to convert all strings |
|---|
| 201 |
to unicode, as well as to correct any datatypes that don't match what |
|---|
| 202 |
self.DataStructure reports. The latter can happen with SQLite, for example, |
|---|
| 203 |
which only knows about a quite limited number of types. |
|---|
| 204 |
""" |
|---|
| 205 |
if field_val is None: |
|---|
| 206 |
return field_val |
|---|
| 207 |
ret = field_val |
|---|
| 208 |
if _newQuery or (field_name in self._fieldsToAlwaysCorrectType): |
|---|
| 209 |
pythonType = self._types.get(field_name, None) |
|---|
| 210 |
if pythonType is None or pythonType == type(None): |
|---|
| 211 |
pythonType = self._types[field_name] = dabo.db.getDataType(type(field_val)) |
|---|
| 212 |
|
|---|
| 213 |
if isinstance(field_val, str) and self._convertStrToUnicode: |
|---|
| 214 |
# convert to unicode |
|---|
| 215 |
pass |
|---|
| 216 |
elif pythonType is None or isinstance(field_val, pythonType): |
|---|
| 217 |
# No conversion needed. |
|---|
| 218 |
return ret |
|---|
| 219 |
else: |
|---|
| 220 |
self._fieldsToAlwaysCorrectType.append(field_name) |
|---|
| 221 |
|
|---|
| 222 |
if pythonType in (unicode,): |
|---|
| 223 |
# Unicode conversion happens below. |
|---|
| 224 |
pass |
|---|
| 225 |
elif pythonType in (datetime.datetime, ) and isinstance(field_val, basestring): |
|---|
| 226 |
ret = dates.getDateTimeFromString(field_val) |
|---|
| 227 |
if ret is None: |
|---|
| 228 |
ret = field_val |
|---|
| 229 |
else: |
|---|
| 230 |
return ret |
|---|
| 231 |
elif pythonType in (datetime.date,) and isinstance(field_val, basestring): |
|---|
| 232 |
ret = dates.getDateFromString(field_val) |
|---|
| 233 |
if ret is None: |
|---|
| 234 |
ret = field_val |
|---|
| 235 |
else: |
|---|
| 236 |
return ret |
|---|
| 237 |
elif pythonType in (Decimal,): |
|---|
| 238 |
ds = self.DataStructure |
|---|
| 239 |
ret = None |
|---|
| 240 |
_field_val = field_val |
|---|
| 241 |
if type(field_val) in (float,): |
|---|
| 242 |
# Can't convert to decimal directly from float |
|---|
| 243 |
_field_val = ustr(_field_val) |
|---|
| 244 |
# Need to convert to the correct scale: |
|---|
| 245 |
scale = None |
|---|
| 246 |
for s in ds: |
|---|
| 247 |
if s[0] == field_name: |
|---|
| 248 |
if len(s) > 5: |
|---|
| 249 |
scale = s[5] |
|---|
| 250 |
if scale is None: |
|---|
| 251 |
scale = 2 |
|---|
| 252 |
return Decimal(_field_val).quantize(Decimal("0.%s" % (scale * "0",))) |
|---|
| 253 |
else: |
|---|
| 254 |
try: |
|---|
| 255 |
return pythonType(field_val) |
|---|
| 256 |
except Exception, e: |
|---|
| 257 |
tfv = type(field_val) |
|---|
| 258 |
dabo.infoLog.write(_("_correctFieldType() failed for field: '%(field_name)s'; value: '%(field_val)s'; type: '%(tfv)s'") |
|---|
| 259 |
% locals()) |
|---|
| 260 |
|
|---|
| 261 |
# Do the unicode conversion last: |
|---|
| 262 |
if isinstance(field_val, str) and self._convertStrToUnicode: |
|---|
| 263 |
try: |
|---|
| 264 |
decoded = field_val.decode(self.Encoding) |
|---|
| 265 |
return decoded |
|---|
| 266 |
except UnicodeDecodeError, e: |
|---|
| 267 |
# Try some common encodings: |
|---|
| 268 |
ok = False |
|---|
| 269 |
for enc in ("utf-8", "latin-1", "iso-8859-1"): |
|---|
| 270 |
if enc != self.Encoding: |
|---|
| 271 |
try: |
|---|
| 272 |
ret = field_val.decode(enc) |
|---|
| 273 |
ok = True |
|---|
| 274 |
except UnicodeDecodeError: |
|---|
| 275 |
continue |
|---|
| 276 |
if ok: |
|---|
| 277 |
# change self.Encoding and log the message |
|---|
| 278 |
self.Encoding = enc |
|---|
| 279 |
dabo.errorLog.write(_("Field %(fname)s: Incorrect unicode encoding set; using '%(enc)s' instead") |
|---|
| 280 |
% {'fname':field_name, 'enc':enc} ) |
|---|
| 281 |
return ret |
|---|
| 282 |
else: |
|---|
| 283 |
raise e |
|---|
| 284 |
# elif isinstance(field_val, array.array): |
|---|
| 285 |
# # Usually blob data |
|---|
| 286 |
# ret = field_val.tostring() |
|---|
| 287 |
|
|---|
| 288 |
rfv = repr(field_val) |
|---|
| 289 |
dabo.errorLog.write(_("%(rfv)s couldn't be converted to %(pythonType)s (field %(field_name)s)") |
|---|
| 290 |
% locals()) |
|---|
| 291 |
return ret |
|---|
| 292 |
|
|---|
| 293 |
|
|---|
| 294 |
def execute(self, sql, params=None, _newQuery=False, errorClass=None, convertQMarks=False): |
|---|
| 295 |
"""Execute the sql, and populate the DataSet if it is a select statement.""" |
|---|
| 296 |
# The idea here is to let the super class do the actual work in |
|---|
| 297 |
# retrieving the data. However, many cursor classes can only return |
|---|
| 298 |
# row information as a list, not as a dictionary. This method will |
|---|
| 299 |
# detect that, and convert the results to a dictionary. |
|---|
| 300 |
if isinstance(sql, unicode): |
|---|
| 301 |
sql = sql.encode(self.Encoding) |
|---|
| 302 |
sql = self.processFields(sql) |
|---|
| 303 |
if convertQMarks: |
|---|
| 304 |
sql = self._qMarkToParamPlaceholder(sql) |
|---|
| 305 |
# Some backends, notably Firebird, require that fields be specially marked. |
|---|
| 306 |
sql = self.processFields(sql) |
|---|
| 307 |
try: |
|---|
| 308 |
if params: |
|---|
| 309 |
res = self.superCursor.execute(self, sql, params) |
|---|
| 310 |
if not self.IsPrefCursor: |
|---|
| 311 |
try: |
|---|
| 312 |
dabo.dbActivityLog.write("SQL: %s, PARAMS: %s" % ( |
|---|
| 313 |
sql.decode(self.Encoding).replace("\n", " "), |
|---|
| 314 |
', '.join("%s" % p for p in params))) |
|---|
| 315 |
except StandardError: |
|---|
| 316 |
# A problem with writing to the log, most likely due to encoding issues |
|---|
| 317 |
dabo.dbActivityLog.write("FAILED SQL: %s") |
|---|
| 318 |
else: |
|---|
| 319 |
res = self.superCursor.execute(self, sql) |
|---|
| 320 |
if not self.IsPrefCursor: |
|---|
| 321 |
dabo.dbActivityLog.write("SQL: %s" % ( |
|---|
| 322 |
sql.decode(self.Encoding).replace("\n", " "),)) |
|---|
| 323 |
except Exception, e: |
|---|
| 324 |
# There can be cases where errors are expected. In those cases, the |
|---|
| 325 |
# calling routine will pass the class of the expected error, and will |
|---|
| 326 |
# handle it appropriately. |
|---|
| 327 |
if errorClass is not None and isinstance(e, errorClass): |
|---|
| 328 |
raise e |
|---|
| 329 |
if params: |
|---|
| 330 |
try: |
|---|
| 331 |
dabo.dbActivityLog.write("FAILED SQL: %s, PARAMS: %s" % ( |
|---|
| 332 |
sql.decode(self.Encoding).replace("\n", " "), |
|---|
| 333 |
', '.join("%s" % p for p in params))) |
|---|
| 334 |
except StandardError: |
|---|
| 335 |
# A problem with writing to the log, most likely due to encoding issues |
|---|
| 336 |
dabo.dbActivityLog.write("FAILED SQL: %s") |
|---|
| 337 |
else: |
|---|
| 338 |
dabo.dbActivityLog.write("FAILED SQL: %s" % ( |
|---|
| 339 |
sql.decode(self.Encoding).replace("\n", " "),)) |
|---|
| 340 |
# Database errors need to be decoded from database encoding. |
|---|
| 341 |
try: |
|---|
| 342 |
errMsg = ustr(e) |
|---|
| 343 |
except UnicodeError: |
|---|
| 344 |
errMsg = unicode(e) |
|---|
| 345 |
# If this is due to a broken connection, let the user know. |
|---|
| 346 |
# Different backends have different messages, but they |
|---|
| 347 |
# should all contain the string 'connect' in them. |
|---|
| 348 |
if "connect" in errMsg.lower(): |
|---|
| 349 |
raise dException.ConnectionLostException(errMsg) |
|---|
| 350 |
elif "access" in errMsg.lower(): |
|---|
| 351 |
raise dException.DBNoAccessException(errMsg) |
|---|
| 352 |
else: |
|---|
| 353 |
dabo.dbActivityLog.write( |
|---|
| 354 |
_("DBQueryException encountered in execute(): %s\n%s") % (errMsg, sql)) |
|---|
| 355 |
raise dException.DBQueryException(errMsg) |
|---|
| 356 |
|
|---|
| 357 |
# Some backend programs do odd things to the description |
|---|
| 358 |
# This allows each backend to handle these quirks individually. |
|---|
| 359 |
self.BackendObject.massageDescription(self) |
|---|
| 360 |
|
|---|
| 361 |
if self._newStructure(sql): |
|---|
| 362 |
self._storeFieldTypes() |
|---|
| 363 |
|
|---|
| 364 |
if sql.split(None, 1)[0].lower() not in ("select", "pragma"): |
|---|
| 365 |
# No need to massage the data for DML commands |
|---|
| 366 |
self._records = dDataSet(tuple()) |
|---|
| 367 |
return res |
|---|
| 368 |
|
|---|
| 369 |
try: |
|---|
| 370 |
_records = self.fetchall() |
|---|
| 371 |
except Exception, e: |
|---|
| 372 |
_records = dabo.db.dDataSet() |
|---|
| 373 |
# Database errors need to be decoded from database encoding. |
|---|
| 374 |
try: |
|---|
| 375 |
errMsg = ustr(e).decode(self.Encoding) |
|---|
| 376 |
except UnicodeError: |
|---|
| 377 |
errMsg = unicode(e) |
|---|
| 378 |
dabo.errorLog.write("Error fetching records: %s" % errMsg) |
|---|
| 379 |
|
|---|
| 380 |
if _records and not self.BackendObject._alreadyCorrectedFieldTypes: |
|---|
| 381 |
if isinstance(_records[0], (tuple, list)): |
|---|
| 382 |
# Need to convert each row to a Dict, since the backend didn't do it. |
|---|
| 383 |
tmpRows = [] |
|---|
| 384 |
fldNames = [f[0] for f in self.FieldDescription] |
|---|
| 385 |
for row in _records: |
|---|
| 386 |
dic = {} |
|---|
| 387 |
for idx, fldName in enumerate(fldNames): |
|---|
| 388 |
dic[fldName] = self._correctFieldType(field_val=row[idx], |
|---|
| 389 |
field_name=fldName, _newQuery=_newQuery) |
|---|
| 390 |
tmpRows.append(dic) |
|---|
| 391 |
_records = tmpRows |
|---|
| 392 |
else: |
|---|
| 393 |
# Already a DictCursor, but we still need to correct the field types. |
|---|
| 394 |
for row in _records: |
|---|
| 395 |
for fld, val in row.items(): |
|---|
| 396 |
row[fld] = self._correctFieldType(field_val=val, |
|---|
| 397 |
field_name=fld, _newQuery=_newQuery) |
|---|
| 398 |
|
|---|
| 399 |
self._records = dDataSet(_records) |
|---|
| 400 |
# This will handle bounds issues |
|---|
| 401 |
self.RowNumber = self.RowNumber |
|---|
| 402 |
return res |
|---|
| 403 |
|
|---|
| 404 |
|
|---|
| 405 |
def executeSafe(self, sql, params=None): |
|---|
| 406 |
"""Execute the passed SQL using an auxiliary cursor. |
|---|
| 407 |
This is considered 'safe', because it won't harm the contents |
|---|
| 408 |
of the main cursor. Returns the temp cursor. |
|---|
| 409 |
""" |
|---|
| 410 |
ac = self.AuxCursor |
|---|
| 411 |
self._syncAuxProperties() |
|---|
| 412 |
ac.execute(sql, params) |
|---|
| 413 |
return ac |
|---|
| 414 |
|
|---|
| 415 |
|
|---|
| 416 |
def _newStructure(self, sql): |
|---|
| 417 |
"""Attempts to parse the SQL to determine if the fields being selected will require |
|---|
| 418 |
a new call to set the structure. Non-select statements likewise will return False. |
|---|
| 419 |
""" |
|---|
| 420 |
if self._isAuxiliary: |
|---|
| 421 |
return False |
|---|
| 422 |
if sql == self.__lastExecute: |
|---|
| 423 |
return False |
|---|
| 424 |
# See if it's a select statement |
|---|
| 425 |
mtch = self._selectStatementPat.search(sql) |
|---|
| 426 |
if not mtch: |
|---|
| 427 |
return False |
|---|
| 428 |
# Normalize white space |
|---|
| 429 |
fldlist = self._whitespacePat.sub(" ", mtch.groups()[0]).strip() |
|---|
| 430 |
if self.__lastFieldList == fldlist: |
|---|
| 431 |
return False |
|---|
| 432 |
else: |
|---|
| 433 |
self.__lastFieldList = fldlist |
|---|
| 434 |
return True |
|---|
| 435 |
|
|---|
| 436 |
|
|---|
| 437 |
def _syncAuxProperties(self): |
|---|
| 438 |
"""Make sure that the auxiliary cursor has the same property |
|---|
| 439 |
settings as the main cursor. |
|---|
| 440 |
""" |
|---|
| 441 |
if self._isAuxiliary: |
|---|
| 442 |
# Redundant! |
|---|
| 443 |
return |
|---|
| 444 |
ac = self.AuxCursor |
|---|
| 445 |
ac.AutoPopulatePK = self.AutoPopulatePK |
|---|
| 446 |
ac.AutoQuoteNames = self.AutoQuoteNames |
|---|
| 447 |
ac.DataStructure = self.DataStructure |
|---|
| 448 |
ac.IsPrefCursor = self.IsPrefCursor |
|---|
| 449 |
ac.KeyField = self.KeyField |
|---|
| 450 |
ac.Table = self.Table |
|---|
| 451 |
|
|---|
| 452 |
|
|---|
| 453 |
def requery(self, params=None): |
|---|
| 454 |
currSQL = self.CurrentSQL |
|---|
| 455 |
newQuery = (self._lastSQL != currSQL) |
|---|
| 456 |
self._lastSQL = currSQL |
|---|
| 457 |
self.lastParams = params |
|---|
| 458 |
self._savedStructureDescription = [] |
|---|
| 459 |
|
|---|
| 460 |
self.execute(currSQL, params, _newQuery=newQuery) |
|---|
| 461 |
|
|---|
| 462 |
# clear mementos and new record flags: |
|---|
| 463 |
self._mementos = {} |
|---|
| 464 |
self._newRecords = {} |
|---|
| 465 |
# Record the requery time for caching purposes |
|---|
| 466 |
self.lastRequeryTime = time.time() |
|---|
| 467 |
|
|---|
| 468 |
if newQuery: |
|---|
| 469 |
# Check for any derived fields that should not be included in |
|---|
| 470 |
# any updates. |
|---|
| 471 |
self.__setNonUpdateFields() |
|---|
| 472 |
|
|---|
| 473 |
# Clear the unsorted list, and then apply the current sort |
|---|
| 474 |
self.__unsortedRows = [] |
|---|
| 475 |
if self.sortColumn: |
|---|
| 476 |
try: |
|---|
| 477 |
self.sort(self.sortColumn, self.sortOrder) |
|---|
| 478 |
except dException.NoRecordsException: |
|---|
| 479 |
# No big deal |
|---|
| 480 |
pass |
|---|
| 481 |
return True |
|---|
| 482 |
|
|---|
| 483 |
|
|---|
| 484 |
def _storeFieldTypes(self, target=None): |
|---|
| 485 |
"""Stores the data type for each column in the result set.""" |
|---|
| 486 |
try: |
|---|
| 487 |
## The Record object must be reinstantiated to reflect the new structure: |
|---|
| 488 |
del(self._cursorRecord) |
|---|
| 489 |
except AttributeError: |
|---|
| 490 |
pass |
|---|
| 491 |
if target is None: |
|---|
| 492 |
target = self |
|---|
| 493 |
target._types = {} |
|---|
| 494 |
for field in self.DataStructure: |
|---|
| 495 |
field_alias, field_type = field[0], field[1] |
|---|
| 496 |
target._types[field_alias] = dabo.db.getPythonType(field_type) |
|---|
| 497 |
|
|---|
| 498 |
|
|---|
| 499 |
def sort(self, col, ordr=None, caseSensitive=True): |
|---|
| 500 |
""" Sort the result set on the specified column in the specified order. If the sort |
|---|
| 501 |
direction is not specified, default to ascending order. If 'cycle' is specified as the |
|---|
| 502 |
direction, use the next ordering in the list [None, 'ASC', 'DESC']. The possible |
|---|
| 503 |
values for 'ordr' are: |
|---|
| 504 |
None |
|---|
| 505 |
"" (i.e., an empty string) |
|---|
| 506 |
ASC |
|---|
| 507 |
DESC |
|---|
| 508 |
CYCLE |
|---|
| 509 |
Only the first three characters are significant; case is ignored. |
|---|
| 510 |
""" |
|---|
| 511 |
currCol = self.sortColumn |
|---|
| 512 |
currOrd = self.sortOrder |
|---|
| 513 |
if ordr is None: |
|---|
| 514 |
ordr = "ASC" |
|---|
| 515 |
elif ordr == "": |
|---|
| 516 |
ordr = None |
|---|
| 517 |
if ordr[:3].upper() == "CYC": |
|---|
| 518 |
ordr = {"ASC": "DESC", "DES": None, None: "ASC"}[currOrd] |
|---|
| 519 |
col = currCol |
|---|
| 520 |
|
|---|
| 521 |
# Make sure that the specified column is a column in the result set |
|---|
| 522 |
if not [True for t in self.DataStructure if t[0] == col] and col not in self.VirtualFields: |
|---|
| 523 |
raise dException.dException( |
|---|
| 524 |
_("Invalid column specified for sort: ") + col) |
|---|
| 525 |
|
|---|
| 526 |
newCol = col |
|---|
| 527 |
if col == currCol: |
|---|
| 528 |
# Not changing the column; most likely they are flipping |
|---|
| 529 |
# the sort order. |
|---|
| 530 |
if (ordr is None) or not ordr: |
|---|
| 531 |
# They didn't specify the sort order. Cycle through the sort orders |
|---|
| 532 |
if currOrd == "ASC": |
|---|
| 533 |
newOrd = "DESC" |
|---|
| 534 |
elif currOrd == "DESC": |
|---|
| 535 |
newOrd = "" |
|---|
| 536 |
else: |
|---|
| 537 |
newOrd = "ASC" |
|---|
| 538 |
else: |
|---|
| 539 |
if ordr.upper() in ("ASC", "DESC", ""): |
|---|
| 540 |
newOrd = ordr.upper() |
|---|
| 541 |
else: |
|---|
| 542 |
raise dException.dException( |
|---|
| 543 |
_("Invalid Sort direction specified: ") + ordr) |
|---|
| 544 |
|
|---|
| 545 |
else: |
|---|
| 546 |
# Different column specified. |
|---|
| 547 |
if (ordr is None) or not ordr: |
|---|
| 548 |
# Start in ASC order |
|---|
| 549 |
newOrd = "ASC" |
|---|
| 550 |
else: |
|---|
| 551 |
if ordr.upper() in ("ASC", "DESC", ""): |
|---|
| 552 |
newOrd = ordr.upper() |
|---|
| 553 |
else: |
|---|
| 554 |
raise dException.dException( |
|---|
| 555 |
_("Invalid Sort direction specified: ") + ordr) |
|---|
| 556 |
|
|---|
| 557 |
self.__sortRows(newCol, newOrd, caseSensitive) |
|---|
| 558 |
# Save the current sort values |
|---|
| 559 |
self.sortColumn = newCol |
|---|
| 560 |
self.sortOrder = newOrd |
|---|
| 561 |
self.sortCase = caseSensitive |
|---|
| 562 |
|
|---|
| 563 |
|
|---|
| 564 |
def __sortRows(self, col, ordr, caseSensitive): |
|---|
| 565 |
""" Sort the rows of the cursor. |
|---|
| 566 |
|
|---|
| 567 |
At this point, we know we have a valid column and order. We need to |
|---|
| 568 |
preserve the unsorted order if we haven't done that yet; then we sort |
|---|
| 569 |
the data according to the request. |
|---|
| 570 |
""" |
|---|
| 571 |
kf = self.KeyField |
|---|
| 572 |
if not kf or not self.RowCount: |
|---|
| 573 |
return |
|---|
| 574 |
|
|---|
| 575 |
if not self.__unsortedRows: |
|---|
| 576 |
# Record the PK values |
|---|
| 577 |
for row in self._records: |
|---|
| 578 |
if self._compoundKey: |
|---|
| 579 |
key = tuple([row[k] for k in kf]) |
|---|
| 580 |
self.__unsortedRows.append(key) |
|---|
| 581 |
else: |
|---|
| 582 |
self.__unsortedRows.append(row[kf]) |
|---|
| 583 |
|
|---|
| 584 |
# First, preserve the PK of the current row so that we can reset |
|---|
| 585 |
# the RowNumber property to point to the same row in the new order. |
|---|
| 586 |
try: |
|---|
| 587 |
if self._compoundKey: |
|---|
| 588 |
currRow = self._records[self.RowNumber] |
|---|
| 589 |
currRowKey = tuple([currRow[k] for k in kf]) |
|---|
| 590 |
else: |
|---|
| 591 |
currRowKey = self._records[self.RowNumber][kf] |
|---|
| 592 |
except IndexError: |
|---|
| 593 |
# Row no longer exists, such as after a Requery that returns |
|---|
| 594 |
# fewer rows. |
|---|
| 595 |
currRowKey = None |
|---|
| 596 |
# Create the list to hold the rows for sorting |
|---|
| 597 |
sortList = [] |
|---|
| 598 |
if not ordr: |
|---|
| 599 |
# Restore the rows to their unsorted order |
|---|
| 600 |
for row in self._records: |
|---|
| 601 |
if self._compoundKey: |
|---|
| 602 |
key = tuple([row[k] for k in kf]) |
|---|
| 603 |
sortList.append([self.__unsortedRows.index(key), row]) |
|---|
| 604 |
else: |
|---|
| 605 |
sortList.append([self.__unsortedRows.index(row[kf]), row]) |
|---|
| 606 |
else: |
|---|
| 607 |
for row, rec in enumerate(self._records): |
|---|
| 608 |
sortList.append([self.getFieldVal(col, row), rec]) |
|---|
| 609 |
# At this point we have a list consisting of lists. Each of these member |
|---|
| 610 |
# lists contain the sort value in the zeroth element, and the row as |
|---|
| 611 |
# the first element. |
|---|
| 612 |
# First, see if we are comparing strings |
|---|
| 613 |
compString = isinstance(sortList[0][0], basestring) |
|---|
| 614 |
|
|---|
| 615 |
if compString and not caseSensitive: |
|---|
| 616 |
sortKey = caseInsensitiveSortKey |
|---|
| 617 |
else: |
|---|
| 618 |
sortKey = noneSortKey |
|---|
| 619 |
sortList.sort(key=sortKey, reverse=(ordr == "DESC")) |
|---|
| 620 |
|
|---|
| 621 |
# Extract the rows into a new list, then convert them back to the _records tuple |
|---|
| 622 |
newRows = [elem[1] for elem in sortList] |
|---|
| 623 |
self._records = dDataSet(newRows) |
|---|
| 624 |
|
|---|
| 625 |
# restore the RowNumber |
|---|
| 626 |
if currRowKey: |
|---|
| 627 |
for ii in range(0, self.RowCount): |
|---|
| 628 |
row = self._records[ii] |
|---|
| 629 |
if self._compoundKey: |
|---|
| 630 |
key = tuple([row[k] for k in kf]) |
|---|
| 631 |
found = (key == currRowKey) |
|---|
| 632 |
else: |
|---|
| 633 |
found = row[kf] == currRowKey |
|---|
| 634 |
if found: |
|---|
| 635 |
self.RowNumber = ii |
|---|
| 636 |
break |
|---|
| 637 |
else: |
|---|
| 638 |
self.RowNumber = 0 |
|---|
| 639 |
|
|---|
| 640 |
|
|---|
| 641 |
def cursorToXML(self): |
|---|
| 642 |
""" Returns an XML string containing the information necessary to |
|---|
| 643 |
re-create this cursor. |
|---|
| 644 |
""" |
|---|
| 645 |
base = """<?xml version="1.0" encoding="%s"?> |
|---|
| 646 |
<dabocursor xmlns="http://www.dabodev.com" |
|---|
| 647 |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|---|
| 648 |
xsi:schemaLocation="http://www.dabodev.com dabocursor.xsd" |
|---|
| 649 |
xsi:noNamespaceSchemaLocation = "http://dabodev.com/schema/dabocursor.xsd"> |
|---|
| 650 |
<cursor autopopulate="%s" keyfield="%s" table="%s"> |
|---|
| 651 |
%s |
|---|
| 652 |
</cursor> |
|---|
| 653 |
</dabocursor>""" |
|---|
| 654 |
|
|---|
| 655 |
rowTemplate = """ <row> |
|---|
| 656 |
%s |
|---|
| 657 |
</row> |
|---|
| 658 |
""" |
|---|
| 659 |
|
|---|
| 660 |
colTemplate = """ <column name="%s" type="%s">%s</column>""" |
|---|
| 661 |
|
|---|
| 662 |
rowXML = "" |
|---|
| 663 |
for rec in self._records: |
|---|
| 664 |
recInfo = [ colTemplate % (k, self.getType(v), self.escape(v)) |
|---|
| 665 |
for k,v in rec.items() ] |
|---|
| 666 |
rowXML += rowTemplate % "\n".join(recInfo) |
|---|
| 667 |
return base % (self.Encoding, self.AutoPopulatePK, self.KeyField, |
|---|
| 668 |
self.Table, rowXML) |
|---|
| 669 |
|
|---|
| 670 |
|
|---|
| 671 |
def _xmlForRow(self, row=None): |
|---|
| 672 |
"""Returns the XML for the specified row; if no row is specified, |
|---|
| 673 |
the current row is used. |
|---|
| 674 |
""" |
|---|
| 675 |
colTemplate = """ <column name="%s" type="%s">%s</column>""" |
|---|
| 676 |
if row is None: |
|---|
| 677 |
row = self.RowNumber |
|---|
| 678 |
recInfo = [colTemplate % (k, self.getType(v), self.escape(v)) |
|---|
| 679 |
for k,v in self._records[row].items()] |
|---|
| 680 |
return "\n".join(recInfo) |
|---|
| 681 |
|
|---|
| 682 |
|
|---|
| 683 |
def getType(self, val): |
|---|
| 684 |
try: |
|---|
| 685 |
ret = re.search("type '([^']+)'", ustr(type(val))).groups()[0] |
|---|
| 686 |
except IndexError: |
|---|
| 687 |
ret = "-unknown-" |
|---|
| 688 |
return ret |
|---|
| 689 |
|
|---|
| 690 |
|
|---|
| 691 |
def escape(self, val): |
|---|
| 692 |
""" Provides the proper escaping of values in XML output """ |
|---|
| 693 |
ret = val |
|---|
| 694 |
if isinstance(val, basestring): |
|---|
| 695 |
if ("\n" in val) or ("<" in val) or ("&" in val): |
|---|
| 696 |
ret = "<![CDATA[%s]]>" % val.encode(self.Encoding) |
|---|
| 697 |
return ret |
|---|
| 698 |
|
|---|
| 699 |
|
|---|
| 700 |
def setNonUpdateFields(self, fldList=None): |
|---|
| 701 |
if fldList is None: |
|---|
| 702 |
fldList = [] |
|---|
| 703 |
self.nonUpdateFields = fldList |
|---|
| 704 |
|
|---|
| 705 |
|
|---|
| 706 |
def getNonUpdateFields(self): |
|---|
| 707 |
if self.__nonUpdateFields is None: |
|---|
| 708 |
# They haven't been set yet |
|---|
| 709 |
self.__setNonUpdateFields() |
|---|
| 710 |
return list(set(self.nonUpdateFields + self.__nonUpdateFields)) |
|---|
| 711 |
|
|---|
| 712 |
|
|---|
| 713 |
def __setNonUpdateFields(self, nonUp=None): |
|---|
| 714 |
"""Automatically set the non-update fields.""" |
|---|
| 715 |
if nonUp is not None: |
|---|
| 716 |
# This is being called back by the BackendObject |
|---|
| 717 |
self.__nonUpdateFields = nonUp |
|---|
| 718 |
return |
|---|
| 719 |
dataStructure = getattr(self, "_dataStructure", None) |
|---|
| 720 |
if dataStructure is not None: |
|---|
| 721 |
# Use the explicitly-set DataStructure to find the NonUpdateFields. |
|---|
| 722 |
self.__nonUpdateFields = [f[0] for f in self.DataStructure |
|---|
| 723 |
if (f[3] != self.Table) or not f[4]] |
|---|
| 724 |
else: |
|---|
| 725 |
# Create the _dataStructure attribute |
|---|
| 726 |
self._getDataStructure() |
|---|
| 727 |
# Delegate to the backend object to figure it out. |
|---|
| 728 |
self.__nonUpdateFields = self.BackendObject.setNonUpdateFields(self) |
|---|
| 729 |
|
|---|
| 730 |
|
|---|
| 731 |
def isChanged(self, allRows=True, includeNewUnchanged=False): |
|---|
| 732 |
"""Return True if there are any changes to the local field values. |
|---|
| 733 |
|
|---|
| 734 |
If allRows is True (the default), all records in the recordset will be |
|---|
| 735 |
considered. Otherwise, only the current record will be checked. |
|---|
| 736 |
|
|---|
| 737 |
If includeNewUnchanged is True, new records that have not been |
|---|
| 738 |
modified from their default values, which normally are not |
|---|
| 739 |
considered 'changed', will be counted as 'changed'. |
|---|
| 740 |
""" |
|---|
| 741 |
if allRows: |
|---|
| 742 |
if includeNewUnchanged: |
|---|
| 743 |
return (len(self._mementos) > 0) or (len(self._newRecords) > 0) |
|---|
| 744 |
else: |
|---|
| 745 |
return len(self._mementos) > 0 |
|---|
| 746 |
else: |
|---|
| 747 |
row = self.RowNumber |
|---|
| 748 |
try: |
|---|
| 749 |
rec = self._records[row] |
|---|
| 750 |
except IndexError: |
|---|
| 751 |
# self.RowNumber doesn't exist (init phase?) Nothing's changed: |
|---|
| 752 |
return False |
|---|
| 753 |
recKey = self.pkExpression(rec) |
|---|
| 754 |
modrec = self._mementos.get(recKey, None) |
|---|
| 755 |
if not modrec and includeNewUnchanged: |
|---|
| 756 |
modrec = recKey in self._newRecords |
|---|
| 757 |
return bool(modrec) |
|---|
| 758 |
|
|---|
| 759 |
|
|---|
| 760 |
def setNewFlag(self): |
|---|
| 761 |
"""Set the current record to be flagged as a new record. |
|---|
| 762 |
|
|---|
| 763 |
dBizobj will automatically call this method as appropriate, but if you are |
|---|
| 764 |
using dCursor without a proxy dBizobj, you'll need to manually call this |
|---|
| 765 |
method after cursor.new(), and (if applicable) after cursor.genTempAutoPK(). |
|---|
| 766 |
For example: |
|---|
| 767 |
cursor.new() |
|---|
| 768 |
cursor.genTempAutoPK() |
|---|
| 769 |
cursor.setNewFlag() |
|---|
| 770 |
""" |
|---|
| 771 |
pk = None |
|---|
| 772 |
if self.KeyField: |
|---|
| 773 |
pk = self.getPK() |
|---|
| 774 |
self._newRecords[pk] = None |
|---|
| 775 |
# Add the 'new record' flag |
|---|
| 776 |
self._records[self.RowNumber][kons.CURSOR_TMPKEY_FIELD] = pk |
|---|
| 777 |
|
|---|
| 778 |
|
|---|
| 779 |
def genTempAutoPK(self): |
|---|
| 780 |
""" Create a temporary PK for a new record. Set the key field to this |
|---|
| 781 |
value, and also create a temp field to hold it so that when saving the |
|---|
| 782 |
new record, child records that are linked to this one can be updated |
|---|
| 783 |
with the actual PK value. |
|---|
| 784 |
""" |
|---|
| 785 |
rec = self._records[self.RowNumber] |
|---|
| 786 |
kf = self.KeyField |
|---|
| 787 |
try: |
|---|
| 788 |
if isinstance(kf, tuple): |
|---|
| 789 |
pkVal = rec[kf[0]] |
|---|
| 790 |
else: |
|---|
| 791 |
pkVal = rec[kf] |
|---|
| 792 |
except (IndexError, KeyError): |
|---|
| 793 |
# No records; default to string |
|---|
| 794 |
pkVal = "" |
|---|
| 795 |
|
|---|
| 796 |
tmpPK = self._genTempPKVal(pkVal) |
|---|
| 797 |
if isinstance(kf, tuple): |
|---|
| 798 |
for key in kf: |
|---|
| 799 |
rec[key] = tmpPK |
|---|
| 800 |
else: |
|---|
| 801 |
rec[kf] = tmpPK |
|---|
| 802 |
rec[kons.CURSOR_TMPKEY_FIELD] = tmpPK |
|---|
| 803 |
return tmpPK |
|---|
| 804 |
|
|---|
| 805 |
|
|---|
| 806 |
def _genTempPKVal(self, pkValue): |
|---|
| 807 |
""" Return the next available temp PK value. It will be a string, and |
|---|
| 808 |
postfixed with '-dabotmp' to avoid potential conflicts with actual PKs |
|---|
| 809 |
""" |
|---|
| 810 |
ret = self.__tmpPK |
|---|
| 811 |
# Decrement the temp PK value |
|---|
| 812 |
self.__tmpPK -= 1 |
|---|
| 813 |
if isinstance(pkValue, basestring): |
|---|
| 814 |
ret = "%s-dabotmp" % ret |
|---|
| 815 |
return ret |
|---|
| 816 |
|
|---|
| 817 |
|
|---|
| 818 |
def getPK(self, row=None): |
|---|
| 819 |
""" Returns the value of the PK field in the current or passed record number. |
|---|
| 820 |
If that record is a new unsaved record, return the temp PK value. If this is a |
|---|
| 821 |
compound PK, return a tuple containing each field's values. |
|---|
| 822 |
""" |
|---|
| 823 |
if self.RowCount <= 0: |
|---|
| 824 |
raise dException.NoRecordsException( |
|---|
| 825 |
_("No records in the data set.")) |
|---|
| 826 |
ret = None |
|---|
| 827 |
if row is None: |
|---|
| 828 |
row = self.RowNumber |
|---|
| 829 |
rec = self._records[row] |
|---|
| 830 |
recKey = self.pkExpression(rec) |
|---|
| 831 |
if (recKey in self._newRecords) and self.AutoPopulatePK: |
|---|
| 832 |
# New, unsaved record |
|---|
| 833 |
ret = rec[kons.CURSOR_TMPKEY_FIELD] |
|---|
| 834 |
else: |
|---|
| 835 |
kf = self.KeyField |
|---|
| 836 |
if isinstance(kf, tuple): |
|---|
| 837 |
ret = tuple([rec[k] for k in kf]) |
|---|
| 838 |
else: |
|---|
| 839 |
ret = rec.get(kf, None) |
|---|
| 840 |
return ret |
|---|
| 841 |
|
|---|
| 842 |
|
|---|
| 843 |
def getFieldVal(self, fld, row=None, _rowChangeCallback=None): |
|---|
| 844 |
"""Return the value of the specified field in the current or specified row.""" |
|---|
| 845 |
if self.RowCount <= 0: |
|---|
| 846 |
raise dException.NoRecordsException(_("No records in the data set.")) |
|---|
| 847 |
if row is None: |
|---|
| 848 |
row = self.RowNumber |
|---|
| 849 |
try: |
|---|
| 850 |
rec = self._records[row] |
|---|
| 851 |
except IndexError: |
|---|
| 852 |
cnt = len(self._records) |
|---|
| 853 |
raise dException.RowNotFoundException( |
|---|
| 854 |
_("Row #%(row)s requested, but the data set has only %(cnt)s row(s),") % locals()) |
|---|
| 855 |
if isinstance(fld, (tuple, list)): |
|---|
| 856 |
ret = [] |
|---|
| 857 |
for xFld in fld: |
|---|
| 858 |
ret.append(self.getFieldVal(xFld, row=row)) |
|---|
| 859 |
return tuple(ret) |
|---|
| 860 |
else: |
|---|
| 861 |
try: |
|---|
| 862 |
return rec[fld] |
|---|
| 863 |
except KeyError: |
|---|
| 864 |
try: |
|---|
| 865 |
vf = self.VirtualFields[fld] |
|---|
| 866 |
if not isinstance(vf, dict): |
|---|
| 867 |
vf = {"func": vf} |
|---|
| 868 |
|
|---|
| 869 |
requery_children = (vf.get("requery_children", False) and bool(_rowChangeCallback)) |
|---|
| 870 |
|
|---|
| 871 |
# Move to specified row if necessary, and then call the VirtualFields |
|---|
| 872 |
# function, which expects to be on the correct row. |
|---|
| 873 |
if not requery_children: |
|---|
| 874 |
# The VirtualFields 'requery_children' key is False, or |
|---|
| 875 |
# we aren't being called by a bizobj, so there aren't child bizobjs. |
|---|
| 876 |
_oldrow = self.RowNumber |
|---|
| 877 |
self.RowNumber = row |
|---|
| 878 |
ret = vf["func"]() |
|---|
| 879 |
self.RowNumber = _oldrow |
|---|
| 880 |
return ret |
|---|
| 881 |
else: |
|---|
| 882 |
# The VirtualFields definition's 'requery_children' key is True, so |
|---|
| 883 |
# we need to request a row change and requery of any child bizobjs |
|---|
| 884 |
# as necessary, before executing the virtual field function. |
|---|
| 885 |
_rowChangeCallback(row) |
|---|
| 886 |
return vf["func"]() |
|---|
| 887 |
except KeyError: |
|---|
| 888 |
raise dException.FieldNotFoundException("%s '%s' %s" % ( |
|---|
| 889 |
_("Field"), fld, _("does not exist in the data set"))) |
|---|
| 890 |
|
|---|
| 891 |
|
|---|
| 892 |
def _fldTypeFromDB(self, fld): |
|---|
| 893 |
"""Try to determine the field type from the database information |
|---|
| 894 |
If the field isn't found, return None. |
|---|
| 895 |
""" |
|---|
| 896 |
ret = None |
|---|
| 897 |
flds = self.getFields() |
|---|
| 898 |
try: |
|---|
| 899 |
typ = [ff[1] for ff in flds if ff[0] == fld][0] |
|---|
| 900 |
except IndexError: |
|---|
| 901 |
# This 'fld' value is not a native field, so no way to |
|---|
| 902 |
# determine its type |
|---|
| 903 |
typ = None |
|---|
| 904 |
if typ: |
|---|
| 905 |
try: |
|---|
| 906 |
ret = {"C": unicode, "D": datetime.date, "B": bool, "G": long, |
|---|
| 907 |
"N": float, "M": unicode, "I": int, "T": datetime.datetime}[typ] |
|---|
| 908 |
except KeyError: |
|---|
| 909 |
ret = None |
|---|
| 910 |
return ret |
|---|
| 911 |
|
|---|
| 912 |
|
|---|
| 913 |
def _hasValidKeyField(self): |
|---|
| 914 |
"""Return True if the KeyField exists and names valid fields.""" |
|---|
| 915 |
try: |
|---|
| 916 |
self.checkPK() |
|---|
| 917 |
except dException.MissingPKException: |
|---|
| 918 |
return False |
|---|
| 919 |
return True |
|---|
| 920 |
|
|---|
| 921 |
|
|---|
| 922 |
def setValuesByDict(self, valDict, row=None): |
|---|
| 923 |
"""Set the value for multiple fields with one call by passing a dict containing |
|---|
| 924 |
the field names as keys, and the new values as values. |
|---|
| 925 |
""" |
|---|
| 926 |
for fld, val in valDict.items(): |
|---|
| 927 |
self.setFieldVal(fld, val, row) |
|---|
| 928 |
|
|---|
| 929 |
|
|---|
| 930 |
def setFieldVal(self, fld, val, row=None): |
|---|
| 931 |
"""Set the value of the specified field.""" |
|---|
| 932 |
if self.RowCount <= 0: |
|---|
| 933 |
raise dException.NoRecordsException(_("No records in the data set")) |
|---|
| 934 |
if row is None: |
|---|
| 935 |
row = self.RowNumber |
|---|
| 936 |
|
|---|
| 937 |
try: |
|---|
| 938 |
rec = self._records[row] |
|---|
| 939 |
except IndexError: |
|---|
| 940 |
cnt = len(self._records) |
|---|
| 941 |
raise dException.RowNotFoundException( |
|---|
| 942 |
_("Row #%(row)s requested, but the data set has only %(cnt)s row(s),") % locals()) |
|---|
| 943 |
valid_pk = self._hasValidKeyField() |
|---|
| 944 |
keyField = self.KeyField |
|---|
| 945 |
if fld not in rec: |
|---|
| 946 |
if fld in self.VirtualFields: |
|---|
| 947 |
# ignore |
|---|
| 948 |
return |
|---|
| 949 |
raise dException.FieldNotFoundException( |
|---|
| 950 |
_("Field '%s' does not exist in the data set.") % (fld,)) |
|---|
| 951 |
|
|---|
| 952 |
try: |
|---|
| 953 |
fldType = self._types[fld] |
|---|
| 954 |
except KeyError: |
|---|
| 955 |
fldType = self._fldTypeFromDB(fld) |
|---|
| 956 |
nonUpdateFields = self.getNonUpdateFields() |
|---|
| 957 |
if fldType is not None: |
|---|
| 958 |
if fldType != type(val): |
|---|
| 959 |
convTypes = (str, unicode, int, float, long, complex) |
|---|
| 960 |
if issubclass(fldType, basestring) and isinstance(val, convTypes): |
|---|
| 961 |
if issubclass(fldType, str): |
|---|
| 962 |
val = ustr(val) |
|---|
| 963 |
else: |
|---|
| 964 |
val = unicode(val) |
|---|
| 965 |
elif issubclass(fldType, int) and isinstance(val, bool): |
|---|
| 966 |
# convert bool to int (original field val was bool, but UI |
|---|
| 967 |
# changed to int. |
|---|
| 968 |
val = int(val) |
|---|
| 969 |
elif issubclass(fldType, int) and isinstance(val, long): |
|---|
| 970 |
# convert long to int (original field val was int, but UI |
|---|
| 971 |
# changed to long. |
|---|
| 972 |
val = int(val) |
|---|
| 973 |
elif issubclass(fldType, long) and isinstance(val, int): |
|---|
| 974 |
# convert int to long (original field val was long, but UI |
|---|
| 975 |
# changed to int. |
|---|
| 976 |
val = long(val) |
|---|
| 977 |
|
|---|
| 978 |
if fldType != type(val): |
|---|
| 979 |
ignore = False |
|---|
| 980 |
# Date and DateTime types are handled as character, even if the |
|---|
| 981 |
# native field type is not. Ignore these. NOTE: we have to deal with the |
|---|
| 982 |
# string representation of these classes, as there is no primitive for either |
|---|
| 983 |
# 'DateTime' or 'Date'. |
|---|
| 984 |
dtStrings = ("<type 'DateTime'>", "<type 'Date'>", "<type 'datetime.datetime'>") |
|---|
| 985 |
if ustr(fldType) in dtStrings and isinstance(val, basestring): |
|---|
| 986 |
ignore = True |
|---|
| 987 |
elif issubclass(fldType, basestring) and isinstance(val, basestring): |
|---|
| 988 |
ignore = True |
|---|
| 989 |
elif val is None or fldType is type(None): |
|---|
| 990 |
# Any field type can potentially hold None values (NULL). Ignore these. |
|---|
| 991 |
ignore = True |
|---|
| 992 |
elif isinstance(val, dNoEscQuoteStr): |
|---|
| 993 |
# Sometimes you want to set it to a sql function, equation, ect. |
|---|
| 994 |
ignore = True |
|---|
| 995 |
elif fld in nonUpdateFields: |
|---|
| 996 |
# don't worry so much if this is just a calculated field. |
|---|
| 997 |
ignore = True |
|---|
| 998 |
else: |
|---|
| 999 |
# This can also happen with a new record, since we just stuff the |
|---|
| 1000 |
# fields full of empty strings. |
|---|
| 1001 |
ignore = (rec.get(keyField, None) in self._newRecords) |
|---|
| 1002 |
|
|---|
| 1003 |
if not ignore: |
|---|
| 1004 |
sft, stv = ustr(fldType), ustr(type(val)) |
|---|
| 1005 |
msg = _("!!! Data Type Mismatch: field=%(fld)s. Expecting: %(sft)s; got: %(stv)s") % locals() |
|---|
| 1006 |
dabo.errorLog.write(msg) |
|---|
| 1007 |
|
|---|
| 1008 |
# If the new value is different from the current value, change it and also |
|---|
| 1009 |
# update the mementos if necessary. |
|---|
| 1010 |
old_val = rec[fld] |
|---|
| 1011 |
if old_val != val: |
|---|
| 1012 |
if valid_pk: |
|---|
| 1013 |
if fld == keyField: |
|---|
| 1014 |
# Changing the key field value, need to key the mementos on the new |
|---|
| 1015 |
# value, not the old. Additionally, need to copy the mementos from the |
|---|
| 1016 |
# old key value to the new one. |
|---|
| 1017 |
keyFieldValue = val |
|---|
| 1018 |
old_mem = self._mementos.get(old_val, None) |
|---|
| 1019 |
if old_mem is not None: |
|---|
| 1020 |
self._mementos[keyFieldValue] = old_mem |
|---|
| 1021 |
del self._mementos[old_val] |
|---|
| 1022 |
elif self._compoundKey and fld in keyField: |
|---|
| 1023 |
# Changing the value of one key field, need to key the mementos on |
|---|
| 1024 |
# the new compound key value not the old. Additionally, need |
|---|
| 1025 |
#to copy the mementos from the old key value to the new one. |
|---|
| 1026 |
oldKeyFieldValue = tuple([rec[k] for k in keyField]) |
|---|
| 1027 |
old_mem = self._mementos.get(oldKeyFieldValue, None) |
|---|
| 1028 |
keyFieldValue = list(oldKeyFieldValue) |
|---|
| 1029 |
keyFieldValue[list(keyField).index(fld)] = val |
|---|
| 1030 |
keyFieldValue = tuple(keyFieldValue) |
|---|
| 1031 |
if old_mem is not None: |
|---|
| 1032 |
self._mementos[keyFieldValue] = old_mem |
|---|
| 1033 |
del self._mementos[oldKeyFieldValue] |
|---|
| 1034 |
else: |
|---|
| 1035 |
if self._compoundKey: |
|---|
| 1036 |
keyFieldValue = tuple([rec[k] for k in keyField]) |
|---|
| 1037 |
else: |
|---|
| 1038 |
keyFieldValue = rec[keyField] |
|---|
| 1039 |
mem = self._mementos.get(keyFieldValue, {}) |
|---|
| 1040 |
if (fld in mem) or (fld in nonUpdateFields): |
|---|
| 1041 |
# Memento is already there, or it isn't updateable. |
|---|
| 1042 |
pass |
|---|
| 1043 |
else: |
|---|
| 1044 |
# Save the memento for this field. |
|---|
| 1045 |
mem[fld] = old_val |
|---|
| 1046 |
|
|---|
| 1047 |
try: |
|---|
| 1048 |
if mem[fld] == val: |
|---|
| 1049 |
# Value changed back to the original memento value; delete the memento. |
|---|
| 1050 |
del mem[fld] |
|---|
| 1051 |
except KeyError: |
|---|
| 1052 |
pass |
|---|
| 1053 |
if mem: |
|---|
| 1054 |
self._mementos[keyFieldValue] = mem |
|---|
| 1055 |
else: |
|---|
| 1056 |
self._clearMemento(row) |
|---|
| 1057 |
else: |
|---|
| 1058 |
dabo.infoLog.write("Field value changed, but the memento" |
|---|
| 1059 |
" can't be saved, because there is no valid KeyField.") |
|---|
| 1060 |
|
|---|
| 1061 |
# Finally, save the new value to the field: |
|---|
| 1062 |
rec[fld] = val |
|---|
| 1063 |
|
|---|
| 1064 |
|
|---|
| 1065 |
def getRecordStatus(self, row=None, pk=None): |
|---|
| 1066 |
""" Returns a dictionary containing an element for each changed |
|---|
| 1067 |
field in the specified record (or the current record if none is specified). |
|---|
| 1068 |
The field name is the key for each element; the value is a 2-element |
|---|
| 1069 |
tuple, with the first element being the original value, and the second |
|---|
| 1070 |
being the current value. You can specify the record by either the |
|---|
| 1071 |
row number or the PK. |
|---|
| 1072 |
""" |
|---|
| 1073 |
ret = {} |
|---|
| 1074 |
if pk is not None: |
|---|
| 1075 |
recs = [r for r in self._records |
|---|
| 1076 |
if r[self._keyField] == pk] |
|---|
| 1077 |
try: |
|---|
| 1078 |
rec = recs[0] |
|---|
| 1079 |
except IndexError: |
|---|
| 1080 |
return ret |
|---|
| 1081 |
else: |
|---|
| 1082 |
if row is None: |
|---|
| 1083 |
row = self.RowNumber |
|---|
| 1084 |
rec = self._records[row] |
|---|
| 1085 |
pk = self.pkExpression(rec) |
|---|
| 1086 |
|
|---|
| 1087 |
mem = self._mementos.get(pk, {}) |
|---|
| 1088 |
|
|---|
| 1089 |
for k, v in mem.items(): |
|---|
| 1090 |
ret[k] = (v, rec[k]) |
|---|
| 1091 |
return ret |
|---|
| 1092 |
|
|---|
| 1093 |
|
|---|
| 1094 |
def _getNewRecordDiff(self, row=None, pk=None): |
|---|
| 1095 |
""" Returns a dictionary containing an element for each field |
|---|
| 1096 |
in the specified record (or the current record if none is specified). You |
|---|
| 1097 |
may specify the record by either row number or PK value. |
|---|
| 1098 |
The field name is the key for each element; the value is a 2-element |
|---|
| 1099 |
tuple, with the first element being the original value, and the second |
|---|
| 1100 |
being the current value. |
|---|
| 1101 |
|
|---|
| 1102 |
This is used internally in __saverow, and only applies to new records. |
|---|
| 1103 |
""" |
|---|
| 1104 |
ret = {} |
|---|
| 1105 |
if pk is not None: |
|---|
| 1106 |
recs = [r for r in self._records |
|---|
| 1107 |
if r[self._keyField] == pk] |
|---|
| 1108 |
try: |
|---|
| 1109 |
rec = recs[0] |
|---|
| 1110 |
except IndexError: |
|---|
| 1111 |
return ret |
|---|
| 1112 |
else: |
|---|
| 1113 |
if row is None: |
|---|
| 1114 |
row = self.RowNumber |
|---|
| 1115 |
rec = self._records[row] |
|---|
| 1116 |
pk = self.pkExpression(rec) |
|---|
| 1117 |
|
|---|
| 1118 |
for k, v in rec.items(): |
|---|
| 1119 |
if k not in (kons.CURSOR_TMPKEY_FIELD,): |
|---|
| 1120 |
ret[k] = (None, v) |
|---|
| 1121 |
return ret |
|---|
| 1122 |
|
|---|
| 1123 |
|
|---|
| 1124 |
def getCurrentRecord(self): |
|---|
| 1125 |
"""Returns the current record (as determined by self.RowNumber) |
|---|
| 1126 |
as a dict, or None if the RowNumber is not a valid record. |
|---|
| 1127 |
""" |
|---|
| 1128 |
try: |
|---|
| 1129 |
ret = self.getDataSet(rowStart=self.RowNumber, rows=1)[0] |
|---|
| 1130 |
except IndexError: |
|---|
| 1131 |
ret = None |
|---|
| 1132 |
return ret |
|---|
| 1133 |
|
|---|
| 1134 |
|
|---|
| 1135 |
def getDataSet(self, flds=(), rowStart=0, rows=None, returnInternals=False): |
|---|
| 1136 |
""" Get the entire data set encapsulated in a dDataSet object. |
|---|
| 1137 |
|
|---|
| 1138 |
If the optional 'flds' parameter is given, the result set will be filtered |
|---|
| 1139 |
to only include the specified fields. rowStart specifies the starting row |
|---|
| 1140 |
to include, and rows is the number of rows to return. |
|---|
| 1141 |
""" |
|---|
| 1142 |
ds = [] |
|---|
| 1143 |
internals = (kons.CURSOR_TMPKEY_FIELD,) |
|---|
| 1144 |
|
|---|
| 1145 |
if rows is None: |
|---|
| 1146 |
rows = self.RowCount |
|---|
| 1147 |
for row, rec in enumerate(self._records): |
|---|
| 1148 |
if row >= rowStart and row < (rowStart+rows): |
|---|
| 1149 |
tmprec = rec.copy() |
|---|
| 1150 |
for k, v in self.VirtualFields.items(): |
|---|
| 1151 |
# only calc requested virtualFields |
|---|
| 1152 |
if (flds and k in flds) or not flds: |
|---|
| 1153 |
tmprec.update({k: self.getFieldVal(k, row)}) |
|---|
| 1154 |
if flds: |
|---|
| 1155 |
# user specified specific fields - get rid of all others |
|---|
| 1156 |
for k in tmprec.keys(): |
|---|
| 1157 |
if k not in flds: |
|---|
| 1158 |
del tmprec[k] |
|---|
| 1159 |
if not flds and not returnInternals: |
|---|
| 1160 |
# user didn't specify explicit fields and doesn't want internals |
|---|
| 1161 |
for internal in internals: |
|---|
| 1162 |
tmprec.pop(internal, None) |
|---|
| 1163 |
ds.append(tmprec) |
|---|
| 1164 |
return dDataSet(ds) |
|---|
| 1165 |
|
|---|
| 1166 |
|
|---|
| 1167 |
def appendDataSet(self, ds): |
|---|
| 1168 |
"""Appends the rows in the passed dataset to this cursor's dataset. No checking |
|---|
| 1169 |
is done on the dataset columns to make sure that they are correct for this cursor; |
|---|
| 1170 |
it is the responsibility of the caller to make sure that they match. If invalid data is |
|---|
| 1171 |
passed, a dException.FieldNotFoundException will be raised. |
|---|
| 1172 |
""" |
|---|
| 1173 |
kf = self.KeyField |
|---|
| 1174 |
if not isinstance(kf, tuple): |
|---|
| 1175 |
kf = (kf, ) |
|---|
| 1176 |
for rec in ds: |
|---|
| 1177 |
self.new() |
|---|
| 1178 |
for col, val in rec.items(): |
|---|
| 1179 |
if self.AutoPopulatePK and (col in kf): |
|---|
| 1180 |
continue |
|---|
| 1181 |
self.setFieldVal(col, val) |
|---|
| 1182 |
|
|---|
| 1183 |
|
|---|
| 1184 |
def cloneRecord(self): |
|---|
| 1185 |
"""Creates a copy of the current record and adds it to the dataset.""" |
|---|
| 1186 |
if not self.RowCount: |
|---|
| 1187 |
raise dException.NoRecordsException(_("No records in the data set.")) |
|---|
| 1188 |
rec = self._records[self.RowNumber].copy() |
|---|
| 1189 |
if self.AutoPopulatePK: |
|---|
| 1190 |
kf = self.KeyField |
|---|
| 1191 |
blank = self._getBlankRecord() |
|---|
| 1192 |
if not isinstance(kf, tuple): |
|---|
| 1193 |
kf = (kf, ) |
|---|
| 1194 |
for fld in kf: |
|---|
| 1195 |
rec[fld] = blank[fld] |
|---|
| 1196 |
self.appendDataSet((rec, )) |
|---|
| 1197 |
|
|---|
| 1198 |
|
|---|
| 1199 |
def getDataTypes(self): |
|---|
| 1200 |
"""Returns the internal _types dict.""" |
|---|
| 1201 |
return self._types |
|---|
| 1202 |
|
|---|
| 1203 |
|
|---|
| 1204 |
def _storeData(self, data, typs): |
|---|
| 1205 |
"""Accepts a dataset and type dict from an external source and |
|---|
| 1206 |
uses it as its own. Also resets the lastRequeryTime value. |
|---|
| 1207 |
""" |
|---|
| 1208 |
# clear mementos and new record flags: |
|---|
| 1209 |
self._mementos = {} |
|---|
| 1210 |
self._newRecords = {} |
|---|
| 1211 |
self.lastRequeryTime = time.time() |
|---|
| 1212 |
# If None is passed as the data, exit after resetting the flags |
|---|
| 1213 |
if data is None: |
|---|
| 1214 |
return |
|---|
| 1215 |
# Store the values |
|---|
| 1216 |
self._records = data |
|---|
| 1217 |
self._types = typs |
|---|
| 1218 |
# Clear the unsorted list, and then apply the current sort |
|---|
| 1219 |
self.__unsortedRows = [] |
|---|
| 1220 |
if self.sortColumn: |
|---|
| 1221 |
try: |
|---|
| 1222 |
self.sort(self.sortColumn, self.sortOrder) |
|---|
| 1223 |
except dException.NoRecordsException: |
|---|
| 1224 |
# No big deal |
|---|
| 1225 |
pass |
|---|
| 1226 |
|
|---|
| 1227 |
|
|---|
| 1228 |
def filter(self, fld, expr, op="="): |
|---|
| 1229 |
"""Apply a filter to the current records.""" |
|---|
| 1230 |
self._records = self._records.filter(fld=fld, expr=expr, op=op) |
|---|
| 1231 |
|
|---|
| 1232 |
|
|---|
| 1233 |
def filterByExpression(self, expr): |
|---|
| 1234 |
"""Allows you to filter by any valid Python expression.""" |
|---|
| 1235 |
self._records = self._records.filterByExpression(expr) |
|---|
| 1236 |
|
|---|
| 1237 |
|
|---|
| 1238 |
def removeFilter(self): |
|---|
| 1239 |
"""Remove the most recently applied filter.""" |
|---|
| 1240 |
self._records = self._records.removeFilter() |
|---|
| 1241 |
|
|---|
| 1242 |
|
|---|
| 1243 |
def removeFilters(self): |
|---|
| 1244 |
"""Remove all applied filters, going back to the original data set.""" |
|---|
| 1245 |
self._records = self._records.removeFilters() |
|---|
| 1246 |
|
|---|
| 1247 |
|
|---|
| 1248 |
def replace(self, field, valOrExpr, scope=None): |
|---|
| 1249 |
"""Replaces the value of the specified field with the given value |
|---|
| 1250 |
or expression. All records matching the scope are affected; if |
|---|
| 1251 |
no scope is specified, all records are affected. |
|---|
| 1252 |
|
|---|
| 1253 |
'valOrExpr' will be treated as a literal value, unless it is prefixed |
|---|
| 1254 |
with an equals sign. All expressions will therefore be a string |
|---|
| 1255 |
beginning with '='. Literals can be of any type. |
|---|
| 1256 |
|
|---|
| 1257 |
NOTE: this does NOT work with the memento framework for |
|---|
| 1258 |
determining modified records. It is strongly recommended that |
|---|
| 1259 |
instead of calling this directly that the bizobj.replace() method |
|---|
| 1260 |
be used in any programming. |
|---|
| 1261 |
""" |
|---|
| 1262 |
# Make sure that the data set object has any necessary references |
|---|
| 1263 |
self._records.Cursor = self |
|---|
| 1264 |
self._records.Bizobj = self._bizobj |
|---|
| 1265 |
self._records.replace(field, valOrExpr, scope=scope) |
|---|
| 1266 |
|
|---|
| 1267 |
|
|---|
| 1268 |
def first(self): |
|---|
| 1269 |
""" Move the record pointer to the first record of the data set.""" |
|---|
| 1270 |
if self.RowCount > 0: |
|---|
| 1271 |
self.RowNumber = 0 |
|---|
| 1272 |
else: |
|---|
| 1273 |
raise dException.NoRecordsException(_("No records in data set")) |
|---|
| 1274 |
|
|---|
| 1275 |
|
|---|
| 1276 |
def prior(self): |
|---|
| 1277 |
""" Move the record pointer back one position in the recordset.""" |
|---|
| 1278 |
if self.RowCount > 0: |
|---|
| 1279 |
if self.RowNumber > 0: |
|---|
| 1280 |
self.RowNumber -= 1 |
|---|
| 1281 |
else: |
|---|
| 1282 |
raise dException.BeginningOfFileException( |
|---|
| 1283 |
_("Already at the beginning of the data set.")) |
|---|
| 1284 |
else: |
|---|
| 1285 |
raise dException.NoRecordsException( |
|---|
| 1286 |
_("No records in data set")) |
|---|
| 1287 |
|
|---|
| 1288 |
|
|---|
| 1289 |
def next(self): |
|---|
| 1290 |
""" Move the record pointer forward one position in the recordset.""" |
|---|
| 1291 |
if self.RowCount > 0: |
|---|
| 1292 |
if self.RowNumber < (self.RowCount-1): |
|---|
| 1293 |
self.RowNumber += 1 |
|---|
| 1294 |
else: |
|---|
| 1295 |
raise dException.EndOfFileException( |
|---|
| 1296 |
_("Already at the end of the data set.")) |
|---|
| 1297 |
else: |
|---|
| 1298 |
raise dException.NoRecordsException(_("No records in data set")) |
|---|
| 1299 |
|
|---|
| 1300 |
|
|---|
| 1301 |
def last(self): |
|---|
| 1302 |
""" Move the record pointer to the last record in the recordset.""" |
|---|
| 1303 |
if self.RowCount > 0: |
|---|
| 1304 |
self.RowNumber = self.RowCount-1 |
|---|
| 1305 |
else: |
|---|
| 1306 |
raise dException.NoRecordsException(_("No records in data set")) |
|---|
| 1307 |
|
|---|
| 1308 |
|
|---|
| 1309 |
def save(self, allRows=False, includeNewUnchanged=False): |
|---|
| 1310 |
""" Save any changes to the current record back to the data store.""" |
|---|
| 1311 |
# Make sure that there is data to save |
|---|
| 1312 |
if self.RowCount <= 0: |
|---|
| 1313 |
raise dException.NoRecordsException(_("No data to save")) |
|---|
| 1314 |
# Make sure that there is a PK |
|---|
| 1315 |
self.checkPK() |
|---|
| 1316 |
|
|---|
| 1317 |
def saverow(row): |
|---|
| 1318 |
try: |
|---|
| 1319 |
self.__saverow(row) |
|---|
| 1320 |
except dException.DBQueryException, e: |
|---|
| 1321 |
# Error was encountered. Raise an exception so that the |
|---|
| 1322 |
# calling bizobj can rollback the transaction if necessary |
|---|
| 1323 |
try: |
|---|
| 1324 |
errMsg = ustr(e).decode(self.Encoding) |
|---|
| 1325 |
except UnicodeError: |
|---|
| 1326 |
errMsg = unicode(e) |
|---|
| 1327 |
dabo.dbActivityLog.write( |
|---|
| 1328 |
_("DBQueryException encountered in save(): %s") % errMsg) |
|---|
| 1329 |
raise e |
|---|
| 1330 |
except StandardError, e: |
|---|
| 1331 |
errMsg = ustr(e) |
|---|
| 1332 |
if "connect" in errMsg.lower(): |
|---|
| 1333 |
dabo.dbActivityLog.write( |
|---|
| 1334 |
_("Connection Lost exception encountered in saverow(): %s") % errMsg) |
|---|
| 1335 |
raise dException.ConnectionLostException(errMsg) |
|---|
| 1336 |
else: |
|---|
| 1337 |
# Error was encountered. Raise an exception so that the |
|---|
| 1338 |
# calling bizobj can rollback the transaction if necessary |
|---|
| 1339 |
raise |
|---|
| 1340 |
|
|---|
| 1341 |
self._syncAuxProperties() |
|---|
| 1342 |
|
|---|
| 1343 |
if allRows: |
|---|
| 1344 |
# This branch doesn't happen when called from dBizobj (not sure if |
|---|
| 1345 |
# we really need the allRows arg at all). |
|---|
| 1346 |
rows = self.getChangedRows(includeNewUnchanged=includeNewUnchanged) |
|---|
| 1347 |
else: |
|---|
| 1348 |
# This branch results in redundant isChanged() call when called from |
|---|
| 1349 |
# dBizobj.saveAll(), but it needs to be here because dBizobj.save() |
|---|
| 1350 |
# doesn't check it. |
|---|
| 1351 |
rows = [] |
|---|
| 1352 |
if self.isChanged(allRows=False, includeNewUnchanged=includeNewUnchanged): |
|---|
| 1353 |
rows = [self.RowNumber] |
|---|
| 1354 |
for row in rows: |
|---|
| 1355 |
saverow(row) |
|---|
| 1356 |
|
|---|
| 1357 |
|
|---|
| 1358 |
def __saverow(self, row): |
|---|
| 1359 |
rec = self._records[row] |
|---|
| 1360 |
recKey = self.pkExpression(rec) |
|---|
| 1361 |
newrec = kons.CURSOR_TMPKEY_FIELD in rec |
|---|
| 1362 |
|
|---|
| 1363 |
newPKVal = None |
|---|
| 1364 |
if newrec and self.AutoPopulatePK: |
|---|
| 1365 |
# Some backends do not provide a means to retrieve |
|---|
| 1366 |
# auto-generated PKs; for those, we need to create the |
|---|
| 1367 |
# PK before inserting the record so that we can pass it on |
|---|
| 1368 |
# to any linked child records. NOTE: if you are using |
|---|
| 1369 |
# compound PKs, this cannot be done. |
|---|
| 1370 |
newPKVal = self.pregenPK() |
|---|
| 1371 |
if newPKVal and not self._compoundKey: |
|---|
| 1372 |
self.setFieldVal(self.KeyField, newPKVal, row) |
|---|
| 1373 |
|
|---|
| 1374 |
if newrec: |
|---|
| 1375 |
diff = self._getNewRecordDiff(row) |
|---|
| 1376 |
else: |
|---|
| 1377 |
diff = self.getRecordStatus(row) |
|---|
| 1378 |
aq = self.AutoQuoteNames |
|---|
| 1379 |
if diff: |
|---|
| 1380 |
if newrec: |
|---|
| 1381 |
flds = "" |
|---|
| 1382 |
vals = [] |
|---|
| 1383 |
kf = self.KeyField |
|---|
| 1384 |
for kk, vv in diff.items(): |
|---|
| 1385 |
if self.AutoPopulatePK: |
|---|
| 1386 |
if self._compoundKey: |
|---|
| 1387 |
skipIt = (kk in kf) |
|---|
| 1388 |
else: |
|---|
| 1389 |
# Skip the key field, unless we pre-generated its value above. |
|---|
| 1390 |
skipIt = (kk == self.KeyField) and not newPKVal |
|---|
| 1391 |
if skipIt: |
|---|
| 1392 |
# we don't want to include the PK in the insert |
|---|
| 1393 |
continue |
|---|
| 1394 |
if kk in self.getNonUpdateFields(): |
|---|
| 1395 |
# Skip it. |
|---|
| 1396 |
continue |
|---|
| 1397 |
if self._nullDefaults and vv == (None, None): |
|---|
| 1398 |
# Skip these, too |
|---|
| 1399 |
continue |
|---|
| 1400 |
# Append the field and its value. |
|---|
| 1401 |
flds += ", " + self.BackendObject.encloseNames(kk, aq) |
|---|
| 1402 |
# add value to expression |
|---|
| 1403 |
fieldType = [ds[1] for ds in self.DataStructure if ds[0] == kk][0] |
|---|
| 1404 |
vals.append(vv[1]) |
|---|
| 1405 |
|
|---|
| 1406 |
# Trim leading comma-space from the 'flds' string |
|---|
| 1407 |
flds = flds[2:] |
|---|
| 1408 |
if not flds: |
|---|
| 1409 |
# Some backends (sqlite) require non-empty field clauses. We already |
|---|
| 1410 |
# know that we are expecting the backend to generate the PK, so send |
|---|
| 1411 |
# NULL as the PK Value: |
|---|
| 1412 |
flds = self.KeyField |
|---|
| 1413 |
vals = "NULL" |
|---|
| 1414 |
nms = self.BackendObject.encloseNames(self.Table, aq) |
|---|
| 1415 |
placeHolders = len(vals) * [self.ParamPlaceholder] |
|---|
| 1416 |
sql = "insert into %s (%s) values (%s) " % (nms, flds, ",".join(placeHolders)) |
|---|
| 1417 |
params = tuple(vals) |
|---|
| 1418 |
else: |
|---|
| 1419 |
pkWhere = self.makePkWhere(row) |
|---|
| 1420 |
updClause, params = self.makeUpdClause(diff) |
|---|
| 1421 |
sql = "update %s set %s where %s" % (self.BackendObject.encloseNames(self.Table, aq), |
|---|
| 1422 |
updClause, pkWhere) |
|---|
| 1423 |
#run the update |
|---|
| 1424 |
aux = self.AuxCursor |
|---|
| 1425 |
res = aux.execute(sql, params) |
|---|
| 1426 |
|
|---|
| 1427 |
if newrec and self.AutoPopulatePK and (newPKVal is None): |
|---|
| 1428 |
# Call the database backend-specific code to retrieve the |
|---|
| 1429 |
# most recently generated PK value. |
|---|
| 1430 |
newPKVal = aux.getLastInsertID() |
|---|
| 1431 |
if newPKVal and not self._compoundKey: |
|---|
| 1432 |
self.setFieldVal(self.KeyField, newPKVal, row) |
|---|
| 1433 |
|
|---|
| 1434 |
if newrec and self._nullDefaults: |
|---|
| 1435 |
# We need to retrieve any new default values |
|---|
| 1436 |
aux = self.AuxCursor |
|---|
| 1437 |
if not isinstance(self.KeyField, tuple): |
|---|
| 1438 |
keyFIelds = [self.KeyField] |
|---|
| 1439 |
else: |
|---|
| 1440 |
keyFIelds = self.KeyField |
|---|
| 1441 |
wheres = [] |
|---|
| 1442 |
for kf in keyFIelds: |
|---|
| 1443 |
fld = self.BackendObject.encloseNames(kf, self.AutoQuoteNames) |
|---|
| 1444 |
val = self.getFieldVal(kf) |
|---|
| 1445 |
if isinstance(val, basestring): |
|---|
| 1446 |
val = "'" + val.encode(self.Encoding) + "' " |
|---|
| 1447 |
elif isinstance(val, (datetime.date, datetime.datetime)): |
|---|
| 1448 |
val = self.formatDateTime(val) |
|---|
| 1449 |
else: |
|---|
| 1450 |
val = ustr(val) |
|---|
| 1451 |
wheres.append("%s = %s" % (fld, val)) |
|---|
| 1452 |
where = " and ".join(wheres) |
|---|
| 1453 |
aux.execute("select * from %s where %s" % (self.Table, where)) |
|---|
| 1454 |
try: |
|---|
| 1455 |
data = aux.getDataSet()[0] |
|---|
| 1456 |
for fld, val in data.items(): |
|---|
| 1457 |
try: |
|---|
| 1458 |
self.setFieldVal(fld, val) |
|---|
| 1459 |
except dException.FieldNotFoundException: |
|---|
| 1460 |
# Field is not in the dataset |
|---|
| 1461 |
pass |
|---|
| 1462 |
except IndexError: |
|---|
| 1463 |
# For some reason we could not retrieve the matching PK record |
|---|
| 1464 |
pass |
|---|
| 1465 |
|
|---|
| 1466 |
self._clearMemento(row) |
|---|
| 1467 |
if newrec: |
|---|
| 1468 |
self._clearNewRecord(row=row, pkVal=recKey) |
|---|
| 1469 |
else: |
|---|
| 1470 |
if not res: |
|---|
| 1471 |
# Different backends may cause res to be None |
|---|
| 1472 |
# even if the save is successful. |
|---|
| 1473 |
self.BackendObject.noResultsOnSave() |
|---|
| 1474 |
|
|---|
| 1475 |
|
|---|
| 1476 |
def _clearMemento(self, row=None): |
|---|
| 1477 |
"""Erase the memento for the passed row, or current row if none passed.""" |
|---|
| 1478 |
if row is None: |
|---|
| 1479 |
row = self.RowNumber |
|---|
| 1480 |
rec = self._records[row] |
|---|
| 1481 |
|
|---|
| 1482 |
try: |
|---|
| 1483 |
pk = self.getPK(row) |
|---|
| 1484 |
del self._mementos[pk] |
|---|
| 1485 |
except KeyError: |
|---|
| 1486 |
# didn't exist |
|---|
| 1487 |
pass |
|---|
| 1488 |
|
|---|
| 1489 |
|
|---|
| 1490 |
def _clearNewRecord(self, row=None, pkVal=None): |
|---|
| 1491 |
"""Erase the new record flag for the passed row, or current row if none passed.""" |
|---|
| 1492 |
# If pkVal passed, delete that reference: |
|---|
| 1493 |
if pkVal is not None: |
|---|
| 1494 |
try: |
|---|
| 1495 |
del self._newRecords[pkVal] |
|---|
| 1496 |
if row is None: |
|---|
| 1497 |
# We deleted based on pk, don't delete flag for the current row. |
|---|
| 1498 |
return |
|---|
| 1499 |
except KeyError: |
|---|
| 1500 |
pass |
|---|
| 1501 |
|
|---|
| 1502 |
if row is None: |
|---|
| 1503 |
row = self.RowNumber |
|---|
| 1504 |
rec = self._records[row] |
|---|
| 1505 |
|
|---|
| 1506 |
try: |
|---|
| 1507 |
pk = self.getPK(row) |
|---|
| 1508 |
del self._newRecords[pk] |
|---|
| 1509 |
except KeyError: |
|---|
| 1510 |
# didn't exist |
|---|
| 1511 |
pass |
|---|
| 1512 |
# Remove the temp key field column, if still present. |
|---|
| 1513 |
rec.pop(kons.CURSOR_TMPKEY_FIELD, None) |
|---|
| 1514 |
|
|---|
| 1515 |
|
|---|
| 1516 |
|
|---|
| 1517 |
def getDataDiff(self, allRows=False): |
|---|
| 1518 |
"""Create a compact representation of all the modified records |
|---|
| 1519 |
for this cursor. |
|---|
| 1520 |
""" |
|---|
| 1521 |
diff = [] |
|---|
| 1522 |
def rowDiff(pk): |
|---|
| 1523 |
newrec = pk in self._newRecords |
|---|
| 1524 |
if newrec: |
|---|
| 1525 |
ret = self._getNewRecordDiff(pk=pk) |
|---|
| 1526 |
else: |
|---|
| 1527 |
ret = self.getRecordStatus(pk=pk) |
|---|
| 1528 |
ret[self._keyField] = pk |
|---|
| 1529 |
ret[kons.CURSOR_TMPKEY_FIELD] = newrec |
|---|
| 1530 |
return ret |
|---|
| 1531 |
|
|---|
| 1532 |
if allRows: |
|---|
| 1533 |
for pk in self._mementos.keys(): |
|---|
| 1534 |
diff.append(rowDiff(pk)) |
|---|
| 1535 |
else: |
|---|
| 1536 |
pk = self.getPK() |
|---|
| 1537 |
if pk in self._mementos: |
|---|
| 1538 |
diff.append(rowDiff(pk)) |
|---|
| 1539 |
return diff |
|---|
| 1540 |
|
|---|
| 1541 |
|
|---|
| 1542 |
def pregenPK(self): |
|---|
| 1543 |
"""Various backend databases require that you manually |
|---|
| 1544 |
generate new PKs if you need to refer to their values afterward. |
|---|
| 1545 |
This method will call the backend to generate and |
|---|
| 1546 |
retrieve a new PK if the backend supports this. We use the |
|---|
| 1547 |
auxiliary cursor so as not to alter the current data. |
|---|
| 1548 |
""" |
|---|
| 1549 |
return self.BackendObject.pregenPK(self.AuxCursor) |
|---|
| 1550 |
|
|---|
| 1551 |
|
|---|
| 1552 |
def _getBlankRecord(self): |
|---|
| 1553 |
"""Returns a record template, with each field set to the 'blank' value |
|---|
| 1554 |
for that data type. |
|---|
| 1555 |
""" |
|---|
| 1556 |
if not self._blank: |
|---|
| 1557 |
self.__setStructure() |
|---|
| 1558 |
return self._blank.copy() |
|---|
| 1559 |
|
|---|
| 1560 |
|
|---|
| 1561 |
def new(self): |
|---|
| 1562 |
"""Add a new record to the data set.""" |
|---|
| 1563 |
blank = self._getBlankRecord() |
|---|
| 1564 |
self._records = dDataSet(self._records + (blank,)) |
|---|
| 1565 |
# Adjust the RowCount and position |
|---|
| 1566 |
self.RowNumber = self.RowCount - 1 |
|---|
| 1567 |
|
|---|
| 1568 |
|
|---|
| 1569 |
def cancel(self, allRows=False, ignoreNoRecords=None): |
|---|
| 1570 |
""" Revert any changes to the data set back to the original values.""" |
|---|
| 1571 |
if ignoreNoRecords is None: |
|---|
| 1572 |
ignoreNoRecords = True |
|---|
| 1573 |
if self.RowCount == 0: |
|---|
| 1574 |
if ignoreNoRecords: |
|---|
| 1575 |
# Nothing to do! |
|---|
| 1576 |
return |
|---|
| 1577 |
else: |
|---|
| 1578 |
raise dException.NoRecordsException(_("No data to cancel.")) |
|---|
| 1579 |
|
|---|
| 1580 |
# Faster to deal with 2 specific cases: all rows or just current row |
|---|
| 1581 |
if allRows: |
|---|
| 1582 |
try: |
|---|
| 1583 |
recs = self._records.UnfilteredDataSet |
|---|
| 1584 |
except AttributeError: |
|---|
| 1585 |
# Not a dDataSet |
|---|
| 1586 |
recs = self._records |
|---|
| 1587 |
|
|---|
| 1588 |
if self._newRecords: |
|---|
| 1589 |
recs = list(recs) |
|---|
| 1590 |
delrecs_ids = self._newRecords.keys() |
|---|
| 1591 |
delrecs_idx = [] |
|---|
| 1592 |
for rec_id in delrecs_ids: |
|---|
| 1593 |
# Remove any memento associated with the canceled new record, and |
|---|
| 1594 |
# append to the list of indexes to delete. |
|---|
| 1595 |
row, rec = self._getRecordByPk(rec_id) |
|---|
| 1596 |
self._clearMemento(row) |
|---|
| 1597 |
delrecs_idx.append(self._records._index(rec)) |
|---|
| 1598 |
delrecs_idx.sort(reverse=True) |
|---|
| 1599 |
for idx in delrecs_idx: |
|---|
| 1600 |
del recs[idx] |
|---|
| 1601 |
self._newRecords = {} |
|---|
| 1602 |
self._records = dDataSet(recs) |
|---|
| 1603 |
if self.RowNumber >= self.RowCount: |
|---|
| 1604 |
self.RowNumber = self.RowCount - 1 |
|---|
| 1605 |
|
|---|
| 1606 |
for rec_pk, mem in self._mementos.items(): |
|---|
| 1607 |
row, rec = self._getRecordByPk(rec_pk) |
|---|
| 1608 |
for fld, val in mem.items(): |
|---|
| 1609 |
self._records[row][fld] = val |
|---|
| 1610 |
self._mementos = {} |
|---|
| 1611 |
|
|---|
| 1612 |
else: |
|---|
| 1613 |
row = self.RowNumber |
|---|
| 1614 |
rec = self._records[row] |
|---|
| 1615 |
recKey = self.pkExpression(rec) |
|---|
| 1616 |
if recKey in self._newRecords: |
|---|
| 1617 |
# We simply need to remove the row, and clear the memento and newrec flag. |
|---|
| 1618 |
self._clearMemento(row) |
|---|
| 1619 |
self._clearNewRecord(row) |
|---|
| 1620 |
recs = list(self._records) |
|---|
| 1621 |
del recs[recs.index(rec)] |
|---|
| 1622 |
self._records = dDataSet(recs) |
|---|
| 1623 |
if self.RowNumber >= self.RowCount: |
|---|
| 1624 |
self.RowNumber = self.RowCount - 1 |
|---|
| 1625 |
return |
|---|
| 1626 |
|
|---|
| 1627 |
# Not a new record: need to manually replace the old values: |
|---|
| 1628 |
for fld, val in self._mementos.get(recKey, {}).items(): |
|---|
| 1629 |
self._records[row][fld] = val |
|---|
| 1630 |
self._clearMemento(row) |
|---|
| 1631 |
|
|---|
| 1632 |
|
|---|
| 1633 |
def delete(self, delRowNum=None): |
|---|
| 1634 |
"""Delete the specified row, or the currently active row.""" |
|---|
| 1635 |
if self.RowNumber < 0 or self.RowCount == 0: |
|---|
| 1636 |
# No query has been run yet |
|---|
| 1637 |
raise dException.NoRecordsException(_("No record to delete")) |
|---|
| 1638 |
if delRowNum is None: |
|---|
| 1639 |
# assume that it is the current row that is to be deleted |
|---|
| 1640 |
delRowNum = self.RowNumber |
|---|
| 1641 |
|
|---|
| 1642 |
rec = self._records[delRowNum] |
|---|
| 1643 |
pk = self.pkExpression(rec) |
|---|
| 1644 |
if pk in self._newRecords: |
|---|
| 1645 |
res = True |
|---|
| 1646 |
del self._newRecords[pk] |
|---|
| 1647 |
else: |
|---|
| 1648 |
pkWhere = self.makePkWhere() |
|---|
| 1649 |
# some backends(PostgreSQL) don't return information about number of deleted rows |
|---|
| 1650 |
# try to fetch it before |
|---|
| 1651 |
sql = "select count(*) as cnt from %s where %s" % (self.Table, pkWhere) |
|---|
| 1652 |
aux = self.AuxCursor |
|---|
| 1653 |
aux.execute(sql) |
|---|
| 1654 |
res = aux.getFieldVal('cnt') |
|---|
| 1655 |
if res: |
|---|
| 1656 |
sql = "delete from %s where %s" % (self.Table, pkWhere) |
|---|
| 1657 |
aux.execute(sql) |
|---|
| 1658 |
|
|---|
| 1659 |
if not res: |
|---|
| 1660 |
# Nothing was deleted |
|---|
| 1661 |
self.BackendObject.noResultsOnDelete() |
|---|
| 1662 |
else: |
|---|
| 1663 |
# Delete the record from the current dataset |
|---|
| 1664 |
if pk in self._mementos: |
|---|
| 1665 |
del self._mementos[pk] |
|---|
| 1666 |
self._removeRow(delRowNum) |
|---|
| 1667 |
|
|---|
| 1668 |
|
|---|
| 1669 |
def _removeRow(self, row): |
|---|
| 1670 |
## Since record sets are tuples and thus immutable, we need to do this |
|---|
| 1671 |
## little dance to remove a row. |
|---|
| 1672 |
lRec = list(self._records) |
|---|
| 1673 |
del lRec[row] |
|---|
| 1674 |
self._records = dDataSet(lRec) |
|---|
| 1675 |
self.RowNumber = min(self.RowNumber, self.RowCount-1) |
|---|
| 1676 |
|
|---|
| 1677 |
|
|---|
| 1678 |
def flush(self): |
|---|
| 1679 |
""" Some backends need to be prompted to flush changes |
|---|
| 1680 |
to disk even without starting a transaction. This is the method |
|---|
| 1681 |
to call to accomplish this. |
|---|
| 1682 |
""" |
|---|
| 1683 |
self.BackendObject.flush(self) |
|---|
| 1684 |
|
|---|
| 1685 |
|
|---|
| 1686 |
def setDefaults(self, vals): |
|---|
| 1687 |
"""Set the default field values for newly added records. The |
|---|
| 1688 |
'vals' parameter is a dictionary of fields and their default values. |
|---|
| 1689 |
If vals is None, the defaults for all but the KeyField will be set to |
|---|
| 1690 |
None, and their values will not be included in the insert statement |
|---|
| 1691 |
when saved unless the user changes them to some non-null |
|---|
| 1692 |
value. |
|---|
| 1693 |
""" |
|---|
| 1694 |
rec = self._records[self.RowNumber] |
|---|
| 1695 |
keyField = self.KeyField |
|---|
| 1696 |
keyFieldSet = False |
|---|
| 1697 |
self._nullDefaults = (vals is None) |
|---|
| 1698 |
|
|---|
| 1699 |
def setDefault(field, val): |
|---|
| 1700 |
if field in rec: |
|---|
| 1701 |
# If it is a function, execute it to get the value, else use literal. |
|---|
| 1702 |
if callable(val): |
|---|
| 1703 |
val = val() |
|---|
| 1704 |
elif isinstance(val, tuple) and val and callable(val[0]): |
|---|
| 1705 |
# This is a tuple consisting of a function and zero to many parameters |
|---|
| 1706 |
fnc = val[0] |
|---|
| 1707 |
prms = val[1:] |
|---|
| 1708 |
val = fnc(*prms) |
|---|
| 1709 |
self.setFieldVal(field, val) |
|---|
| 1710 |
else: |
|---|
| 1711 |
raise dException.FieldNotFoundException( |
|---|
| 1712 |
_("Can't set default value for nonexistent field '%s'.") % field) |
|---|
| 1713 |
|
|---|
| 1714 |
if self._nullDefaults: |
|---|
| 1715 |
for field in rec.keys(): |
|---|
| 1716 |
if field == keyField: |
|---|
| 1717 |
continue |
|---|
| 1718 |
self.setFieldVal(field, None) |
|---|
| 1719 |
else: |
|---|
| 1720 |
if keyField in vals.keys(): |
|---|
| 1721 |
# Must set the pk default value first, for mementos to be filled in |
|---|
| 1722 |
# correctly. |
|---|
| 1723 |
setDefault(keyField, vals[keyField]) |
|---|
| 1724 |
keyFieldSet = True |
|---|
| 1725 |
|
|---|
| 1726 |
for field, val in vals.items(): |
|---|
| 1727 |
if field == keyField and keyFieldSet: |
|---|
| 1728 |
continue |
|---|
| 1729 |
setDefault(field, val) |
|---|
| 1730 |
|
|---|
| 1731 |
|
|---|
| 1732 |
def __setStructure(self): |
|---|
| 1733 |
"""Set the structure of a newly-added record.""" |
|---|
| 1734 |
for field in self.DataStructure: |
|---|
| 1735 |
field_alias = field[0] |
|---|
| 1736 |
field_type = field[1] |
|---|
| 1737 |
field_ispk = field[2] |
|---|
| 1738 |
table_name = field[3] |
|---|
| 1739 |
field_name = field[4] |
|---|
| 1740 |
field_scale = field[5] |
|---|
| 1741 |
|
|---|
| 1742 |
typ = dabo.db.getPythonType(field_type) |
|---|
| 1743 |
# Handle the non-standard cases |
|---|
| 1744 |
if typ is Decimal: |
|---|
| 1745 |
newval = Decimal() |
|---|
| 1746 |
# If the backend reports a decimal scale, use it. Scale refers to the |
|---|
| 1747 |
# number of decimal places. |
|---|
| 1748 |
scale = field_scale |
|---|
| 1749 |
if scale is None: |
|---|
| 1750 |
scale = 2 |
|---|
| 1751 |
ex = "0.%s" % ("0"*scale) |
|---|
| 1752 |
newval = newval.quantize(Decimal(ex)) |
|---|
| 1753 |
elif typ is datetime.datetime: |
|---|
| 1754 |
newval = datetime.datetime.min |
|---|
| 1755 |
elif typ is datetime.date: |
|---|
| 1756 |
newval = datetime.date.min |
|---|
| 1757 |
elif typ is None: |
|---|
| 1758 |
newval = None |
|---|
| 1759 |
else: |
|---|
| 1760 |
try: |
|---|
| 1761 |
newval = typ() |
|---|
| 1762 |
except Exception, e: |
|---|
| 1763 |
dabo.errorLog.write(_("Failed to create newval for field '%s'") % field_alias) |
|---|
| 1764 |
dabo.errorLog.write(ustr(e)) |
|---|
| 1765 |
newval = u"" |
|---|
| 1766 |
|
|---|
| 1767 |
self._blank[field_alias] = newval |
|---|
| 1768 |
|
|---|
| 1769 |
# Mark the calculated and derived fields. |
|---|
| 1770 |
self.__setNonUpdateFields() |
|---|
| 1771 |
|
|---|
| 1772 |
|
|---|
| 1773 |
def getChangedRows(self, includeNewUnchanged=False): |
|---|
| 1774 |
"""Returns a list of rows with changes.""" |
|---|
| 1775 |
chKeys = self._mementos.keys() |
|---|
| 1776 |
if includeNewUnchanged: |
|---|
| 1777 |
# We need to also count all new records |
|---|
| 1778 |
chKeys = dict.fromkeys(chKeys + self._newRecords.keys()).keys() |
|---|
| 1779 |
return map(self._getRowByPk, chKeys) |
|---|
| 1780 |
|
|---|
| 1781 |
|
|---|
| 1782 |
def _getRecordByPk(self, pk, raiseRowNotFound=True): |
|---|
| 1783 |
"""Find the record with the passed primary key; return (row, record).""" |
|---|
| 1784 |
kf = self.KeyField |
|---|
| 1785 |
if kf: |
|---|
| 1786 |
for idx, rec in enumerate(self._records): |
|---|
| 1787 |
key = self.getFieldVal(kf, row=idx) |
|---|
| 1788 |
if key == pk: |
|---|
| 1789 |
return (idx, rec) |
|---|
| 1790 |
if raiseRowNotFound: |
|---|
| 1791 |
tbl, rc = self.Table, self.RowCount |
|---|
| 1792 |
raise dException.RowNotFoundException(_("PK '%(pk)s' not found in table '%(tbl)s' (RowCount: %(rc)s)") % locals()) |
|---|
| 1793 |
return (None, None) |
|---|
| 1794 |
|
|---|
| 1795 |
|
|---|
| 1796 |
def _getRowByPk(self, pk): |
|---|
| 1797 |
"""Find the record with the passed primary key value; return row number.""" |
|---|
| 1798 |
row, rec = self._getRecordByPk(pk) |
|---|
| 1799 |
return row |
|---|
| 1800 |
|
|---|
| 1801 |
|
|---|
| 1802 |
def hasPK(self, pk): |
|---|
| 1803 |
"""Return True if the passed pk is present in the dataset.""" |
|---|
| 1804 |
kf = self.KeyField |
|---|
| 1805 |
return bool([v[kf] for v in self._records if v[kf] == pk]) |
|---|
| 1806 |
|
|---|
| 1807 |
|
|---|
| 1808 |
def moveToPK(self, pk): |
|---|
| 1809 |
""" Find the record with the passed primary key, and make it active. |
|---|
| 1810 |
|
|---|
| 1811 |
If the record is not found, the position is set to the first record. |
|---|
| 1812 |
""" |
|---|
| 1813 |
row, rec = self._getRecordByPk(pk, raiseRowNotFound=False) |
|---|
| 1814 |
if row is None: |
|---|
| 1815 |
row = 0 |
|---|
| 1816 |
self.RowNumber = row |
|---|
| 1817 |
|
|---|
| 1818 |
|
|---|
| 1819 |
def moveToRowNum(self, rownum): |
|---|
| 1820 |
""" Move the record pointer to the specified row number. |
|---|
| 1821 |
|
|---|
| 1822 |
If the specified row does not exist, the pointer remains where it is, |
|---|
| 1823 |
and an exception is raised. |
|---|
| 1824 |
""" |
|---|
| 1825 |
if (rownum >= self.RowCount) or (rownum < 0): |
|---|
| 1826 |
rc = self.RowCount |
|---|
| 1827 |
raise dException.dException(_("Invalid row specified: %(rownum)s. RowCount=%(rc)s") % locals()) |
|---|
| 1828 |
self.RowNumber = rownum |
|---|
| 1829 |
|
|---|
| 1830 |
|
|---|
| 1831 |
def seek(self, val, fld=None, caseSensitive=True, near=False, movePointer=True): |
|---|
| 1832 |
""" Find the first row where the field value matches the passed value. |
|---|
| 1833 |
|
|---|
| 1834 |
Returns the row number of the first record that matches the passed |
|---|
| 1835 |
value in the designated field, or -1 if there is no match. If 'near' is |
|---|
| 1836 |
True, a match will happen on the row whose value is the greatest value |
|---|
| 1837 |
that is less than the passed value. If 'caseSensitive' is set to False, |
|---|
| 1838 |
string comparisons are done in a case-insensitive fashion. |
|---|
| 1839 |
""" |
|---|
| 1840 |
ret = -1 |
|---|
| 1841 |
if fld is None: |
|---|
| 1842 |
# Default to the current sort order field |
|---|
| 1843 |
fld = self.sortColumn |
|---|
| 1844 |
if self.RowCount <= 0: |
|---|
| 1845 |
# Nothing to seek within |
|---|
| 1846 |
return ret |
|---|
| 1847 |
# Make sure that this is a valid field |
|---|
| 1848 |
if not fld: |
|---|
| 1849 |
raise dException.FieldNotFoundException(_("No field specified for seek()")) |
|---|
| 1850 |
|
|---|
| 1851 |
simpleKey = ("," not in fld) |
|---|
| 1852 |
if simpleKey: |
|---|
| 1853 |
flds = [fld] |
|---|
| 1854 |
else: |
|---|
| 1855 |
flds = [f.strip() for f in fld.split(",")] |
|---|
| 1856 |
badflds = [] |
|---|
| 1857 |
for fldname in flds: |
|---|
| 1858 |
if (fldname not in self._records[0]) and (fldname not in self.VirtualFields): |
|---|
| 1859 |
badflds.append(fldname) |
|---|
| 1860 |
if badflds: |
|---|
| 1861 |
raise dException.FieldNotFoundException(_("Non-existent field(s) '%s'") % ", ".join(badflds)) |
|---|
| 1862 |
|
|---|
| 1863 |
# Copy the specified field vals and their row numbers to a list, and |
|---|
| 1864 |
# add those lists to the sort list |
|---|
| 1865 |
sortList = [] |
|---|
| 1866 |
for row in xrange(0, self.RowCount): |
|---|
| 1867 |
if simpleKey: |
|---|
| 1868 |
rowval = self.getFieldVal(fld, row=row) |
|---|
| 1869 |
else: |
|---|
| 1870 |
rowval = tuple([self.getFieldVal(f, row=row) for f in flds]) |
|---|
| 1871 |
sortList.append([rowval, row]) |
|---|
| 1872 |
|
|---|
| 1873 |
if simpleKey: |
|---|
| 1874 |
# Determine if we are seeking string values |
|---|
| 1875 |
field_type = self._types.get(fld, type(sortList[0][0])) |
|---|
| 1876 |
compString = issubclass(field_type, basestring) |
|---|
| 1877 |
else: |
|---|
| 1878 |
compString = False |
|---|
| 1879 |
|
|---|
| 1880 |
if simpleKey and not compString: |
|---|
| 1881 |
# coerce val to be the same type as the field type |
|---|
| 1882 |
if issubclass(field_type, int): |
|---|
| 1883 |
try: |
|---|
| 1884 |
val = int(val) |
|---|
| 1885 |
except ValueError: |
|---|
| 1886 |
val = int(0) |
|---|
| 1887 |
|
|---|
| 1888 |
elif issubclass(field_type, long): |
|---|
| 1889 |
try: |
|---|
| 1890 |
val = long(val) |
|---|
| 1891 |
except ValueError: |
|---|
| 1892 |
val = long(0) |
|---|
| 1893 |
|
|---|
| 1894 |
elif issubclass(field_type, float): |
|---|
| 1895 |
try: |
|---|
| 1896 |
val = float(val) |
|---|
| 1897 |
except ValueError: |
|---|
| 1898 |
val = float(0) |
|---|
| 1899 |
|
|---|
| 1900 |
if compString and not caseSensitive: |
|---|
| 1901 |
sortList.sort(key=caseInsensitiveSortKey) |
|---|
| 1902 |
else: |
|---|
| 1903 |
sortList.sort() |
|---|
| 1904 |
|
|---|
| 1905 |
if compString and not caseSensitive: |
|---|
| 1906 |
# Change all of the first elements to lower case |
|---|
| 1907 |
def safeLower(val): |
|---|
| 1908 |
try: |
|---|
| 1909 |
return val.lower() |
|---|
| 1910 |
except AttributeError: |
|---|
| 1911 |
return val |
|---|
| 1912 |
searchList = [safeLower(first) for first, second in sortList] |
|---|
| 1913 |
try: |
|---|
| 1914 |
matchVal = val.lower() |
|---|
| 1915 |
except AttributeError: |
|---|
| 1916 |
# this is a string colum, but seeking a null value. |
|---|
| 1917 |
matchVal = val |
|---|
| 1918 |
else: |
|---|
| 1919 |
matchVal = val |
|---|
| 1920 |
searchList = [first for first, second in sortList] |
|---|
| 1921 |
|
|---|
| 1922 |
# See if we have an exact match before we look for 'near' values |
|---|
| 1923 |
try: |
|---|
| 1924 |
idx = searchList.index(matchVal) |
|---|
| 1925 |
ret = sortList[idx][1] |
|---|
| 1926 |
except ValueError: |
|---|
| 1927 |
if near: |
|---|
| 1928 |
# Find the first row greater than the match value |
|---|
| 1929 |
numSmaller = len([testVal for testVal in searchList |
|---|
| 1930 |
if testVal < matchVal]) |
|---|
| 1931 |
try: |
|---|
| 1932 |
ret = sortList[numSmaller][1] |
|---|
| 1933 |
except IndexError: |
|---|
| 1934 |
ret = 0 |
|---|
| 1935 |
if movePointer and ret > -1: |
|---|
| 1936 |
# Move the record pointer |
|---|
| 1937 |
self.RowNumber = ret |
|---|
| 1938 |
return ret |
|---|
| 1939 |
|
|---|
| 1940 |
|
|---|
| 1941 |
def checkPK(self): |
|---|
| 1942 |
""" Verify that the field(s) specified in the KeyField prop exist.""" |
|---|
| 1943 |
# First, make sure that there is *something* in the field |
|---|
| 1944 |
kf = self.KeyField |
|---|
| 1945 |
if not kf: |
|---|
| 1946 |
raise dException.MissingPKException( |
|---|
| 1947 |
_("checkPK failed; no primary key specified")) |
|---|
| 1948 |
|
|---|
| 1949 |
if isinstance(kf, basestring): |
|---|
| 1950 |
kf = [kf] |
|---|
| 1951 |
# Make sure that there is a field with that name in the data set |
|---|
| 1952 |
try: |
|---|
| 1953 |
for fld in kf: |
|---|
| 1954 |
self._records[0][fld] |
|---|
| 1955 |
except KeyError: |
|---|
| 1956 |
raise dException.MissingPKException( |
|---|
| 1957 |
_("Primary key field does not exist in the data set: ") + fld) |
|---|
| 1958 |
|
|---|
| 1959 |
|
|---|
| 1960 |
def makePkWhere(self, row=None): |
|---|
| 1961 |
""" Create the WHERE clause used for updates, based on the pk field. |
|---|
| 1962 |
|
|---|
| 1963 |
Optionally pass in a row number, otherwise use the current record. |
|---|
| 1964 |
""" |
|---|
| 1965 |
if not self.KeyField: |
|---|
| 1966 |
# Cannot update without a KeyField |
|---|
| 1967 |
return "1 = 0" |
|---|
| 1968 |
bo = self.BackendObject |
|---|
| 1969 |
tblPrefix = bo.getWhereTablePrefix(self.Table, |
|---|
| 1970 |
autoQuote=self.AutoQuoteNames) |
|---|
| 1971 |
if not row: |
|---|
| 1972 |
row = self.RowNumber |
|---|
| 1973 |
rec = self._records[row] |
|---|
| 1974 |
|
|---|
| 1975 |
if self._compoundKey: |
|---|
| 1976 |
keyFields = [fld for fld in self.KeyField] |
|---|
| 1977 |
else: |
|---|
| 1978 |
keyFields = [self.KeyField] |
|---|
| 1979 |
recKey = self.pkExpression(rec) |
|---|
| 1980 |
mem = self._mementos.get(recKey, {}) |
|---|
| 1981 |
|
|---|
| 1982 |
def getPkVal(fld): |
|---|
| 1983 |
try: |
|---|
| 1984 |
return mem[fld] |
|---|
| 1985 |
except KeyError: |
|---|
| 1986 |
return rec[fld] |
|---|
| 1987 |
|
|---|
| 1988 |
ret = [] |
|---|
| 1989 |
for fld in keyFields: |
|---|
| 1990 |
fldSafe = bo.encloseNames(fld, self.AutoQuoteNames) |
|---|
| 1991 |
if ret: |
|---|
| 1992 |
ret.append(" AND ") |
|---|
| 1993 |
pkVal = getPkVal(fld) |
|---|
| 1994 |
if isinstance(pkVal, basestring): |
|---|
| 1995 |
ret.extend([tblPrefix, fldSafe, "='", pkVal.encode(self.Encoding), "' "]) |
|---|
| 1996 |
elif isinstance(pkVal, (datetime.date, datetime.datetime)): |
|---|
| 1997 |
ret.extend([tblPrefix, fldSafe, "=", self.formatDateTime(pkVal), " "]) |
|---|
| 1998 |
else: |
|---|
| 1999 |
ret.extend([tblPrefix, fldSafe, "=", ustr(pkVal), " "]) |
|---|
| 2000 |
return "".join(ret) |
|---|
| 2001 |
|
|---|
| 2002 |
|
|---|
| 2003 |
def makeUpdClause(self, diff): |
|---|
| 2004 |
"""Create the 'set field=val' section of the Update statement. Return a 2-tuple |
|---|
| 2005 |
containing the sql portion as the first element, and the parameters for the |
|---|
| 2006 |
values as the second. |
|---|
| 2007 |
""" |
|---|
| 2008 |
retSql = [] |
|---|
| 2009 |
retParams = [] |
|---|
| 2010 |
bo = self.BackendObject |
|---|
| 2011 |
aq = self.AutoQuoteNames |
|---|
| 2012 |
tblPrefix = bo.getUpdateTablePrefix(self.Table, autoQuote=aq) |
|---|
| 2013 |
nonup = self.getNonUpdateFields() |
|---|
| 2014 |
for fld, val in diff.items(): |
|---|
| 2015 |
old_val, new_val = val |
|---|
| 2016 |
# Skip the fields that are not to be updated. |
|---|
| 2017 |
if fld in nonup: |
|---|
| 2018 |
continue |
|---|
| 2019 |
fieldType = [ds[1] for ds in self.DataStructure if ds[0] == fld][0] |
|---|
| 2020 |
nms = bo.encloseNames(fld, aq) |
|---|
| 2021 |
retSql.append("%s%s = %s" % (tblPrefix, nms, self.ParamPlaceholder)) |
|---|
| 2022 |
#thisVal =self.formatForQuery(new_val, fieldType) |
|---|
| 2023 |
retParams.append(new_val) |
|---|
| 2024 |
return (", ".join(retSql), tuple(retParams)) |
|---|
| 2025 |
|
|---|
| 2026 |
|
|---|
| 2027 |
def processFields(self, txt): |
|---|
| 2028 |
return self.BackendObject.processFields(txt) |
|---|
| 2029 |
|
|---|
| 2030 |
|
|---|
| 2031 |
def escQuote(self, val): |
|---|
| 2032 |
""" Escape special characters in SQL strings. """ |
|---|
| 2033 |
ret = val |
|---|
| 2034 |
if isinstance(val, basestring): |
|---|
| 2035 |
ret = self.BackendObject.escQuote(val) |
|---|
| 2036 |
return ret |
|---|
| 2037 |
|
|---|
| 2038 |
|
|---|
| 2039 |
def getTables(self, includeSystemTables=False): |
|---|
| 2040 |
""" Return a tuple of tables in the current database.""" |
|---|
| 2041 |
return self.BackendObject.getTables(self.AuxCursor, includeSystemTables) |
|---|
| 2042 |
|
|---|
| 2043 |
|
|---|
| 2044 |
def getTableRecordCount(self, tableName): |
|---|
| 2045 |
""" Get the number of records in the backend table.""" |
|---|
| 2046 |
return self.BackendObject.getTableRecordCount(tableName, self.AuxCursor) |
|---|
| 2047 |
|
|---|
| 2048 |
|
|---|
| 2049 |
def getFields(self, tableName=None): |
|---|
| 2050 |
""" Get field information about the backend table. |
|---|
| 2051 |
|
|---|
| 2052 |
Returns a list of 3-tuples, where the 3-tuple's elements are: |
|---|
| 2053 |
0: the field name (string) |
|---|
| 2054 |
1: the field type ('I', 'N', 'C', 'M', 'B', 'D', 'T') |
|---|
| 2055 |
2: boolean specifying whether this is a pk field. |
|---|
| 2056 |
""" |
|---|
| 2057 |
if tableName is None: |
|---|
| 2058 |
# Use the default |
|---|
| 2059 |
tableName = self.Table |
|---|
| 2060 |
key = "%s:::%s" % (tableName, self.CurrentSQL) |
|---|
| 2061 |
try: |
|---|
| 2062 |
return self._fieldStructure[key] |
|---|
| 2063 |
except KeyError: |
|---|
| 2064 |
flds = self.BackendObject.getFields(tableName, self.AuxCursor) |
|---|
| 2065 |
self._fieldStructure[key] = flds |
|---|
| 2066 |
return flds |
|---|
| 2067 |
|
|---|
| 2068 |
|
|---|
| 2069 |
def getFieldInfoFromDescription(self): |
|---|
| 2070 |
""" Get field information from the cursor description. |
|---|
| 2071 |
|
|---|
| 2072 |
Returns a tuple of 3-tuples, where the 3-tuple's elements are: |
|---|
| 2073 |
0: the field name (string) |
|---|
| 2074 |
1: the field type ('I', 'N', 'C', 'M', 'B', 'D', 'T'), or None. |
|---|
| 2075 |
2: boolean specifying whether this is a pk field, or None. |
|---|
| 2076 |
""" |
|---|
| 2077 |
return self.BackendObject.getFieldInfoFromDescription(self.descriptionClean) |
|---|
| 2078 |
|
|---|
| 2079 |
|
|---|
| 2080 |
def getLastInsertID(self): |
|---|
| 2081 |
""" Return the most recently generated PK """ |
|---|
| 2082 |
ret = None |
|---|
| 2083 |
if self.BackendObject: |
|---|
| 2084 |
ret = self.BackendObject.getLastInsertID(self) |
|---|
| 2085 |
return ret |
|---|
| 2086 |
|
|---|
| 2087 |
|
|---|
| 2088 |
def formatForQuery(self, val, fieldType=None): |
|---|
| 2089 |
""" Format any value for the backend """ |
|---|
| 2090 |
ret = val |
|---|
| 2091 |
if self.BackendObject: |
|---|
| 2092 |
ret = self.BackendObject.formatForQuery(val, fieldType) |
|---|
| 2093 |
return ret |
|---|
| 2094 |
|
|---|
| 2095 |
|
|---|
| 2096 |
def formatDateTime(self, val): |
|---|
| 2097 |
""" Format DateTime values for the backend """ |
|---|
| 2098 |
ret = val |
|---|
| 2099 |
if self.BackendObject: |
|---|
| 2100 |
ret = self.BackendObject.formatDateTime(val) |
|---|
| 2101 |
return ret |
|---|
| 2102 |
|
|---|
| 2103 |
|
|---|
| 2104 |
def formatNone(self): |
|---|
| 2105 |
""" Format None values for the backend """ |
|---|
| 2106 |
if self.BackendObject: |
|---|
| 2107 |
return self.BackendObject.formatNone() |
|---|
| 2108 |
|
|---|
| 2109 |
|
|---|
| 2110 |
def beginTransaction(self): |
|---|
| 2111 |
""" Begin a SQL transaction.""" |
|---|
| 2112 |
ret = None |
|---|
| 2113 |
if self.BackendObject: |
|---|
| 2114 |
ret = self.BackendObject.beginTransaction(self.AuxCursor) |
|---|
| 2115 |
return ret |
|---|
| 2116 |
|
|---|
| 2117 |
|
|---|
| 2118 |
def commitTransaction(self): |
|---|
| 2119 |
""" Commit a SQL transaction.""" |
|---|
| 2120 |
ret = None |
|---|
| 2121 |
if self.BackendObject: |
|---|
| 2122 |
ret = self.BackendObject.commitTransaction(self.AuxCursor) |
|---|
| 2123 |
return ret |
|---|
| 2124 |
|
|---|
| 2125 |
|
|---|
| 2126 |
def rollbackTransaction(self): |
|---|
| 2127 |
""" Roll back (revert) a SQL transaction.""" |
|---|
| 2128 |
ret = None |
|---|
| 2129 |
if self.BackendObject: |
|---|
| 2130 |
ret = self.BackendObject.rollbackTransaction(self.AuxCursor) |
|---|
| 2131 |
return ret |
|---|
| 2132 |
|
|---|
| 2133 |
|
|---|
| 2134 |
def createTable(self, tabledef): |
|---|
| 2135 |
"""Create a table based on the table definition.""" |
|---|
| 2136 |
self.BackendObject.createJustTable(tabledef, self) |
|---|
| 2137 |
|
|---|
| 2138 |
|
|---|
| 2139 |
def createIndexes(self, tabledef): |
|---|
| 2140 |
"""Create indexes based on the table definition.""" |
|---|
| 2141 |
self.BackendObject.createJustIndexes(tabledef, self) |
|---|
| 2142 |
|
|---|
| 2143 |
|
|---|
| 2144 |
def createTableAndIndexes(self, tabledef): |
|---|
| 2145 |
"""Create a table and its indexes based on the table definition.""" |
|---|
| 2146 |
self.BackendObject.createTableAndIndexes(tabledef, self) |
|---|
| 2147 |
|
|---|
| 2148 |
|
|---|
| 2149 |
### SQL Builder methods ######## |
|---|
| 2150 |
def getFieldClause(self): |
|---|
| 2151 |
""" Get the field clause of the sql statement.""" |
|---|
| 2152 |
return self.sqlManager._fieldClause |
|---|
| 2153 |
|
|---|
| 2154 |
|
|---|
| 2155 |
def setFieldClause(self, clause): |
|---|
| 2156 |
""" Set the field clause of the sql statement.""" |
|---|
| 2157 |
self.sqlManager._fieldClause = self.sqlManager.BackendObject.setFieldClause(clause) |
|---|
| 2158 |
|
|---|
| 2159 |
|
|---|
| 2160 |
def addField(self, exp, alias=None): |
|---|
| 2161 |
""" Add a field to the field clause.""" |
|---|
| 2162 |
sm = self.sqlManager |
|---|
| 2163 |
beo = sm.BackendObject |
|---|
| 2164 |
if beo: |
|---|
| 2165 |
sm._fieldClause = beo.addField(sm._fieldClause, exp, alias, |
|---|
| 2166 |
autoQuote=self.AutoQuoteNames) |
|---|
| 2167 |
return sm._fieldClause |
|---|
| 2168 |
|
|---|
| 2169 |
|
|---|
| 2170 |
def getFromClause(self): |
|---|
| 2171 |
""" Get the from clause of the sql statement.""" |
|---|
| 2172 |
return self.sqlManager._fromClause |
|---|
| 2173 |
|
|---|
| 2174 |
|
|---|
| 2175 |
def setFromClause(self, clause): |
|---|
| 2176 |
""" Set the from clause of the sql statement.""" |
|---|
| 2177 |
self.sqlManager._fromClause = self.sqlManager.BackendObject.setFromClause(clause, |
|---|
| 2178 |
autoQuote=self.AutoQuoteNames) |
|---|
| 2179 |
|
|---|
| 2180 |
|
|---|
| 2181 |
def addFrom(self, exp, alias=None): |
|---|
| 2182 |
""" Add a table to the sql statement. For joins, use |
|---|
| 2183 |
the addJoin() method. |
|---|
| 2184 |
""" |
|---|
| 2185 |
if self.sqlManager.BackendObject: |
|---|
| 2186 |
self.sqlManager._fromClause = self.sqlManager.BackendObject.addFrom(self.sqlManager._fromClause, |
|---|
| 2187 |
exp, alias, autoQuote=self.AutoQuoteNames) |
|---|
| 2188 |
return self.sqlManager._fromClause |
|---|
| 2189 |
|
|---|
| 2190 |
|
|---|
| 2191 |
def getJoinClause(self): |
|---|
| 2192 |
""" Get the join clause of the sql statement.""" |
|---|
| 2193 |
return self.sqlManager._joinClause |
|---|
| 2194 |
|
|---|
| 2195 |
|
|---|
| 2196 |
def setJoinClause(self, clause): |
|---|
| 2197 |
""" Set the join clause of the sql statement.""" |
|---|
| 2198 |
self.sqlManager._joinClause = self.sqlManager.BackendObject.setJoinClause(clause, |
|---|
| 2199 |
autoQuote=self.AutoQuoteNames) |
|---|
| 2200 |
|
|---|
| 2201 |
|
|---|
| 2202 |
def addJoin(self, tbl, joinCondition, joinType=None): |
|---|
| 2203 |
""" Add a joined table to the sql statement.""" |
|---|
| 2204 |
if self.sqlManager.BackendObject: |
|---|
| 2205 |
self.sqlManager._joinClause = self.sqlManager.BackendObject.addJoin(tbl, |
|---|
| 2206 |
joinCondition, self.sqlManager._joinClause, joinType, |
|---|
| 2207 |
autoQuote=self.AutoQuoteNames) |
|---|
| 2208 |
return self.sqlManager._joinClause |
|---|
| 2209 |
|
|---|
| 2210 |
|
|---|
| 2211 |
|
|---|
| 2212 |
def getWhereClause(self): |
|---|
| 2213 |
""" Get the where clause of the sql statement.""" |
|---|
| 2214 |
return self.sqlManager._whereClause |
|---|
| 2215 |
|
|---|
| 2216 |
|
|---|
| 2217 |
def setWhereClause(self, clause): |
|---|
| 2218 |
""" Set the where clause of the sql statement.""" |
|---|
| 2219 |
self.sqlManager._whereClause = self.sqlManager.BackendObject.setWhereClause(clause, |
|---|
| 2220 |
autoQuote=self.AutoQuoteNames) |
|---|
| 2221 |
|
|---|
| 2222 |
|
|---|
| 2223 |
def addWhere(self, exp, comp="and"): |
|---|
| 2224 |
""" Add an expression to the where clause.""" |
|---|
| 2225 |
if self.sqlManager.BackendObject: |
|---|
| 2226 |
self.sqlManager._whereClause = self.sqlManager.BackendObject.addWhere( |
|---|
| 2227 |
self.sqlManager._whereClause, exp, comp, autoQuote=self.AutoQuoteNames) |
|---|
| 2228 |
return self.sqlManager._whereClause |
|---|
| 2229 |
|
|---|
| 2230 |
|
|---|
| 2231 |
def prepareWhere(self, clause): |
|---|
| 2232 |
""" Modifies WHERE clauses as needed for each backend. """ |
|---|
| 2233 |
return self.sqlManager.BackendObject.prepareWhere(clause, |
|---|
| 2234 |
autoQuote=self.AutoQuoteNames) |
|---|
| 2235 |
|
|---|
| 2236 |
|
|---|
| 2237 |
def setChildFilter(self, fld): |
|---|
| 2238 |
""" This method sets the appropriate WHERE filter for dependent child queries.""" |
|---|
| 2239 |
|
|---|
| 2240 |
def getTableAlias(fromClause): |
|---|
| 2241 |
if not fromClause.strip(): |
|---|
| 2242 |
return None |
|---|
| 2243 |
|
|---|
| 2244 |
joinStrings = ["left join", "right join", "outer join", "inner join", "join"] |
|---|
| 2245 |
foundAlias = None |
|---|
| 2246 |
for joinString in joinStrings: |
|---|
| 2247 |
at = fromClause.lower().find(joinString) |
|---|
| 2248 |
if at >= 0: |
|---|
| 2249 |
foundAlias = fromClause[:at].strip() |
|---|
| 2250 |
break |
|---|
| 2251 |
if not foundAlias: |
|---|
| 2252 |
# The alias is the last 'word' in the FROM clause |
|---|
| 2253 |
foundAlias = fromClause.strip().split()[-1] |
|---|
| 2254 |
return foundAlias |
|---|
| 2255 |
|
|---|
| 2256 |
alias = getTableAlias(self.sqlManager._fromClause) |
|---|
| 2257 |
if not alias: |
|---|
| 2258 |
# Use the old way (pre 2180) of using the Table (DataSource) property. |
|---|
| 2259 |
alias = self.Table |
|---|
| 2260 |
filtExpr = " %s.%s = %s " % (alias, fld, self.ParamPlaceholder) |
|---|
| 2261 |
self.setChildFilterClause(filtExpr) |
|---|
| 2262 |
|
|---|
| 2263 |
|
|---|
| 2264 |
def setNonMatchChildFilterClause(self): |
|---|
| 2265 |
""" Called when the parent has no records, which implies that the child |
|---|
| 2266 |
cannot have any, either. |
|---|
| 2267 |
""" |
|---|
| 2268 |
self.setChildFilterClause(" 1 = 0 ") |
|---|
| 2269 |
|
|---|
| 2270 |
|
|---|
| 2271 |
def getChildFilterClause(self): |
|---|
| 2272 |
""" Get the child filter part of the sql statement.""" |
|---|
| 2273 |
return self.sqlManager._childFilterClause |
|---|
| 2274 |
|
|---|
| 2275 |
|
|---|
| 2276 |
def setChildFilterClause(self, clause): |
|---|
| 2277 |
""" Set the child filter clause of the sql statement.""" |
|---|
| 2278 |
self.sqlManager._childFilterClause = self.sqlManager.BackendObject.setChildFilterClause(clause) |
|---|
| 2279 |
|
|---|
| 2280 |
|
|---|
| 2281 |
def getGroupByClause(self): |
|---|
| 2282 |
""" Get the group-by clause of the sql statement.""" |
|---|
| 2283 |
return self.sqlManager._groupByClause |
|---|
| 2284 |
|
|---|
| 2285 |
|
|---|
| 2286 |
def setGroupByClause(self, clause): |
|---|
| 2287 |
""" Set the group-by clause of the sql statement.""" |
|---|
| 2288 |
self.sqlManager._groupByClause = self.sqlManager.BackendObject.setGroupByClause(clause) |
|---|
| 2289 |
|
|---|
| 2290 |
|
|---|
| 2291 |
def addGroupBy(self, exp): |
|---|
| 2292 |
""" Add an expression to the group-by clause.""" |
|---|
| 2293 |
if self.sqlManager.BackendObject: |
|---|
| 2294 |
self.sqlManager._groupByClause = self.sqlManager.BackendObject.addGroupBy(self.sqlManager._groupByClause, |
|---|
| 2295 |
exp, autoQuote=self.AutoQuoteNames) |
|---|
| 2296 |
return self.sqlManager._groupByClause |
|---|
| 2297 |
|
|---|
| 2298 |
|
|---|
| 2299 |
def getOrderByClause(self): |
|---|
| 2300 |
""" Get the order-by clause of the sql statement.""" |
|---|
| 2301 |
return self.sqlManager._orderByClause |
|---|
| 2302 |
|
|---|
| 2303 |
|
|---|
| 2304 |
def setOrderByClause(self, clause): |
|---|
| 2305 |
""" Set the order-by clause of the sql statement.""" |
|---|
| 2306 |
self.sqlManager._orderByClause = self.sqlManager.BackendObject.setOrderByClause(clause) |
|---|
| 2307 |
|
|---|
| 2308 |
|
|---|
| 2309 |
def addOrderBy(self, exp): |
|---|
| 2310 |
""" Add an expression to the order-by clause.""" |
|---|
| 2311 |
if self.sqlManager.BackendObject: |
|---|
| 2312 |
self.sqlManager._orderByClause = self.sqlManager.BackendObject.addOrderBy(self.sqlManager._orderByClause, |
|---|
| 2313 |
exp, autoQuote=self.AutoQuoteNames) |
|---|
| 2314 |
return self.sqlManager._orderByClause |
|---|
| 2315 |
|
|---|
| 2316 |
|
|---|
| 2317 |
def getLimitClause(self): |
|---|
| 2318 |
""" Get the limit clause of the sql statement.""" |
|---|
| 2319 |
return self.sqlManager._limitClause |
|---|
| 2320 |
|
|---|
| 2321 |
|
|---|
| 2322 |
def setLimitClause(self, clause): |
|---|
| 2323 |
""" Set the limit clause of the sql statement.""" |
|---|
| 2324 |
self.sqlManager._limitClause = clause |
|---|
| 2325 |
|
|---|
| 2326 |
# For simplicity's sake, create aliases |
|---|
| 2327 |
setLimit, getLimit = setLimitClause, getLimitClause |
|---|
| 2328 |
|
|---|
| 2329 |
|
|---|
| 2330 |
|
|---|
| 2331 |
def getLimitWord(self): |
|---|
| 2332 |
""" Return the word to use in the db-specific limit clause.""" |
|---|
| 2333 |
ret = "limit" |
|---|
| 2334 |
if self.sqlManager.BackendObject: |
|---|
| 2335 |
ret = self.sqlManager.BackendObject.getLimitWord() |
|---|
| 2336 |
return ret |
|---|
| 2337 |
|
|---|
| 2338 |
|
|---|
| 2339 |
def getLimitPosition(self): |
|---|
| 2340 |
""" Return the position to place the limit clause. |
|---|
| 2341 |
|
|---|
| 2342 |
For currently-supported dbapi's, the return values of 'top' or 'bottom' |
|---|
| 2343 |
are sufficient. |
|---|
| 2344 |
""" |
|---|
| 2345 |
ret = "bottom" |
|---|
| 2346 |
if self.sqlManager.BackendObject: |
|---|
| 2347 |
ret = self.sqlManager.BackendObject.getLimitPosition() |
|---|
| 2348 |
return ret |
|---|
| 2349 |
|
|---|
| 2350 |
|
|---|
| 2351 |
def getSQL(self): |
|---|
| 2352 |
""" Get the complete SQL statement from all the parts.""" |
|---|
| 2353 |
fieldClause = self.sqlManager._fieldClause |
|---|
| 2354 |
fromClause = self.sqlManager._fromClause |
|---|
| 2355 |
joinClause = self.sqlManager._joinClause |
|---|
| 2356 |
whereClause = self.sqlManager._whereClause |
|---|
| 2357 |
childFilterClause = self.sqlManager._childFilterClause |
|---|
| 2358 |
groupByClause = self.sqlManager._groupByClause |
|---|
| 2359 |
orderByClause = self.sqlManager._orderByClause |
|---|
| 2360 |
limitClause = self.sqlManager._limitClause |
|---|
| 2361 |
|
|---|
| 2362 |
if not fieldClause: |
|---|
| 2363 |
fieldClause = "*" |
|---|
| 2364 |
|
|---|
| 2365 |
if not fromClause: |
|---|
| 2366 |
fromClause = self.Table |
|---|
| 2367 |
|
|---|
| 2368 |
if childFilterClause: |
|---|
| 2369 |
# Prepend it to the where clause |
|---|
| 2370 |
if whereClause: |
|---|
| 2371 |
childFilterClause += "\nand " |
|---|
| 2372 |
whereClause = childFilterClause + " " + whereClause |
|---|
| 2373 |
|
|---|
| 2374 |
if fromClause: |
|---|
| 2375 |
fromClause = " from " + fromClause |
|---|
| 2376 |
else: |
|---|
| 2377 |
fromClause = " from " + self.sqlManager.Table |
|---|
| 2378 |
if joinClause: |
|---|
| 2379 |
joinClause = " " + joinClause |
|---|
| 2380 |
if whereClause: |
|---|
| 2381 |
whereClause = " where " + whereClause |
|---|
| 2382 |
if groupByClause: |
|---|
| 2383 |
groupByClause = " group by " + groupByClause |
|---|
| 2384 |
if orderByClause: |
|---|
| 2385 |
orderByClause = " order by " + orderByClause |
|---|
| 2386 |
if limitClause: |
|---|
| 2387 |
limitClause = " %s %s" % (self.sqlManager.getLimitWord(), limitClause) |
|---|
| 2388 |
elif limitClause is None: |
|---|
| 2389 |
# The limit clause was specifically disabled. |
|---|
| 2390 |
limitClause = "" |
|---|
| 2391 |
else: |
|---|
| 2392 |
limitClause = " %s %s" % (self.sqlManager.getLimitWord(), self.sqlManager._defaultLimit) |
|---|
| 2393 |
|
|---|
| 2394 |
return self.sqlManager.BackendObject.formSQL(fieldClause, fromClause, joinClause, |
|---|
| 2395 |
whereClause, groupByClause, orderByClause, limitClause) |
|---|
| 2396 |
|
|---|
| 2397 |
|
|---|
| 2398 |
def getStructureOnlySql(self): |
|---|
| 2399 |
"""Creates a SQL statement that will not return any records.""" |
|---|
| 2400 |
holdWhere = self.sqlManager._whereClause |
|---|
| 2401 |
self.sqlManager.setWhereClause("") |
|---|
| 2402 |
holdLimit = self.sqlManager._limitClause |
|---|
| 2403 |
self.sqlManager.setLimitClause(1) |
|---|
| 2404 |
ret = self.sqlManager.getSQL() |
|---|
| 2405 |
self.sqlManager.setWhereClause(holdWhere) |
|---|
| 2406 |
self.sqlManager.setLimitClause(holdLimit) |
|---|
| 2407 |
return ret |
|---|
| 2408 |
|
|---|
| 2409 |
|
|---|
| 2410 |
def executeSQL(self, *args, **kwargs): |
|---|
| 2411 |
self.sqlManager.execute(self.sqlManager.getSQL(), *args, **kwargs) |
|---|
| 2412 |
### end - SQL Builder methods ######## |
|---|
| 2413 |
|
|---|
| 2414 |
|
|---|
| 2415 |
def getWordMatchFormat(self): |
|---|
| 2416 |
return self.sqlManager.BackendObject.getWordMatchFormat() |
|---|
| 2417 |
|
|---|
| 2418 |
|
|---|
| 2419 |
def oldVal(self, fieldName, row=None): |
|---|
| 2420 |
"""Returns the value of the field as it existed after the last requery.""" |
|---|
| 2421 |
if self.RowCount < 1: |
|---|
| 2422 |
raise dabo.dException.NoRecordsException |
|---|
| 2423 |
if row is None: |
|---|
| 2424 |
row = self.RowNumber |
|---|
| 2425 |
rec = self._records[row] |
|---|
| 2426 |
pk = self.pkExpression(rec) |
|---|
| 2427 |
mem = self._mementos.get(pk, None) |
|---|
| 2428 |
if mem and (fieldName in mem): |
|---|
| 2429 |
return mem[fieldName] |
|---|
| 2430 |
return self.getFieldVal(fieldName, row) |
|---|
| 2431 |
|
|---|
| 2432 |
|
|---|
| 2433 |
def _qMarkToParamPlaceholder(self, sql): |
|---|
| 2434 |
"""Given SQL with ? placeholders, convert to the placeholder for the current backend. |
|---|
| 2435 |
|
|---|
| 2436 |
Allows for all UserSQL to be written with ? as the placeholder. |
|---|
| 2437 |
""" |
|---|
| 2438 |
boPlaceholder = self.BackendObject.paramPlaceholder |
|---|
| 2439 |
if boPlaceholder in sql: |
|---|
| 2440 |
# Better not change the sql, because the ? might have a different meaning. |
|---|
| 2441 |
return sql |
|---|
| 2442 |
return sql.replace("?", "%s" % self.BackendObject.paramPlaceholder) |
|---|
| 2443 |
|
|---|
| 2444 |
|
|---|
| 2445 |
def _setTableForRemote(self, tbl): |
|---|
| 2446 |
"""Used when running as a remote application. We don't want to trigger |
|---|
| 2447 |
the methods to query the database for field information. |
|---|
| 2448 |
""" |
|---|
| 2449 |
self._table = self.AuxCursor._table = self.sqlManager._table = "%s" % tbl |
|---|
| 2450 |
|
|---|
| 2451 |
|
|---|
| 2452 |
## Property getter/setter methods ## |
|---|
| 2453 |
def _getAutoSQL(self): |
|---|
| 2454 |
return self.getSQL() |
|---|
| 2455 |
|
|---|
| 2456 |
|
|---|
| 2457 |
def _getAutoPopulatePK(self): |
|---|
| 2458 |
try: |
|---|
| 2459 |
return self._autoPopulatePK and bool(self.KeyField) |
|---|
| 2460 |
except AttributeError: |
|---|
| 2461 |
return True |
|---|
| 2462 |
|
|---|
| 2463 |
def _setAutoPopulatePK(self, autopop): |
|---|
| 2464 |
self._autoPopulatePK = self.AuxCursor._autoPopulatePK = bool(autopop) |
|---|
| 2465 |
|
|---|
| 2466 |
|
|---|
| 2467 |
def _getAutoQuoteNames(self): |
|---|
| 2468 |
return self._autoQuoteNames |
|---|
| 2469 |
|
|---|
| 2470 |
def _setAutoQuoteNames(self, val): |
|---|
| 2471 |
self._autoQuoteNames = self.AuxCursor._autoQuoteNames = val |
|---|
| 2472 |
|
|---|
| 2473 |
|
|---|
| 2474 |
def _getAuxCursor(self): |
|---|
| 2475 |
isnew = self.__auxCursor is None |
|---|
| 2476 |
if isnew: |
|---|
| 2477 |
if self._cursorFactoryClass is not None: |
|---|
| 2478 |
if self._cursorFactoryFunc is not None: |
|---|
| 2479 |
self.__auxCursor = self._cursorFactoryFunc(self._cursorFactoryClass) |
|---|
| 2480 |
if not self.__auxCursor: |
|---|
| 2481 |
self.__auxCursor = self.BackendObject.getCursor(self.__class__) |
|---|
| 2482 |
self.__auxCursor.BackendObject = self.BackendObject |
|---|
| 2483 |
self.__auxCursor._isAuxiliary = True |
|---|
| 2484 |
if isnew: |
|---|
| 2485 |
ac = self.__auxCursor |
|---|
| 2486 |
ac._autoPopulatePK = self._autoPopulatePK |
|---|
| 2487 |
ac._autoQuoteNames = self._autoQuoteNames |
|---|
| 2488 |
ac._dataStructure = self._dataStructure |
|---|
| 2489 |
if self.BackendObject: |
|---|
| 2490 |
ac._encoding = self.Encoding |
|---|
| 2491 |
ac._isPrefCursor = self._isPrefCursor |
|---|
| 2492 |
ac._keyField = self._keyField |
|---|
| 2493 |
ac._table = self._table |
|---|
| 2494 |
return self.__auxCursor |
|---|
| 2495 |
|
|---|
| 2496 |
|
|---|
| 2497 |
def _getBackendObject(self): |
|---|
| 2498 |
return self.__backend |
|---|
| 2499 |
|
|---|
| 2500 |
def _setBackendObject(self, obj): |
|---|
| 2501 |
self.__backend = obj |
|---|
| 2502 |
if obj and obj._cursor is None: |
|---|
| 2503 |
obj._cursor = self |
|---|
| 2504 |
if self.__auxCursor: |
|---|
| 2505 |
self.__auxCursor.__backend = obj |
|---|
| 2506 |
|
|---|
| 2507 |
|
|---|
| 2508 |
def _getCurrentSQL(self): |
|---|
| 2509 |
if self.UserSQL: |
|---|
| 2510 |
return self.UserSQL |
|---|
| 2511 |
return self.AutoSQL |
|---|
| 2512 |
|
|---|
| 2513 |
|
|---|
| 2514 |
def _getDescrip(self): |
|---|
| 2515 |
return self.__backend.getDescription(self) |
|---|
| 2516 |
|
|---|
| 2517 |
|
|---|
| 2518 |
def _getDataStructure(self): |
|---|
| 2519 |
val = getattr(self, "_dataStructure", None) |
|---|
| 2520 |
if val is None: |
|---|
| 2521 |
# Get the information from the backend. Note that elements 3 and 4 get |
|---|
| 2522 |
# guessed-at values. |
|---|
| 2523 |
val = getattr(self, "_savedStructureDescription", []) |
|---|
| 2524 |
if not val: |
|---|
| 2525 |
if self.BackendObject is None: |
|---|
| 2526 |
# Nothing we can do. We are probably an AuxCursor |
|---|
| 2527 |
pass |
|---|
| 2528 |
else: |
|---|
| 2529 |
ds = self.BackendObject.getStructureDescription(self) |
|---|
| 2530 |
gf_names = [gf[0] for gf in self.getFields(self.Table)] |
|---|
| 2531 |
for field in ds: |
|---|
| 2532 |
field_name, field_type, pk = field[0], field[1], field[2] |
|---|
| 2533 |
try: |
|---|
| 2534 |
field_scale = field[5] |
|---|
| 2535 |
except IndexError: |
|---|
| 2536 |
field_scale = None |
|---|
| 2537 |
if field_name in gf_names: |
|---|
| 2538 |
table_name = self.Table |
|---|
| 2539 |
else: |
|---|
| 2540 |
table_name = "_foreign_table_" |
|---|
| 2541 |
val.append((field_name, field_type, pk, table_name, field_name, field_scale)) |
|---|
| 2542 |
self._savedStructureDescription = val |
|---|
| 2543 |
self._dataStructure = val |
|---|
| 2544 |
return tuple(val) |
|---|
| 2545 |
|
|---|
| 2546 |
def _setDataStructure(self, val): |
|---|
| 2547 |
# Go through the sequence, raising exceptions or adding default values as |
|---|
| 2548 |
# appropriate. |
|---|
| 2549 |
val = list(val) |
|---|
| 2550 |
for idx, field in enumerate(val): |
|---|
| 2551 |
field_alias = field[0] |
|---|
| 2552 |
field_type = field[1] |
|---|
| 2553 |
try: |
|---|
| 2554 |
field_pk = field[2] |
|---|
| 2555 |
except IndexError: |
|---|
| 2556 |
field_pk = False |
|---|
| 2557 |
try: |
|---|
| 2558 |
table_name = field[3] |
|---|
| 2559 |
except IndexError: |
|---|
| 2560 |
table_name = self.Table |
|---|
| 2561 |
try: |
|---|
| 2562 |
field_name = field[4] |
|---|
| 2563 |
except IndexError: |
|---|
| 2564 |
field_name = field_alias |
|---|
| 2565 |
try: |
|---|
| 2566 |
field_scale = field[5] |
|---|
| 2567 |
except IndexError: |
|---|
| 2568 |
field_scale = None |
|---|
| 2569 |
val[idx] = (field_alias, field_type, field_pk, table_name, field_name, field_scale) |
|---|
| 2570 |
self._dataStructure = self.AuxCursor._dataStructure = tuple(val) |
|---|
| 2571 |
|
|---|
| 2572 |
|
|---|
| 2573 |
def _getEncoding(self): |
|---|
| 2574 |
return self.BackendObject.Encoding |
|---|
| 2575 |
|
|---|
| 2576 |
def _setEncoding(self, val): |
|---|
| 2577 |
self.BackendObject.Encoding = val |
|---|
| 2578 |
|
|---|
| 2579 |
|
|---|
| 2580 |
def _getIsAdding(self): |
|---|
| 2581 |
""" Return True if the current record is a new record.""" |
|---|
| 2582 |
if self.RowCount <= 0: |
|---|
| 2583 |
return False |
|---|
| 2584 |
try: |
|---|
| 2585 |
getattr(self.Record, kons.CURSOR_TMPKEY_FIELD) |
|---|
| 2586 |
return True |
|---|
| 2587 |
except dException.FieldNotFoundException: |
|---|
| 2588 |
return False |
|---|
| 2589 |
|
|---|
| 2590 |
|
|---|
| 2591 |
def _getIsPrefCursor(self): |
|---|
| 2592 |
return self._isPrefCursor |
|---|
| 2593 |
|
|---|
| 2594 |
def _setIsPrefCursor(self, val): |
|---|
| 2595 |
self._isPrefCursor = self.AuxCursor._isPrefCursor = val |
|---|
| 2596 |
|
|---|
| 2597 |
|
|---|
| 2598 |
def _getKeyField(self): |
|---|
| 2599 |
return self._keyField |
|---|
| 2600 |
|
|---|
| 2601 |
def _setKeyField(self, kf): |
|---|
| 2602 |
if "," in kf: |
|---|
| 2603 |
flds = [f.strip() for f in kf.split(",")] |
|---|
| 2604 |
self._keyField = tuple(flds) |
|---|
| 2605 |
self._compoundKey = True |
|---|
| 2606 |
else: |
|---|
| 2607 |
self._keyField = ustr(kf) |
|---|
| 2608 |
self._compoundKey = False |
|---|
| 2609 |
self.AuxCursor._keyField = self._keyField |
|---|
| 2610 |
self.AuxCursor._compoundKey = self._compoundKey |
|---|
| 2611 |
self._keyFieldSet = self.AuxCursor._keyFieldSet = (self._hasValidKeyField) |
|---|
| 2612 |
|
|---|
| 2613 |
|
|---|
| 2614 |
def _getLastSQL(self): |
|---|
| 2615 |
try: |
|---|
| 2616 |
v = self._lastSQL |
|---|
| 2617 |
except AttributeError: |
|---|
| 2618 |
v = self._lastSQL = None |
|---|
| 2619 |
return v |
|---|
| 2620 |
|
|---|
| 2621 |
|
|---|
| 2622 |
def _getParamPlaceholder(self): |
|---|
| 2623 |
if self._paramPlaceholder: |
|---|
| 2624 |
ret = self._paramPlaceholder |
|---|
| 2625 |
else: |
|---|
| 2626 |
ret = self._paramPlaceholder = self.BackendObject.paramPlaceholder |
|---|
| 2627 |
return ret |
|---|
| 2628 |
|
|---|
| 2629 |
|
|---|
| 2630 |
def _getRecord(self): |
|---|
| 2631 |
try: |
|---|
| 2632 |
ret = self._cursorRecord |
|---|
| 2633 |
except AttributeError: |
|---|
| 2634 |
class CursorRecord(object): |
|---|
| 2635 |
def __init__(self, _cursor): |
|---|
| 2636 |
self._cursor = _cursor |
|---|
| 2637 |
super(CursorRecord, self).__init__() |
|---|
| 2638 |
|
|---|
| 2639 |
def __getattr__(self, att): |
|---|
| 2640 |
return self._cursor.getFieldVal(att) |
|---|
| 2641 |
|
|---|
| 2642 |
def __setattr__(self, att, val): |
|---|
| 2643 |
if att in ("_cursor"): |
|---|
| 2644 |
super(CursorRecord, self).__setattr__(att, val) |
|---|
| 2645 |
else: |
|---|
| 2646 |
self._cursor.setFieldVal(att, val) |
|---|
| 2647 |
|
|---|
| 2648 |
def __getitem__(self, key): |
|---|
| 2649 |
return self.__getattr__(key) |
|---|
| 2650 |
|
|---|
| 2651 |
def __setitem__(self, key, val): |
|---|
| 2652 |
return self.__setattr__(key, val) |
|---|
| 2653 |
|
|---|
| 2654 |
## The rest of this block add a property to the Record object |
|---|
| 2655 |
## for each field, the sole purpose being to have the field |
|---|
| 2656 |
## names appear in the command window intellisense dropdown. |
|---|
| 2657 |
def getFieldProp(field_name): |
|---|
| 2658 |
def fget(self): |
|---|
| 2659 |
return self._cursor.getFieldVal(field_name) |
|---|
| 2660 |
def fset(self, val): |
|---|
| 2661 |
self._cursor.setFieldVal(field_name, val) |
|---|
| 2662 |
return property(fget, fset) |
|---|
| 2663 |
|
|---|
| 2664 |
field_aliases = [ds[0] for ds in self.DataStructure] |
|---|
| 2665 |
field_aliases.extend(self.VirtualFields.keys()) |
|---|
| 2666 |
for field_alias in field_aliases: |
|---|
| 2667 |
setattr(CursorRecord, field_alias, getFieldProp(field_alias)) |
|---|
| 2668 |
ret = self._cursorRecord = CursorRecord(self) |
|---|
| 2669 |
return ret |
|---|
| 2670 |
|
|---|
| 2671 |
|
|---|
| 2672 |
def _getRowCount(self): |
|---|
| 2673 |
try: |
|---|
| 2674 |
ret = len(self._records) |
|---|
| 2675 |
except AttributeError: |
|---|
| 2676 |
ret = -1 |
|---|
| 2677 |
return ret |
|---|
| 2678 |
|
|---|
| 2679 |
|
|---|
| 2680 |
def _getRowNumber(self): |
|---|
| 2681 |
try: |
|---|
| 2682 |
ret = min(self.__rownumber, self._getRowCount()-1) |
|---|
| 2683 |
except AttributeError: |
|---|
| 2684 |
ret = -1 |
|---|
| 2685 |
return ret |
|---|
| 2686 |
|
|---|
| 2687 |
|
|---|
| 2688 |
def _setRowNumber(self, num): |
|---|
| 2689 |
rn = max(0, num) |
|---|
| 2690 |
maxrow = max(0, (self.RowCount-1) ) |
|---|
| 2691 |
self.__rownumber = min(rn, maxrow) |
|---|
| 2692 |
|
|---|
| 2693 |
|
|---|
| 2694 |
def _getTable(self): |
|---|
| 2695 |
return self._table |
|---|
| 2696 |
|
|---|
| 2697 |
def _setTable(self, table): |
|---|
| 2698 |
self._table = self.AuxCursor._table = self.sqlManager._table = "%s" % table |
|---|
| 2699 |
if table and not self._keyFieldSet: |
|---|
| 2700 |
flds = self.getFields(table) |
|---|
| 2701 |
if flds is None: |
|---|
| 2702 |
return |
|---|
| 2703 |
# Get the PK field, if any |
|---|
| 2704 |
try: |
|---|
| 2705 |
self._keyField = [fld[0] for fld in flds |
|---|
| 2706 |
if fld[2] ][0] |
|---|
| 2707 |
except IndexError: |
|---|
| 2708 |
pass |
|---|
| 2709 |
|
|---|
| 2710 |
|
|---|
| 2711 |
def _getUserSQL(self): |
|---|
| 2712 |
return self._userSQL |
|---|
| 2713 |
|
|---|
| 2714 |
def _setUserSQL(self, val): |
|---|
| 2715 |
if val: |
|---|
| 2716 |
val = self._qMarkToParamPlaceholder(val) |
|---|
| 2717 |
self._userSQL = val |
|---|
| 2718 |
|
|---|
| 2719 |
|
|---|
| 2720 |
def _getVirtualFields(self): |
|---|
| 2721 |
return self._virtualFields |
|---|
| 2722 |
|
|---|
| 2723 |
def _setVirtualFields(self, val): |
|---|
| 2724 |
assert isinstance(val, dict) |
|---|
| 2725 |
self._virtualFields = val |
|---|
| 2726 |
|
|---|
| 2727 |
|
|---|
| 2728 |
AutoPopulatePK = property(_getAutoPopulatePK, _setAutoPopulatePK, None, |
|---|
| 2729 |
_("When inserting a new record, does the backend populate the PK field?")) |
|---|
| 2730 |
|
|---|
| 2731 |
AutoQuoteNames = property(_getAutoQuoteNames, _setAutoQuoteNames, None, |
|---|
| 2732 |
_("""When True (default), table and column names are enclosed with |
|---|
| 2733 |
quotes during SQL creation. (bool)""")) |
|---|
| 2734 |
|
|---|
| 2735 |
AutoSQL = property(_getAutoSQL, None, None, |
|---|
| 2736 |
_("Returns the SQL statement automatically generated by the sql manager.")) |
|---|
| 2737 |
|
|---|
| 2738 |
AuxCursor = property(_getAuxCursor, None, None, |
|---|
| 2739 |
_("""Auxiliary cursor object that handles queries that would otherwise |
|---|
| 2740 |
affect the main cursor's data set. (dCursorMixin subclass)""")) |
|---|
| 2741 |
|
|---|
| 2742 |
BackendObject = property(_getBackendObject, _setBackendObject, None, |
|---|
| 2743 |
_("Returns a reference to the object defining backend-specific behavior (dBackend)")) |
|---|
| 2744 |
|
|---|
| 2745 |
CurrentSQL = property(_getCurrentSQL, None, None, |
|---|
| 2746 |
_("Returns the current SQL that will be run, which is one of UserSQL or AutoSQL.")) |
|---|
| 2747 |
|
|---|
| 2748 |
DataStructure = property(_getDataStructure, _setDataStructure, None, |
|---|
| 2749 |
_("""Returns the structure of the cursor in a tuple of 6-tuples. |
|---|
| 2750 |
|
|---|
| 2751 |
0: field alias (str) |
|---|
| 2752 |
1: data type code (str) |
|---|
| 2753 |
2: pk field (bool) |
|---|
| 2754 |
3: table name (str) |
|---|
| 2755 |
4: field name (str) |
|---|
| 2756 |
5: field scale (int or None) |
|---|
| 2757 |
|
|---|
| 2758 |
This information will try to come from a few places, in order: |
|---|
| 2759 |
1) The explicitly-set DataStructure property |
|---|
| 2760 |
2) The backend table method""")) |
|---|
| 2761 |
|
|---|
| 2762 |
Encoding = property(_getEncoding, _setEncoding, None, |
|---|
| 2763 |
_("Encoding type used by the Backend (string)") ) |
|---|
| 2764 |
|
|---|
| 2765 |
FieldDescription = property(_getDescrip, None, None, |
|---|
| 2766 |
_("Tuple of field names and types, as returned by the backend (tuple)") ) |
|---|
| 2767 |
|
|---|
| 2768 |
IsAdding = property(_getIsAdding, None, None, |
|---|
| 2769 |
_("Returns True if the current record is new and unsaved")) |
|---|
| 2770 |
|
|---|
| 2771 |
IsPrefCursor = property(_getIsPrefCursor, _setIsPrefCursor, None, |
|---|
| 2772 |
_("""Returns True if this cursor is used for managing internal |
|---|
| 2773 |
Dabo preferences and settings. Default=False. (bool)""")) |
|---|
| 2774 |
|
|---|
| 2775 |
LastSQL = property(_getLastSQL, None, None, |
|---|
| 2776 |
_("Returns the last executed SQL statement.")) |
|---|
| 2777 |
|
|---|
| 2778 |
KeyField = property(_getKeyField, _setKeyField, None, |
|---|
| 2779 |
_("""Name of field that is the PK. If multiple fields make up the key, |
|---|
| 2780 |
separate the fields with commas. (str)""")) |
|---|
| 2781 |
|
|---|
| 2782 |
ParamPlaceholder = property(_getParamPlaceholder, None, None, |
|---|
| 2783 |
_("""The character(s) used to indicate a parameter in an SQL statement. |
|---|
| 2784 |
This can be different for different backend systems. Read-only. (str)""")) |
|---|
| 2785 |
|
|---|
| 2786 |
Record = property(_getRecord, None, None, |
|---|
| 2787 |
_("""Represents a record in the data set. You can address individual |
|---|
| 2788 |
columns by referring to 'self.Record.fieldName' (read-only) (no type)""")) |
|---|
| 2789 |
|
|---|
| 2790 |
RowNumber = property(_getRowNumber, _setRowNumber, None, |
|---|
| 2791 |
_("Current row in the recordset.")) |
|---|
| 2792 |
|
|---|
| 2793 |
RowCount = property(_getRowCount, None, None, |
|---|
| 2794 |
_("Current number of rows in the recordset. Read-only.")) |
|---|
| 2795 |
|
|---|
| 2796 |
Table = property(_getTable, _setTable, None, |
|---|
| 2797 |
_("The name of the table in the database that this cursor is updating.")) |
|---|
| 2798 |
|
|---|
| 2799 |
UserSQL = property(_getUserSQL, _setUserSQL, None, |
|---|
| 2800 |
_("SQL statement to run. If set, the automatic SQL builder will not be used.")) |
|---|
| 2801 |
|
|---|
| 2802 |
VirtualFields = property(_getVirtualFields, _setVirtualFields, None, |
|---|
| 2803 |
_("""A dictionary mapping virtual_field_name to a function to call. |
|---|
| 2804 |
|
|---|
| 2805 |
The specified function will be called when getFieldVal() is called on |
|---|
| 2806 |
the specified field name.""")) |
|---|