~ubuntu-branches/ubuntu/hardy/gnue-common/hardy

« back to all changes in this revision

Viewing changes to src/datasources/drivers/odbc/wodbc/RecordSet.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Mitchell
  • Date: 2005-03-09 11:06:31 UTC
  • Revision ID: james.westby@ubuntu.com-20050309110631-8gvvn39q7tjz1kj6
Tags: upstream-0.5.14
ImportĀ upstreamĀ versionĀ 0.5.14

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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-2005 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 = u_("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, recordNumber=None):
 
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(9, "_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._connection._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(9, '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._connection._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 (9,"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(9,"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(9,q)
 
336
 
 
337
    return q
 
338
 
 
339
  def commit(self):
 
340
    GDebug.printMesg (9,"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 (9,"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(9, 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 = u_('Condition element "%(element)s" expects at least '
 
410
                    '%(expected)s arguments; found %(found)s') \
 
411
                 % {'element' : otype,
 
412
                    'expected': self.conditionElements[otype][0],
 
413
                    'found'   : len(element._children)}
 
414
          raise GConditions.ConditionError, tmsg
 
415
 
 
416
        if len(element._children) > self.conditionElements[otype][1]: 
 
417
          tmsg = u_('Condition element "%(element)s" expects at most '
 
418
                    '%(expected)s arguments; found %(found)s') \
 
419
                 % {'element' : otype,
 
420
                    'expected': self.conditionElements[otype][1],
 
421
                    'found'   : len (element._children)}
 
422
          raise GConditions.ConditionError, tmsg
 
423
        if self.conditionElements[otype][3] == None: 
 
424
          return self.conditionElements[otype][2] % tuple(element._children)
 
425
        else: 
 
426
          return self.conditionElements[otype][2] % \
 
427
            (string.join(element._children, self.conditionElements[otype][3]))
 
428
      else: 
 
429
        tmsg = u_('Condition clause "%s" is not supported by this db driver.') % otype
 
430
        raise GConditions.ConditionNotSupported, tmsg
 
431
 
 
432
  # Code necessary to force the connection into transaction mode... 
 
433
  # this is usually not necessary (MySQL is one of few DBs that must force)
 
434
  def _beginTransaction(self): 
 
435
    pass      
 
436
 
 
437
 
 
438
class ODBC_DataObject_Object(ODBC_DataObject): 
 
439
  def _buildQuery(self, conditions={}): 
 
440
    GDebug.printMesg(9,'Implicit Fields: %s' % self._fieldReferences)
 
441
    if len(self._fieldReferences):
 
442
      q = "SELECT %s FROM %s%s" % \
 
443
           (string.join(self._fieldReferences.keys(),","), self.table, 
 
444
            self._conditionToSQL(conditions))
 
445
    else: 
 
446
      q = "SELECT * FROM %s%s" % (self.table, self._conditionToSQL(conditions))
 
447
 
 
448
    if hasattr(self,'order_by'):
 
449
     q = "%s ORDER BY %s " % (q, self.order_by)
 
450
 
 
451
    GDebug.printMesg(9,q)
 
452
 
 
453
    return q
 
454
 
 
455
 
 
456
 
 
457
#
 
458
#  Extensions to Trigger Namespaces
 
459
#
 
460
class TriggerExtensions:
 
461
 
 
462
  def __init__(self, connection):
 
463
    self.__connection = connection
 
464
 
 
465
 
 
466
 
 
467
 
 
468
######################################
 
469
#
 
470
#  The following hashes describe
 
471
#  this driver's characteristings.
 
472
#
 
473
######################################
 
474
 
 
475
#
 
476
#  All datasouce "types" and corresponding DataObject class
 
477
#
 
478
supportedDataObjects = {
 
479
  'object': ODBC_DataObject_Object,
 
480
#  'sql':    ODBC_DataObject_SQL
 
481
}
 
482
 
 
483