| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.test.test_reflector -*- | |
| 2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 | |
| 6 from twisted.enterprise import reflector | |
| 7 from twisted.enterprise.util import DBError, getKeyColumn, quote, safe | |
| 8 from twisted.enterprise.util import _TableInfo | |
| 9 from twisted.enterprise.row import RowObject | |
| 10 | |
| 11 from twisted.python import reflect | |
| 12 | |
| 13 class SQLReflector(reflector.Reflector): | |
| 14 """ | |
| 15 DEPRECATED. | |
| 16 | |
| 17 I reflect on a database and load RowObjects from it. | |
| 18 | |
| 19 In order to do this, I interrogate a relational database to | |
| 20 extract schema information and interface with RowObject class | |
| 21 objects that can interact with specific tables. | |
| 22 """ | |
| 23 populated = 0 | |
| 24 conditionalLabels = { | |
| 25 reflector.EQUAL : "=", | |
| 26 reflector.LESSTHAN : "<", | |
| 27 reflector.GREATERTHAN : ">", | |
| 28 reflector.LIKE : "like" | |
| 29 } | |
| 30 | |
| 31 def __init__(self, dbpool, rowClasses): | |
| 32 """Initialize me against a database. | |
| 33 """ | |
| 34 reflector.Reflector.__init__(self, rowClasses) | |
| 35 self.dbpool = dbpool | |
| 36 | |
| 37 def _populate(self): | |
| 38 self._transPopulateSchema() | |
| 39 | |
| 40 def _transPopulateSchema(self): | |
| 41 """Used to construct the row classes in a single interaction. | |
| 42 """ | |
| 43 for rc in self.rowClasses: | |
| 44 if not issubclass(rc, RowObject): | |
| 45 raise DBError("Stub class (%s) is not derived from RowObject" %
reflect.qual(rc.rowClass)) | |
| 46 | |
| 47 self._populateSchemaFor(rc) | |
| 48 self.populated = 1 | |
| 49 | |
| 50 def _populateSchemaFor(self, rc): | |
| 51 """Construct all the SQL templates for database operations on | |
| 52 <tableName> and populate the class <rowClass> with that info. | |
| 53 """ | |
| 54 attributes = ("rowColumns", "rowKeyColumns", "rowTableName" ) | |
| 55 for att in attributes: | |
| 56 if not hasattr(rc, att): | |
| 57 raise DBError("RowClass %s must have class variable: %s" % (rc,
att)) | |
| 58 | |
| 59 tableInfo = _TableInfo(rc) | |
| 60 tableInfo.updateSQL = self.buildUpdateSQL(tableInfo) | |
| 61 tableInfo.insertSQL = self.buildInsertSQL(tableInfo) | |
| 62 tableInfo.deleteSQL = self.buildDeleteSQL(tableInfo) | |
| 63 self.populateSchemaFor(tableInfo) | |
| 64 | |
| 65 def escape_string(self, text): | |
| 66 """Escape a string for use in an SQL statement. The default | |
| 67 implementation escapes ' with '' and \ with \\. Redefine this | |
| 68 function in a subclass if your database server uses different | |
| 69 escaping rules. | |
| 70 """ | |
| 71 return safe(text) | |
| 72 | |
| 73 def quote_value(self, value, type): | |
| 74 """Format a value for use in an SQL statement. | |
| 75 | |
| 76 @param value: a value to format as data in SQL. | |
| 77 @param type: a key in util.dbTypeMap. | |
| 78 """ | |
| 79 return quote(value, type, string_escaper=self.escape_string) | |
| 80 | |
| 81 def loadObjectsFrom(self, tableName, parentRow=None, data=None, | |
| 82 whereClause=None, forceChildren=0): | |
| 83 """Load a set of RowObjects from a database. | |
| 84 | |
| 85 Create a set of python objects of <rowClass> from the contents | |
| 86 of a table populated with appropriate data members. | |
| 87 Example:: | |
| 88 | |
| 89 | class EmployeeRow(row.RowObject): | |
| 90 | pass | |
| 91 | | |
| 92 | def gotEmployees(employees): | |
| 93 | for emp in employees: | |
| 94 | emp.manager = "fred smith" | |
| 95 | manager.updateRow(emp) | |
| 96 | | |
| 97 | reflector.loadObjectsFrom("employee", | |
| 98 | data = userData, | |
| 99 | whereClause = [("manager" , EQUAL, "fred sm
ith")] | |
| 100 | ).addCallback(gotEmployees) | |
| 101 | |
| 102 NOTE: the objects and all children should be loaded in a single transact
ion. | |
| 103 NOTE: can specify a parentRow _OR_ a whereClause. | |
| 104 | |
| 105 """ | |
| 106 if parentRow and whereClause: | |
| 107 raise DBError("Must specify one of parentRow _OR_ whereClause") | |
| 108 if parentRow: | |
| 109 info = self.getTableInfo(parentRow) | |
| 110 relationship = info.getRelationshipFor(tableName) | |
| 111 whereClause = self.buildWhereClause(relationship, parentRow) | |
| 112 elif whereClause: | |
| 113 pass | |
| 114 else: | |
| 115 whereClause = [] | |
| 116 return self.dbpool.runInteraction(self._rowLoader, tableName, | |
| 117 parentRow, data, whereClause, | |
| 118 forceChildren) | |
| 119 | |
| 120 def _rowLoader(self, transaction, tableName, parentRow, data, | |
| 121 whereClause, forceChildren): | |
| 122 """immediate loading of rowobjects from the table with the whereClause. | |
| 123 """ | |
| 124 tableInfo = self.schema[tableName] | |
| 125 # Build the SQL for the query | |
| 126 sql = "SELECT " | |
| 127 first = 1 | |
| 128 for column, type in tableInfo.rowColumns: | |
| 129 if first: | |
| 130 first = 0 | |
| 131 else: | |
| 132 sql = sql + "," | |
| 133 sql = sql + " %s" % column | |
| 134 sql = sql + " FROM %s " % (tableName) | |
| 135 if whereClause: | |
| 136 sql += " WHERE " | |
| 137 first = 1 | |
| 138 for wItem in whereClause: | |
| 139 if first: | |
| 140 first = 0 | |
| 141 else: | |
| 142 sql += " AND " | |
| 143 (columnName, cond, value) = wItem | |
| 144 t = self.findTypeFor(tableName, columnName) | |
| 145 quotedValue = self.quote_value(value, t) | |
| 146 sql += "%s %s %s" % (columnName, self.conditionalLabels[cond], | |
| 147 quotedValue) | |
| 148 | |
| 149 # execute the query | |
| 150 transaction.execute(sql) | |
| 151 rows = transaction.fetchall() | |
| 152 | |
| 153 # construct the row objects | |
| 154 results = [] | |
| 155 newRows = [] | |
| 156 for args in rows: | |
| 157 kw = {} | |
| 158 for i in range(0,len(args)): | |
| 159 ColumnName = tableInfo.rowColumns[i][0].lower() | |
| 160 for attr, type in tableInfo.rowClass.rowColumns: | |
| 161 if attr.lower() == ColumnName: | |
| 162 kw[attr] = args[i] | |
| 163 break | |
| 164 # find the row in the cache or add it | |
| 165 resultObject = self.findInCache(tableInfo.rowClass, kw) | |
| 166 if not resultObject: | |
| 167 meth = tableInfo.rowFactoryMethod[0] | |
| 168 resultObject = meth(tableInfo.rowClass, data, kw) | |
| 169 self.addToCache(resultObject) | |
| 170 newRows.append(resultObject) | |
| 171 results.append(resultObject) | |
| 172 | |
| 173 # add these rows to the parentRow if required | |
| 174 if parentRow: | |
| 175 self.addToParent(parentRow, newRows, tableName) | |
| 176 | |
| 177 # load children or each of these rows if required | |
| 178 for relationship in tableInfo.relationships: | |
| 179 if not forceChildren and not relationship.autoLoad: | |
| 180 continue | |
| 181 for row in results: | |
| 182 # build where clause | |
| 183 childWhereClause = self.buildWhereClause(relationship, row) | |
| 184 # load the children immediately, but do nothing with them | |
| 185 self._rowLoader(transaction, | |
| 186 relationship.childRowClass.rowTableName, | |
| 187 row, data, childWhereClause, forceChildren) | |
| 188 | |
| 189 return results | |
| 190 | |
| 191 def findTypeFor(self, tableName, columnName): | |
| 192 tableInfo = self.schema[tableName] | |
| 193 columnName = columnName.lower() | |
| 194 for column, type in tableInfo.rowColumns: | |
| 195 if column.lower() == columnName: | |
| 196 return type | |
| 197 | |
| 198 def buildUpdateSQL(self, tableInfo): | |
| 199 """(Internal) Build SQL template to update a RowObject. | |
| 200 | |
| 201 Returns: SQL that is used to contruct a rowObject class. | |
| 202 """ | |
| 203 sql = "UPDATE %s SET" % tableInfo.rowTableName | |
| 204 # build update attributes | |
| 205 first = 1 | |
| 206 for column, type in tableInfo.rowColumns: | |
| 207 if getKeyColumn(tableInfo.rowClass, column): | |
| 208 continue | |
| 209 if not first: | |
| 210 sql = sql + ", " | |
| 211 sql = sql + " %s = %s" % (column, "%s") | |
| 212 first = 0 | |
| 213 | |
| 214 # build where clause | |
| 215 first = 1 | |
| 216 sql = sql + " WHERE " | |
| 217 for keyColumn, type in tableInfo.rowKeyColumns: | |
| 218 if not first: | |
| 219 sql = sql + " AND " | |
| 220 sql = sql + " %s = %s " % (keyColumn, "%s") | |
| 221 first = 0 | |
| 222 return sql | |
| 223 | |
| 224 def buildInsertSQL(self, tableInfo): | |
| 225 """(Internal) Build SQL template to insert a new row. | |
| 226 | |
| 227 Returns: SQL that is used to insert a new row for a rowObject | |
| 228 instance not created from the database. | |
| 229 """ | |
| 230 sql = "INSERT INTO %s (" % tableInfo.rowTableName | |
| 231 # build column list | |
| 232 first = 1 | |
| 233 for column, type in tableInfo.rowColumns: | |
| 234 if not first: | |
| 235 sql = sql + ", " | |
| 236 sql = sql + column | |
| 237 first = 0 | |
| 238 | |
| 239 sql = sql + " ) VALUES (" | |
| 240 | |
| 241 # build values list | |
| 242 first = 1 | |
| 243 for column, type in tableInfo.rowColumns: | |
| 244 if not first: | |
| 245 sql = sql + ", " | |
| 246 sql = sql + "%s" | |
| 247 first = 0 | |
| 248 | |
| 249 sql = sql + ")" | |
| 250 return sql | |
| 251 | |
| 252 def buildDeleteSQL(self, tableInfo): | |
| 253 """Build the SQL template to delete a row from the table. | |
| 254 """ | |
| 255 sql = "DELETE FROM %s " % tableInfo.rowTableName | |
| 256 # build where clause | |
| 257 first = 1 | |
| 258 sql = sql + " WHERE " | |
| 259 for keyColumn, type in tableInfo.rowKeyColumns: | |
| 260 if not first: | |
| 261 sql = sql + " AND " | |
| 262 sql = sql + " %s = %s " % (keyColumn, "%s") | |
| 263 first = 0 | |
| 264 return sql | |
| 265 | |
| 266 def updateRowSQL(self, rowObject): | |
| 267 """Build SQL to update the contents of rowObject. | |
| 268 """ | |
| 269 args = [] | |
| 270 tableInfo = self.schema[rowObject.rowTableName] | |
| 271 # build update attributes | |
| 272 for column, type in tableInfo.rowColumns: | |
| 273 if not getKeyColumn(rowObject.__class__, column): | |
| 274 args.append(self.quote_value(rowObject.findAttribute(column), | |
| 275 type)) | |
| 276 # build where clause | |
| 277 for keyColumn, type in tableInfo.rowKeyColumns: | |
| 278 args.append(self.quote_value(rowObject.findAttribute(keyColumn), | |
| 279 type)) | |
| 280 | |
| 281 return self.getTableInfo(rowObject).updateSQL % tuple(args) | |
| 282 | |
| 283 def updateRow(self, rowObject): | |
| 284 """Update the contents of rowObject to the database. | |
| 285 """ | |
| 286 sql = self.updateRowSQL(rowObject) | |
| 287 rowObject.setDirty(0) | |
| 288 return self.dbpool.runOperation(sql) | |
| 289 | |
| 290 def insertRowSQL(self, rowObject): | |
| 291 """Build SQL to insert the contents of rowObject. | |
| 292 """ | |
| 293 args = [] | |
| 294 tableInfo = self.schema[rowObject.rowTableName] | |
| 295 # build values | |
| 296 for column, type in tableInfo.rowColumns: | |
| 297 args.append(self.quote_value(rowObject.findAttribute(column),type)) | |
| 298 return self.getTableInfo(rowObject).insertSQL % tuple(args) | |
| 299 | |
| 300 def insertRow(self, rowObject): | |
| 301 """Insert a new row for rowObject. | |
| 302 """ | |
| 303 rowObject.setDirty(0) | |
| 304 sql = self.insertRowSQL(rowObject) | |
| 305 return self.dbpool.runOperation(sql) | |
| 306 | |
| 307 def deleteRowSQL(self, rowObject): | |
| 308 """Build SQL to delete rowObject from the database. | |
| 309 """ | |
| 310 args = [] | |
| 311 tableInfo = self.schema[rowObject.rowTableName] | |
| 312 # build where clause | |
| 313 for keyColumn, type in tableInfo.rowKeyColumns: | |
| 314 args.append(self.quote_value(rowObject.findAttribute(keyColumn), | |
| 315 type)) | |
| 316 | |
| 317 return self.getTableInfo(rowObject).deleteSQL % tuple(args) | |
| 318 | |
| 319 def deleteRow(self, rowObject): | |
| 320 """Delete the row for rowObject from the database. | |
| 321 """ | |
| 322 sql = self.deleteRowSQL(rowObject) | |
| 323 self.removeFromCache(rowObject) | |
| 324 return self.dbpool.runOperation(sql) | |
| 325 | |
| 326 | |
| 327 __all__ = ['SQLReflector'] | |
| OLD | NEW |