odb.py
Go to the documentation of this file.
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     


pyclearsilver
Author(s): Scott Hassan/hassan@willowgarage.com
autogenerated on Wed Apr 23 2014 10:35:42