Mercurial > p > mysql-python > mysqldb-2
comparison tests/dbapi20.py @ 18:d55bfb1a4701 MySQLdb
Tons of changes from major refactoring/cleanup. This is all really broken
right now. In particular, all results are returned as strings.
author | adustman |
---|---|
date | Fri, 14 Mar 2008 23:06:29 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
17:7c7a89123d65 | 18:d55bfb1a4701 |
---|---|
1 #!/usr/bin/env python | |
2 ''' Python DB API 2.0 driver compliance unit test suite. | |
3 | |
4 This software is Public Domain and may be used without restrictions. | |
5 | |
6 "Now we have booze and barflies entering the discussion, plus rumours of | |
7 DBAs on drugs... and I won't tell you what flashes through my mind each | |
8 time I read the subject line with 'Anal Compliance' in it. All around | |
9 this is turning out to be a thoroughly unwholesome unit test." | |
10 | |
11 -- Ian Bicking | |
12 ''' | |
13 | |
14 __rcs_id__ = '$Id$' | |
15 __version__ = '$Revision$'[11:-2] | |
16 __author__ = 'Stuart Bishop <[email protected]>' | |
17 | |
18 import unittest | |
19 import time | |
20 | |
21 # $Log$ | |
22 # Revision 1.1.2.1 2006/02/25 03:44:32 adustman | |
23 # Generic DB-API unit test module | |
24 # | |
25 # Revision 1.10 2003/10/09 03:14:14 zenzen | |
26 # Add test for DB API 2.0 optional extension, where database exceptions | |
27 # are exposed as attributes on the Connection object. | |
28 # | |
29 # Revision 1.9 2003/08/13 01:16:36 zenzen | |
30 # Minor tweak from Stefan Fleiter | |
31 # | |
32 # Revision 1.8 2003/04/10 00:13:25 zenzen | |
33 # Changes, as per suggestions by M.-A. Lemburg | |
34 # - Add a table prefix, to ensure namespace collisions can always be avoided | |
35 # | |
36 # Revision 1.7 2003/02/26 23:33:37 zenzen | |
37 # Break out DDL into helper functions, as per request by David Rushby | |
38 # | |
39 # Revision 1.6 2003/02/21 03:04:33 zenzen | |
40 # Stuff from Henrik Ekelund: | |
41 # added test_None | |
42 # added test_nextset & hooks | |
43 # | |
44 # Revision 1.5 2003/02/17 22:08:43 zenzen | |
45 # Implement suggestions and code from Henrik Eklund - test that cursor.arraysize | |
46 # defaults to 1 & generic cursor.callproc test added | |
47 # | |
48 # Revision 1.4 2003/02/15 00:16:33 zenzen | |
49 # Changes, as per suggestions and bug reports by M.-A. Lemburg, | |
50 # Matthew T. Kromer, Federico Di Gregorio and Daniel Dittmar | |
51 # - Class renamed | |
52 # - Now a subclass of TestCase, to avoid requiring the driver stub | |
53 # to use multiple inheritance | |
54 # - Reversed the polarity of buggy test in test_description | |
55 # - Test exception heirarchy correctly | |
56 # - self.populate is now self._populate(), so if a driver stub | |
57 # overrides self.ddl1 this change propogates | |
58 # - VARCHAR columns now have a width, which will hopefully make the | |
59 # DDL even more portible (this will be reversed if it causes more problems) | |
60 # - cursor.rowcount being checked after various execute and fetchXXX methods | |
61 # - Check for fetchall and fetchmany returning empty lists after results | |
62 # are exhausted (already checking for empty lists if select retrieved | |
63 # nothing | |
64 # - Fix bugs in test_setoutputsize_basic and test_setinputsizes | |
65 # | |
66 | |
67 class DatabaseAPI20Test(unittest.TestCase): | |
68 ''' Test a database self.driver for DB API 2.0 compatibility. | |
69 This implementation tests Gadfly, but the TestCase | |
70 is structured so that other self.drivers can subclass this | |
71 test case to ensure compiliance with the DB-API. It is | |
72 expected that this TestCase may be expanded in the future | |
73 if ambiguities or edge conditions are discovered. | |
74 | |
75 The 'Optional Extensions' are not yet being tested. | |
76 | |
77 self.drivers should subclass this test, overriding setUp, tearDown, | |
78 self.driver, connect_args and connect_kw_args. Class specification | |
79 should be as follows: | |
80 | |
81 import dbapi20 | |
82 class mytest(dbapi20.DatabaseAPI20Test): | |
83 [...] | |
84 | |
85 Don't 'import DatabaseAPI20Test from dbapi20', or you will | |
86 confuse the unit tester - just 'import dbapi20'. | |
87 ''' | |
88 | |
89 # The self.driver module. This should be the module where the 'connect' | |
90 # method is to be found | |
91 driver = None | |
92 connect_args = () # List of arguments to pass to connect | |
93 connect_kw_args = {} # Keyword arguments for connect | |
94 table_prefix = 'dbapi20test_' # If you need to specify a prefix for tables | |
95 | |
96 ddl1 = 'create table %sbooze (name varchar(20))' % table_prefix | |
97 ddl2 = 'create table %sbarflys (name varchar(20))' % table_prefix | |
98 xddl1 = 'drop table %sbooze' % table_prefix | |
99 xddl2 = 'drop table %sbarflys' % table_prefix | |
100 | |
101 lowerfunc = 'lower' # Name of stored procedure to convert string->lowercase | |
102 | |
103 # Some drivers may need to override these helpers, for example adding | |
104 # a 'commit' after the execute. | |
105 def executeDDL1(self,cursor): | |
106 cursor.execute(self.ddl1) | |
107 | |
108 def executeDDL2(self,cursor): | |
109 cursor.execute(self.ddl2) | |
110 | |
111 def setUp(self): | |
112 ''' self.drivers should override this method to perform required setup | |
113 if any is necessary, such as creating the database. | |
114 ''' | |
115 pass | |
116 | |
117 def tearDown(self): | |
118 ''' self.drivers should override this method to perform required cleanup | |
119 if any is necessary, such as deleting the test database. | |
120 The default drops the tables that may be created. | |
121 ''' | |
122 con = self._connect() | |
123 try: | |
124 cur = con.cursor() | |
125 for ddl in (self.xddl1,self.xddl2): | |
126 try: | |
127 cur.execute(ddl) | |
128 con.commit() | |
129 except self.driver.Error: | |
130 # Assume table didn't exist. Other tests will check if | |
131 # execute is busted. | |
132 pass | |
133 finally: | |
134 con.close() | |
135 | |
136 def _connect(self): | |
137 try: | |
138 return self.driver.connect( | |
139 *self.connect_args,**self.connect_kw_args | |
140 ) | |
141 except AttributeError: | |
142 self.fail("No connect method found in self.driver module") | |
143 | |
144 def test_connect(self): | |
145 con = self._connect() | |
146 con.close() | |
147 | |
148 def test_apilevel(self): | |
149 try: | |
150 # Must exist | |
151 apilevel = self.driver.apilevel | |
152 # Must equal 2.0 | |
153 self.assertEqual(apilevel,'2.0') | |
154 except AttributeError: | |
155 self.fail("Driver doesn't define apilevel") | |
156 | |
157 def test_threadsafety(self): | |
158 try: | |
159 # Must exist | |
160 threadsafety = self.driver.threadsafety | |
161 # Must be a valid value | |
162 self.failUnless(threadsafety in (0,1,2,3)) | |
163 except AttributeError: | |
164 self.fail("Driver doesn't define threadsafety") | |
165 | |
166 def test_paramstyle(self): | |
167 try: | |
168 # Must exist | |
169 paramstyle = self.driver.paramstyle | |
170 # Must be a valid value | |
171 self.failUnless(paramstyle in ( | |
172 'qmark','numeric','named','format','pyformat' | |
173 )) | |
174 except AttributeError: | |
175 self.fail("Driver doesn't define paramstyle") | |
176 | |
177 def test_Exceptions(self): | |
178 # Make sure required exceptions exist, and are in the | |
179 # defined heirarchy. | |
180 self.failUnless(issubclass(self.driver.Warning,StandardError)) | |
181 self.failUnless(issubclass(self.driver.Error,StandardError)) | |
182 self.failUnless( | |
183 issubclass(self.driver.InterfaceError,self.driver.Error) | |
184 ) | |
185 self.failUnless( | |
186 issubclass(self.driver.DatabaseError,self.driver.Error) | |
187 ) | |
188 self.failUnless( | |
189 issubclass(self.driver.OperationalError,self.driver.Error) | |
190 ) | |
191 self.failUnless( | |
192 issubclass(self.driver.IntegrityError,self.driver.Error) | |
193 ) | |
194 self.failUnless( | |
195 issubclass(self.driver.InternalError,self.driver.Error) | |
196 ) | |
197 self.failUnless( | |
198 issubclass(self.driver.ProgrammingError,self.driver.Error) | |
199 ) | |
200 self.failUnless( | |
201 issubclass(self.driver.NotSupportedError,self.driver.Error) | |
202 ) | |
203 | |
204 def test_ExceptionsAsConnectionAttributes(self): | |
205 # OPTIONAL EXTENSION | |
206 # Test for the optional DB API 2.0 extension, where the exceptions | |
207 # are exposed as attributes on the Connection object | |
208 # I figure this optional extension will be implemented by any | |
209 # driver author who is using this test suite, so it is enabled | |
210 # by default. | |
211 con = self._connect() | |
212 drv = self.driver | |
213 self.failUnless(con.Warning is drv.Warning) | |
214 self.failUnless(con.Error is drv.Error) | |
215 self.failUnless(con.InterfaceError is drv.InterfaceError) | |
216 self.failUnless(con.DatabaseError is drv.DatabaseError) | |
217 self.failUnless(con.OperationalError is drv.OperationalError) | |
218 self.failUnless(con.IntegrityError is drv.IntegrityError) | |
219 self.failUnless(con.InternalError is drv.InternalError) | |
220 self.failUnless(con.ProgrammingError is drv.ProgrammingError) | |
221 self.failUnless(con.NotSupportedError is drv.NotSupportedError) | |
222 | |
223 | |
224 def test_commit(self): | |
225 con = self._connect() | |
226 try: | |
227 # Commit must work, even if it doesn't do anything | |
228 con.commit() | |
229 finally: | |
230 con.close() | |
231 | |
232 def test_rollback(self): | |
233 con = self._connect() | |
234 # If rollback is defined, it should either work or throw | |
235 # the documented exception | |
236 if hasattr(con,'rollback'): | |
237 try: | |
238 con.rollback() | |
239 except self.driver.NotSupportedError: | |
240 pass | |
241 | |
242 def test_cursor(self): | |
243 con = self._connect() | |
244 try: | |
245 cur = con.cursor() | |
246 finally: | |
247 con.close() | |
248 | |
249 def test_cursor_isolation(self): | |
250 con = self._connect() | |
251 try: | |
252 # Make sure cursors created from the same connection have | |
253 # the documented transaction isolation level | |
254 cur1 = con.cursor() | |
255 cur2 = con.cursor() | |
256 self.executeDDL1(cur1) | |
257 cur1.execute("insert into %sbooze values ('Victoria Bitter')" % ( | |
258 self.table_prefix | |
259 )) | |
260 cur2.execute("select name from %sbooze" % self.table_prefix) | |
261 booze = cur2.fetchall() | |
262 self.assertEqual(len(booze),1) | |
263 self.assertEqual(len(booze[0]),1) | |
264 self.assertEqual(booze[0][0],'Victoria Bitter') | |
265 finally: | |
266 con.close() | |
267 | |
268 def test_description(self): | |
269 con = self._connect() | |
270 try: | |
271 cur = con.cursor() | |
272 self.executeDDL1(cur) | |
273 self.assertEqual(cur.description,None, | |
274 'cursor.description should be none after executing a ' | |
275 'statement that can return no rows (such as DDL)' | |
276 ) | |
277 cur.execute('select name from %sbooze' % self.table_prefix) | |
278 self.assertEqual(len(cur.description),1, | |
279 'cursor.description describes too many columns' | |
280 ) | |
281 self.assertEqual(len(cur.description[0]),7, | |
282 'cursor.description[x] tuples must have 7 elements' | |
283 ) | |
284 self.assertEqual(cur.description[0][0].lower(),'name', | |
285 'cursor.description[x][0] must return column name' | |
286 ) | |
287 self.assertEqual(cur.description[0][1],self.driver.STRING, | |
288 'cursor.description[x][1] must return column type. Got %r' | |
289 % cur.description[0][1] | |
290 ) | |
291 | |
292 # Make sure self.description gets reset | |
293 self.executeDDL2(cur) | |
294 self.assertEqual(cur.description,None, | |
295 'cursor.description not being set to None when executing ' | |
296 'no-result statements (eg. DDL)' | |
297 ) | |
298 finally: | |
299 con.close() | |
300 | |
301 def test_rowcount(self): | |
302 con = self._connect() | |
303 try: | |
304 cur = con.cursor() | |
305 self.executeDDL1(cur) | |
306 self.assertEqual(cur.rowcount,-1, | |
307 'cursor.rowcount should be -1 after executing no-result ' | |
308 'statements' | |
309 ) | |
310 cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( | |
311 self.table_prefix | |
312 )) | |
313 self.failUnless(cur.rowcount in (-1,1), | |
314 'cursor.rowcount should == number or rows inserted, or ' | |
315 'set to -1 after executing an insert statement' | |
316 ) | |
317 cur.execute("select name from %sbooze" % self.table_prefix) | |
318 self.failUnless(cur.rowcount in (-1,1), | |
319 'cursor.rowcount should == number of rows returned, or ' | |
320 'set to -1 after executing a select statement' | |
321 ) | |
322 self.executeDDL2(cur) | |
323 self.assertEqual(cur.rowcount,-1, | |
324 'cursor.rowcount not being reset to -1 after executing ' | |
325 'no-result statements' | |
326 ) | |
327 finally: | |
328 con.close() | |
329 | |
330 lower_func = 'lower' | |
331 def test_callproc(self): | |
332 con = self._connect() | |
333 try: | |
334 cur = con.cursor() | |
335 if self.lower_func and hasattr(cur,'callproc'): | |
336 r = cur.callproc(self.lower_func,('FOO',)) | |
337 self.assertEqual(len(r),1) | |
338 self.assertEqual(r[0],'FOO') | |
339 r = cur.fetchall() | |
340 self.assertEqual(len(r),1,'callproc produced no result set') | |
341 self.assertEqual(len(r[0]),1, | |
342 'callproc produced invalid result set' | |
343 ) | |
344 self.assertEqual(r[0][0],'foo', | |
345 'callproc produced invalid results' | |
346 ) | |
347 finally: | |
348 con.close() | |
349 | |
350 def test_close(self): | |
351 con = self._connect() | |
352 try: | |
353 cur = con.cursor() | |
354 finally: | |
355 con.close() | |
356 | |
357 # cursor.execute should raise an Error if called after connection | |
358 # closed | |
359 self.assertRaises(self.driver.Error,self.executeDDL1,cur) | |
360 | |
361 # connection.commit should raise an Error if called after connection' | |
362 # closed.' | |
363 self.assertRaises(self.driver.Error,con.commit) | |
364 | |
365 # connection.close should raise an Error if called more than once | |
366 self.assertRaises(self.driver.Error,con.close) | |
367 | |
368 def test_execute(self): | |
369 con = self._connect() | |
370 try: | |
371 cur = con.cursor() | |
372 self._paraminsert(cur) | |
373 finally: | |
374 con.close() | |
375 | |
376 def _paraminsert(self,cur): | |
377 self.executeDDL1(cur) | |
378 cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( | |
379 self.table_prefix | |
380 )) | |
381 self.failUnless(cur.rowcount in (-1,1)) | |
382 | |
383 if self.driver.paramstyle == 'qmark': | |
384 cur.execute( | |
385 'insert into %sbooze values (?)' % self.table_prefix, | |
386 ("Cooper's",) | |
387 ) | |
388 elif self.driver.paramstyle == 'numeric': | |
389 cur.execute( | |
390 'insert into %sbooze values (:1)' % self.table_prefix, | |
391 ("Cooper's",) | |
392 ) | |
393 elif self.driver.paramstyle == 'named': | |
394 cur.execute( | |
395 'insert into %sbooze values (:beer)' % self.table_prefix, | |
396 {'beer':"Cooper's"} | |
397 ) | |
398 elif self.driver.paramstyle == 'format': | |
399 cur.execute( | |
400 'insert into %sbooze values (%%s)' % self.table_prefix, | |
401 ("Cooper's",) | |
402 ) | |
403 elif self.driver.paramstyle == 'pyformat': | |
404 cur.execute( | |
405 'insert into %sbooze values (%%(beer)s)' % self.table_prefix, | |
406 {'beer':"Cooper's"} | |
407 ) | |
408 else: | |
409 self.fail('Invalid paramstyle') | |
410 self.failUnless(cur.rowcount in (-1,1)) | |
411 | |
412 cur.execute('select name from %sbooze' % self.table_prefix) | |
413 res = cur.fetchall() | |
414 self.assertEqual(len(res),2,'cursor.fetchall returned too few rows') | |
415 beers = [res[0][0],res[1][0]] | |
416 beers.sort() | |
417 self.assertEqual(beers[0],"Cooper's", | |
418 'cursor.fetchall retrieved incorrect data, or data inserted ' | |
419 'incorrectly' | |
420 ) | |
421 self.assertEqual(beers[1],"Victoria Bitter", | |
422 'cursor.fetchall retrieved incorrect data, or data inserted ' | |
423 'incorrectly' | |
424 ) | |
425 | |
426 def test_executemany(self): | |
427 con = self._connect() | |
428 try: | |
429 cur = con.cursor() | |
430 self.executeDDL1(cur) | |
431 largs = [ ("Cooper's",) , ("Boag's",) ] | |
432 margs = [ {'beer': "Cooper's"}, {'beer': "Boag's"} ] | |
433 if self.driver.paramstyle == 'qmark': | |
434 cur.executemany( | |
435 'insert into %sbooze values (?)' % self.table_prefix, | |
436 largs | |
437 ) | |
438 elif self.driver.paramstyle == 'numeric': | |
439 cur.executemany( | |
440 'insert into %sbooze values (:1)' % self.table_prefix, | |
441 largs | |
442 ) | |
443 elif self.driver.paramstyle == 'named': | |
444 cur.executemany( | |
445 'insert into %sbooze values (:beer)' % self.table_prefix, | |
446 margs | |
447 ) | |
448 elif self.driver.paramstyle == 'format': | |
449 cur.executemany( | |
450 'insert into %sbooze values (%%s)' % self.table_prefix, | |
451 largs | |
452 ) | |
453 elif self.driver.paramstyle == 'pyformat': | |
454 cur.executemany( | |
455 'insert into %sbooze values (%%(beer)s)' % ( | |
456 self.table_prefix | |
457 ), | |
458 margs | |
459 ) | |
460 else: | |
461 self.fail('Unknown paramstyle') | |
462 self.failUnless(cur.rowcount in (-1,2), | |
463 'insert using cursor.executemany set cursor.rowcount to ' | |
464 'incorrect value %r' % cur.rowcount | |
465 ) | |
466 cur.execute('select name from %sbooze' % self.table_prefix) | |
467 res = cur.fetchall() | |
468 self.assertEqual(len(res),2, | |
469 'cursor.fetchall retrieved incorrect number of rows' | |
470 ) | |
471 beers = [res[0][0],res[1][0]] | |
472 beers.sort() | |
473 self.assertEqual(beers[0],"Boag's",'incorrect data retrieved') | |
474 self.assertEqual(beers[1],"Cooper's",'incorrect data retrieved') | |
475 finally: | |
476 con.close() | |
477 | |
478 def test_fetchone(self): | |
479 con = self._connect() | |
480 try: | |
481 cur = con.cursor() | |
482 | |
483 # cursor.fetchone should raise an Error if called before | |
484 # executing a select-type query | |
485 self.assertRaises(self.driver.Error,cur.fetchone) | |
486 | |
487 # cursor.fetchone should raise an Error if called after | |
488 # executing a query that cannnot return rows | |
489 self.executeDDL1(cur) | |
490 self.assertRaises(self.driver.Error,cur.fetchone) | |
491 | |
492 cur.execute('select name from %sbooze' % self.table_prefix) | |
493 self.assertEqual(cur.fetchone(),None, | |
494 'cursor.fetchone should return None if a query retrieves ' | |
495 'no rows' | |
496 ) | |
497 self.failUnless(cur.rowcount in (-1,0)) | |
498 | |
499 # cursor.fetchone should raise an Error if called after | |
500 # executing a query that cannnot return rows | |
501 cur.execute("insert into %sbooze values ('Victoria Bitter')" % ( | |
502 self.table_prefix | |
503 )) | |
504 self.assertRaises(self.driver.Error,cur.fetchone) | |
505 | |
506 cur.execute('select name from %sbooze' % self.table_prefix) | |
507 r = cur.fetchone() | |
508 self.assertEqual(len(r),1, | |
509 'cursor.fetchone should have retrieved a single row' | |
510 ) | |
511 self.assertEqual(r[0],'Victoria Bitter', | |
512 'cursor.fetchone retrieved incorrect data' | |
513 ) | |
514 self.assertEqual(cur.fetchone(),None, | |
515 'cursor.fetchone should return None if no more rows available' | |
516 ) | |
517 self.failUnless(cur.rowcount in (-1,1)) | |
518 finally: | |
519 con.close() | |
520 | |
521 samples = [ | |
522 'Carlton Cold', | |
523 'Carlton Draft', | |
524 'Mountain Goat', | |
525 'Redback', | |
526 'Victoria Bitter', | |
527 'XXXX' | |
528 ] | |
529 | |
530 def _populate(self): | |
531 ''' Return a list of sql commands to setup the DB for the fetch | |
532 tests. | |
533 ''' | |
534 populate = [ | |
535 "insert into %sbooze values ('%s')" % (self.table_prefix,s) | |
536 for s in self.samples | |
537 ] | |
538 return populate | |
539 | |
540 def test_fetchmany(self): | |
541 con = self._connect() | |
542 try: | |
543 cur = con.cursor() | |
544 | |
545 # cursor.fetchmany should raise an Error if called without | |
546 #issuing a query | |
547 self.assertRaises(self.driver.Error,cur.fetchmany,4) | |
548 | |
549 self.executeDDL1(cur) | |
550 for sql in self._populate(): | |
551 cur.execute(sql) | |
552 | |
553 cur.execute('select name from %sbooze' % self.table_prefix) | |
554 r = cur.fetchmany() | |
555 self.assertEqual(len(r),1, | |
556 'cursor.fetchmany retrieved incorrect number of rows, ' | |
557 'default of arraysize is one.' | |
558 ) | |
559 cur.arraysize=10 | |
560 r = cur.fetchmany(3) # Should get 3 rows | |
561 self.assertEqual(len(r),3, | |
562 'cursor.fetchmany retrieved incorrect number of rows' | |
563 ) | |
564 r = cur.fetchmany(4) # Should get 2 more | |
565 self.assertEqual(len(r),2, | |
566 'cursor.fetchmany retrieved incorrect number of rows' | |
567 ) | |
568 r = cur.fetchmany(4) # Should be an empty sequence | |
569 self.assertEqual(len(r),0, | |
570 'cursor.fetchmany should return an empty sequence after ' | |
571 'results are exhausted' | |
572 ) | |
573 self.failUnless(cur.rowcount in (-1,6)) | |
574 | |
575 # Same as above, using cursor.arraysize | |
576 cur.arraysize=4 | |
577 cur.execute('select name from %sbooze' % self.table_prefix) | |
578 r = cur.fetchmany() # Should get 4 rows | |
579 self.assertEqual(len(r),4, | |
580 'cursor.arraysize not being honoured by fetchmany' | |
581 ) | |
582 r = cur.fetchmany() # Should get 2 more | |
583 self.assertEqual(len(r),2) | |
584 r = cur.fetchmany() # Should be an empty sequence | |
585 self.assertEqual(len(r),0) | |
586 self.failUnless(cur.rowcount in (-1,6)) | |
587 | |
588 cur.arraysize=6 | |
589 cur.execute('select name from %sbooze' % self.table_prefix) | |
590 rows = cur.fetchmany() # Should get all rows | |
591 self.failUnless(cur.rowcount in (-1,6)) | |
592 self.assertEqual(len(rows),6) | |
593 self.assertEqual(len(rows),6) | |
594 rows = [r[0] for r in rows] | |
595 rows.sort() | |
596 | |
597 # Make sure we get the right data back out | |
598 for i in range(0,6): | |
599 self.assertEqual(rows[i],self.samples[i], | |
600 'incorrect data retrieved by cursor.fetchmany' | |
601 ) | |
602 | |
603 rows = cur.fetchmany() # Should return an empty list | |
604 self.assertEqual(len(rows),0, | |
605 'cursor.fetchmany should return an empty sequence if ' | |
606 'called after the whole result set has been fetched' | |
607 ) | |
608 self.failUnless(cur.rowcount in (-1,6)) | |
609 | |
610 self.executeDDL2(cur) | |
611 cur.execute('select name from %sbarflys' % self.table_prefix) | |
612 r = cur.fetchmany() # Should get empty sequence | |
613 self.assertEqual(len(r),0, | |
614 'cursor.fetchmany should return an empty sequence if ' | |
615 'query retrieved no rows' | |
616 ) | |
617 self.failUnless(cur.rowcount in (-1,0)) | |
618 | |
619 finally: | |
620 con.close() | |
621 | |
622 def test_fetchall(self): | |
623 con = self._connect() | |
624 try: | |
625 cur = con.cursor() | |
626 # cursor.fetchall should raise an Error if called | |
627 # without executing a query that may return rows (such | |
628 # as a select) | |
629 self.assertRaises(self.driver.Error, cur.fetchall) | |
630 | |
631 self.executeDDL1(cur) | |
632 for sql in self._populate(): | |
633 cur.execute(sql) | |
634 | |
635 # cursor.fetchall should raise an Error if called | |
636 # after executing a a statement that cannot return rows | |
637 self.assertRaises(self.driver.Error,cur.fetchall) | |
638 | |
639 cur.execute('select name from %sbooze' % self.table_prefix) | |
640 rows = cur.fetchall() | |
641 self.failUnless(cur.rowcount in (-1,len(self.samples))) | |
642 self.assertEqual(len(rows),len(self.samples), | |
643 'cursor.fetchall did not retrieve all rows' | |
644 ) | |
645 rows = [r[0] for r in rows] | |
646 rows.sort() | |
647 for i in range(0,len(self.samples)): | |
648 self.assertEqual(rows[i],self.samples[i], | |
649 'cursor.fetchall retrieved incorrect rows' | |
650 ) | |
651 rows = cur.fetchall() | |
652 self.assertEqual( | |
653 len(rows),0, | |
654 'cursor.fetchall should return an empty list if called ' | |
655 'after the whole result set has been fetched' | |
656 ) | |
657 self.failUnless(cur.rowcount in (-1,len(self.samples))) | |
658 | |
659 self.executeDDL2(cur) | |
660 cur.execute('select name from %sbarflys' % self.table_prefix) | |
661 rows = cur.fetchall() | |
662 self.failUnless(cur.rowcount in (-1,0)) | |
663 self.assertEqual(len(rows),0, | |
664 'cursor.fetchall should return an empty list if ' | |
665 'a select query returns no rows' | |
666 ) | |
667 | |
668 finally: | |
669 con.close() | |
670 | |
671 def test_mixedfetch(self): | |
672 con = self._connect() | |
673 try: | |
674 cur = con.cursor() | |
675 self.executeDDL1(cur) | |
676 for sql in self._populate(): | |
677 cur.execute(sql) | |
678 | |
679 cur.execute('select name from %sbooze' % self.table_prefix) | |
680 rows1 = cur.fetchone() | |
681 rows23 = cur.fetchmany(2) | |
682 rows4 = cur.fetchone() | |
683 rows56 = cur.fetchall() | |
684 self.failUnless(cur.rowcount in (-1,6)) | |
685 self.assertEqual(len(rows23),2, | |
686 'fetchmany returned incorrect number of rows' | |
687 ) | |
688 self.assertEqual(len(rows56),2, | |
689 'fetchall returned incorrect number of rows' | |
690 ) | |
691 | |
692 rows = [rows1[0]] | |
693 rows.extend([rows23[0][0],rows23[1][0]]) | |
694 rows.append(rows4[0]) | |
695 rows.extend([rows56[0][0],rows56[1][0]]) | |
696 rows.sort() | |
697 for i in range(0,len(self.samples)): | |
698 self.assertEqual(rows[i],self.samples[i], | |
699 'incorrect data retrieved or inserted' | |
700 ) | |
701 finally: | |
702 con.close() | |
703 | |
704 def help_nextset_setUp(self,cur): | |
705 ''' Should create a procedure called deleteme | |
706 that returns two result sets, first the | |
707 number of rows in booze then "name from booze" | |
708 ''' | |
709 raise NotImplementedError,'Helper not implemented' | |
710 #sql=""" | |
711 # create procedure deleteme as | |
712 # begin | |
713 # select count(*) from booze | |
714 # select name from booze | |
715 # end | |
716 #""" | |
717 #cur.execute(sql) | |
718 | |
719 def help_nextset_tearDown(self,cur): | |
720 'If cleaning up is needed after nextSetTest' | |
721 raise NotImplementedError,'Helper not implemented' | |
722 #cur.execute("drop procedure deleteme") | |
723 | |
724 def test_nextset(self): | |
725 con = self._connect() | |
726 try: | |
727 cur = con.cursor() | |
728 if not hasattr(cur,'nextset'): | |
729 return | |
730 | |
731 try: | |
732 self.executeDDL1(cur) | |
733 sql=self._populate() | |
734 for sql in self._populate(): | |
735 cur.execute(sql) | |
736 | |
737 self.help_nextset_setUp(cur) | |
738 | |
739 cur.callproc('deleteme') | |
740 numberofrows=cur.fetchone() | |
741 assert numberofrows[0]== len(self.samples) | |
742 assert cur.nextset() | |
743 names=cur.fetchall() | |
744 assert len(names) == len(self.samples) | |
745 s=cur.nextset() | |
746 assert s == None,'No more return sets, should return None' | |
747 finally: | |
748 self.help_nextset_tearDown(cur) | |
749 | |
750 finally: | |
751 con.close() | |
752 | |
753 def test_nextset(self): | |
754 raise NotImplementedError,'Drivers need to override this test' | |
755 | |
756 def test_arraysize(self): | |
757 # Not much here - rest of the tests for this are in test_fetchmany | |
758 con = self._connect() | |
759 try: | |
760 cur = con.cursor() | |
761 self.failUnless(hasattr(cur,'arraysize'), | |
762 'cursor.arraysize must be defined' | |
763 ) | |
764 finally: | |
765 con.close() | |
766 | |
767 def test_setinputsizes(self): | |
768 con = self._connect() | |
769 try: | |
770 cur = con.cursor() | |
771 cur.setinputsizes( (25,) ) | |
772 self._paraminsert(cur) # Make sure cursor still works | |
773 finally: | |
774 con.close() | |
775 | |
776 def test_setoutputsize_basic(self): | |
777 # Basic test is to make sure setoutputsize doesn't blow up | |
778 con = self._connect() | |
779 try: | |
780 cur = con.cursor() | |
781 cur.setoutputsize(1000) | |
782 cur.setoutputsize(2000,0) | |
783 self._paraminsert(cur) # Make sure the cursor still works | |
784 finally: | |
785 con.close() | |
786 | |
787 def test_setoutputsize(self): | |
788 # Real test for setoutputsize is driver dependant | |
789 raise NotImplementedError,'Driver need to override this test' | |
790 | |
791 def test_None(self): | |
792 con = self._connect() | |
793 try: | |
794 cur = con.cursor() | |
795 self.executeDDL1(cur) | |
796 cur.execute('insert into %sbooze values (NULL)' % self.table_prefix) | |
797 cur.execute('select name from %sbooze' % self.table_prefix) | |
798 r = cur.fetchall() | |
799 self.assertEqual(len(r),1) | |
800 self.assertEqual(len(r[0]),1) | |
801 self.assertEqual(r[0][0],None,'NULL value not returned as None') | |
802 finally: | |
803 con.close() | |
804 | |
805 def test_Date(self): | |
806 d1 = self.driver.Date(2002,12,25) | |
807 d2 = self.driver.DateFromTicks(time.mktime((2002,12,25,0,0,0,0,0,0))) | |
808 # Can we assume this? API doesn't specify, but it seems implied | |
809 # self.assertEqual(str(d1),str(d2)) | |
810 | |
811 def test_Time(self): | |
812 t1 = self.driver.Time(13,45,30) | |
813 t2 = self.driver.TimeFromTicks(time.mktime((2001,1,1,13,45,30,0,0,0))) | |
814 # Can we assume this? API doesn't specify, but it seems implied | |
815 # self.assertEqual(str(t1),str(t2)) | |
816 | |
817 def test_Timestamp(self): | |
818 t1 = self.driver.Timestamp(2002,12,25,13,45,30) | |
819 t2 = self.driver.TimestampFromTicks( | |
820 time.mktime((2002,12,25,13,45,30,0,0,0)) | |
821 ) | |
822 # Can we assume this? API doesn't specify, but it seems implied | |
823 # self.assertEqual(str(t1),str(t2)) | |
824 | |
825 def test_Binary(self): | |
826 b = self.driver.Binary('Something') | |
827 b = self.driver.Binary('') | |
828 | |
829 def test_STRING(self): | |
830 self.failUnless(hasattr(self.driver,'STRING'), | |
831 'module.STRING must be defined' | |
832 ) | |
833 | |
834 def test_BINARY(self): | |
835 self.failUnless(hasattr(self.driver,'BINARY'), | |
836 'module.BINARY must be defined.' | |
837 ) | |
838 | |
839 def test_NUMBER(self): | |
840 self.failUnless(hasattr(self.driver,'NUMBER'), | |
841 'module.NUMBER must be defined.' | |
842 ) | |
843 | |
844 def test_DATETIME(self): | |
845 self.failUnless(hasattr(self.driver,'DATETIME'), | |
846 'module.DATETIME must be defined.' | |
847 ) | |
848 | |
849 def test_ROWID(self): | |
850 self.failUnless(hasattr(self.driver,'ROWID'), | |
851 'module.ROWID must be defined.' | |
852 ) | |
853 |