root / trunk / twisted / enterprise / sqlreflector.py

Revision 22677, 11.6 kB (checked in by exarkun, 1 year ago)

Merge deprecated-enterprise-3022

Author: exarkun
Reviewer: therve, dreid
Fixes #3022
Refs #3040

Deprecate twisted.enterprise.util and everything in it.

Line 
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 smith")]
100           |                          ).addCallback(gotEmployees)
101
102         NOTE: the objects and all children should be loaded in a single transaction.
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']
Note: See TracBrowser for help on using the browser.