/[gnue]/gnue-common/src/datasources/drivers/odbc/wodbc/RecordSet.py
ViewVC logotype

Diff of /gnue-common/src/datasources/drivers/odbc/wodbc/RecordSet.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.1 by jcater, Fri Oct 10 01:21:22 2003 UTC revision 1.2 by jcater, Tue Nov 25 17:01:39 2003 UTC
# Line 0  Line 1 
1    #
2    # This file is part of GNU Enterprise.
3    #
4    # GNU Enterprise is free software; you can redistribute it
5    # and/or modify it under the terms of the GNU General Public
6    # License as published by the Free Software Foundation; either
7    # version 2, or (at your option) any later version.
8    #
9    # GNU Enterprise is distributed in the hope that it will be
10    # useful, but WITHOUT ANY WARRANTY; without even the implied
11    # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12    # PURPOSE. See the GNU General Public License for more details.
13    #
14    # You should have received a copy of the GNU General Public
15    # License along with program; see the file COPYING. If not,
16    # write to the Free Software Foundation, Inc., 59 Temple Place
17    # - Suite 330, Boston, MA 02111-1307, USA.
18    #
19    # Copyright 2000-2003 Free Software Foundation
20    #
21    # FILE:
22    # odbc/DBdriver.py
23    #
24    # DESCRIPTION:
25    # Driver to provide access to data via the public domain win32all ODBC Driver
26    #
27    # NOTES:
28    # Only works under Win32... requires the win32all extensions.
29    # (http://aspn.activestate.com/ASPN/Downloads/ActivePython/Extensions/Win32all)
30    #
31    #   Supported attributes (via connections.conf or <database> tag)
32    #
33    #     service=   This is the ODBC DSN= string to use.
34    #
35    #
36    
37    
38    import sys, string, types
39    from gnue.common.datasources import GDataObjects, GConditions, GConnections
40    from gnue.common.apps import GDebug
41    
42    
43    try:
44      import dbi, odbc
45    except ImportError, message:
46      tmsg = _("Driver not installed: win32all ODBC driver\n\n[%s") % message
47      raise GConnections.AdapterNotInstalled, tmsg
48    
49    
50    
51    
52    class ODBC_RecordSet(GDataObjects.RecordSet):
53      def _postChanges(self):
54        if not self.isPending(): return
55        if self._deleteFlag:
56          statement = self._buildDeleteStatement()
57        elif self._insertFlag:
58          statement = self._buildInsertStatement()
59        elif self._updateFlag:
60          statement = self._buildUpdateStatement()
61    
62        GDebug.printMesg(5, "_postChanges: statement=%s" % statement)
63    
64        try:
65          self._parent._update_cursor.execute(statement)
66    
67          # Set _initialData to be the just-now posted values
68          if not self._deleteFlag:
69            self._initialData = {}
70            for key in self._fields.keys():
71              self._initialData[key] = self._fields[key]
72    
73        except self._parent._dataObject._DatabaseError, err:
74          raise GDataObjects.ConnectionError, err
75    
76        self._updateFlag = 0
77        self._insertFlag = 0
78        self._deleteFlag = 0
79    
80        return 1
81            
82    
83      # If a vendor can do any of these more efficiently (i.e., use a known
84      # PRIMARY KEY or ROWID, then override these methods. Otherwise, leave
85      # as default.  Note that these functions are specific to DB-SIG based
86      # drivers (i.e., these functions are not in the base RecordSet class)
87    
88      def _buildDeleteStatement(self):
89        if self._initialData.has_key(self._parent._primaryIdField):
90          where = [self._parent._primaryIdFormat % \
91              self._initialData[self._parent._primaryIdField]  ]
92        else:
93          where = []
94          for field in self._initialData.keys():
95            if self._parent.isFieldBound(field):
96              if self._initialData[field] == None:
97                where.append ("%s IS NULL" % field)
98              else:
99                where.append ("%s='%s'" % (field, self._initialData[field]))
100    
101        statement = "DELETE FROM %s WHERE %s" % \
102           (self._parent._dataObject.table, string.join(where,' AND ') )
103        return statement
104    
105      def _buildInsertStatement(self):
106        vals = []
107        fields = []
108    
109        # TODO: This should actually only insert modified fields.
110        # TODO: Unfortunately, self._modifiedFlags is not being
111        # TODO: set for new records (!@#$)
112        #for field in self._modifiedFlags.keys():
113    
114        for field in self._fields.keys():
115          if self._parent.isFieldBound(field):
116            fields.append (field)
117            if self._fields[field] == None or self._fields[field] == '':
118              vals.append ("NULL") #  % (self._fields[field]))
119            else:
120              try:
121                if self._parent._fieldTypes[field] == 'number':
122                  vals.append ("%s" % (self._fields[field]))
123                else:
124                  vals.append ("'%s'" % (self._fields[field]))
125              except ValueError:
126                vals.append ("%s" % (self._fields[field]))
127    
128        return "INSERT INTO %s (%s) VALUES (%s)" % \
129           (self._parent._dataObject.table, string.join(fields,','), \
130            string.join(vals,',') )
131    
132    
133      def _buildUpdateStatement(self):
134        updates = []
135        for field in self._modifiedFlags.keys():
136          try:
137            if self._parent._fieldTypes[field] == 'number':
138              updates.append ("%s=%s" % (field, self._fields[field]))
139            else:
140              updates.append ("%s='%s'" % (field, self._fields[field]))
141          except KeyError:
142            updates.append ("%s='%s'" % (field, self._fields[field]))
143    
144        if self._initialData.has_key(self._parent._primaryIdField):
145          where = [self._parent._primaryIdFormat % \
146              self._initialData[self._parent._primaryIdField]  ]
147        else:
148          where = []
149          for field in self._initialData.keys():
150            if self._initialData[field] == None:
151              where.append ("%s IS NULL" % field)
152            else:
153              try:
154                if self._parent._fieldTypes[field] == 'number':
155                  where.append ("%s=%s" % (field, self._initialData[field]))
156                else:
157                  where.append ("%s='%s'" % (field, self._initialData[field]))
158              except KeyError:
159                where.append ("%s='%s'" % (field, self._initialData[field]))
160    
161        return "UPDATE %s SET %s WHERE %s" % \
162           (self._parent._dataObject.table, string.join(updates,','), \
163            string.join(where,' AND ') )
164    
165    
166    class ODBC_ResultSet(GDataObjects.ResultSet):
167      def __init__(self, dataObject, cursor=None, \
168            defaultValues={}, masterRecordSet=None):
169        GDataObjects.ResultSet.__init__(
170               self,dataObject,cursor,defaultValues,masterRecordSet)
171        self._recordSetClass = ODBC_RecordSet
172        self._fieldNames = None
173        self._fieldTypes = {}
174    
175    #    self._recordCount = cursor.rowcount > 0 and cursor.rowcount or 0
176        self._recordCount = 0
177    
178        # If a DB driver supports a unique identifier for rows,
179        # list it here.  _primaryIdField is the field name (lower case)
180        # that would appear in the recordset (note that this can be
181        # a system generated format). If a primary id is supported,
182        # _primaryIdFormat is the WHERE clause to be used. It will have
183        # the string  % (fieldvalue) format applied to it.
184        self._primaryIdField = None
185        self._primaryIdFormat = "__gnue__ = '%s'"
186    
187        GDebug.printMesg(5, 'ResultSet created')
188    
189      def _loadNextRecord(self):
190        if self._cursor:
191          rs = None
192    
193          try:
194            rs = self._cursor.fetchone()
195          except self._dataObject._DatabaseError, err:
196            pass
197    # TODO: It seems that popy does what the other drivers don't
198    # TODO: and raises this error ALOT need to find out why
199    #        raise GDataObjects.ConnectionError, err
200    
201          if rs:
202            if not self._fieldNames:
203              self._fieldNames = []
204              for t in (self._cursor.description):
205                self._fieldNames.append (string.lower(t[0]))
206                self._fieldTypes[string.lower(t[0])] = (string.lower(t[1]))
207            i = 0
208            dict = {}
209            for f in (rs):
210              dict[self._fieldNames[i]] = f
211              i = i + 1
212            self._cachedRecords.append (self._recordSetClass(parent=self, \
213                                                initialData=dict))
214            return 1
215          else:
216            return 0
217        else:
218          return 0
219    
220    
221    class ODBC_DataObject(GDataObjects.DataObject):
222    
223      conditionElements = {
224           'add':             (2, 999, '(%s)',                   '+'      ),
225           'sub':             (2, 999, '(%s)',                   '-'      ),
226           'mul':             (2, 999, '(%s)',                   '*'      ),
227           'div':             (2, 999, '(%s)',                   '/'      ),
228           'and':             (1, 999, '(%s)',                   ' AND '  ),
229           'or':              (2, 999, '(%s)',                   ' OR '   ),
230           'not':             (1,   1, '(NOT %s)',               None     ),
231           'negate':          (1,   1, '-%s',                    None     ),
232           'eq':              (2,   2, '(%s = %s)',              None     ),
233           'ne':              (2,   2, '(%s != %s)',             None     ),
234           'gt':              (2,   2, '(%s > %s)',              None     ),
235           'ge':              (2,   2, '(%s >= %s)',             None     ),
236           'lt':              (2,   2, '(%s < %s)',              None     ),
237           'le':              (2,   2, '(%s <= %s)',             None     ),
238           'like':            (2,   2, '%s LIKE %s',             None     ),
239           'notlike':         (2,   2, '%s NOT LIKE %s',         None     ),
240           'between':         (3,   3, '%s BETWEEN %s AND %s',   None     ) }
241    
242      def __init__(self, strictQueryCount=1):
243        GDataObjects.DataObject.__init__(self)
244    
245        GDebug.printMesg (1,"DB-SIG database driver backend initializing")
246    
247        self._resultSetClass = ODBC_ResultSet
248        self._DatabaseError = None
249        self._strictQueryCount = strictQueryCount
250    
251    
252      # This should be over-ridden only if driver needs more than user/pass
253      def getLoginFields(self):
254        return [['_username', 'User Name',0],['_password', 'Password',1]]
255    
256    
257      def connect(self, connectData={}):
258    
259        GDebug.printMesg(1,"ODBC database driver initializing")
260        self._DatabaseError = odbc.error
261    
262        try:
263          service = connectData['service']
264        except KeyError:
265          service = ""
266    
267        try:
268          self._dataConnection = odbc.odbc( "%s/%s/%s" % (
269                       service,
270                       connectData['_username'],
271                       connectData['_password']))
272    
273        except dbi.opError, value:
274          raise GDataObjects.LoginError, value
275    
276        except self._DatabaseError, value:
277          raise GDataObjects.LoginError, value
278    
279        self._postConnect()
280    
281    
282      #
283      # Schema (metadata) functions
284      #
285    
286      # TODO: See postgresql for an example of what these functions do.
287    
288      # Return a list of the types of Schema objects this driver provides
289      def getSchemaTypes(self):
290        return None # [('table',_('Tables'),1)]
291    
292      # Return a list of Schema objects
293      def getSchemaList(self, type=None):
294        return []
295    
296      # Find a schema object with specified name
297      def getSchemaByName(self, name, type=None):
298        return None
299    
300      def _postConnect(self):
301        self.triggerExtensions = TriggerExtensions(self._dataConnection)
302    
303      def _createResultSet(self, conditions={}, readOnly=0, masterRecordSet=None,sql=""):
304        try:
305          cursor = self._dataConnection.cursor()
306          cursor.execute(self._buildQuery(conditions))
307    
308        except dbi.progError, err:
309          raise GDataObjects.ConnectionError, err
310    
311        except self._DatabaseError, err:
312          raise GDataObjects.ConnectionError, err
313        rs = self._resultSetClass(self, cursor=cursor, masterRecordSet=masterRecordSet)
314    
315        # pull a record count for the upcomming query
316        if self._strictQueryCount:
317          rs._recordCount = self._getQueryCount(conditions)
318    
319        if readOnly:
320          rs._readonly = readOnly
321        return rs
322    
323    
324      def _getQueryCount(self,conditions={}):
325        cursor = self._dataConnection.cursor()
326    
327        cursor.execute(self._buildQueryCount(conditions))
328        rs = cursor.fetchone()
329        return int(rs[0])
330    
331        
332      def _buildQueryCount(self, conditions={}):
333        q = "SELECT count(*) FROM %s%s" % (self.table, self._conditionToSQL(conditions))
334    
335        GDebug.printMesg(5,q)
336    
337        return q
338    
339      def commit(self):
340        GDebug.printMesg (5,"DB-SIG database driver: commit()")
341    
342        try:
343          self._dataConnection.commit()
344        except self._DatabaseError, value:
345          raise GDataObjects.ConnectionError, value
346        
347        self._beginTransaction()
348    
349      def rollback(self):
350        GDebug.printMesg (5,"DB-SIG database driver: rollback()")
351    
352        try:
353          self._dataConnection.rollback()
354        except:
355          pass      # I'm SURE this isn't right (jcater)
356                    # But not all db's support transactions
357    
358        self._beginTransaction()
359    
360    
361      def _buildQuery(self, conditions={},forDetail=None,additionalSQL=""):
362        return None
363    
364    
365      # Used to convert a condition tree to an sql where clause
366      def _conditionToSQL (self, condition):
367        if condition == {} or condition == None:
368          return ""
369        elif type(condition) == types.DictType:
370          cond = GConditions.buildConditionFromDict(condition)
371        else:
372          cond = condition
373      
374        if not len(cond._children):
375          return ""
376        elif len(cond._children) > 1:
377          chillun = cond._children
378          cond._children = []
379          _and = GConditions.GCand(cond)
380          _and._children = chillun
381      
382    
383        where = " WHERE (%s)" % (self.__conditionToSQL (cond._children[0]))
384        GDebug.printMesg(5, where)
385        return where
386      
387      # Used internally by _conditionToSQL
388      def __conditionToSQL (self, element):
389        if type(element) != types.InstanceType:
390          return "%s" % element
391        else:
392          otype = string.lower(element._type[2:])
393          if otype == 'cfield':
394            return "%s" % element.name
395          elif otype == 'cconst':
396            if element.value == None:
397              return "NULL"
398            elif element.type == 'number':
399              return "%s" % element.value
400            else:
401              return "'%s'" % element.value
402          elif otype == 'param':
403            v = element.getValue()
404            return (v == None and "NULL") or ("'%s'" % element.getValue())
405          elif self.conditionElements.has_key(otype):
406            for i in range(0, len(element._children)):
407              element._children[i] = self.__conditionToSQL(element._children[i])
408            if len(element._children) < self.conditionElements[otype][0]:
409              tmsg = _('Condition element "%s" expects at least %s arguments; found %s') % \
410                    (otype, self.conditionElements[otype][0], len(element._children))
411              raise GConditions.ConditionError, tmsg
412            if len(element._children) > self.conditionElements[otype][1]:
413              tmsg = _('Condition element "%s" expects at most %s arguments; found %s') % \
414                    (otype, self.conditionElements[otype][0], len(element._children))
415              raise GConditions.ConditionError, tmsg
416            if self.conditionElements[otype][3] == None:
417              return self.conditionElements[otype][2] % tuple(element._children)
418            else:
419              return self.conditionElements[otype][2] % \
420                (string.join(element._children, self.conditionElements[otype][3]))
421          else:
422            tmsg = _('Condition clause "%s" is not supported by this db driver.') % otype
423            raise GConditions.ConditionNotSupported, tmsg
424    
425      # Code necessary to force the connection into transaction mode...
426      # this is usually not necessary (MySQL is one of few DBs that must force)
427      def _beginTransaction(self):
428        pass      
429    
430    
431    class ODBC_DataObject_Object(ODBC_DataObject):
432      def _buildQuery(self, conditions={}):
433        GDebug.printMesg(7,'Implicit Fields: %s' % self._fieldReferences)
434        if len(self._fieldReferences):
435          q = "SELECT %s FROM %s%s" % \
436               (string.join(self._fieldReferences.keys(),","), self.table,
437                self._conditionToSQL(conditions))
438        else:
439          q = "SELECT * FROM %s%s" % (self.table, self._conditionToSQL(conditions))
440    
441        if hasattr(self,'order_by'):
442         q = "%s ORDER BY %s " % (q, self.order_by)
443    
444        GDebug.printMesg(5,q)
445    
446        return q
447    
448    
449    
450    #
451    #  Extensions to Trigger Namespaces
452    #
453    class TriggerExtensions:
454    
455      def __init__(self, connection):
456        self.__connection = connection
457    
458    
459    
460    
461    ######################################
462    #
463    #  The following hashes describe
464    #  this driver's characteristings.
465    #
466    ######################################
467    
468    #
469    #  All datasouce "types" and corresponding DataObject class
470    #
471    supportedDataObjects = {
472      'object': ODBC_DataObject_Object,
473    #  'sql':    ODBC_DataObject_SQL
474    }
475    
476    

Legend:
Removed from v.1.1  
changed lines
  Added in v.1.2

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26