$search
00001 #!/usr/bin/env python 00002 # 00003 # odb.py 00004 # 00005 # Object Database Api 00006 # 00007 # Written by David Jeske <jeske@chat.net>, 2001/07. 00008 # Inspired by eGroups' sqldb.py originally written by Scott Hassan circa 1998. 00009 # 00010 # Copyright (C) 2001, by David Jeske 00011 # 00012 # Goals: 00013 # - a simple object-like interface to database data 00014 # - database independent (someday) 00015 # - relational-style "rigid schema definition" 00016 # - object style easy-access 00017 # 00018 # Example: 00019 # 00020 # import odb 00021 # 00022 # # define table 00023 # class AgentsTable(odb.Table): 00024 # def _defineRows(self): 00025 # self.d_addColumn("agent_id",kInteger,None,primarykey = 1,autoincrement = 1) 00026 # self.d_addColumn("login",kVarString,200,notnull=1) 00027 # self.d_addColumn("ticket_count",kIncInteger,None) 00028 # 00029 # if __name__ == "__main__": 00030 # 00031 # # open database 00032 # import odb_mysql 00033 # conn = odb_mysql.Connection(host = 'localhost', 00034 # user='username', 00035 # passwd = 'password', 00036 # db='testdb') 00037 # db = Database(conn) 00038 # tbl = AgentsTable(db,"agents") 00039 # 00040 # # create row 00041 # agent_row = tbl.newRow() 00042 # agent_row.login = "foo" 00043 # agent_row.save() 00044 # 00045 # # fetch row (must use primary key) 00046 # try: 00047 # get_row = tbl.fetchRow( ('agent_id', agent_row.agent_id) ) 00048 # except odb.eNoMatchingRows: 00049 # print "this is bad, we should have found the row" 00050 # 00051 # # fetch rows (can return empty list) 00052 # list_rows = tbl.fetchRows( ('login', "foo") ) 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 # COLUMN TYPES 00103 ################ ###################### 00104 # typename ####################### size data means: 00105 # # # 00106 ## kInteger = "kInteger" # - 00107 ## kFixedString = "kFixedString" # size 00108 ## kVarString = "kVarString" # maxsize 00109 ## kBigString = "kBigString" # - 00110 ## kIncInteger = "kIncInteger" # - 00111 ## kDateTime = "kDateTime" 00112 ## kTimeStamp = "kTimeStamp" 00113 ## kReal = "kReal" 00114 00115 ## kEnumeration = "kEnumeration" # - 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 ## convertTo - converts 'data' to database representation from the 00147 ## local representation 00148 def convertTo(self, data, options): 00149 try: 00150 return str(data) 00151 except (ValueError,TypeError): 00152 raise eInvalidData, data 00153 00154 ## convertFrom - converts 'val' from database representation to the 00155 ## local representation 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 # DATABASE 00409 # 00410 # this will ultimately turn into a mostly abstract base class for 00411 # the DB adaptors for different database types.... 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 # base type is list... 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 ## for name, tbl in self._tables.items(): 00492 ## tbl.db = None 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 #cursor.execute("begin") 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 #cursor.execute("commit") 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 #cursor.execute("rollback") 00547 00548 ## 00549 ## schema creation code 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 # warn("table %s does not exist" % tblname) 00560 tbl.createTable() 00561 else: 00562 invalidAppCols, invalidDBCols = tbl.checkTable() 00563 00564 ## self.alterTableToMatch(tbl) 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 # warn("creating index for %s for %s" % (tbl.getTableName(), str(columns))) 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 ## parse the schema of an existing db and build table objects to 00607 ## reflect the schema. 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 # Table 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 # get this stuff ready! 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 # this will be used during init... 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 # ask the subclass to def his rows 00695 self._defineRows() 00696 00697 if self.__replication: 00698 self.__replication.addTable(self) 00699 00700 # get ready to run! 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) # clean out the table 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) # clean out the table 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 ## Column Definition 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 ## handle joined columns 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 # really simplistic database size computation: 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 # add a 'odb_value column' before we lockdown the table def 01014 if self.__has_value_column: 01015 self.d_addColumn("odb_value",kBlob,None, default='', notnull=1) 01016 # self.d_addColumn("odb_value",kBigString,None, default='', notnull=1) 01017 01018 self.__columns_locked = 1 01019 # walk column list and make lookup hashes, primary_key_list, etc.. 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 # setup the value columns! 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 # table definition methods, these are only available while inside the 01050 # subclass's _defineRows method 01051 # 01052 # Ex: 01053 # 01054 # import odb 01055 # class MyTable(odb.Table): 01056 # def _defineRows(self): 01057 # self.d_addColumn("id",kInteger,primarykey = 1,autoincrement = 1) 01058 # self.d_addColumn("name",kVarString,120) 01059 # self.d_addColumn("type",kInteger, 01060 # enum_values = { 0 : "alive", 1 : "dead" } 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 # _checkColMatchSpec(col_match_spec,should_match_unique_row = 0) 01204 # 01205 # raise an error if the col_match_spec contains invalid columns, or 01206 # (in the case of should_match_unique_row) if it does not fully specify 01207 # a unique row. 01208 # 01209 # NOTE: we don't currently support where clauses with value column fields! 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 # first the primary key list 01231 my_primary_key_list = [] 01232 for a_key in self.__primary_key_list: 01233 my_primary_key_list.append(a_key) 01234 01235 # then other unique keys 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 # newname = string.lower(name) 01251 # what is this doing?? - jeske 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 # it's okay if they specify too many columns! 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 # we matched at least one unique colum spec! 01270 # log("using unique column (%s) for query %s" % (name,col_match_spec)) 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 # build column list 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 # build join information 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 #joinTable = "_%s_%s" % (col, a_table.getTableName(), ) 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 # start buildling SQL 01394 sql = "SELECT %s FROM %s" % (string.join(sql_columns,","), 01395 self.__table_name) 01396 01397 # add join clause 01398 if join_clauses: 01399 sql = sql + string.join(join_clauses," ") 01400 01401 # add where clause elements 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 # add order by clause 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 # add limit 01432 if not limit_to is None: 01433 if not skip_to is None: 01434 ## log("limit,skip = %s,%s" % (limit_to,skip_to)) 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 #warn(sql) 01448 try: 01449 cursor.execute(sql) 01450 except: 01451 warn(sql) 01452 raise 01453 01454 # create defaultRowListClass instance... 01455 return_rows = self.__defaultRowListClass() 01456 01457 # should do fetchmany! 01458 all_rows = cursor.fetchall() 01459 if raw_rows == True: ## bug out is the user justs want the raw rows 01460 return all_rows 01461 01462 for a_row in all_rows: 01463 data_dict = {} 01464 01465 col_num = 0 01466 01467 # for a_col in cursor.description: 01468 # (name,type_code,display_size,internal_size,precision,scale,null_ok) = a_col 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 # only include declared columns! 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 # build the where clause! 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 # build the set list! 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 # build the where clause! 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 # sys.stderr.write("errror in statement: " + sql + "\n") 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 # Helper methods for Rows... 01659 # ---------------------------------------------------- 01660 01661 01662 01663 ##################### 01664 # r_deleteRow(a_row_obj,cursor = None) 01665 # 01666 # normally this is called from within the Row "delete()" method 01667 # but you can call it yourself if you want 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 # r_updateRow(a_row_obj,cursor = None) 01677 # 01678 # normally this is called from within the Row "save()" method 01679 # but you can call it yourself if you want 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 # InsertRow(a_row_obj,cursor = None) 01688 # 01689 # normally this is called from within the Row "save()" method 01690 # but you can call it yourself if you want 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 # Public Methods 01700 # ---------------------------------------------------- 01701 01702 01703 01704 ##################### 01705 # deleteRow(col_match_spec) 01706 # 01707 # The col_match_spec paramaters must include all primary key columns. 01708 # 01709 # Ex: 01710 # a_row = tbl.fetchRow( ("order_id", 1) ) 01711 # a_row = tbl.fetchRow( [ ("order_id", 1), ("enterTime", now) ] ) 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 # build sql where clause elements 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 # fetchRow(col_match_spec) 01730 # 01731 # The col_match_spec paramaters must include all primary key columns. 01732 # 01733 # Ex: 01734 # a_row = tbl.fetchRow( ("order_id", 1) ) 01735 # a_row = tbl.fetchRow( [ ("order_id", 1), ("enterTime", now) ] ) 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 # fetchRows(col_match_spec) 01753 # 01754 # Ex: 01755 # a_row_list = tbl.fetchRows( ("order_id", 1) ) 01756 # a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] ) 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 # fetchAllRows() 01798 # 01799 # Ex: 01800 # a_row_list = tbl.fetchRows( ("order_id", 1) ) 01801 # a_row_list = tbl.fetchRows( [ ("order_id", 1), ("enterTime", now) ] ) 01802 01803 def fetchAllRows(self, join2=None): 01804 try: 01805 return self.__fetchRows([], join2=join2) 01806 except eNoMatchingRows: 01807 # else return empty list... 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 # stop recursive __getattr__ 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 # rebuild primary column match spec 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 ## #warn(self) 01955 ## val2 = self.getDB().decode(val) 01956 ## warn("val2", repr(val2)) 01957 ## self.__vcoldata = marshal.loads(val2) 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 ## ----- utility stuff ---------------------------------- 01967 01968 def __del__(self): 01969 # check for unsaved changes 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 ## ---- class emulation -------------------------------- 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 # try and put it into the rowdata 02008 try: 02009 self[key] = val 02010 except KeyError, reason: 02011 raise AttributeError, reason 02012 02013 02014 ## ---- dict emulation --------------------------------- 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 # Ugh, this sucks, we can't determine the type for a joined 02023 # row, so we just default to kVarString and let the code below 02024 # determine if this is a joined column or not 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 # make sure that .keys(), and .items() come out in a nice order! 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 ## real interface 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 # changedList() 02216 # 02217 # returns a list of tuples for the columns which have changed 02218 # 02219 # changedList() -> [ ('name', 'fred'), ('age', 20) ] 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() # rebuild the primary key list 02257 else: 02258 curs = cursor 02259 toTable.r_updateRow(self,cursor = curs) 02260 02261 # the table will mark us clean! 02262 # self.markClean() 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 ##warn("updateRow", tbl.getTableName(), whereList, changeSet) 02330 02331 key = self.__getPrimaryKeyForTable(tbl, row) 02332 self.__recordUpdate(tbl, key) 02333 02334 def deleteRow(self, tbl, row): 02335 #warn("deleteRow", tbl.getTableName(), row) 02336 02337 key = self.__getPrimaryKeyForTable(tbl, row) 02338 #warn("key", key) 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 # the server guid who changed this key 02376 02377 self.d_addColumn("timestamp", kCreatedStampMS, primarykey = 1) 02378 # when the change took place. 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 # the primarykey of the change 02386 # [columnName,length,data]... 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 # the table where the key was deleted 02397 02398 self.d_addColumn("key", kVarString, primarykey = 1) 02399 # the deleted primarykey 02400 02401 self.d_addColumn("timestamp", kCreatedStampMS) 02402 # timestamp of the deletion 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