Changeset 1912
- Timestamp:
- 02/08/06 14:44:41 (3 years ago)
- Files:
-
- trunk/dabo/db/dBackend.py (modified) (18 diffs)
- trunk/dabo/db/dCursorMixin.py (modified) (18 diffs)
- trunk/dabo/db/dbFirebird.py (modified) (7 diffs)
- trunk/dabo/db/dbMySQL.py (modified) (5 diffs)
- trunk/dabo/db/dbPostgreSQL.py (modified) (12 diffs)
- trunk/dabo/db/dbSQLite.py (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/dabo/db/dBackend.py
r1876 r1912 7 7 8 8 class dBackend(dObject): 9 """ Abstract object: inherit from this to define new dabo db interfaces. 10 """ 9 """ Abstract object: inherit from this to define new dabo db interfaces.""" 11 10 def __init__(self): 12 11 self._baseClass = dBackend 13 #dBackend.doDefault(self) 12 self._autoCommit = False 13 # This forces the setting on the connection 14 self.AutoCommit = False 14 15 super(dBackend, self).__init__() 15 16 self.dbModuleName = None 16 17 self._connection = None 17 # sysenc = sys.getdefaultencoding()18 # self._encoding = sysenc == 'ascii' and 'latin-1' or sysenc19 18 self._encoding = "utf-8" 20 19 20 21 21 def isValidModule(self): 22 """ Test the dbapi to see if it is supported on this computer. 23 """ 22 """ Test the dbapi to see if it is supported on this computer.""" 24 23 try: 25 24 dbapi = __import__(self.dbModuleName) … … 28 27 return False 29 28 29 30 30 def getConnection(self, connectInfo): 31 31 """ override in subclasses """ 32 32 return None 33 33 34 34 35 def getDictCursorClass(self): 35 36 """ override in subclasses """ 36 37 return None 38 37 39 38 40 def getCursor(self, cursorClass): 39 41 """ override in subclasses if necessary """ 40 42 return cursorClass(self._connection) 43 41 44 42 45 def formatDateTime(self, val): … … 48 51 """ 49 52 return val 53 50 54 51 55 def formatNone(self): … … 55 59 """ 56 60 return "NULL" 61 57 62 58 63 def noResultsOnSave(self): … … 62 67 raise dException.dException, _("No records updated") 63 68 69 64 70 def noResultsOnDelete(self): 65 71 """ Most backends will return a non-zero number if there are deletions. … … 68 74 raise dException.dException, _("No records deleted") 69 75 76 70 77 def flush(self, cursor): 71 78 """ Only used in some backends """ 72 79 return 80 73 81 74 82 def processFields(self, txt): … … 88 96 # OVERRIDE IN SUBCLASSES! 89 97 return val 98 90 99 91 100 def getLastInsertID(self, cursor): … … 96 105 This method should be coded in backend-specific subclasses to address 97 106 that database's approach. 98 """ 99 107 """ 100 108 # Here is some code to fall back on if the specific subclass doesn't 101 109 # override. … … 109 117 return None 110 118 119 111 120 def getTables(self, includeSystemTables=False): 112 121 """ Return a tuple of the tables in the current database. … … 116 125 return tuple() 117 126 127 118 128 def getTableRecordCount(self, tableName): 119 129 """ Return the number of records in the backend table. 120 130 """ 121 131 return -1 132 122 133 123 134 def getFields(self, tableName): … … 146 157 147 158 159 def getAutoCommitStatus(self, cursor): 160 return self._autoCommit 161 162 163 def setAutoCommitStatus(self, cursor, val): 164 if hasattr(self._connection, "autocommit"): 165 self._connection.autocommit(val) 166 self._autoCommit = val 167 else: 168 # Without an autocommit method, assume 169 # no autocommit. 170 self._autoCommit = False 171 172 148 173 def beginTransaction(self, cursor): 149 174 """ Begin a SQL transaction.""" 150 try:175 if not cursor.AutoCommit: 151 176 cursor.connection.begin() 152 except: 153 # Should we raise an error? 154 pass 177 155 178 156 179 def commitTransaction(self, cursor): 157 180 """ Commit a SQL transaction.""" 158 try:181 if not cursor.AutoCommit: 159 182 cursor.connection.commit() 160 except: 161 # Should we raise an error? 162 pass 183 163 184 164 185 def rollbackTransaction(self, cursor): 165 186 """ Roll back (revert) a SQL transaction.""" 166 try: 167 cursor.connection.rollback() 168 except: 169 # Should we raise an error? 170 pass 187 cursor.connection.rollback() 188 171 189 172 190 def addWithSep(self, base, new, sep=",\n\t"): … … 181 199 return ret 182 200 201 183 202 def addField(self, clause, exp): 184 """ Add a field to the field clause. 185 """ 203 """ Add a field to the field clause.""" 186 204 return self.addWithSep(clause, exp) 187 205 206 188 207 def addFrom(self, clause, exp): 189 """ Add a table to the sql statement. 190 """ 208 """ Add a table to the sql statement.""" 191 209 return self.addWithSep(clause, exp) 210 192 211 193 212 def addWhere(self, clause, exp, comp="and"): 194 """ Add an expression to the where clause. 195 """ 213 """ Add an expression to the where clause.""" 196 214 return self.addWithSep(clause, exp, sep=" %s " % comp) 215 197 216 198 217 def addGroupBy(self, clause, exp): 199 """ Add an expression to the group-by clause. 200 """ 218 """ Add an expression to the group-by clause.""" 201 219 return self.addWithSep(clause, exp) 220 202 221 203 222 def addOrderBy(self, clause, exp): 204 """ Add an expression to the order-by clause. 205 """ 223 """ Add an expression to the order-by clause.""" 206 224 return self.addWithSep(clause, exp) 225 207 226 208 227 def getLimitWord(self): … … 211 230 """ 212 231 return "limit" 232 213 233 214 234 def formSQL(self, fieldClause, fromClause, … … 223 243 return sql 224 244 245 225 246 def prepareWhere(self, clause): 226 247 """ Normally, just return the original. Can be overridden as needed … … 228 249 """ 229 250 return clause 251 230 252 231 253 def getWordMatchFormat(self): … … 239 261 """ 240 262 return " %(field)s = %(value)s " 263 241 264 242 265 def getUpdateTablePrefix(self, tbl): … … 315 338 descFlds = cursor.FieldDescription 316 339 # Get the raw version of the table 317 sql = " ""select * from %s where 1=0 """ % cursor.Table340 sql = "select * from %s where 1=0 " % cursor.Table 318 341 auxCrs = cursor._getAuxCursor() 319 342 auxCrs.execute( sql ) … … 409 432 """ 410 433 self._encoding = enc 434 411 435 def _getEncoding(self): 412 436 """ Get backend encoding.""" 413 437 return self._encoding 414 438 415 Encoding = property(_getEncoding, _setEncoding, None, "Backend encoding") 439 Encoding = property(_getEncoding, _setEncoding, None, 440 _("Backend encoding (str)")) trunk/dabo/db/dCursorMixin.py
r1899 r1912 137 137 138 138 def execute(self, sql, params=()): 139 """ 140 The idea here is to let the super class do the actual work in retrieving the data. However,141 many cursor classes can only return row information as a list, not as a dictionary. This142 method willdetect that, and convert the results to a dictionary.139 """The idea here is to let the super class do the actual work in 140 retrieving the data. However, many cursor classes can only return 141 row information as a list, not as a dictionary. This method will 142 detect that, and convert the results to a dictionary. 143 143 """ 144 144 # Some backends, notably Firebird, require that fields be specially … … 265 265 # Add mementos to each row of the result set 266 266 self.addMemento(-1) 267 268 267 # Check for any derived fields that should not be included in 269 268 # any updates. 270 269 self.__setNonUpdateFields() 271 272 273 270 # Clear the unsorted list, and then apply the current sort 274 271 self.__unsortedRows = [] … … 491 488 492 489 def isChanged(self, allRows=True): 493 """ Scan all the records and compare them with their mementos.490 """Scan all the records and compare them with their mementos. 494 491 Returns True if any differ, False otherwise. 495 492 """ 496 493 ret = False 497 498 494 if self.RowCount > 0: 499 495 if allRows: … … 579 575 if self.RowCount <= 0: 580 576 raise dException.NoRecordsException, _("No records in the data set.") 581 582 577 if row is None: 583 578 row = self.RowNumber … … 655 650 656 651 if not ignore: 657 msg = "!!! Data Type Mismatch: field=%s. Expecting: %s; got: %s"\652 msg = _("!!! Data Type Mismatch: field=%s. Expecting: %s; got: %s") \ 658 653 % (fld, str(fldType), str(type(val))) 659 654 dabo.errorLog.write(msg) … … 779 774 780 775 781 def save(self, allrows=False ):776 def save(self, allrows=False, useTransaction=False): 782 777 """ Save any changes to the data back to the data store.""" 783 778 # Make sure that there is data to save 784 779 if self.RowCount <= 0: 785 780 raise dException.dException, _("No data to save") 786 787 781 # Make sure that there is a PK 788 782 self.checkPK() 789 790 783 if allrows: 791 784 recs = self._records … … 793 786 recs = (self._records[self.RowNumber],) 794 787 795 self.beginTransaction() 788 if useTransaction: 789 self.beginTransaction() 796 790 797 791 for rec in recs: … … 802 796 raise dException.ConnectionLostException, e 803 797 else: 804 # Error was raised. Exit and rollback the changes 805 self.rollbackTransaction() 798 # Error was raised. Exit and rollback the changes if 799 # this object started the transaction. 800 if useTransaction: 801 self.rollbackTransaction() 806 802 raise dException.QueryException, e 807 808 self.commitTransaction() 803 if useTransaction: 804 self.commitTransaction() 805 809 806 810 807 def __saverow(self, rec): … … 1196 1193 1197 1194 def checkPK(self): 1198 """ Verify that the field(s) specified in the KeyField prop exist. 1199 """ 1195 """ Verify that the field(s) specified in the KeyField prop exist.""" 1200 1196 # First, make sure that there is *something* in the field 1201 1197 if not self.KeyField: … … 1280 1276 return self.BackendObject.getTables(includeSystemTables) 1281 1277 1278 1282 1279 def getTableRecordCount(self, tableName): 1283 1280 """ Get the number of records in the backend table.""" 1284 1281 return self.BackendObject.getTableRecordCount(tableName) 1282 1285 1283 1286 1284 def getFields(self, tableName=None): … … 1335 1333 ret = None 1336 1334 if self.BackendObject: 1337 ret = self.BackendObject.beginTransaction(self.AuxCursor) 1335 if not self.AutoCommit: 1336 ret = self.BackendObject.beginTransaction(self.AuxCursor) 1338 1337 return ret 1339 1338 … … 1343 1342 ret = None 1344 1343 if self.BackendObject: 1345 ret = self.BackendObject.commitTransaction(self.AuxCursor) 1344 if not self.AutoCommit: 1345 ret = self.BackendObject.commitTransaction(self.AuxCursor) 1346 1346 return ret 1347 1347 … … 1565 1565 1566 1566 ## Property getter/setter methods ## 1567 def _getAutoCommit(self): 1568 return self.BackendObject.getAutoCommitStatus(self) 1569 1570 1571 def _setAutoCommit(self, val): 1572 self.BackendObject.setAutoCommitStatus(self, val) 1573 1574 1567 1575 def _getAutoSQL(self): 1568 1576 return self.getSQL() … … 1603 1611 1604 1612 1613 def _getDescrip(self): 1614 return self.__backend.getDescription(self) 1615 1616 1605 1617 def _getEncoding(self): 1606 1618 return self.BackendObject.Encoding … … 1610 1622 1611 1623 1612 def _getDescrip(self): 1613 return self.__backend.getDescription(self) 1614 1615 1624 def _getIsAdding(self): 1625 """ Return True if the current record is a new record.""" 1626 return self._records[self.RowNumber].has_key(kons.CURSOR_NEWFLAG) 1627 1628 1629 def _getKeyField(self): 1630 try: 1631 return self._keyField 1632 except AttributeError: 1633 return "" 1634 1635 def _setKeyField(self, kf): 1636 self._keyField = str(kf) 1637 self.AuxCursor._keyField = str(kf) 1638 self._keyFieldSet = True 1639 1640 1616 1641 def _getLastSQL(self): 1617 1642 try: … … 1622 1647 1623 1648 1624 def _getKeyField(self):1625 try:1626 return self._keyField1627 except AttributeError:1628 return ""1629 1630 def _setKeyField(self, kf):1631 self._keyField = str(kf)1632 self.AuxCursor._keyField = str(kf)1633 self._keyFieldSet = True1634 1635 1636 1649 def _getRowNumber(self): 1637 1650 try: … … 1679 1692 1680 1693 1681 def _isAdding(self): 1682 """ Return True if the current record is a new record.""" 1683 return self._records[self.RowNumber].has_key(kons.CURSOR_NEWFLAG) 1684 1694 AutoCommit = property(_getAutoCommit, _setAutoCommit, None, 1695 _("Do we need explicit begin/commit/rollback commands for transactions? (bool)")) 1685 1696 1686 1697 AutoSQL = property(_getAutoSQL, None, None, … … 1706 1717 _("Tuple of field names and types, as returned by the backend (tuple)") ) 1707 1718 1708 IsAdding = property(_ isAdding, None, None,1719 IsAdding = property(_getIsAdding, None, None, 1709 1720 _("Returns True if the current record is new and unsaved")) 1710 1721 trunk/dabo/db/dbFirebird.py
r1251 r1912 19 19 if not port: 20 20 port = 3050 21 22 21 # kinterbasdb will barf with unicode strings: 23 22 host = str(connectInfo.Host) … … 30 29 return self._connection 31 30 31 32 32 def getDictCursorClass(self): 33 33 return self.dbapi.Cursor 34 34 35 35 36 def noResultsOnSave(self): … … 40 41 return 41 42 43 42 44 def noResultsOnDelete(self): 43 45 """ Firebird does not return the number of records deleted, so … … 46 48 """ 47 49 return 50 48 51 49 52 def processFields(self, txt): … … 61 64 return qt + val.replace(sl, sl+sl).replace(qt, qt+qt) + qt 62 65 66 63 67 def formatDateTime(self, val): 64 68 """ We need to wrap the value in quotes. """ 65 69 sqt = "'" # single quote 66 70 return "%s%s%s" % (sqt, str(val), sqt) 71 67 72 68 73 def getTables(self, includeSystemTables=False): … … 90 95 def getFields(self, tableName): 91 96 tempCursor = self._connection.cursor() 92 93 97 # Get the PK 94 98 sql = """ select inseg.rdb$field_name … … 173 177 """ Override the default 'limit', since Firebird doesn't use that. """ 174 178 return "first" 179 175 180 176 181 def formSQL(self, fieldClause, fromClause, trunk/dabo/db/dbMySQL.py
r1693 r1912 11 11 dBackend.__init__(self) 12 12 self.dbModuleName = "MySQLdb" 13 # Are we using a version of MySQL that supports transactions? 14 self.useTransactions = False 13 15 14 16 15 def getConnection(self, connectInfo): … … 42 41 db=connectInfo.Database, 43 42 port=port, **kwargs) 43 return self._connection 44 44 45 return self._connection46 45 47 46 def getDictCursorClass(self): 48 47 import MySQLdb.cursors as cursors 49 48 return cursors.DictCursor 49 50 50 51 51 def escQuote(self, val): … … 56 56 return qt + val.replace(sl, sl+sl).replace(qt, sl+qt) + qt 57 57 58 58 59 def formatDateTime(self, val): 59 60 """ We need to wrap the value in quotes. """ 60 61 sqt = "'" # single quote 61 62 return "%s%s%s" % (sqt, str(val), sqt) 62 63 64 63 65 def getTables(self, includeSystemTables=False): 64 66 # MySQL doesn't have system tables, in the traditional sense, as … … 72 74 return tuple(tables) 73 75 76 74 77 def getTableRecordCount(self, tableName): 75 78 tempCursor = self._connection.cursor() 76 79 tempCursor.execute("select count(*) as ncount from %s" % tableName) 77 80 return tempCursor.fetchall()[0][0] 81 78 82 79 83 def getFields(self, tableName): … … 162 166 "VAR_STRING": "C", 163 167 "YEAR": "?"} 168 return daboMapping[typeMapping[backendFieldType]] 164 169 165 return daboMapping[typeMapping[backendFieldType]] 170 171 def getWordMatchFormat(self): 172 """ MySQL's fulltext search expression""" 173 return """ match (%(field)s) against ("%(value)s") """ 166 174 167 175 168 176 def beginTransaction(self, cursor): 169 177 """ Begin a SQL transaction.""" 170 if self.useTransactions: 171 cursor.execute("BEGIN") 178 if not cursor.AutoCommit: 179 if hasattr(cursor.connection, "begin"): 180 cursor.connection.begin() 181 else: 182 cursor.execute("BEGIN") 183 172 184 173 def commitTransaction(self, cursor): 174 """ Commit a SQL transaction.""" 175 if self.useTransactions: 176 cursor.execute("COMMIT") 177 178 def rollbackTransaction(self, cursor): 179 """ Roll back (revert) a SQL transaction.""" 180 if self.useTransactions: 181 cursor.execute("ROLLBACK") 182 183 def getWordMatchFormat(self): 184 """ MySQL's fulltext search expression 185 """ 186 return """ match (%(field)s) against ("%(value)s") """ 185 trunk/dabo/db/dbPostgreSQL.py
r1186 r1912 14 14 self.useTransactions = True # this does not appear to be required 15 15 16 16 17 def getConnection(self, connectInfo): 17 ### TODO: what connector should we use?18 18 import psycopg2 as dbapi 19 19 #from pyPgSQL import PgSQL as dbapi … … 21 21 #- jfcs 11/01/04 port needs to be a string 22 22 port = str(connectInfo.Port) 23 if not port or port == 'None':24 port = '5432'23 if not port or port == "None": 24 port = "5432" 25 25 26 26 DSN = "host=%s port=%s dbname=%s user=%s password=%s" % (connectInfo.Host, … … 33 33 return self._connection 34 34 35 35 36 def getDictCursorClass(self): 36 ### TODO: If PostgreSQL doesn't offer specific Dict cursors,37 ### return a plain one, and Dabo will convert it.38 37 # the new psycopg 2.0 supports DictCursor 39 38 import psycopg2.extras as cursors 40 39 return cursors.DictCursor 41 40 41 42 42 def escQuote(self, val): 43 ### TODO: This method needs to escape any 'dangerous' characters,44 ### and properly enclose a string value in quotes.45 43 # escape backslashes and single quotes, and 46 44 # wrap the result in single quotes … … 49 47 return qt + val.replace(sl, sl+sl).replace(qt, sl+qt) + qt 50 48 49 51 50 def formatDateTime(self, val): 52 51 """ We need to wrap the value in quotes. """ 53 ### TODO: How does PostgreSQL handle date-time values?54 52 sqt = "'" # single quote 55 53 return "%s%s%s" % (sqt, str(val), sqt) 56 54 55 57 56 def getTables(self, includeSystemTables=False): 58 57 tempCursor = self._connection.cursor() 59 ### TODO: Verify that this is the correct syntax60 58 # jfcs 11/01/04 assumed public schema 61 59 tempCursor.execute("select tablename from pg_tables where schemaname = 'public'") … … 64 62 for record in rs: 65 63 tables.append(record[0]) 64 return tuple(tables) 66 65 67 return tuple(tables) 68 66 69 67 def getTableRecordCount(self, tableName): 70 68 tempCursor = self._connection.cursor() 71 ### TODO: Verify syntax72 69 tempCursor.execute("select count(*) as ncount from %s" % tableName) 73 70 return tempCursor.fetchall()[0][0] 74 71 72 75 73 def getFields(self, tableName): 76 74 tempCursor = self._connection.cursor() 77 ### TODO: Verify syntax78 75 #jfcs 11/01/04 works great from psql (but does not work with the psycopg 79 76 #module) and only with postgres 7.4.x and later. Too bad, the statement … … 91 88 #LEFT JOIN pg_index i ON(cl.oid= i.indrelid) WHERE c.table_name= '%s'" % tableName) 92 89 #rs=tempCursor.fetchall() 93 94 90 95 91 # jfcs 11/01/04 Below sucks but works with 7.3.x and 7.4.x (don't know anything … … 116 112 117 113 fields = [] 118 ### TODO: Verify the field type names returned.119 114 for r in rs: 120 121 115 name = r[1] 122 116 fldType =r[2] … … 124 118 if thePKFieldName is not None: 125 119 pk = (name == thePKFieldName) 126 127 120 if 'int' in fldType: 128 121 fldType = 'I' … … 139 132 else: 140 133 fldType = "?" 141 142 143 134 fields.append((name.strip(), fldType, pk)) 144 145 135 return tuple(fields) 146 136 147 ### TODO: Customize these for PostgreSQL syntax.148 137 149 def beginTransaction(self, cursor):150 """ Begin a SQL transaction."""151 # jfcs 11/01/04 not sure of this152 # Normally Postgres is in the153 # transaction mode always????154 #if self.useTransactions:155 cursor.execute("BEGIN")156 157 def commitTransaction(self, cursor):158 """ Commit a SQL transaction."""159 #if self.useTransactions:160 cursor.execute("COMMIT")161 162 def rollbackTransaction(self, cursor):163 """ Roll back (revert) a SQL transaction."""164 #if self.useTransactions:165 cursor.execute("ROLLBACK")166 167 168 169 138 def getUpdateTablePrefix(self, tbl): 170 139 """ By default, the update SQL statement will be in the form of … … 175 144 Postgres needs to return an empty string.""" 176 145 return "" 146 147 177 148 def noResultsOnSave(self): 178 149 """ Most backends will return a non-zero number if there are updates. 179 150 Some do not, so this will have to be customized in those cases. 180 151 """ 181 #TODO find out what it should return when it fails....182 152 return 153 183 154 184 155 def noResultsOnDelete(self): … … 187 158 """ 188 159 #raise dException.dException, _("No records deleted") 189 #TODO find out what it should return when it fails....190 160 return 161 191 162 192 163 def flush(self, cursor): … … 194 165 to the database written to disk. 195 166 """ 196 cursor.execute("COMMIT")167 self.commitTransaction() trunk/dabo/db/dbSQLite.py
r1876 r1912 10 10 from pysqlite2 import dbapi2 as dbapi 11 11 self.dbapi = dbapi 12 12 13 13 14 def getConnection(self, connectInfo): … … 15 16 self._connection = self.dbapi.connect(pth) 16 17 return self._connection 18 17 19 18 20 def getDictCursorClass(self): 19 21 return self.dbapi.Cursor 22 20 23 21 24 def escQuote(self, val): 22 25 if val is None: 23 26 return self.formatNone() 24 25 27 sl = "\\" 26 28 qt = "\'" … … 28 30 29 31 32 def setAutoCommitStatus(self, cursor, val): 33 """SQLite doesn't use an 'autocommit()' method. Instead, 34 set the isolation_level property of the connection. 35 """ 36 if val: 37 self._connection.isolation_level = None 38 else: 39 self._connection.isolation_level = "" 40 self._autoCommit = val 41 42 43 def beginTransaction(self, cursor): 44 """ Begin a SQL transaction. Since pysqlite does an implicit 45 'begin' even when not using autocommit, simply do nothing. 46 """ 47 pass 48 49 30 50 def formatDateTime(self, val): 31 51 """ We need to wrap the value in quotes. """ 32 #### TODO: Make sure that the format for DateTime33 #### values is returned correctly34 52 sqt = "'" # single quote 35 53 return "%s%s%s" % (sqt, str(val), sqt) … … 49 67 return tuple(tables) 50 68 69 51 70 def getTableRecordCount(self, tableName): 52 71 tempCursor = self._connection.cursor() 53 72 tempCursor.execute("select count(*) as ncount from %s" % tableName) 54 73 return tempCursor.fetchall()[0][0] 74 55 75 56 76 def getFields(self, tableName): … … 131 151 return "" 132 152 153 133 154 def noResultsOnSave(self): 134 155 """ SQLite does not return anything on a successful update""" 135 156 pass 157 136 158 137 159 def getStructureDescription(self, cursor): … … 184 206 else: 185 207 sql = "CREATE TEMP TABLE " 186 187 208 sql = sql + tabledef.Name + " (" 188 209 … … 225 246 if idx.Name.lower() != "primary": 226 247 sql = "CREATE INDEX " + idx.Name + " ON " + tabledef.Name + "(" 227 228 248 for fld in idx.Fields: 229 249 sql = sql + fld + "," 230 231 250 if sql[-1:] == ",": 232 251 sql = sql[:-1]
