Code

Ticket #5062: pymssql.2.py

File pymssql.2.py, 9.9 KB (added by gregoire@…, 7 years ago)

pymssql.py with conversion to latin-1 as unicode error workaround

Line 
1"""DB-SIG compliant module for communicating with MS SQL servers"""
2#***************************************************************************
3#                          pymssql.py  -  description
4#
5#    begin                : 2003-03-03
6#    copyright            : (C) 2003-03-03 by Joon-cheol Park
7#    email                : jooncheol@gmail.com
8#    current developer    : Andrzej Kukula <akukula@gmail.com>
9#    homepage             : http://pymssql.sourceforge.net
10#
11#***************************************************************************
12# This library is free software; you can redistribute it and/or
13# modify it under the terms of the GNU Lesser General Public
14# License as published by the Free Software Foundation; either
15# version 2.1 of the License, or (at your option) any later version.
16#
17# This library is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20# Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public
23# License along with this library; if not, write to the Free Software
24# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25# MA  02110-1301  USA
26#***************************************************************************
27
28__author__ = "Joon-cheol Park <jooncheol@gmail.com>, Andrzej Kukula <akukula@gmail.com>"
29__version__ = '0.8.0'
30import _mssql
31import types
32import string
33import time
34import datetime
35
36### module constants
37
38# compliant with DB SIG 2.0
39apilevel = '2.0'
40
41# module may be shared, but not connections
42threadsafety = 1
43
44# this module use extended python format codes
45paramstyle = 'pyformat'
46
47#export column type names from _mssql
48class DBAPITypeObject:
49        def __init__(self,*values):
50                self.values = values
51        def __cmp__(self,other):
52                if other in self.values:
53                        return 0
54                if other < self.values:
55                        return 1
56                else:
57                        return -1
58
59STRING = DBAPITypeObject(_mssql.STRING)
60BINARY = DBAPITypeObject(_mssql.BINARY)
61NUMBER = DBAPITypeObject(_mssql.NUMBER)
62DATETIME = DBAPITypeObject(_mssql.DATETIME)
63DECIMAL = DBAPITypeObject(_mssql.DECIMAL)
64
65### exception hierarchy
66
67class Warning(StandardError):
68        pass
69
70class Error(StandardError):
71        pass
72
73class InterfaceError(Error):
74        pass
75
76class DatabaseError(Error):
77        pass
78
79class DataError(DatabaseError):
80        pass
81
82class OperationalError(DatabaseError):
83        pass
84
85class IntegrityError(DatabaseError):
86        pass
87
88class InternalError(DatabaseError):
89        pass
90
91class ProgrammingError(DatabaseError):
92        pass
93
94class NotSupportedError(DatabaseError):
95        pass
96
97def unicode_string(s, encoding='ascii'):
98        if type(s) == types.StringType:
99                return unicode(s, encoding)
100        else:
101                return s
102
103def unicode_tuple_encode(t, encoding='ascii'):
104    return tuple([unicode_string(i, encoding) for i in t])
105
106### cursor object
107
108class pymssqlCursor:
109
110        def __init__(self, src):
111                self.__source = src
112                self.description = None
113                self.rowcount = -1
114                self.arraysize = 1
115                self._result = []
116                self.__fetchpos = 0
117                self.__resultpos = 0
118
119        def close(self):
120                self.__source = None
121                self.description = None
122                self.result = []
123                self.rowcount = -1
124
125        def execute(self, operation, params = None):
126                # "The parameters may also be specified as list of
127                # tuples to e.g. insert multiple rows in a single
128                # operation, but this kind of usage is depreciated:
129                if params and type(params) == types.ListType and \
130                                        type(params[0]) == types.TupleType:
131                        self.executemany(operation, params)
132                else:
133                        # not a list of tuples
134                        self.executemany(operation, (params,))
135
136        def executemany(self, operation, param_seq):
137                self.description = None
138                self.rowcount = -1
139                self.__fetchpos = 0
140                self.__resultpos = 0
141
142                # first try to execute all queries
143                totrows = 0
144
145                #Respect GO terminator
146                for sql in operation.split('\nGO'):
147                        if sql=='':
148                                continue
149                        try:
150                                for params in param_seq:
151                                        if params != None:
152                                                sql = _quoteparams(sql, params)
153
154                                #print sql
155                                ret = self.__source.query(sql)
156                                if ret == 1:
157                                        self._result = self.__source.fetch_array()
158                                        for res in range(len(self._result[self.__resultpos][2])):
159                                                self._result[self.__resultpos][2][res] = unicode_tuple_encode(self._result[self.__resultpos][2][res], 'latin-1')
160                                        totrows = totrows + self._result[self.__resultpos][1]
161                                else:
162                                    self._result = None
163                                    raise DatabaseError, "error: %s" % self.__source.errmsg()
164                        except Exception,e:
165                                if self.__source.errmsg() == None:
166                                        raise e
167                                else:
168                                        raise DatabaseError, "internal error: %s" % self.__source.errmsg()
169
170                # then initialize result raw count and description
171                if len(self._result[self.__resultpos][0]) > 0:
172                        self.description = map(lambda (colname,coltype): (colname, coltype, None, None, None, None, None),self._result[self.__resultpos][0])
173                        self.rowcount = totrows
174                else:
175                        self.description = None
176                        self.rowcount = self._result[self.__resultpos][1]
177
178        def nextset(self):
179                if self._result ==None:
180                        return 0
181
182                resultlen =len(self._result)
183                if resultlen>1 and self.__resultpos+1<resultlen:
184                        self.__resultpos = self.__resultpos + 1
185                        return 1
186                else:
187                        return 0
188
189        def fetchone(self):
190                ret = self.fetchmany(1)
191                if ret: return ret[0]
192                else: return None
193
194        def fetchall(self):
195                return self._result[self.__resultpos][2][self.__fetchpos:]
196
197        def fetchmany(self, size = None, keep = 1):
198                if size == None:
199                        size = self.arraysize
200                if keep == 1:
201                        self.arraysize = size
202                res = self._result
203                if res[self.__resultpos][1]==self.__fetchpos:
204                    return []
205                reslen = len(res[self.__resultpos][2][self.__fetchpos:])
206                if reslen < size:
207                    size = res[self.__resultpos][1]
208                ret = res[self.__resultpos][2][self.__fetchpos:self.__fetchpos+size]
209                self.__fetchpos = self.__fetchpos + size
210                return ret
211
212        def setinputsizes(self, sizes):
213                pass
214
215        def setoutputsize(self, size, col = 0):
216                pass
217
218def _quote(x):
219        if type(x) == types.StringType:
220                x = "'" + string.replace(str(x), "'", "''") + "'"
221        elif type(x) == types.UnicodeType:
222                x = "'" + string.replace(str(x.encode('latin-1')), "'", "''") + "'"
223        elif type(x) in (types.IntType, types.LongType, types.FloatType):
224                pass
225        elif x is None:
226                x = 'NULL'
227        # datetime quoting (thanks Jan Finell <jfinell@regionline.fi>)
228        # described under "Writing International Transact-SQL Statements" in BOL
229        # beware the order: isinstance(x,datetime.date)=True if x is
230        # datetime.datetime ! Also round x.microsecond to milliseconds,
231        # otherwise we get Msg 241, Level 16, State 1: Syntax error
232        elif isinstance(x, datetime.datetime):
233                x = "{ts '%04d-%02d-%02d %02d:%02d:%02d.%s'}" % \
234                        (x.year,x.month, x.day,
235                        x.hour, x.minute, x.second, x.microsecond / 1000)
236        elif isinstance(x, datetime.date):
237                x = "{d '%04d-%02d-%02d'}" % (x.year, x.month, x.day)
238        # alternative quoting by Luciano Pacheco <lucmult@gmail.com>
239        #elif hasattr(x, 'timetuple'):
240        #       x = time.strftime('\'%Y%m%d %H:%M:%S\'', x.timetuple())
241        elif type(x) == types.BooleanType: 
242                x = x and 1 or 0 
243        else:
244                #print "didn't like " + x + " " + str(type(x))
245                raise InterfaceError, 'do not know how to handle type %s' % type(x)
246
247        return x
248
249def _quoteparams(s, params):
250        if hasattr(params, 'has_key'):
251                x = {}
252                for k, v in params.items():
253                        x[k] = _quote(v)
254                params = x
255        else:
256                params = tuple(map(_quote, params))
257        return s % params
258
259
260
261### connection object
262
263class pymssqlCnx:
264
265        def __init__(self, cnx):
266                self.__cnx = cnx
267                self.__autocommit = False
268                try:
269                        self.__cnx.query("IF @@TRANCOUNT>0 begin tran")
270                        self.__cnx.fetch_array()
271                except:
272                        raise OperationalError, "invalid connection."
273
274        def close(self):
275                if self.__cnx == None:
276                        raise OperationalError, "invalid connection."
277                self.__cnx.close()
278                self.__cnx = None
279
280        def commit(self):
281                if self.__cnx == None:
282                        raise OperationalError, "invalid connection."
283
284                if self.__autocommit == True:
285                        return
286
287                try:
288                        self.__cnx.query("IF @@TRANCOUNT>0 commit tran")
289                        self.__cnx.fetch_array()
290                        self.__cnx.query("IF @@TRANCOUNT>0 begin tran")
291                        self.__cnx.fetch_array()
292                except:
293                        raise OperationalError, "can't commit."
294
295        def rollback(self):
296                if self.__cnx == None:
297                        raise OperationalError, "invalid connection."
298
299                if self.__autocommit == True:
300                        return
301
302                try:
303                        self.__cnx.query("IF @@TRANCOUNT>0 rollback tran")
304                        self.__cnx.fetch_array()
305                        self.__cnx.query("IF @@TRANCOUNT>0 begin tran")
306                        self.__cnx.fetch_array()
307                except:
308                        raise OperationalError, "can't rollback."
309
310        def autocommit(self,status):
311                if status:
312                        if self.__autocommit == False:
313                                self.__cnx.query("IF @@TRANCOUNT>0 rollback tran")
314                                self.__cnx.fetch_array()
315                                self.__autocommit = True
316                        else:
317                                if self.__autocommit == True:
318                                        self.__cnx.query("IF @@TRANCOUNT>0 begin tran")
319                                        self.__cnx.fetch_array()
320                                        self.__autocommit = False
321
322        def cursor(self):
323                if self.__cnx == None:
324                        raise OperationalError, "invalid connection."
325                try:
326                        return pymssqlCursor(self.__cnx)
327                except:
328                        raise OperationalError, "invalid connection."
329
330
331
332# connects to a database
333def connect(dsn = None, user = "sa", password = "", host = ".", database = "master"):
334        # first get params from DSN
335        dbhost = ""
336        dbbase = ""
337        dbuser = ""
338        dbpasswd = ""
339        dbopt = ""
340        dbtty = ""
341        try:
342                params = string.split(dsn, ":")
343                dbhost = params[0]
344                dbbase = params[1]
345                dbuser = params[2]
346                dbpasswd = params[3]
347                dbopt = params[4]
348                dbtty = params[5]
349        except:
350                pass
351
352        # override if necessary
353        if user != "":
354                dbuser = user
355        if password != "":
356                dbpasswd = password
357        if database != "":
358                dbbase = database
359        if host != "":
360                dbhost = host
361
362        # empty host is localhost
363        if dbhost == "":
364                dbhost = "."
365        if dbuser == "":
366                dbuser = "sa"
367
368        # open the connection
369        con = _mssql.connect(dbhost, dbuser, dbpasswd)
370        con.select_db(dbbase)
371        return pymssqlCnx(con)
372