00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055 import os, sys, string, types, re
00056 import zlib
00057 import marshal
00058
00059 from log import *
00060 if 0:
00061 debugfull()
00062 LOGGING_STATUS[DEV_UPDATE] = 1
00063 LOGGING_STATUS[DEV_SELECT] = 1
00064 LOGGING_STATUS[DEV_REPORT] = 1
00065 else:
00066 debugoff()
00067
00068
00069 import weakref
00070
00071 import handle_error
00072
00073 class odb_Exception(Exception):
00074 def __init__(self, message):
00075 self.message = message
00076
00077 def __str__(self):
00078 return repr(self.message)
00079
00080 class eNoSuchColumn(odb_Exception):
00081 pass
00082
00083 class eNonUniqueMatchSpec(odb_Exception):
00084 pass
00085
00086 class eNoMatchingRows(odb_Exception):
00087 pass
00088 class eInternalError(odb_Exception):
00089 pass
00090 class eInvalidMatchSpec(odb_Exception):
00091 pass
00092 class eInvalidData(odb_Exception):
00093 pass
00094 class eUnsavedObjectLost(odb_Exception):
00095 pass
00096 class eDuplicateKey(odb_Exception):
00097 pass
00098 class eInvalidJoinSpec(odb_Exception):
00099 pass
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118 DEBUG = 0
00119
00120 class _ODB_Object:
00121 def get(self, data, options):
00122 return data
00123
00124 def set(self, val, options):
00125 return val
00126
00127 def convertTo(self, data, options):
00128 try:
00129 return str(data)
00130 except UnicodeEncodeError:
00131 return data.encode("utf-8")
00132
00133 def convertFrom(self, val, options):
00134 return val
00135
00136 def needQuoting(self): return False
00137 def needEscape(self): return False
00138 def needEncode(self): return False
00139 def compressionOk(self): return False
00140
00141 class _ODB_Integer(_ODB_Object):
00142 def odbType(self): return "kInteger"
00143 def sqlColType(self, options):
00144 return "integer"
00145
00146
00147
00148 def convertTo(self, data, options):
00149 try:
00150 return str(data)
00151 except (ValueError,TypeError):
00152 raise eInvalidData, data
00153
00154
00155
00156 def convertFrom(self, val, options):
00157 try:
00158 return int(val)
00159 except ValueError:
00160 return val
00161
00162 def needEscape(self): return False
00163
00164 class _ODB_IncInteger(_ODB_Integer):
00165 def odbType(self): return "kIncInteger"
00166 def sqlColType(self, options):
00167 return "integer"
00168
00169 class _ODB_Enumeration(_ODB_Integer):
00170 def odbType(self): return "kEnumeration"
00171 def set(self, data, options):
00172 try:
00173 n = options["enum_values"][data]
00174 except KeyError:
00175 raise eInvalidData, data
00176 return n
00177
00178 def get(self, val, options):
00179 return options['inv_enum_values'][int(val)]
00180
00181
00182 class _ODB_FixedString(_ODB_Object):
00183 def odbType(self): return "kFixedString"
00184 def sqlColType(self, options):
00185 sz = options.get('size', None)
00186 if sz is None: coltype = 'char'
00187 else: coltype = "char(%s)" % sz
00188
00189 return coltype
00190
00191 def needEscape(self): return True
00192 def needQuoting(self): return True
00193
00194 class _ODB_VarString(_ODB_FixedString):
00195 def odbType(self): return "kVarString"
00196 def sqlColType(self, options):
00197 sz = options.get('size', None)
00198 if sz is None: coltype = 'varchar'
00199 else: coltype = "varchar(%s)" % sz
00200 return coltype
00201
00202 class _ODB_BigString(_ODB_FixedString):
00203 def odbType(self): return "kBigString"
00204 def sqlColType(self, options): return "text"
00205
00206 def convertTo(self, data, options):
00207 if options.get("compress_ok", False):
00208 cdata = zlib.compress(data, 9)
00209 if len(cdata) < len(data):
00210 return cdata
00211 return data
00212
00213 def convertFrom(self, val, options):
00214 if options.get('compress_ok', False) and val:
00215 try:
00216 data = zlib.decompress(val)
00217 except zlib.error:
00218 data = val
00219 return data
00220 return val
00221
00222 def needEscape(self): return True
00223 def compressionOk(self): return True
00224
00225 class _ODB_Blob(_ODB_BigString):
00226 def odbType(self): return "kBlob"
00227 def sqlColType(self, options): return "text"
00228
00229 def needEscape(self): return False
00230 def needEncode(self): return True
00231 def compressionOk(self): return True
00232
00233 class _ODB_DateTime(_ODB_FixedString):
00234 def odbType(self): return "kDateTime"
00235 def sqlColType(self, options): return "datetime"
00236
00237 class _ODB_TimeStamp(_ODB_FixedString):
00238 def odbType(self): return "kTimeStamp"
00239 def sqlColType(self, options): return "timestamp"
00240
00241 class _ODB_CreatedStamp(_ODB_FixedString):
00242 def sqlColType(self, options): return "integer"
00243 def odbType(self): return "kCreatedStamp"
00244 def beforeInsert(self, row, colname):
00245 row[colname] = int(time.time())
00246
00247 class _ODB_CreatedStampMS(_ODB_FixedString):
00248 def sqlColType(self, options): return "real"
00249 def odbType(self): return "kCreatedStampMS"
00250 def beforeInsert(self, row, colname):
00251 row[colname] = time.time()
00252
00253 class _ODB_ModifiedStamp(_ODB_CreatedStamp):
00254 def odbType(self): return "kModifiedStamp"
00255 def beforeUpdate(self, row, colname):
00256 row[colname] = int(time.time())
00257
00258
00259
00260
00261 class _ODB_Real(_ODB_Object):
00262 def odbType(self): return "kReal"
00263 def sqlColType(self, options): return "real"
00264
00265 def convertTo(self, val, options):
00266 return str(val)
00267
00268 def convertFrom(self, val, options):
00269 try:
00270 return float(val)
00271 except (ValueError,TypeError):
00272 raise eInvalidData, val
00273
00274 def needEscape(self): return False
00275
00276 import guid
00277 class _ODB_GUID(_ODB_FixedString):
00278 def odbType(self): return "kGUID"
00279 def sqlColType(self, options):
00280 return "char(40)"
00281 def generate(self):
00282 return guid.generate()
00283
00284
00285 import fixedpoint
00286
00287 class ODB_FixedPoint(_ODB_VarString):
00288 def convertTo(self, data, options):
00289 return str(data)
00290
00291 def convertFrom(self, val, options):
00292 try:
00293 return fixedpoint.FixedPoint(val, 2)
00294 except TypeError:
00295 return val
00296
00297 kFixedPoint = ODB_FixedPoint()
00298
00299
00300
00301 kInteger = _ODB_Integer()
00302 kIncInteger = _ODB_IncInteger()
00303 kFixedString = _ODB_FixedString()
00304 kVarString = _ODB_VarString()
00305 kBigString = _ODB_BigString()
00306 kBlob = _ODB_Blob()
00307 kDateTime = _ODB_DateTime()
00308 kTimeStamp = _ODB_TimeStamp()
00309
00310 kModifiedStamp = _ODB_ModifiedStamp()
00311 kCreatedStamp = _ODB_CreatedStamp()
00312 kCreatedStampMS = _ODB_CreatedStampMS()
00313
00314 kReal = _ODB_Real()
00315 kEnumeration = _ODB_Enumeration()
00316 kGUID = _ODB_GUID()
00317
00318 def parseFieldType(dataStr):
00319 patStr = "([a-z]+)(\(([0-9]+)\))?"
00320 pat = re.compile(patStr)
00321 dataStr = dataStr.lower().strip()
00322 m = pat.match(dataStr)
00323 if not m:
00324 raise TypeError
00325
00326 dataType = m.group(1)
00327 arg = m.group(3)
00328
00329 if dataType == "integer":
00330 fieldType = kInteger
00331 elif dataType == "varchar":
00332 fieldType = kVarString
00333 elif dataType == "real":
00334 fieldType = kReal
00335 elif dataType == "datetime":
00336 fieldType = kDateTime
00337 elif dataType == "timestamp":
00338 fieldType = kTimeStamp
00339 elif dataType == "text":
00340 fieldType = kBigString
00341 else:
00342 fieldType = kVarString
00343
00344 return fieldType
00345
00346 class Cursor:
00347 def __init__(self, cursor):
00348 self.cursor = cursor
00349
00350 def description(self): return self.cursor.description
00351 def arraysize(self): return self.cursor.arraysize
00352 def rowcount(self): return self.cursor.rowcount
00353
00354 def execute(self, sql):
00355 try:
00356 return self.cursor.execute(sql)
00357 except:
00358 warn(sql)
00359 raise
00360
00361 def fetchone(self):
00362 return self.cursor.fetchone()
00363
00364 def fetchmany(self, size=None, keep=None):
00365 return self.cursor.fetchmany(size=size, keep=keep)
00366
00367 def fetchall(self):
00368 return self.cursor.fetchall()
00369
00370 def insert_id(self):
00371 raise "Unimplemented Error"
00372
00373 def close(self):
00374 return self.cursor.close()
00375
00376 class Connection:
00377 def __init__(self):
00378 self._conn = None
00379
00380 def cursor(self):
00381 return Cursor(self._conn.cursor())
00382
00383 def begin(self):
00384 pass
00385
00386 def commit(self):
00387 return self._conn.commit()
00388
00389 def rollback(self):
00390 return self._conn.rollback()
00391
00392 def close(self):
00393 return self._conn.close()
00394
00395 def auto_increment(self, coltype):
00396 return coltype, "AUTO_INCREMENT"
00397
00398 def createTable(self, sql, cursor):
00399 return sql
00400
00401 def supportsTriggers(self): return False
00402
00403 def listTriggers(self):
00404 raise Unimplemented, "triggers are not implemented in this connection type."
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414 class Database:
00415 def __init__(self, conn, debug=0):
00416 self._tables = {}
00417 self.conn = conn
00418 self._cursor = None
00419 self.compression_enabled = False
00420 self.debug = debug
00421 self.SQLError = conn.SQLError
00422
00423 self.__defaultRowClass = self.defaultRowClass()
00424 self.__defaultRowListClass = self.defaultRowListClass()
00425
00426 def getTableList(self):
00427 tblList = []
00428 for tblName in self._tables.keys():
00429 if tblName.find("_repl_") == 0: continue
00430 tblList.append(tblName)
00431 return tblList
00432
00433 def hasReplication(self):
00434 if self._tables.has_key("_repl_log"): return True
00435 return False
00436
00437 def enabledCompression(self):
00438 self.compression_enabled = True
00439
00440 def defaultCursor(self):
00441 if self._cursor is None:
00442 self._cursor = self.conn.cursor()
00443 return self._cursor
00444
00445 def escape_string(self, str):
00446 def subfn(m):
00447 c = m.group(0)
00448 return "%%%02X" % ord(c)
00449
00450 return re.sub("('|\0|%)",subfn,str)
00451
00452 def unescape_string(self, str):
00453 def subfn(m):
00454 hexnum = int(m.group(1),16)
00455 return "%c" % hexnum
00456 return re.sub("%(..)",subfn,str)
00457
00458
00459 def escape(self,str):
00460 return self.conn.escape(str)
00461 def encode(self,str):
00462 return self.conn.encode(str)
00463 def decode(self,str):
00464 return self.conn.decode(str)
00465
00466 def getDefaultRowClass(self): return self.__defaultRowClass
00467 def setDefaultRowClass(self, clss): self.__defaultRowClass = clss
00468 def getDefaultRowListClass(self): return self.__defaultRowListClass
00469 def setDefaultRowListClass(self, clss): self.__defaultRowListClass = clss
00470
00471 def defaultRowClass(self):
00472 return Row
00473
00474 def defaultRowListClass(self):
00475
00476 return list
00477
00478 def addTable(self, attrname, tblname, tblclass,
00479 rowClass = None,
00480 check = 0,
00481 create = 0,
00482 rowListClass = None,
00483 replication = None):
00484 tbl = tblclass(self, tblname, rowClass=rowClass, check=check,
00485 create=create, rowListClass=rowListClass,
00486 replication=replication)
00487 self._tables[attrname] = tbl
00488 return tbl
00489
00490 def close(self):
00491
00492
00493 self._tables = {}
00494
00495 if self.conn is not None:
00496 cursor = self.defaultCursor()
00497 cursor.close()
00498 self._cursor = None
00499
00500 self.conn.commit()
00501 self.conn.close()
00502 self.conn = None
00503
00504 def __del__(self):
00505 self.close()
00506
00507 def __getitem__(self, tblname):
00508 if not self._tables:
00509 raise AttributeError, "odb.Database: not initialized properly, self._tables does not exist"
00510
00511 try:
00512 return self._tables[tblname]
00513 except KeyError:
00514 raise AttributeError, "odb.Database: unknown table %s" % (tblname)
00515
00516
00517 def __getattr__(self, key):
00518 if key == "_tables":
00519 raise AttributeError, "odb.Database: not initialized properly, self._tables does not exist"
00520
00521 try:
00522 table_dict = getattr(self,"_tables")
00523 return table_dict[key]
00524 except KeyError:
00525 raise AttributeError, "odb.Database: unknown attribute %s" % (key)
00526
00527 def beginTransaction(self, cursor=None):
00528 if cursor is None:
00529 cursor = self.defaultCursor()
00530 dlog(DEV_UPDATE,"begin")
00531 self.conn.begin()
00532
00533
00534 def commitTransaction(self, cursor=None):
00535 if cursor is None:
00536 cursor = self.defaultCursor()
00537 dlog(DEV_UPDATE,"commit")
00538 self.conn.commit()
00539
00540
00541 def rollbackTransaction(self, cursor=None):
00542 if cursor is None:
00543 cursor = self.defaultCursor()
00544 dlog(DEV_UPDATE,"rollback")
00545 self.conn.rollback()
00546
00547
00548
00549
00550
00551
00552 def createTables(self):
00553 tables = self.listTables()
00554
00555 for attrname, tbl in self._tables.items():
00556 tblname = tbl.getTableName()
00557
00558 if tblname not in tables:
00559
00560 tbl.createTable()
00561 else:
00562 invalidAppCols, invalidDBCols = tbl.checkTable()
00563
00564
00565
00566 def createIndices(self):
00567 for attrname, tbl in self._tables.items():
00568 indices = self.listIndices(tbl.getTableName())
00569 for indexName, (columns, unique) in tbl.getIndices().items():
00570 if indexName in indices: continue
00571
00572
00573 tbl.createIndex(columns, indexName=indexName, unique=unique)
00574
00575 def dropIndices(self):
00576 cursor = self.defaultCursor()
00577 indices = self.listIndices("")
00578 for indexName in indices:
00579 sql = "DROP INDEX %s" % indexName
00580 cursor.execute(sql)
00581
00582 def createTriggers(self):
00583 triggers = self.listTriggers()
00584
00585 for attrname, tbl in self._tables.items():
00586 for triggerName, triggerSQL in tbl._triggers.items():
00587
00588 if triggerName in triggers:
00589 self.dropTrigger(triggerName)
00590 triggers.remove(triggerName)
00591 self.createTrigger(triggerName, triggerSQL)
00592
00593 if triggers:
00594 for trigger in triggers:
00595 self.dropTrigger(triggerName)
00596
00597 def createTrigger(self, triggerName, sql, cursor=None):
00598 if cursor is None: cursor = self.defaultCursor()
00599 cursor.execute(sql)
00600
00601 def dropTrigger(self, triggerName, cursor=None):
00602 if cursor is None: cursor = self.defaultCursor()
00603 sql = "DROP TRIGGER %s" % triggerName
00604 cursor.execute(sql)
00605
00606
00607
00608 def reflect(self):
00609 tables = self.listTables()
00610 for tablename in tables:
00611 tbl = self.addTable(tablename, tablename, _ReflectTable)
00612
00613
00614 def synchronizeSchema(self):
00615 tables = self.listTables()
00616
00617 cursor = self.defaultCursor()
00618 for attrname, tbl in self._tables.items():
00619 tblname = tbl.getTableName()
00620 self.conn.alterTableToMatch(tbl, cursor)
00621
00622 self.createIndices()
00623 if self.conn.supportsTriggers():
00624 self.createTriggers()
00625
00626 def listTables(self, cursor=None):
00627 if cursor is None: cursor = self.defaultCursor()
00628 return self.conn.listTables(cursor)
00629
00630 def listTriggers(self, cursor=None):
00631 if cursor is None: cursor = self.defaultCursor()
00632 return self.conn.listTriggers(cursor)
00633
00634 def listIndices(self, tableName, cursor=None):
00635 if cursor is None: cursor = self.defaultCursor()
00636 return self.conn.listIndices(tableName, cursor)
00637
00638
00639 def listFieldsDict(self, table_name, cursor=None):
00640 if cursor is None: cursor = self.defaultCursor()
00641 return self.conn.listFieldsDict(table_name, cursor)
00642
00643 def listFields(self, table_name, cursor=None):
00644 columns = self.listFieldsDict(table_name, cursor=cursor)
00645 return columns.keys()
00646
00647
00648
00649
00650
00651
00652
00653 class Table:
00654 def subclassinit(self):
00655 pass
00656 def __init__(self,database,table_name,
00657 rowClass = None,
00658 check = 0,
00659 create = 0,
00660 rowListClass = None,
00661 replication = None):
00662 self.__db = weakref.ref(database)
00663 self.__table_name = table_name
00664 self.__replication = replication
00665
00666 if rowClass:
00667 self.__defaultRowClass = rowClass
00668 else:
00669 self.__defaultRowClass = database.getDefaultRowClass()
00670
00671 if rowListClass:
00672 self.__defaultRowListClass = rowListClass
00673 else:
00674 self.__defaultRowListClass = database.getDefaultRowListClass()
00675
00676
00677
00678 self.__column_list = []
00679 self.__vcolumn_list = []
00680 self.__columns_locked = 0
00681 self.__has_value_column = 0
00682
00683 self.__indices = {}
00684 self._triggers = {}
00685
00686
00687 self.__col_def_hash = None
00688 self.__vcol_def_hash = None
00689 self.__primary_key_list = None
00690 self.__relations_by_table = {}
00691
00692 self.__fullTextSearchable = False
00693
00694
00695 self._defineRows()
00696
00697 if self.__replication:
00698 self.__replication.addTable(self)
00699
00700
00701 self.__lockColumnsAndInit()
00702
00703 self._defineRelations()
00704
00705 self.subclassinit()
00706
00707 if create:
00708 self.createTable()
00709
00710 if check:
00711 self.checkTable()
00712
00713 def hasReplication(self):
00714 if self.__replication is None: return False
00715 return True
00716
00717 def getReplication(self):
00718 return self.__replication
00719
00720 def _colTypeToSQLType(self, colname, coltype, options, singlePrimaryKey=0):
00721 coltype = coltype.sqlColType(options)
00722
00723 coldef = ""
00724
00725 if options.get('notnull', 0): coldef = coldef + " NOT NULL"
00726 if options.get('autoincrement', 0):
00727 coltype, acoldef = self.getDB().conn.auto_increment(coltype)
00728 if acoldef:
00729 coldef = coldef + " " + acoldef
00730
00731 if options.get('unique', 0): coldef = coldef + " UNIQUE"
00732
00733 if singlePrimaryKey:
00734 if options.get('primarykey', 0): coldef = coldef + " PRIMARY KEY"
00735
00736 if options.has_key('default'):
00737 defaultValue = options.get('default')
00738 if defaultValue is None:
00739 coldef = coldef + " DEFAULT NULL"
00740 elif type(defaultValue) in (types.IntType, types.LongType, types.FloatType):
00741 coldef = coldef + " DEFAULT %s" % defaultValue
00742 else:
00743 coldef = coldef + " DEFAULT '%s'" % defaultValue
00744
00745
00746 coldef = "%s %s %s" % (colname, coltype, coldef)
00747
00748 return coldef
00749
00750 def getDB(self):
00751 return self.__db()
00752
00753 def getTableName(self): return self.__table_name
00754 def setTableName(self, tablename): self.__table_name = tablename
00755
00756 def getIndices(self): return self.__indices
00757
00758 def _createTableSQL(self):
00759 primarykeys = self.getPrimaryKeyList()
00760 singlePrimaryKey = 0
00761 if len(primarykeys) == 1: singlePrimaryKey = 1
00762
00763 defs = []
00764 for colname, coltype, options in self.__column_list:
00765 defs.append(self._colTypeToSQLType(colname, coltype, options, singlePrimaryKey))
00766
00767 defs = string.join(defs, ", ")
00768
00769 primarykey_str = ""
00770 if singlePrimaryKey == 0:
00771 primarykeys = self.getPrimaryKeyList()
00772 if primarykeys:
00773 primarykey_str = ", PRIMARY KEY (" + string.join(primarykeys, ",") + ")"
00774
00775 if self.__fullTextSearchable:
00776 sql = self.getDB().conn.create_fullTextSearchTable(self.__table_name, self.__column_list)
00777 else:
00778 sql = "CREATE TABLE %s (%s %s)" % (self.__table_name, defs, primarykey_str)
00779 return sql
00780
00781 def createTable(self, cursor=None):
00782 if cursor is None: cursor = self.__db().defaultCursor()
00783 sql = self._createTableSQL()
00784
00785 sql = self.__db().conn.createTable(sql, cursor)
00786
00787 debug("CREATING TABLE:", sql)
00788
00789 cursor.execute(sql)
00790
00791 def dropTable(self, cursor=None):
00792 if cursor is None: cursor = self.__db().defaultCursor()
00793 try:
00794 cursor.execute("drop table %s" % self.__table_name)
00795 except self.getDB().SQLError, reason:
00796 pass
00797
00798 def deleteAllRows(self, cursor=None):
00799 if cursor is None: cursor = self.__db().defaultCursor()
00800 try:
00801 cursor.execute("delete from %s" % self.__table_name)
00802 except self.getDB().SQLError, reason:
00803 pass
00804
00805 def renameTable(self, newTableName, cursor=None):
00806 if cursor is None: cursor = self.__db().defaultCursor()
00807 try:
00808 cursor.execute("rename table %s to %s" % (self.__table_name, newTableName))
00809 except self.getDB().SQLError, reason:
00810 pass
00811
00812 self.setTableName(newTableName)
00813
00814 def getTableColumnsFromDB(self):
00815 return self.__db().listFieldsDict(self.__table_name)
00816
00817 def checkTable(self, warnflag=1):
00818 invalidDBCols = {}
00819 invalidAppCols = {}
00820
00821 dbcolumns = self.getTableColumnsFromDB()
00822 for coldef in self.__column_list:
00823 colname = coldef[0]
00824
00825 dbcoldef = dbcolumns.get(colname, None)
00826 if dbcoldef is None:
00827 invalidAppCols[colname] = 1
00828
00829 for colname, row in dbcolumns.items():
00830 coldef = self.__col_def_hash.get(colname, None)
00831 if coldef is None:
00832 invalidDBCols[colname] = 1
00833
00834 if self.__fullTextSearchable:
00835 if 'docid' in invalidAppCols: del invalidAppCols['docid']
00836 if 'rowid' in invalidAppCols: del invalidAppCols['rowid']
00837
00838 if warnflag == 1:
00839 if invalidDBCols:
00840 warn("----- WARNING ------------------------------------------")
00841 warn(" There are columns defined in the database schema that do")
00842 warn(" not match the application's schema: %s" % self.getTableName())
00843 warn(" columns:", invalidDBCols.keys())
00844 warn("--------------------------------------------------------")
00845
00846 if invalidAppCols:
00847 warn("----- WARNING ------------------------------------------")
00848 warn(" There are new columns defined in the application schema")
00849 warn(" that do not match the database's schema: %s" % self.getTableName())
00850 warn(" columns:", invalidAppCols.keys())
00851 warn("--------------------------------------------------------")
00852
00853 return invalidAppCols, invalidDBCols
00854
00855
00856 def alterTableToMatch(self, cursor=None):
00857 if cursor is None: cursor = self.defaultCursor()
00858 return self.conn.alterTableToMatch(cursor)
00859
00860 def addIndex(self, columns, indexName=None, unique=0):
00861 if indexName is None:
00862 indexName = self.getTableName() + "_index_" + string.join(columns, "_")
00863
00864 self.__indices[indexName] = (columns, unique)
00865
00866 def createIndex(self, columns, indexName=None, unique=0, cursor=None):
00867 if cursor is None: cursor = self.__db().defaultCursor()
00868 cols = string.join(columns, ",")
00869
00870 if indexName is None:
00871 indexName = self.getTableName() + "_index_" + string.join(columns, "_")
00872
00873 uniquesql = ""
00874 if unique:
00875 uniquesql = " UNIQUE"
00876 sql = "CREATE %s INDEX %s ON %s (%s)" % (uniquesql, indexName, self.getTableName(), cols)
00877 debug("creating index: ", sql)
00878 cursor.execute(sql)
00879
00880
00881
00882
00883 def hasColumn(self, column_name):
00884 try:
00885 coldef = self.getColumnDef(column_name)
00886 except eNoSuchColumn:
00887 return False
00888 return True
00889
00890 def getColumnDef(self,column_name):
00891 try:
00892 return self.__col_def_hash[column_name]
00893 except KeyError:
00894 try:
00895 return self.__vcol_def_hash[column_name]
00896 except KeyError:
00897
00898 if column_name.startswith("_"):
00899 parts = column_name[1:].split(".")
00900 if len(parts) == 2:
00901 table_column_name = parts[0]
00902 column_name = parts[1]
00903
00904 c_name,c_type,c_options = self.__col_def_hash[table_column_name]
00905 foreign_table = c_options["foreign_table"]
00906 foreign_key = c_options["foreign_key"]
00907
00908 a_table = self.getDB()[foreign_table]
00909 return a_table.getColumnDef(column_name)
00910
00911 raise eNoSuchColumn("no column (%s) on table '%s'" % (column_name,self.__table_name))
00912
00913 def getColumnList(self):
00914 return self.__column_list + self.__vcolumn_list
00915 def getAppColumnList(self):
00916 return self.__column_list
00917
00918 def databaseSizeForData_ColumnName_(self,data,col_name):
00919 try:
00920 col_def = self.__col_def_hash[col_name]
00921 except KeyError:
00922 try:
00923 col_def = self.__vcol_def_hash[col_name]
00924 except KeyError:
00925 raise eNoSuchColumn("no column (%s) on table %s" % (col_name,self.__table_name))
00926
00927 c_name,c_type,c_options = col_def
00928
00929 if c_type == kBigString:
00930 if c_options.get("compress_ok",0) and self.__db().compression_enabled:
00931 z_size = len(zlib.compress(data,9))
00932 r_size = len(data)
00933 if z_size < r_size:
00934 return z_size
00935 else:
00936 return r_size
00937 else:
00938 return len(data)
00939 else:
00940
00941 try:
00942 a = data[0]
00943 return len(data)
00944 except:
00945 return 4
00946
00947 def getColumnOption(self, columnName, optionName):
00948 a,b,options = self.getColumnDef(columnName)
00949 return options[optionName]
00950
00951
00952 def columnType(self, col_name):
00953 try:
00954 col_def = self.__col_def_hash[col_name]
00955 except KeyError:
00956 try:
00957 col_def = self.__vcol_def_hash[col_name]
00958 except KeyError:
00959 raise eNoSuchColumn("no column (%s) on table %s" % (col_name,self.__table_name))
00960
00961 c_name,c_type,c_options = col_def
00962 return c_type
00963
00964 def convertDataForColumn(self,data,col_name):
00965 try:
00966 col_def = self.__col_def_hash[col_name]
00967 except KeyError:
00968 try:
00969 col_def = self.__vcol_def_hash[col_name]
00970 except KeyError:
00971 raise eNoSuchColumn("no column (%s) on table %s" % (col_name,self.__table_name))
00972
00973 c_name,c_type,c_options = col_def
00974
00975 if c_type == kIncInteger:
00976 raise eInvalidData("invalid operation for column (%s:%s) on table (%s)" % (col_name,c_type,self.__table_name))
00977
00978 if data is None: return None
00979
00980 try:
00981 val = c_type.set(data, c_options)
00982 return val
00983 except eInvalidData, reason:
00984 raise eInvalidData("invalid data (%s) for col (%s:%s) on table (%s)" % (repr(data),col_name,c_type,self.__table_name))
00985
00986
00987 def getPrimaryKeyList(self):
00988 if self.__primary_key_list is not None:
00989 return tuple(self.__primary_key_list)
00990
00991 primary_keys = []
00992 for col_name, ctype, options in self.__column_list:
00993 if options.get('primarykey', 0): primary_keys.append(col_name)
00994
00995 return tuple(primary_keys)
00996
00997 def hasValueColumn(self):
00998 return self.__has_value_column
00999
01000 def hasColumn(self,name):
01001 return self.__col_def_hash.has_key(name)
01002 def hasVColumn(self,name):
01003 return self.__vcol_def_hash.has_key(name)
01004
01005
01006 def _defineRows(self):
01007 raise odb_Exception("can't instantiate base odb.Table type, make a subclass and override _defineRows()")
01008
01009 def _defineRelations(self):
01010 pass
01011
01012 def __lockColumnsAndInit(self):
01013
01014 if self.__has_value_column:
01015 self.d_addColumn("odb_value",kBlob,None, default='', notnull=1)
01016
01017
01018 self.__columns_locked = 1
01019
01020
01021 primary_key_list = []
01022 col_def_hash = {}
01023 for a_col in self.__column_list:
01024 name,type,options = a_col
01025 col_def_hash[name] = a_col
01026 if options.has_key('primarykey'):
01027 primary_key_list.append(name)
01028
01029 self.__col_def_hash = col_def_hash
01030 self.__primary_key_list = primary_key_list
01031
01032
01033
01034 if (not self.__has_value_column) and (len(self.__vcolumn_list) > 0):
01035 raise odb_Exception("can't define vcolumns on table without ValueColumn, call d_addValueColumn() in your _defineRows()")
01036
01037 vcol_def_hash = {}
01038 for a_col in self.__vcolumn_list:
01039 name,type,options = a_col
01040 vcol_def_hash[name] = a_col
01041
01042 self.__vcol_def_hash = vcol_def_hash
01043
01044
01045 def __checkColumnLock(self):
01046 if self.__columns_locked:
01047 raise odb_Exception("can't change column definitions outside of subclass' _defineRows() method!")
01048
01049
01050
01051
01052
01053
01054
01055
01056
01057
01058
01059
01060
01061
01062 def d_addColumn(self,col_name,ctype,size=None,primarykey = 0,
01063 notnull = 0,indexed=0,
01064 default=None,
01065 unique=0,
01066 autoincrement=0,
01067 autoguid=0,
01068 safeupdate=0,
01069 enum_values = None,
01070 no_export = 0,
01071 relations=None,
01072 foreign_key=None,
01073 compress_ok=0,
01074 int_date=0):
01075
01076 self.__checkColumnLock()
01077 if ctype in (kCreatedStamp, kModifiedStamp):
01078 int_date = 1
01079
01080 options = {}
01081 options['default'] = default
01082 if primarykey:
01083 options['primarykey'] = primarykey
01084 if unique:
01085 options['unique'] = unique
01086 if indexed:
01087 options['indexed'] = indexed
01088 self.addIndex((col_name,))
01089 if safeupdate:
01090 options['safeupdate'] = safeupdate
01091 if autoincrement:
01092 options['autoincrement'] = autoincrement
01093 if autoguid:
01094 options['autoguid'] = autoguid
01095 if ctype != kGUID:
01096 raise eInvalidData("cannot set autoguid for non-kGUID columns")
01097 if notnull:
01098 options['notnull'] = notnull
01099 if size:
01100 options['size'] = size
01101 if no_export:
01102 options['no_export'] = no_export
01103 if int_date:
01104 if ctype not in (kInteger, kCreatedStamp, kModifiedStamp):
01105 raise eInvalidData("can't flag columns int_date unless they are kInteger")
01106 else:
01107 options['int_date'] = int_date
01108
01109 if enum_values:
01110 options['enum_values'] = enum_values
01111 inv_enum_values = {}
01112 for k,v in enum_values.items():
01113 if inv_enum_values.has_key(v):
01114 raise eInvalidData("enum_values parameter must be a 1 to 1 mapping for Table(%s)" % self.__table_name)
01115 else:
01116 inv_enum_values[v] = k
01117 options['inv_enum_values'] = inv_enum_values
01118 if foreign_key:
01119 try:
01120 foreign_table, foreign_column_name = foreign_key.split(".")
01121 except ValueError:
01122 foreign_table = foreign_key
01123 foreign_column_name = col_name
01124 options['foreign_table'] = foreign_table
01125 options['foreign_key'] = foreign_column_name
01126
01127 self.__relations_by_table[foreign_table] = (col_name, foreign_column_name)
01128 if relations:
01129 options['relations'] = relations
01130 for a_relation in relations:
01131 table, foreign_column_name = a_relation
01132 if self.__relations_by_table.has_key(table):
01133 raise eInvalidData("multiple relations for the same foreign table are not yet supported" )
01134 self.__relations_by_table[table] = (col_name,foreign_column_name)
01135 if compress_ok and self.__db().compression_enabled:
01136 if ctype.compressionOk():
01137 options['compress_ok'] = 1
01138 else:
01139 raise eInvalidData("this column cannot be compress_ok=1")
01140
01141 self.__column_list.append( (col_name,ctype,options) )
01142
01143 def d_addInsertTrigger(self, triggerName, tsql):
01144 sql = "CREATE TRIGGER %s INSERT ON %s\n BEGIN\n %s;\n END;" % (triggerName, self.getTableName(), tsql)
01145 self._triggers[triggerName] = sql
01146
01147 def d_addUpdateTrigger(self, triggerName, tsql):
01148 sql = "CREATE TRIGGER %s UPDATE ON %s\n BEGIN\n %s;\n END;" % (triggerName, self.getTableName(), tsql)
01149 self._triggers[triggerName] = sql
01150
01151 def d_addUpdateColumnsTrigger(self, triggerName, columns, tsql):
01152 sql = "CREATE TRIGGER %s UPDATE OF %s ON %s\n BEGIN\n %s;\n END;" % (triggerName, string.join(columns, ","), self.getTableName(), tsql)
01153 self._triggers[triggerName] = sql
01154
01155 def d_addDeleteTrigger(self, triggerName, tsql):
01156 sql = "CREATE TRIGGER %s DELETE ON %s\n BEGIN\n %s;\n END;" % (triggerName, self.getTableName(), tsql)
01157 self._triggers[triggerName] = sql
01158
01159
01160 def d_addValueColumn(self):
01161 self.__checkColumnLock()
01162 self.__has_value_column = 1
01163
01164 def d_addVColumn(self,col_name,type,size=None,default=None):
01165 self.__checkColumnLock()
01166
01167 if (not self.__has_value_column):
01168 raise odb_Exception("can't define VColumns on table without ValueColumn, call d_addValueColumn() first")
01169
01170 options = {}
01171 if default:
01172 options['default'] = default
01173 if size:
01174 options['size'] = size
01175
01176 self.__vcolumn_list.append( (col_name,type,options) )
01177
01178 def getRelations(self):
01179 return self.__relations_by_table
01180
01181 def d_fullTextSearch(self):
01182 self.__fullTextSearchable = True
01183
01184 def d_belongsTo(self, col_name, tblNameStr=None, foreign_key=None, order=None):
01185 if foreign_key is None: foreign_key = col_name
01186
01187 self.__relations_by_table[tblNameStr] = (col_name, foreign_key)
01188
01189 def d_hasMany(self, tblname, col_name, foreign_key=None, order=None):
01190 if foreign_key is None: foreign_key = col_name
01191 self.__relations_by_table[tblname] = (col_name, foreign_key)
01192
01193 def d_hasOne(self, col_name, tblname, foreign_key=None, order=None):
01194 if foreign_key is None: foreign_key = col_name
01195
01196 a,b,options = self.getColumnDef(col_name)
01197 options['foreign.table'] = tblname
01198 options['foreign.key'] = foreign_key
01199 self.__relations_by_table[tblname] = (col_name, foreign_key)
01200
01201
01202
01203
01204
01205
01206
01207
01208
01209
01210
01211
01212 def _fixColMatchSpec(self,col_match_spec, should_match_unique_row = 0):
01213 if type(col_match_spec) == type([]):
01214 if type(col_match_spec[0]) != type((0,)):
01215 raise eInvalidMatchSpec("invalid types in match spec, use [(,)..] or (,)")
01216 elif type(col_match_spec) == type((0,)):
01217 col_match_spec = [ col_match_spec ]
01218 elif type(col_match_spec) == type(None):
01219 if should_match_unique_row:
01220 raise eNonUniqueMatchSpec("can't use a non-unique match spec (%s) here" % col_match_spec)
01221 else:
01222 return None
01223 else:
01224 raise eInvalidMatchSpec("invalid types in match spec, use [(,)..] or (,)")
01225
01226 unique_column_lists = []
01227
01228 if should_match_unique_row:
01229
01230
01231 my_primary_key_list = []
01232 for a_key in self.__primary_key_list:
01233 my_primary_key_list.append(a_key)
01234
01235
01236 for a_col in self.__column_list:
01237 col_name,a_type,options = a_col
01238 if options.has_key('unique'):
01239 unique_column_lists.append( (col_name, [col_name]) )
01240
01241 for indexName, (columns, unique) in self.getIndices().items():
01242 if unique:
01243 unique_column_lists.append((indexName, list(columns)))
01244
01245 unique_column_lists.append( ('primary_key', my_primary_key_list) )
01246
01247 new_col_match_spec = []
01248 for a_col in col_match_spec:
01249 name,val = a_col
01250
01251
01252 newname = name
01253 if not self.__col_def_hash.has_key(newname):
01254 raise eNoSuchColumn("no such column in match spec: '%s'" % str(newname))
01255
01256 new_col_match_spec.append( (newname,val) )
01257
01258 if should_match_unique_row:
01259 for name,a_list in unique_column_lists:
01260 try:
01261 a_list.remove(newname)
01262 except ValueError:
01263
01264 pass
01265
01266 if should_match_unique_row:
01267 for name,a_list in unique_column_lists:
01268 if len(a_list) == 0:
01269
01270
01271 return new_col_match_spec
01272
01273
01274
01275
01276 raise eNonUniqueMatchSpec("can't use a non-unique match spec (%s) here" % col_match_spec)
01277
01278 return new_col_match_spec
01279
01280 def __buildWhereClause (self, col_match_spec,other_clauses = None):
01281 sql_where_list = []
01282
01283 if not col_match_spec is None:
01284 for m_col in col_match_spec:
01285 m_col_name,m_col_val = m_col
01286 c_name,c_type,c_options = self.__col_def_hash[m_col_name]
01287
01288 c_name = "%s.%s" % (self.getTableName(), c_name)
01289
01290 if m_col_val is None:
01291 sql_where_list.append("%s = NULl" % (c_name,))
01292 else:
01293 try:
01294 val = c_type.convertFrom(m_col_val, c_options)
01295 except eInvalidData, data:
01296 raise eInvalidData("invalid literal for %s in table %s" % (repr(m_col_val),self.__table_name))
01297
01298
01299 if c_type.needEscape():
01300 val2 = self.__db().escape(val)
01301 elif c_type.needEncode():
01302 val2 = self.__db().encode(val)
01303 else:
01304 val2 = val
01305
01306 if c_type.needQuoting():
01307 sql_where_list.append("%s = '%s'" % (c_name, val2))
01308 else:
01309 sql_where_list.append("%s = %s" % (c_name, val2))
01310
01311
01312 if other_clauses is None:
01313 pass
01314 elif type(other_clauses) == type(""):
01315 sql_where_list = sql_where_list + [other_clauses]
01316 elif type(other_clauses) == type([]):
01317 sql_where_list = sql_where_list + other_clauses
01318 else:
01319 raise eInvalidData("unknown type of extra where clause: %s" % repr(other_clauses))
01320
01321 return sql_where_list
01322
01323 def __fetchRows(self,col_match_spec,cursor = None, where = None,
01324 order_by = None, limit_to = None,
01325 skip_to = None, join = None,
01326 join2 = None,
01327 column_list = None,
01328 raw_rows = False):
01329 if cursor is None:
01330 cursor = self.__db().defaultCursor()
01331
01332
01333 sql_columns = []
01334 if column_list is None:
01335 column_list = map(lambda x: x[0], self.__column_list)
01336
01337 for name in column_list:
01338 sql_columns.append("%s.%s" % (self.__table_name, name))
01339
01340
01341
01342 joined_cols = []
01343 joined_cols_hash = {}
01344 join_clauses = []
01345 if not join is None:
01346 for a_table,retrieve_foreign_cols in join:
01347 try:
01348 if isinstance(a_table, Table):
01349 atbl = a_table
01350 a_table = atbl.getTableName()
01351 else:
01352 parts = a_table.split(".")
01353 atbl = self
01354 for atbln in parts[:-1]:
01355 atbl = self.getDB()[atbln]
01356 a_table = parts[-1]
01357
01358 my_col,foreign_col = self.__relations_by_table[a_table]
01359 except KeyError,reason:
01360 raise eInvalidJoinSpec("can't find table %s in defined relations for %s (%s) reason=%s" % (a_table,self.__table_name, repr(self.__relations_by_table.items()), reason))
01361
01362 for a_col in retrieve_foreign_cols:
01363 full_col_name = "%s.%s" % (a_table,a_col)
01364 joined_cols_hash[full_col_name] = 1
01365 joined_cols.append(full_col_name)
01366 sql_columns.append( full_col_name )
01367
01368 join_clauses.append(" left join %s on %s.%s=%s.%s " % (a_table,atbl.getTableName(),my_col,a_table, foreign_col))
01369
01370 if not join2 is None:
01371 for col in join2:
01372 c_name,c_type,c_options = self.__col_def_hash[col]
01373 foreign_table = c_options["foreign_table"]
01374 foreign_key = c_options["foreign_key"]
01375
01376
01377 a_table = self.getDB()[foreign_table]
01378
01379
01380 joinTable = "_%s" % (col, )
01381 joinColumn = "%s.%s" % (joinTable, foreign_key)
01382
01383 for col_name, ctype, options in a_table.getAppColumnList():
01384 full_col_name = "%s.%s" % (joinTable, col_name)
01385
01386 joined_cols_hash[full_col_name] = 1
01387 joined_cols.append(full_col_name)
01388 sql_columns.append(full_col_name)
01389
01390 join_clauses.append(" left join %s AS %s on %s.%s=%s " % (a_table.getTableName(), joinTable, self.getTableName(), col, joinColumn))
01391
01392
01393
01394 sql = "SELECT %s FROM %s" % (string.join(sql_columns,","),
01395 self.__table_name)
01396
01397
01398 if join_clauses:
01399 sql = sql + string.join(join_clauses," ")
01400
01401
01402 sql_where_list = self.__buildWhereClause (col_match_spec,where)
01403 if sql_where_list:
01404 sql = sql + " WHERE %s" % (string.join(sql_where_list," and "))
01405
01406
01407 if order_by:
01408 ob = []
01409 for col in order_by:
01410 order = "asc"
01411 if type(col) == types.TupleType:
01412 col,order = col
01413 elif type(col) == types.StringType:
01414 aparts = col.split(" ", 1)
01415 if len(aparts) == 2:
01416 col,order = aparts
01417
01418 if col.find(".") == -1:
01419 obstr = "%s.%s" % (self.__table_name, col)
01420 else:
01421 obstr = col
01422
01423 if order == "desc":
01424 obstr = obstr + " " + order
01425
01426 ob.append(obstr)
01427
01428
01429 sql = sql + " ORDER BY %s " % string.join(ob,",")
01430
01431
01432 if not limit_to is None:
01433 if not skip_to is None:
01434
01435 if self.__db().conn.getConnType() == "sqlite":
01436 sql = sql + " LIMIT %s OFFSET %s " % (limit_to,skip_to)
01437 else:
01438 sql = sql + " LIMIT %s, %s" % (skip_to,limit_to)
01439 else:
01440 sql = sql + " LIMIT %s" % limit_to
01441 else:
01442 if not skip_to is None:
01443 raise eInvalidData("can't specify skip_to without limit_to in MySQL")
01444
01445 dlog(DEV_SELECT,sql)
01446
01447
01448 try:
01449 cursor.execute(sql)
01450 except:
01451 warn(sql)
01452 raise
01453
01454
01455 return_rows = self.__defaultRowListClass()
01456
01457
01458 all_rows = cursor.fetchall()
01459 if raw_rows == True:
01460 return all_rows
01461
01462 for a_row in all_rows:
01463 data_dict = {}
01464
01465 col_num = 0
01466
01467
01468
01469 for fullname in sql_columns:
01470 parts = string.split(fullname, ".", 1)
01471 table = parts[0]
01472 name = parts[1]
01473
01474 if self.__col_def_hash.has_key(name) or joined_cols_hash.has_key(fullname):
01475
01476 if joined_cols_hash.has_key(fullname):
01477 data_dict[fullname] = a_row[col_num]
01478 elif self.__col_def_hash.has_key(name):
01479 c_name,c_type,c_options = self.__col_def_hash[name]
01480 if a_row[col_num] is None:
01481 data_dict[name] = None
01482 else:
01483 aval = a_row[col_num]
01484
01485 if c_type.needEncode():
01486 aval = self.__db().decode(aval)
01487 data_dict[name] = c_type.convertFrom(aval, c_options)
01488 else:
01489 data_dict[name] = a_row[col_num]
01490
01491 col_num = col_num + 1
01492
01493 newrowobj = self.__defaultRowClass(self,data_dict,joined_cols = joined_cols)
01494 return_rows.append(newrowobj)
01495
01496
01497
01498 return return_rows
01499
01500 def __deleteRow(self,a_row,cursor = None):
01501 if cursor is None:
01502 cursor = self.__db().defaultCursor()
01503
01504
01505 match_spec = a_row.getPKMatchSpec()
01506 sql_where_list = self.__buildWhereClause (match_spec)
01507
01508 sql = "DELETE FROM %s WHERE %s" % (self.__table_name,
01509 string.join(sql_where_list," and "))
01510 dlog(DEV_UPDATE,sql)
01511 cursor.execute(sql)
01512
01513 if self.__replication:
01514 self.__replication.deleteRow(self, a_row)
01515
01516
01517
01518 def __updateRowList(self,a_row_list,cursor = None):
01519 if cursor is None:
01520 cursor = self.__db().defaultCursor()
01521
01522 for a_row in a_row_list:
01523 for name,c_type,options in self.__column_list:
01524 if hasattr(c_type, "beforeUpdate"):
01525 c_type.beforeUpdate(a_row, name)
01526
01527 update_list = a_row.changedList()
01528
01529
01530 sql_set_list = []
01531
01532 for a_change in update_list:
01533 col_name,col_val,col_inc_val = a_change
01534 c_name,c_type,c_options = self.__col_def_hash[col_name]
01535
01536 if c_type != kIncInteger and col_val is None:
01537 sql_set_list.append("%s = NULL" % c_name)
01538 elif c_type == kIncInteger and col_inc_val is None:
01539 sql_set_list.append("%s = 0" % c_name)
01540 else:
01541 if c_type == kIncInteger:
01542 sql_set_list.append("%s = %s + %d" % (c_name,c_name,long(col_inc_val)))
01543 else:
01544 if col_val is None:
01545 sql_set_list.append("%s = NULL" % c_name)
01546 else:
01547 val = c_type.convertTo(col_val, c_options)
01548
01549 if c_type.needEscape():
01550 val2 = self.__db().escape(val)
01551 elif c_type.needEncode():
01552 val2 = self.__db().encode(val)
01553 else:
01554 val2 = val
01555
01556 if c_type.needQuoting():
01557 sql_set_list.append("%s = '%s'" % (c_name, val2))
01558 else:
01559 sql_set_list.append("%s = %s" % (c_name, val2))
01560
01561
01562
01563 match_spec = a_row.getPKMatchSpec()
01564 sql_where_list = self.__buildWhereClause (match_spec)
01565
01566 if sql_set_list:
01567 sql = "UPDATE %s SET %s WHERE %s" % (self.__table_name,
01568 string.join(sql_set_list,","),
01569 string.join(sql_where_list," and "))
01570
01571 dlog(DEV_UPDATE,sql)
01572 try:
01573 cursor.execute(sql)
01574 except Exception, reason:
01575 if string.find(str(reason), "Duplicate entry") != -1:
01576 raise eDuplicateKey(reason)
01577 raise odb_Exception(reason)
01578
01579 if self.__replication:
01580 self.__replication.updateRow(self, a_row)
01581
01582 a_row.markClean()
01583
01584 def __insertRow(self,a_row_obj,cursor = None,replace=0):
01585 if cursor is None:
01586 cursor = self.__db().defaultCursor()
01587
01588 sql_col_list = []
01589 sql_data_list = []
01590 auto_increment_column_name = None
01591
01592 a_row_obj.changedList()
01593
01594 for name,c_type,options in self.__column_list:
01595 try:
01596 if not a_row_obj.has_key(name):
01597 if hasattr(c_type, "beforeInsert"):
01598 c_type.beforeInsert(a_row_obj, name)
01599
01600 data = a_row_obj._getRaw(name, convert=0)
01601
01602 sql_col_list.append(name)
01603 if data is None:
01604 sql_data_list.append("NULL")
01605 else:
01606 if c_type.needEscape():
01607 val = c_type.convertTo(data, options)
01608 val2 = self.__db().escape(val)
01609 elif c_type.needEncode():
01610 val = c_type.convertTo(data, options)
01611 val2 = self.__db().encode(val)
01612 else:
01613 val2 = data
01614
01615 if c_type.needQuoting():
01616 sql_data_list.append("'%s'" % val2)
01617 else:
01618 sql_data_list.append(str(val2))
01619
01620 except KeyError, reason:
01621 if options.has_key("autoguid"):
01622 sql_col_list.append(name)
01623 a_row_obj[name] = c_type.generate()
01624 sql_data_list.append("'%s'" % a_row_obj[name])
01625 elif options.has_key("autoincrement"):
01626 if auto_increment_column_name:
01627 raise eInternalError("two autoincrement columns (%s,%s) in table (%s)" % (auto_increment_column_name, name,self.__table_name))
01628 else:
01629 auto_increment_column_name = name
01630
01631 if replace:
01632 sql = "REPLACE INTO %s (%s) VALUES (%s)" % (self.__table_name,
01633 string.join(sql_col_list,","),
01634 string.join(sql_data_list,","))
01635 else:
01636 sql = "INSERT INTO %s (%s) VALUES (%s)" % (self.__table_name,
01637 string.join(sql_col_list,","),
01638 string.join(sql_data_list,","))
01639
01640 dlog(DEV_UPDATE,sql)
01641
01642 try:
01643 cursor.execute(sql)
01644 except Exception, reason:
01645
01646 log("error in statement: " + sql + "\n")
01647 if string.find(str(reason), "Duplicate entry") != -1:
01648 raise eDuplicateKey(reason)
01649 raise odb_Exception(reason)
01650
01651 if self.__replication:
01652 self.__replication.updateRow(self, a_row_obj)
01653
01654 if auto_increment_column_name:
01655 a_row_obj[auto_increment_column_name] = cursor.insert_id(self.__table_name, auto_increment_column_name)
01656
01657
01658
01659
01660
01661
01662
01663
01664
01665
01666
01667
01668
01669
01670 def r_deleteRow(self,a_row_obj, cursor = None):
01671 curs = cursor
01672 self.__deleteRow(a_row_obj, cursor = curs)
01673
01674
01675
01676
01677
01678
01679
01680
01681
01682 def r_updateRow(self,a_row_obj, cursor = None):
01683 curs = cursor
01684 self.__updateRowList([a_row_obj], cursor = curs)
01685
01686
01687
01688
01689
01690
01691
01692
01693 def r_insertRow(self,a_row_obj, cursor = None,replace=0):
01694 curs = cursor
01695 self.__insertRow(a_row_obj, cursor = curs,replace=replace)
01696
01697
01698
01699
01700
01701
01702
01703
01704
01705
01706
01707
01708
01709
01710
01711
01712
01713
01714 def deleteRow(self,col_match_spec, where=None):
01715 n_match_spec = self._fixColMatchSpec(col_match_spec)
01716 cursor = self.__db().defaultCursor()
01717
01718
01719 sql_where_list = self.__buildWhereClause (n_match_spec,where)
01720 if not sql_where_list:
01721 return
01722
01723 sql = "DELETE FROM %s WHERE %s" % (self.__table_name, string.join(sql_where_list," and "))
01724
01725 dlog(DEV_UPDATE,sql)
01726 cursor.execute(sql)
01727
01728
01729
01730
01731
01732
01733
01734
01735
01736
01737
01738 def fetchRow(self, col_match_spec, cursor = None, join2=None):
01739 n_match_spec = self._fixColMatchSpec(col_match_spec, should_match_unique_row = 1)
01740
01741 rows = self.__fetchRows(n_match_spec, cursor = cursor, join2=join2)
01742 if len(rows) == 0:
01743 raise eNoMatchingRows("no row matches %s" % repr(n_match_spec))
01744
01745 if len(rows) > 1:
01746 raise eInternalError("unique where clause shouldn't return > 1 row")
01747
01748 return rows[0]
01749
01750
01751
01752
01753
01754
01755
01756
01757
01758
01759 def fetchRows(self, col_match_spec = None, cursor = None,
01760 where = None, order_by = None, limit_to = None,
01761 skip_to = None, join = None,
01762 join2 = None,
01763 column_list = None,
01764 raw_rows = False):
01765 n_match_spec = self._fixColMatchSpec(col_match_spec)
01766
01767 return self.__fetchRows(n_match_spec,
01768 cursor = cursor,
01769 where = where,
01770 order_by = order_by,
01771 limit_to = limit_to,
01772 skip_to = skip_to,
01773 join = join,
01774 join2 = join2,
01775 column_list = column_list,
01776 raw_rows = raw_rows)
01777
01778 def fetchRowCount (self, col_match_spec = None,
01779 cursor = None, where = None):
01780 n_match_spec = self._fixColMatchSpec(col_match_spec)
01781 sql_where_list = self.__buildWhereClause (n_match_spec,where)
01782 sql = "SELECT COUNT(*) FROM %s" % self.__table_name
01783 if sql_where_list:
01784 sql = "%s WHERE %s" % (sql,string.join(sql_where_list," and "))
01785 if cursor is None:
01786 cursor = self.__db().defaultCursor()
01787 dlog(DEV_SELECT,sql)
01788 cursor.execute(sql)
01789 try:
01790 count, = cursor.fetchone()
01791 except TypeError:
01792 count = 0
01793 return count
01794
01795
01796
01797
01798
01799
01800
01801
01802
01803 def fetchAllRows(self, join2=None):
01804 try:
01805 return self.__fetchRows([], join2=join2)
01806 except eNoMatchingRows:
01807
01808 return self.__defaultRowListClass()
01809
01810 def newRow(self,replace=0,save=0,**kws):
01811 row = self.__defaultRowClass(self,None,create=1,replace=replace)
01812 for (cname, ctype, opts) in self.__column_list:
01813 if opts['default'] is not None and ctype is not kIncInteger:
01814 row[cname] = opts['default']
01815 if kws:
01816 for k,v in kws.items():
01817 row[k] = v
01818
01819 if save:
01820 row.save()
01821
01822 return row
01823
01824 def fetchRowUsingPrimaryKey(self, *args):
01825 kl = self.getPrimaryKeyList()
01826
01827 if len(kl) != len(args):
01828 raise eInternalData("wrong number of primary key arguments")
01829
01830 keylist = []
01831 i = 0
01832 for field in kl:
01833 keylist.append((field, args[i]))
01834 i = i + 1
01835
01836 return self.fetchRow(keylist)
01837
01838 def lookup(self, join2=None, **kws):
01839 keylist = []
01840 for k,v in kws.items():
01841 keylist.append((k,v))
01842
01843 try:
01844 row = self.fetchRow(keylist, join2=join2)
01845 except eNoMatchingRows:
01846 row = None
01847 return row
01848
01849 def lookupRows(self, join2=None, **kws):
01850 keylist = []
01851 for k,v in kws.items():
01852 keylist.append((k,v))
01853
01854 try:
01855 rows = self.fetchRows(keylist, join2=join2)
01856 except eNoMatchingRows:
01857 rows = []
01858 return rows
01859
01860 def lookupCreate(self, **kws):
01861 row = self.lookup(**kws)
01862
01863 if row is None:
01864 row = self.newRow()
01865 for k,v in kws.items():
01866 row[k] = v
01867
01868 return row
01869
01870
01871 class Row:
01872 __instance_data_locked = 0
01873 def subclassinit(self):
01874 pass
01875
01876 def __init__(self,_table,data_dict,create=0,joined_cols = None,replace=0):
01877
01878 self._inside_getattr = 0
01879 self._table = _table
01880 self._should_insert = create or replace
01881 self._should_replace = replace
01882 self._rowInactive = None
01883 self._joinedRows = []
01884
01885 self.__pk_match_spec = None
01886 self.__vcoldata = {}
01887 self.__inc_coldata = {}
01888
01889 self.__joined_cols_dict = {}
01890 for a_col in joined_cols or []:
01891 self.__joined_cols_dict[a_col] = 1
01892
01893 if create:
01894 self.__coldata = {}
01895 else:
01896 if type(data_dict) != type({}):
01897 raise eInternalError, "rowdict instantiate with bad data_dict"
01898 self.__coldata = data_dict
01899 self.__unpackVColumn()
01900
01901 self.markClean()
01902
01903 self.subclassinit()
01904 self.__instance_data_locked = 1
01905
01906 def getTable(self):
01907 return self._table
01908
01909 def getDB(self):
01910 return self._table.getDB()
01911
01912 def joinRowData(self,another_row):
01913 self._joinedRows.append(another_row)
01914
01915 def getPKMatchSpec(self):
01916 return self.__pk_match_spec
01917
01918 def isClean(self):
01919 changed_list = self.changedList()
01920 if len(changed_list):
01921 return 0
01922 return 1
01923
01924 def markClean(self):
01925 self.__vcolchanged = 0
01926 self.__colchanged_dict = {}
01927
01928 for key in self.__inc_coldata.keys():
01929 self.__coldata[key] = self.__coldata.get(key, 0) + self.__inc_coldata[key]
01930
01931 self.__inc_coldata = {}
01932
01933 if not self._should_insert:
01934
01935 new_match_spec = []
01936 for col_name in self._table.getPrimaryKeyList():
01937 try:
01938 rdata = self[col_name]
01939 except KeyError:
01940 raise eInternalError, "must have primary key data filled in to save %s:Row(col:%s)" % (self._table.getTableName(),col_name)
01941
01942 new_match_spec.append( (col_name, rdata) )
01943 self.__pk_match_spec = new_match_spec
01944
01945 def __unpackVColumn(self):
01946 if self._table.hasValueColumn():
01947 if self.__coldata.has_key("odb_value") and self.__coldata['odb_value']:
01948 val = self.__coldata['odb_value']
01949 val2 = self.getDB().unescape_string(val)
01950
01951 try:
01952 self.__vcoldata = marshal.loads(val2)
01953 except ValueError:
01954
01955
01956
01957
01958 raise
01959
01960 def __packVColumn(self):
01961 if self._table.hasValueColumn():
01962 self.__coldata['odb_value'] = self.getDB().escape_string(marshal.dumps(self.__vcoldata))
01963 self.__colchanged_dict['odb_value'] = 1
01964
01965
01966
01967
01968 def __del__(self):
01969
01970 changed_list = self.changedList()
01971 if len(changed_list):
01972 info = "unsaved Row for table (%s) lost, call discard() to avoid this error. Lost changes: %s\n" % (self._table.getTableName(), repr(changed_list)[:256])
01973 if 0:
01974 raise eUnsavedObjectLost, info
01975 else:
01976 sys.stderr.write(info)
01977
01978
01979 def __repr__(self):
01980 return "Row from (%s): %s" % (self._table.getTableName(),repr(self.__coldata) + repr(self.__vcoldata))
01981
01982
01983
01984 def __getattr__(self,key):
01985 if self._inside_getattr:
01986 raise AttributeError, "recursively called __getattr__ (%s,%s)" % (key,self._table.getTableName())
01987 try:
01988 self._inside_getattr = 1
01989 try:
01990 return self[key]
01991 except KeyError:
01992 if self._table.hasColumn(key) or self._table.hasVColumn(key):
01993 return None
01994 else:
01995 raise AttributeError, "unknown field '%s' in Row(%s)" % (key,self._table.getTableName())
01996 finally:
01997 self._inside_getattr = 0
01998
01999 def __setattr__(self,key,val):
02000 if not self.__instance_data_locked:
02001 self.__dict__[key] = val
02002 else:
02003 my_dict = self.__dict__
02004 if my_dict.has_key(key):
02005 my_dict[key] = val
02006 else:
02007
02008 try:
02009 self[key] = val
02010 except KeyError, reason:
02011 raise AttributeError, reason
02012
02013
02014
02015
02016 def _getRaw(self, key, convert=1):
02017 self.checkRowActive()
02018
02019 try:
02020 c_name, c_type, c_options = self._table.getColumnDef(key)
02021 except eNoSuchColumn:
02022
02023
02024
02025 c_type = kVarString
02026 c_options = {}
02027 c_name = key
02028
02029 if c_type == kIncInteger:
02030 c_data = self.__coldata.get(key, 0)
02031 if c_data is None: c_data = 0
02032 i_data = self.__inc_coldata.get(key, 0)
02033 if i_data is None: i_data = 0
02034 return c_data + i_data
02035
02036 try:
02037 if convert:
02038 return c_type.get(self.__coldata[key], c_options)
02039 else:
02040 return self.__coldata[key]
02041
02042 except KeyError:
02043 try:
02044 return self.__vcoldata[key]
02045 except KeyError:
02046 for a_joined_row in self._joinedRows:
02047 try:
02048 return a_joined_row[key]
02049 except KeyError:
02050 pass
02051
02052 raise KeyError, "unknown column %s in '%s'" % (key,self.getTable().getTableName())
02053
02054
02055 def __getitem__(self,key):
02056 return self._getRaw(key)
02057
02058 def __setitem__(self,key,data):
02059 self.checkRowActive()
02060
02061 try:
02062 newdata = self._table.convertDataForColumn(data,key)
02063 except eNoSuchColumn, reason:
02064 raise KeyError, reason
02065
02066 if self._table.hasColumn(key):
02067 self.__coldata[key] = newdata
02068 self.__colchanged_dict[key] = 1
02069 elif self._table.hasVColumn(key):
02070 self.__vcoldata[key] = newdata
02071 self.__vcolchanged = 1
02072 else:
02073 for a_joined_row in self._joinedRows:
02074 try:
02075 a_joined_row[key] = data
02076 return
02077 except KeyError:
02078 pass
02079 raise KeyError, "unknown column name %s" % key
02080
02081
02082 def __delitem__(self,key,data):
02083 self.checkRowActive()
02084
02085 if self.table.hasVColumn(key):
02086 del self.__vcoldata[key]
02087 else:
02088 for a_joined_row in self._joinedRows:
02089 try:
02090 del a_joined_row[key]
02091 return
02092 except KeyError:
02093 pass
02094 raise KeyError, "unknown column name %s" % key
02095
02096
02097 def copyFrom(self,source):
02098 for name,t,options in self._table.getColumnList():
02099 if not options.has_key("autoincrement"):
02100 self[name] = source[name]
02101
02102
02103
02104
02105 def keys(self):
02106 self.checkRowActive()
02107
02108 key_list = []
02109 for name,t,options in self._table.getColumnList():
02110 key_list.append(name)
02111 for name in self.__joined_cols_dict.keys():
02112 key_list.append(name)
02113
02114 for a_joined_row in self._joinedRows:
02115 key_list = key_list + a_joined_row.keys()
02116
02117 return key_list
02118
02119
02120 def items(self):
02121 self.checkRowActive()
02122
02123 item_list = []
02124 for name,t,options in self._table.getColumnList():
02125 item_list.append( (name,self[name]) )
02126
02127 for name in self.__joined_cols_dict.keys():
02128 item_list.append( (name,self[name]) )
02129
02130 for a_joined_row in self._joinedRows:
02131 item_list = item_list + a_joined_row.items()
02132
02133
02134 return item_list
02135
02136 def values(elf):
02137 self.checkRowActive()
02138
02139 value_list = self.__coldata.values() + self.__vcoldata.values()
02140
02141 for a_joined_row in self._joinedRows:
02142 value_list = value_list + a_joined_row.values()
02143
02144 return value_list
02145
02146
02147 def __len__(self):
02148 self.checkRowActive()
02149
02150 my_len = len(self.__coldata) + len(self.__vcoldata)
02151
02152 for a_joined_row in self._joinedRows:
02153 my_len = my_len + len(a_joined_row)
02154
02155 return my_len
02156
02157 def has_key(self,key):
02158 self.checkRowActive()
02159
02160 if self.__coldata.has_key(key) or self.__vcoldata.has_key(key):
02161 return 1
02162 else:
02163
02164 for a_joined_row in self._joinedRows:
02165 if a_joined_row.has_key(key):
02166 return 1
02167 return 0
02168
02169 def get(self,key,default = None):
02170 self.checkRowActive()
02171
02172
02173
02174 if self.__coldata.has_key(key):
02175 return self.__coldata[key]
02176 elif self.__vcoldata.has_key(key):
02177 return self.__vcoldata[key]
02178 else:
02179 for a_joined_row in self._joinedRows:
02180 try:
02181 return a_joined_row.get(key,default)
02182 except eNoSuchColumn:
02183 pass
02184
02185 if self._table.hasColumn(key):
02186 return default
02187
02188 raise eNoSuchColumn, "no such column %s" % key
02189
02190 def inc(self,key,count=1):
02191 self.checkRowActive()
02192
02193 if self._table.hasColumn(key):
02194 try:
02195 self.__inc_coldata[key] = self.__inc_coldata[key] + count
02196 except KeyError:
02197 self.__inc_coldata[key] = count
02198
02199 self.__colchanged_dict[key] = 1
02200 else:
02201 raise AttributeError, "unknown field '%s' in Row(%s)" % (key,self._table.getTableName())
02202
02203
02204
02205
02206
02207
02208 def fillDefaults(self):
02209 for field_def in self._table.fieldList():
02210 name,type,size,options = field_def
02211 if options.has_key("default"):
02212 self[name] = options["default"]
02213
02214
02215
02216
02217
02218
02219
02220
02221 def changedList(self):
02222 if self.__vcolchanged:
02223 self.__packVColumn()
02224
02225 changed_list = []
02226 for a_col in self.__colchanged_dict.keys():
02227 changed_list.append( (a_col,self.get(a_col,None),self.__inc_coldata.get(a_col,None)) )
02228
02229 return changed_list
02230
02231 def discard(self):
02232 self.__coldata = None
02233 self.__vcoldata = None
02234 self.__colchanged_dict = {}
02235 self.__vcolchanged = 0
02236
02237 def delete(self,cursor = None):
02238 self.checkRowActive()
02239
02240 fromTable = self._table
02241 curs = cursor
02242 fromTable.r_deleteRow(self,cursor=curs)
02243 self._rowInactive = "deleted"
02244
02245
02246
02247 def save(self,cursor = None):
02248 toTable = self._table
02249
02250 self.checkRowActive()
02251
02252 if self._should_insert:
02253 toTable.r_insertRow(self,replace=self._should_replace)
02254 self._should_insert = 0
02255 self._should_replace = 0
02256 self.markClean()
02257 else:
02258 curs = cursor
02259 toTable.r_updateRow(self,cursor = curs)
02260
02261
02262
02263
02264 def checkRowActive(self):
02265 if self._rowInactive:
02266 raise eInvalidData, "row is inactive: %s" % self._rowInactive
02267
02268 def databaseSizeForColumn(self,key):
02269 return self._table.databaseSizeForData_ColumnName_(self[key],key)
02270
02271
02272
02273
02274
02275 class _ReflectTable(Table):
02276 def _defineRows(self):
02277 fields = self.getDB().listFieldsDict(self.getTableName())
02278 for fieldname, dict in fields.items():
02279 fieldStr = dict[2]
02280
02281 fieldType = parseFieldType(fieldStr)
02282
02283 self.d_addColumn(fieldname, fieldType)
02284
02285
02286
02287
02288 class ReplicationLog:
02289 def __init__(self, db):
02290 self.__db = weakref.ref(db)
02291
02292 self._server_guid = None
02293
02294 self.getDB().addTable("_repl_keyval", "repl_keyval", Replication_KeyValueTable)
02295 self.getDB().addTable("_repl_log", "repl_log", Replication_LogTable,
02296 rowClass = Replication_LogRow)
02297 self.getDB().addTable("_repl_deleted", "repl_deleted", Replication_DeletedTable)
02298
02299 def getDB(self):
02300 return self.__db()
02301
02302 def addTable(self, tbl):
02303 tbl.d_addColumn("__modified", kModifiedStamp, no_export=1)
02304
02305 def getServerGUID(self):
02306 if not self._server_guid:
02307 row = self.getDB()._repl_keyval.lookup(key="server_guid")
02308 self._server_guid = row.val
02309
02310 return self._server_guid
02311
02312 def __getPrimaryKeyForTable(self, tbl, row):
02313 keyList = []
02314
02315 for col_name in tbl.getPrimaryKeyList():
02316 val = str(row[col_name])
02317 keyList.append("%s,%s,%s" % (col_name, len(val), val))
02318 key = string.join(keyList, ",")
02319 return key
02320
02321 def __recordUpdate(self, tbl, key):
02322 rrow = self.getDB()._repl_log.newRow(replace=1)
02323 rrow.tableName = tbl.getTableName()
02324 rrow.server_guid = self.getServerGUID()
02325 rrow.key = key
02326 rrow.save()
02327
02328 def updateRow(self, tbl, row):
02329
02330
02331 key = self.__getPrimaryKeyForTable(tbl, row)
02332 self.__recordUpdate(tbl, key)
02333
02334 def deleteRow(self, tbl, row):
02335
02336
02337 key = self.__getPrimaryKeyForTable(tbl, row)
02338
02339
02340 drow = self.getDB()._repl_deleted.newRow(replace=1)
02341 drow.tableName = tbl.getTableName()
02342 drow.key = key
02343 drow.save()
02344
02345 self.__recordUpdate(tbl, key)
02346
02347 def getLogSince(self, tableName, startTime, endTime):
02348 rows = self.getDB()._repl_log.fetchRows(('tableName', tableName), where = ['timestamp >= %s' % startTime, 'timestamp <= %s' % endTime])
02349 return rows
02350
02351
02352
02353
02354
02355 class Replication_KeyValueTable(Table):
02356 def _defineRows(self):
02357 self.d_addColumn("key", kVarString, primarykey = 1)
02358 self.d_addColumn("val", kVarString)
02359
02360 def createTable(self, cursor=None):
02361 Table.createTable(self, cursor=cursor)
02362
02363 self.__makeServerGUID()
02364
02365 def __makeServerGUID(self):
02366 server_guid = guid.generate()
02367 row = self.getDB()._repl_keyval.newRow(replace=1,key="server_guid", val=server_guid)
02368 row.save()
02369 return server_guid
02370
02371
02372 class Replication_LogTable(Table):
02373 def _defineRows(self):
02374 self.d_addColumn("server_guid", kGUID, primarykey = 1)
02375
02376
02377 self.d_addColumn("timestamp", kCreatedStampMS, primarykey = 1)
02378
02379
02380 self.d_addColumn("rep_guid", kGUID, primarykey = 1, autoguid=1)
02381
02382 self.d_addColumn("tableName", kVarString)
02383
02384 self.d_addColumn("key", kVarString)
02385
02386
02387
02388 class Replication_LogRow(Row):
02389 def pkey(self):
02390 return repl_parsePrimaryKey(self.key)
02391
02392
02393 class Replication_DeletedTable(Table):
02394 def _defineRows(self):
02395 self.d_addColumn("tableName", kVarString, primarykey = 1)
02396
02397
02398 self.d_addColumn("key", kVarString, primarykey = 1)
02399
02400
02401 self.d_addColumn("timestamp", kCreatedStampMS)
02402
02403
02404 def repl_parsePrimaryKey(key):
02405 i = 0
02406
02407 keyList = []
02408 while 1:
02409 j = key.find(",", i)
02410 if j == -1: break
02411 columnName = key[i:j]
02412
02413 j = j + 1
02414 k = key.find(",", j)
02415 if k == -1: break
02416
02417 valLength = key[j:k]
02418
02419 k = k + 1
02420 i = k + int(valLength)
02421
02422 val = key[k:i]
02423
02424 keyList.append((columnName, val))
02425 return keyList
02426
02427
02428