comparison MySQLdb/cursors.py @ 0:e48810735f11 MySQLdb

Copying 1.2.1 to be the new trunk
author adustman
date Sun, 02 Apr 2006 18:20:53 +0000
parents
children b5a377255eea
comparison
equal deleted inserted replaced
-1:000000000000 0:e48810735f11
1 """MySQLdb Cursors
2
3 This module implements Cursors of various types for MySQLdb. By
4 default, MySQLdb uses the Cursor class.
5
6 """
7
8 import re
9 insert_values = re.compile(r'\svalues\s*(\(.+\))', re.IGNORECASE)
10 from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \
11 DatabaseError, OperationalError, IntegrityError, InternalError, \
12 NotSupportedError, ProgrammingError
13
14
15 class BaseCursor(object):
16
17 """A base for Cursor classes. Useful attributes:
18
19 description
20 A tuple of DB API 7-tuples describing the columns in
21 the last executed query; see PEP-249 for details.
22
23 description_flags
24 Tuple of column flags for last query, one entry per column
25 in the result set. Values correspond to those in
26 MySQLdb.constants.FLAG. See MySQL documentation (C API)
27 for more information. Non-standard extension.
28
29 arraysize
30 default number of rows fetchmany() will fetch
31
32 """
33
34 from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \
35 DatabaseError, DataError, OperationalError, IntegrityError, \
36 InternalError, ProgrammingError, NotSupportedError
37
38 def __init__(self, connection):
39 from weakref import proxy
40
41 self.connection = proxy(connection)
42 self.description = None
43 self.description_flags = None
44 self.rowcount = -1
45 self.arraysize = 1
46 self._executed = None
47 self.lastrowid = None
48 self.messages = []
49 self.errorhandler = connection.errorhandler
50 self._result = None
51 self._warnings = 0
52 self._info = None
53 self.rownumber = None
54
55 def __del__(self):
56 self.close()
57 self.errorhandler = None
58 self._result = None
59
60 def close(self):
61 """Close the cursor. No further queries will be possible."""
62 if not self.connection: return
63 while self.nextset(): pass
64 self.connection = None
65
66 def _check_executed(self):
67 if not self._executed:
68 self.errorhandler(self, ProgrammingError, "execute() first")
69
70 def _warning_check(self):
71 from warnings import warn
72 if self._warnings:
73 warnings = self._get_db().show_warnings()
74 if warnings:
75 # This is done in two loops in case
76 # Warnings are set to raise exceptions.
77 for w in warnings:
78 self.messages.append((self.Warning, w))
79 for w in warnings:
80 warn(w[-1], self.Warning, 3)
81 elif self._info:
82 self.messages.append((self.Warning, self._info))
83 warn(self._info, self.Warning, 3)
84
85 def nextset(self):
86 """Advance to the next result set.
87
88 Returns None if there are no more result sets.
89 """
90 if self._executed:
91 self.fetchall()
92 del self.messages[:]
93
94 db = self._get_db()
95 nr = db.next_result()
96 if nr == -1:
97 return None
98 self._do_get_result()
99 self._post_get_result()
100 self._warning_check()
101 return 1
102
103 def _post_get_result(self): pass
104
105 def _do_get_result(self):
106 db = self._get_db()
107 self._result = self._get_result()
108 self.rowcount = db.affected_rows()
109 self.rownumber = 0
110 self.description = self._result and self._result.describe() or None
111 self.description_flags = self._result and self._result.field_flags() or None
112 self.lastrowid = db.insert_id()
113 self._warnings = db.warning_count()
114 self._info = db.info()
115
116 def setinputsizes(self, *args):
117 """Does nothing, required by DB API."""
118
119 def setoutputsizes(self, *args):
120 """Does nothing, required by DB API."""
121
122 def _get_db(self):
123 if not self.connection:
124 self.errorhandler(self, ProgrammingError, "cursor closed")
125 return self.connection
126
127 def execute(self, query, args=None):
128
129 """Execute a query.
130
131 query -- string, query to execute on server
132 args -- optional sequence or mapping, parameters to use with query.
133
134 Note: If args is a sequence, then %s must be used as the
135 parameter placeholder in the query. If a mapping is used,
136 %(key)s must be used as the placeholder.
137
138 Returns long integer rows affected, if any
139
140 """
141 from types import ListType, TupleType
142 from sys import exc_info
143 del self.messages[:]
144 db = self._get_db()
145 charset = db.character_set_name()
146 query = query.encode(charset)
147 if args is not None:
148 query = query % db.literal(args)
149 try:
150 r = self._query(query)
151 except TypeError, m:
152 if m.args[0] in ("not enough arguments for format string",
153 "not all arguments converted"):
154 self.messages.append((ProgrammingError, m.args[0]))
155 self.errorhandler(self, ProgrammingError, m.args[0])
156 else:
157 self.messages.append((TypeError, m))
158 self.errorhandler(self, TypeError, m)
159 except:
160 exc, value, tb = exc_info()
161 del tb
162 self.messages.append((exc, value))
163 self.errorhandler(self, exc, value)
164 self._executed = query
165 self._warning_check()
166 return r
167
168 def executemany(self, query, args):
169
170 """Execute a multi-row query.
171
172 query -- string, query to execute on server
173
174 args
175
176 Sequence of sequences or mappings, parameters to use with
177 query.
178
179 Returns long integer rows affected, if any.
180
181 This method improves performance on multiple-row INSERT and
182 REPLACE. Otherwise it is equivalent to looping over args with
183 execute().
184
185 """
186 del self.messages[:]
187 db = self._get_db()
188 if not args: return
189 m = insert_values.search(query)
190 if not m:
191 r = 0
192 for a in args:
193 r = r + self.execute(query, a)
194 return r
195 p = m.start(1)
196 charset = db.character_set_name()
197 query = query.encode(charset)
198 qv = query[p:]
199 qargs = db.literal(args)
200 try:
201 q = [ query % qargs[0] ]
202 q.extend([ qv % a for a in qargs[1:] ])
203 except TypeError, msg:
204 if msg.args[0] in ("not enough arguments for format string",
205 "not all arguments converted"):
206 self.messages.append((ProgrammingError, msg.args[0]))
207 self.errorhandler(self, ProgrammingError, msg.args[0])
208 else:
209 self.messages.append((TypeError, msg))
210 self.errorhandler(self, TypeError, msg)
211 except:
212 from sys import exc_info
213 exc, value, tb = exc_info()
214 del tb
215 self.errorhandler(self, exc, value)
216 r = self._query(',\n'.join(q))
217 self._warning_check()
218 return r
219
220 def callproc(self, procname, args=()):
221
222 """Execute stored procedure procname with args
223
224 procname -- string, name of procedure to execute on server
225
226 args -- Sequence of parameters to use with procedure
227
228 Returns the original args.
229
230 Compatibility warning: PEP-249 specifies that any modified
231 parameters must be returned. This is currently impossible
232 as they are only available by storing them in a server
233 variable and then retrieved by a query. Since stored
234 procedures return zero or more result sets, there is no
235 reliable way to get at OUT or INOUT parameters via callproc.
236 The server variables are named @_procname_n, where procname
237 is the parameter above and n is the position of the parameter
238 (from zero). Once all result sets generated by the procedure
239 have been fetched, you can issue a SELECT @_procname_0, ...
240 query using .execute() to get any OUT or INOUT values.
241
242 Compatibility warning: The act of calling a stored procedure
243 itself creates an empty result set. This appears after any
244 result sets generated by the procedure. This is non-standard
245 behavior with respect to the DB-API. Be sure to use nextset()
246 to advance through all result sets; otherwise you may get
247 disconnected.
248 """
249
250 from types import UnicodeType
251 db = self._get_db()
252 charset = db.character_set_name()
253 for index, arg in enumerate(args):
254 q = "SET @_%s_%d=%s" % (procname, index,
255 db.literal(arg))
256 if type(q) is UnicodeType:
257 q = q.encode(charset)
258 self._query(q)
259 self.nextset()
260
261 q = "CALL %s(%s)" % (procname,
262 ','.join(['@_%s_%d' % (procname, i)
263 for i in range(len(args))]))
264 if type(q) is UnicodeType:
265 q = q.encode(charset)
266 self._query(q)
267 self._warning_check()
268 return args
269
270 def _do_query(self, q):
271 db = self._get_db()
272 self._last_executed = q
273 db.query(q)
274 self._do_get_result()
275 return self.rowcount
276
277 def _query(self, q): return self._do_query(q)
278
279 def _fetch_row(self, size=1):
280 if not self._result:
281 return ()
282 return self._result.fetch_row(size, self._fetch_type)
283
284 def __iter__(self):
285 return iter(self.fetchone, None)
286
287 Warning = Warning
288 Error = Error
289 InterfaceError = InterfaceError
290 DatabaseError = DatabaseError
291 DataError = DataError
292 OperationalError = OperationalError
293 IntegrityError = IntegrityError
294 InternalError = InternalError
295 ProgrammingError = ProgrammingError
296 NotSupportedError = NotSupportedError
297
298
299 class CursorStoreResultMixIn(object):
300
301 """This is a MixIn class which causes the entire result set to be
302 stored on the client side, i.e. it uses mysql_store_result(). If the
303 result set can be very large, consider adding a LIMIT clause to your
304 query, or using CursorUseResultMixIn instead."""
305
306 def _get_result(self): return self._get_db().store_result()
307
308 def _query(self, q):
309 rowcount = self._do_query(q)
310 self._post_get_result()
311 return rowcount
312
313 def _post_get_result(self):
314 self._rows = self._fetch_row(0)
315 self._result = None
316
317 def fetchone(self):
318 """Fetches a single row from the cursor. None indicates that
319 no more rows are available."""
320 self._check_executed()
321 if self.rownumber >= len(self._rows): return None
322 result = self._rows[self.rownumber]
323 self.rownumber = self.rownumber+1
324 return result
325
326 def fetchmany(self, size=None):
327 """Fetch up to size rows from the cursor. Result set may be smaller
328 than size. If size is not defined, cursor.arraysize is used."""
329 self._check_executed()
330 end = self.rownumber + (size or self.arraysize)
331 result = self._rows[self.rownumber:end]
332 self.rownumber = min(end, len(self._rows))
333 return result
334
335 def fetchall(self):
336 """Fetchs all available rows from the cursor."""
337 self._check_executed()
338 if self.rownumber:
339 result = self._rows[self.rownumber:]
340 else:
341 result = self._rows
342 self.rownumber = len(self._rows)
343 return result
344
345 def scroll(self, value, mode='relative'):
346 """Scroll the cursor in the result set to a new position according
347 to mode.
348
349 If mode is 'relative' (default), value is taken as offset to
350 the current position in the result set, if set to 'absolute',
351 value states an absolute target position."""
352 self._check_executed()
353 if mode == 'relative':
354 r = self.rownumber + value
355 elif mode == 'absolute':
356 r = value
357 else:
358 self.errorhandler(self, ProgrammingError,
359 "unknown scroll mode %s" % `mode`)
360 if r < 0 or r >= len(self._rows):
361 self.errorhandler(self, IndexError, "out of range")
362 self.rownumber = r
363
364 def __iter__(self):
365 self._check_executed()
366 result = self.rownumber and self._rows[self.rownumber:] or self._rows
367 return iter(result)
368
369
370 class CursorUseResultMixIn(object):
371
372 """This is a MixIn class which causes the result set to be stored
373 in the server and sent row-by-row to client side, i.e. it uses
374 mysql_use_result(). You MUST retrieve the entire result set and
375 close() the cursor before additional queries can be peformed on
376 the connection."""
377
378 def _get_result(self): return self._get_db().use_result()
379
380 def fetchone(self):
381 """Fetches a single row from the cursor."""
382 self._check_executed()
383 r = self._fetch_row(1)
384 if not r: return None
385 self.rownumber = self.rownumber + 1
386 return r[0]
387
388 def fetchmany(self, size=None):
389 """Fetch up to size rows from the cursor. Result set may be smaller
390 than size. If size is not defined, cursor.arraysize is used."""
391 self._check_executed()
392 r = self._fetch_row(size or self.arraysize)
393 self.rownumber = self.rownumber + len(r)
394 return r
395
396 def fetchall(self):
397 """Fetchs all available rows from the cursor."""
398 self._check_executed()
399 r = self._fetch_row(0)
400 self.rownumber = self.rownumber + len(r)
401 return r
402
403 def __iter__(self):
404 return self
405
406 def next(self):
407 row = self.fetchone()
408 if row is None:
409 raise StopIteration
410 return row
411
412
413 class CursorTupleRowsMixIn(object):
414
415 """This is a MixIn class that causes all rows to be returned as tuples,
416 which is the standard form required by DB API."""
417
418 _fetch_type = 0
419
420
421 class CursorDictRowsMixIn(object):
422
423 """This is a MixIn class that causes all rows to be returned as
424 dictionaries. This is a non-standard feature."""
425
426 _fetch_type = 1
427
428 def fetchoneDict(self):
429 """Fetch a single row as a dictionary. Deprecated:
430 Use fetchone() instead. Will be removed in 1.3."""
431 from warnings import warn
432 warn("fetchoneDict() is non-standard and will be removed in 1.3",
433 DeprecationWarning, 2)
434 return self.fetchone()
435
436 def fetchmanyDict(self, size=None):
437 """Fetch several rows as a list of dictionaries. Deprecated:
438 Use fetchmany() instead. Will be removed in 1.3."""
439 from warnings import warn
440 warn("fetchmanyDict() is non-standard and will be removed in 1.3",
441 DeprecationWarning, 2)
442 return self.fetchmany(size)
443
444 def fetchallDict(self):
445 """Fetch all available rows as a list of dictionaries. Deprecated:
446 Use fetchall() instead. Will be removed in 1.3."""
447 from warnings import warn
448 warn("fetchallDict() is non-standard and will be removed in 1.3",
449 DeprecationWarning, 2)
450 return self.fetchall()
451
452
453 class CursorOldDictRowsMixIn(CursorDictRowsMixIn):
454
455 """This is a MixIn class that returns rows as dictionaries with
456 the same key convention as the old Mysqldb (MySQLmodule). Don't
457 use this."""
458
459 _fetch_type = 2
460
461
462 class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn,
463 BaseCursor):
464
465 """This is the standard Cursor class that returns rows as tuples
466 and stores the result set in the client."""
467
468
469 class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn,
470 BaseCursor):
471
472 """This is a Cursor class that returns rows as dictionaries and
473 stores the result set in the client."""
474
475
476 class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn,
477 BaseCursor):
478
479 """This is a Cursor class that returns rows as tuples and stores
480 the result set in the server."""
481
482
483 class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn,
484 BaseCursor):
485
486 """This is a Cursor class that returns rows as dictionaries and
487 stores the result set in the server."""
488
489