changeset 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 7c7a89123d65
children c3ecc32aea27
files MANIFEST.in MySQLdb/__init__.py MySQLdb/connections.py MySQLdb/cursors.py MySQLdb/exceptions.py _mysql.c _mysql.h _mysql_exceptions.py _mysql_results.c dbapi20.py metadata.cfg setup.py test_MySQLdb_capabilities.py test_MySQLdb_dbapi20.py test_capabilities.py tests/capabilities.py tests/dbapi20.py tests/test_MySQLdb_capabilities.py tests/test_MySQLdb_dbapi20.py
diffstat 19 files changed, 1726 insertions(+), 1620 deletions(-) [+]
line wrap: on
line diff
--- a/MANIFEST.in	Tue Feb 27 00:58:49 2007 +0000
+++ b/MANIFEST.in	Fri Mar 14 23:06:29 2008 +0000
@@ -5,10 +5,7 @@
 include HISTORY
 include GPL
 include pymemcompat.h
-include dbapi20.py
-include test_MySQLdb_dbapi20.py
-include test_capabilities.py
-include test_MySQLdb_capabilities.py
+recursive-include tests *.py
 include metadata.cfg
 include site.cfg
 include setup_*.py
--- a/MySQLdb/__init__.py	Tue Feb 27 00:58:49 2007 +0000
+++ b/MySQLdb/__init__.py	Fri Mar 14 23:06:29 2008 +0000
@@ -18,19 +18,14 @@
 
 __revision__ = """$Revision$"""[11:-2]
 from MySQLdb.release import __version__, version_info, __author__
-
-import _mysql
-
-if version_info != _mysql.version_info:
-    raise ImportError, \
-          "this is MySQLdb version %s, but _mysql is version %r" % \
-          (version_info, _mysql.version_info)
+from MySQLdb.exceptions import Warning, Error, InterfaceError, DataError, \
+     DatabaseError, OperationalError, IntegrityError, InternalError, \
+     NotSupportedError, ProgrammingError
 
 threadsafety = 1
 apilevel = "2.0"
 paramstyle = "format"
 
-from _mysql import *
 from MySQLdb.constants import FIELD_TYPE
 from MySQLdb.times import Date, Time, Timestamp, \
     DateFromTicks, TimeFromTicks, TimestampFromTicks
@@ -42,6 +37,7 @@
     """A special type of set for which A == x is True if A is a
     DBAPISet and x is a member of that set.
     
+      >>> from MySQLdb.constants import FIELD_TYPE
       >>> FIELD_TYPE.VAR_STRING == STRING
       True
       >>> FIELD_TYPE.DATE == NUMBER
@@ -90,15 +86,17 @@
 
 connect = Connection = Connect
 
-__all__ = [ 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'Date',
-            'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks',
-            'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError',
-            'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER',
-            'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError',
-            'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect',
-            'connections', 'constants', 'converters', 'cursors', 'debug', 'escape',
-            'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info',
-            'paramstyle', 'string_literal', 'threadsafety', 'version_info']
+__all__ = [
+    'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'Date',
+    'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', 'TimestampFromTicks',
+    'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError',
+    'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER',
+    'NotSupportedError', 'DBAPISet', 'OperationalError', 'ProgrammingError',
+    'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect',
+    'connections', 'constants', 'converters', 'cursors', 'debug', 'escape',
+    'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info',
+    'paramstyle', 'string_literal', 'threadsafety', 'version_info',
+    ]
 
 
 if __name__ == "__main__":
--- a/MySQLdb/connections.py	Tue Feb 27 00:58:49 2007 +0000
+++ b/MySQLdb/connections.py	Fri Mar 14 23:06:29 2008 +0000
@@ -12,9 +12,6 @@
 __revision__ = "$Revision$"[11:-2]
 __author__ = "$Author$"[9:-2]
 
-from MySQLdb.cursors import Cursor
-import _mysql
-
 
 def defaulterrorhandler(connection, cursor, errorclass, errorvalue):
     """
@@ -35,14 +32,13 @@
     raise errorclass, errorvalue
 
 
-class Connection(_mysql.connection):
+class Connection(object):
 
     """MySQL Database Connection Object"""
 
-    default_cursor = Cursor
     errorhandler = defaulterrorhandler
     
-    from _mysql_exceptions import Warning, Error, InterfaceError, DataError, \
+    from MySQLdb.exceptions import Warning, Error, InterfaceError, DataError, \
          DatabaseError, OperationalError, IntegrityError, InternalError, \
          NotSupportedError, ProgrammingError
 
@@ -131,6 +127,8 @@
         """
         from MySQLdb.constants import CLIENT, FIELD_TYPE
         from MySQLdb.converters import conversions
+        from MySQLdb.cursors import Cursor
+        import _mysql
         from weakref import proxy
         
         kwargs2 = kwargs.copy()
@@ -147,9 +145,9 @@
                     conv2[k] = v[:]
                 else:
                     conv2[k] = v
-        kwargs2['conv'] = conv2
+        #kwargs2['conv'] = conv2
 
-        self.cursorclass = kwargs2.pop('cursorclass', self.default_cursor)
+        self.cursorclass = kwargs2.pop('cursorclass', Cursor)
         charset = kwargs2.pop('charset', '')
 
         if charset:
@@ -170,24 +168,24 @@
             
         kwargs2['client_flag'] = client_flag
 
-        super(Connection, self).__init__(*args, **kwargs2)
+        self._db = _mysql.connection(*args, **kwargs2)
 
         self.encoders = dict(
             [ (k, v) for k, v in conv.items()
               if type(k) is not int ])
         
         self._server_version = tuple(
-            [ int(n) for n in self.get_server_info().split('.')[:2] ])
+            [ int(n) for n in self._db.get_server_info().split('.')[:2] ])
 
         db = proxy(self)
         def _get_string_literal():
             def string_literal(obj, dummy=None):
-                return db.string_literal(obj)
+                return self._db.string_literal(obj)
             return string_literal
 
         def _get_unicode_literal():
             def unicode_literal(u, dummy=None):
-                return db.literal(u.encode(unicode_literal.charset))
+                return self.literal(u.encode(unicode_literal.charset))
             return unicode_literal
 
         def _get_string_decoder():
@@ -199,26 +197,40 @@
         self.unicode_literal = unicode_literal = _get_unicode_literal()
         self.string_decoder = string_decoder = _get_string_decoder()
         if not charset:
-            charset = self.character_set_name()
-        self.set_character_set(charset)
+            charset = self._db.character_set_name()
+        self._db.set_character_set(charset)
 
         if sql_mode:
             self.set_sql_mode(sql_mode)
 
-        if use_unicode:
-            self.converter[FIELD_TYPE.STRING].append((None, string_decoder))
-            self.converter[FIELD_TYPE.VAR_STRING].append((None, string_decoder))
-            self.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder))
-            self.converter[FIELD_TYPE.BLOB].append((None, string_decoder))
+        #if use_unicode:
+            #self._db.converter[FIELD_TYPE.STRING].append((None, string_decoder))
+            #self._db.converter[FIELD_TYPE.VAR_STRING].append((None, string_decoder))
+            #self._db.converter[FIELD_TYPE.VARCHAR].append((None, string_decoder))
+            #self._db.converter[FIELD_TYPE.BLOB].append((None, string_decoder))
 
         self.encoders[str] = string_literal
         self.encoders[unicode] = unicode_literal
-        self._transactional = self.server_capabilities & CLIENT.TRANSACTIONS
+        string_decoder.charset = charset
+        unicode_literal.charset = charset
+        self._transactional = self._db.server_capabilities & CLIENT.TRANSACTIONS
         if self._transactional:
             # PEP-249 requires autocommit to be initially off
             self.autocommit(False)
         self.messages = []
-        
+    
+    def autocommit(self, do_autocommit):
+        return self._db.autocommit(do_autocommit)
+    
+    def commit(self):
+        return self._db.commit()
+    
+    def rollback(self):
+        return self._db.rollback()
+    
+    def close(self):
+        return self._db.close()
+    
     def cursor(self, cursorclass=None):
         """
         Create a cursor on which queries may be performed. The optional
@@ -244,19 +256,21 @@
 
         Non-standard. For internal use; do not use this in your applications.
         """
-        return self.escape(obj, self.encoders)
-
-    if not hasattr(_mysql.connection, 'warning_count'):
+        return self._db.escape(obj, self.encoders)
 
-        def warning_count(self):
-            """Return the number of warnings generated from the last query.
-            This is derived from the info() method."""
-            info = self.info()
+    def _warning_count(self):
+        """Return the number of warnings generated from the last query."""
+        if hasattr(self._db, "warning_count"):
+            return self._db.warning_count()
+        else:
+            info = self._db.info()
             if info:
                 return int(info.split()[-1])
             else:
                 return 0
-
+    def character_set_name(self):
+        return self._db.character_set_name()
+    
     def set_character_set(self, charset):
         """Set the connection character set to charset. The character set can
         only be changed in MySQL-4.1 and newer. If you try to change the
@@ -267,12 +281,12 @@
         connection using the charset parameter."""
         if self.character_set_name() != charset:
             try:
-                super(Connection, self).set_character_set(charset)
+                self._db.set_character_set(charset)
             except AttributeError:
                 if self._server_version < (4, 1):
                     raise self.NotSupportedError, "server is too old to set charset"
-                self.query('SET NAMES %s' % charset)
-                self.store_result()
+                self._db.query('SET NAMES %s' % charset)
+                self._db.store_result()
         self.string_decoder.charset = charset
         self.unicode_literal.charset = charset
 
@@ -284,10 +298,10 @@
         using the sql_mode parameter."""
         if self._server_version < (4, 1):
             raise self.NotSupportedError, "server is too old to set sql_mode"
-        self.query("SET SESSION sql_mode='%s'" % sql_mode)
-        self.store_result()
+        self._db.query("SET SESSION sql_mode='%s'" % sql_mode)
+        self._db.store_result()
         
-    def show_warnings(self):
+    def _show_warnings(self):
         """Return detailed information about warnings as a sequence of tuples
         of (Level, Code, Message). This is only supported in MySQL-4.1 and up.
         If your server is an earlier version, an empty sequence is returned.
@@ -295,8 +309,8 @@
         Non-standard. This is invoked automatically after executing a query,
         so you should not usually call it yourself."""
         if self._server_version < (4, 1): return ()
-        self.query("SHOW WARNINGS")
-        result = self.store_result()
+        self._db.query("SHOW WARNINGS")
+        result = self._db.store_result()
         warnings = result.fetch_row(0)
         return warnings
     
--- a/MySQLdb/cursors.py	Tue Feb 27 00:58:49 2007 +0000
+++ b/MySQLdb/cursors.py	Fri Mar 14 23:06:29 2008 +0000
@@ -35,7 +35,7 @@
 
     """
 
-    from _mysql_exceptions import MySQLError, Warning, Error, InterfaceError, \
+    from MySQLdb.exceptions import MySQLError, Warning, Error, InterfaceError, \
          DatabaseError, DataError, OperationalError, IntegrityError, \
          InternalError, ProgrammingError, NotSupportedError
     
@@ -68,7 +68,10 @@
         """Close the cursor. No further queries will be possible."""
         if not self.connection:
             return
-        while self.nextset():
+        try:
+            while self.nextset():
+                pass
+        except:
             pass
         self.connection = None
 
@@ -142,7 +145,7 @@
         Raises ProgrammingError if the connection has been closed."""
         if not self.connection:
             self.errorhandler(self, self.ProgrammingError, "cursor closed")
-        return self.connection
+        return self.connection._db
     
     def execute(self, query, args=None):
         """Execute a query.
@@ -159,12 +162,12 @@
         """
         from sys import exc_info
         del self.messages[:]
-        connection = self._get_db()
-        charset = connection.character_set_name()
+        db = self._get_db()
+        charset = db.character_set_name()
         if isinstance(query, unicode):
             query = query.encode(charset)
         if args is not None:
-            query = query % connection.literal(args)
+            query = query % self.connection.literal(args)
         try:
             result = self._query(query)
         except TypeError, msg:
@@ -205,10 +208,10 @@
 
         """
         del self.messages[:]
-        connection = self._get_db()
+        db = self._get_db()
         if not args:
             return
-        charset = connection.character_set_name()
+        charset = self.connection.character_set_name()
         if isinstance(query, unicode):
             query = query.encode(charset)
         matched = INSERT_VALUES.match(query)
@@ -221,7 +224,7 @@
         values = matched.group('values')
  
         try:
-            sql_params = [ values % connection.literal(arg) for arg in args ]
+            sql_params = [ values % self.connection.literal(arg) for arg in args ]
         except TypeError, msg:
             if msg.args[0] in ("not enough arguments for format string",
                                "not all arguments converted"):
@@ -273,11 +276,11 @@
         disconnected.
         """
 
-        connection = self._get_db()
-        charset = connection.character_set_name()
+        db = self._get_db()
+        charset = self.connection.character_set_name()
         for index, arg in enumerate(args):
             query = "SET @_%s_%d=%s" % (procname, index,
-                                        connection.literal(arg))
+                                        self.connection.literal(arg))
             if isinstance(query, unicode):
                 query = query.encode(charset)
             self._query(query)
@@ -297,7 +300,7 @@
     def _do_query(self, query):
         """Low-levey query wrapper. Overridden by MixIns."""
         connection = self._get_db()
-        self._last_executed = query
+        self._executed = query
         connection.query(query)
         self._do_get_result()
         return self.rowcount
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MySQLdb/exceptions.py	Fri Mar 14 23:06:29 2008 +0000
@@ -0,0 +1,117 @@
+"""
+MySQLdb.exceptions
+==================
+
+These classes are dictated by the DB API v2.0:
+
+    http://www.python.org/topics/database/DatabaseAPI-2.0.html
+"""
+
+# from __future__ import absolute_import
+# Unfortunately, you cannot put the above in a conditional statement.
+# It would make things much cleaner for Python-2.5, but breaks older.
+
+try:
+    from exceptions import Exception, StandardError, Warning
+except ImportError:
+    import sys
+    e = sys.modules['exceptions']
+    StandardError = e.StandardError
+    Warning = e.Warning
+    
+from MySQLdb.constants import ER
+
+class MySQLError(StandardError):
+    
+    """Exception related to operation with MySQL."""
+
+
+class Warning(Warning, MySQLError):
+
+    """Exception raised for important warnings like data truncations
+    while inserting, etc."""
+
+class Error(MySQLError):
+
+    """Exception that is the base class of all other error exceptions
+    (not Warning)."""
+
+
+class InterfaceError(Error):
+
+    """Exception raised for errors that are related to the database
+    interface rather than the database itself."""
+
+
+class DatabaseError(Error):
+
+    """Exception raised for errors that are related to the
+    database."""
+
+
+class DataError(DatabaseError):
+
+    """Exception raised for errors that are due to problems with the
+    processed data like division by zero, numeric value out of range,
+    etc."""
+
+
+class OperationalError(DatabaseError):
+
+    """Exception raised for errors that are related to the database's
+    operation and not necessarily under the control of the programmer,
+    e.g. an unexpected disconnect occurs, the data source name is not
+    found, a transaction could not be processed, a memory allocation
+    error occurred during processing, etc."""
+
+
+class IntegrityError(DatabaseError):
+
+    """Exception raised when the relational integrity of the database
+    is affected, e.g. a foreign key check fails, duplicate key,
+    etc."""
+
+
+class InternalError(DatabaseError):
+
+    """Exception raised when the database encounters an internal
+    error, e.g. the cursor is not valid anymore, the transaction is
+    out of sync, etc."""
+
+
+class ProgrammingError(DatabaseError):
+
+    """Exception raised for programming errors, e.g. table not found
+    or already exists, syntax error in the SQL statement, wrong number
+    of parameters specified, etc."""
+
+
+class NotSupportedError(DatabaseError):
+
+    """Exception raised in case a method or database API was used
+    which is not supported by the database, e.g. requesting a
+    .rollback() on a connection that does not support transaction or
+    has transactions turned off."""
+
+
+error_map = {}
+
+def _map_error(exc, *errors):
+    for error in errors:
+        error_map[error] = exc
+
+_map_error(ProgrammingError, ER.DB_CREATE_EXISTS, ER.SYNTAX_ERROR,
+           ER.PARSE_ERROR, ER.NO_SUCH_TABLE, ER.WRONG_DB_NAME,
+           ER.WRONG_TABLE_NAME, ER.FIELD_SPECIFIED_TWICE,
+           ER.INVALID_GROUP_FUNC_USE, ER.UNSUPPORTED_EXTENSION,
+           ER.TABLE_MUST_HAVE_COLUMNS, ER.CANT_DO_THIS_DURING_AN_TRANSACTION)
+_map_error(DataError, ER.WARN_DATA_TRUNCATED, ER.WARN_NULL_TO_NOTNULL,
+           ER.WARN_DATA_OUT_OF_RANGE, ER.NO_DEFAULT, ER.PRIMARY_CANT_HAVE_NULL,
+           ER.DATA_TOO_LONG, ER.DATETIME_FUNCTION_OVERFLOW)
+_map_error(IntegrityError, ER.DUP_ENTRY, ER.NO_REFERENCED_ROW,
+           ER.NO_REFERENCED_ROW_2, ER.ROW_IS_REFERENCED, ER.ROW_IS_REFERENCED_2,
+           ER.CANNOT_ADD_FOREIGN)
+_map_error(NotSupportedError, ER.WARNING_NOT_COMPLETE_ROLLBACK,
+           ER.NOT_SUPPORTED_YET, ER.FEATURE_DISABLED, ER.UNKNOWN_STORAGE_ENGINE)
+
+del StandardError, _map_error, ER
--- a/_mysql.c	Tue Feb 27 00:58:49 2007 +0000
+++ b/_mysql.c	Fri Mar 14 23:06:29 2008 +0000
@@ -1,7 +1,17 @@
 #include "_mysql.h"
 
-extern PyTypeObject _mysql_ConnectionObject_Type;
-extern PyTypeObject _mysql_ResultObject_Type;
+PyObject *_mysql_MySQLError;
+ PyObject *_mysql_Warning;
+ PyObject *_mysql_Error;
+  PyObject *_mysql_DatabaseError;
+  PyObject *_mysql_InterfaceError; 
+  PyObject *_mysql_DataError;
+  PyObject *_mysql_OperationalError; 
+  PyObject *_mysql_IntegrityError; 
+  PyObject *_mysql_InternalError; 
+  PyObject *_mysql_ProgrammingError;
+  PyObject *_mysql_NotSupportedError;
+PyObject *_mysql_error_map;
 
 int _mysql_server_init_done = 0;
 
@@ -439,6 +449,7 @@
 	if (!module) return; /* this really should never happen */
 	_mysql_ConnectionObject_Type.ob_type = &PyType_Type;
 	_mysql_ResultObject_Type.ob_type = &PyType_Type;
+	_mysql_FieldObject_Type.ob_type = &PyType_Type;
 #if PY_VERSION_HEX >= 0x02020000
 	_mysql_ConnectionObject_Type.tp_alloc = PyType_GenericAlloc;
 	_mysql_ConnectionObject_Type.tp_new = PyType_GenericNew;
@@ -446,6 +457,9 @@
 	_mysql_ResultObject_Type.tp_alloc = PyType_GenericAlloc;
 	_mysql_ResultObject_Type.tp_new = PyType_GenericNew;
 	_mysql_ResultObject_Type.tp_free = _PyObject_GC_Del;
+	_mysql_FieldObject_Type.tp_alloc = PyType_GenericAlloc;
+	_mysql_FieldObject_Type.tp_new = PyType_GenericNew;
+	_mysql_FieldObject_Type.tp_free = _PyObject_GC_Del;
 #endif
 
 	if (!(dict = PyModule_GetDict(module))) goto error;
@@ -464,7 +478,11 @@
 			       (PyObject *)&_mysql_ResultObject_Type))
 		goto error;	
 	Py_INCREF(&_mysql_ResultObject_Type);
-	if (!(emod = PyImport_ImportModule("_mysql_exceptions")))
+	if (PyDict_SetItemString(dict, "field",
+			       (PyObject *)&_mysql_FieldObject_Type))
+		goto error;	
+	Py_INCREF(&_mysql_FieldObject_Type);
+	if (!(emod = PyImport_ImportModule("MySQLdb.exceptions")))
 		goto error;
 	if (!(edict = PyModule_GetDict(emod))) goto error;
 	if (!(_mysql_MySQLError =
--- a/_mysql.h	Tue Feb 27 00:58:49 2007 +0000
+++ b/_mysql.h	Fri Mar 14 23:06:29 2008 +0000
@@ -57,6 +57,15 @@
 
 extern PyTypeObject _mysql_ResultObject_Type;
 
+typedef struct {
+	PyObject_HEAD
+	PyObject *result;
+	MYSQL_FIELD field;
+	unsigned int index;
+} _mysql_FieldObject;
+
+extern PyTypeObject _mysql_FieldObject_Type;
+
 int _mysql_server_init_done;
 #if MYSQL_VERSION_ID >= 40000
 #define check_server_init(x) if (!_mysql_server_init_done) { if (mysql_server_init(0, NULL, NULL)) { _mysql_Exception(NULL); return x; } else { _mysql_server_init_done = 1;} }
@@ -64,18 +73,18 @@
 #define check_server_init(x) if (!_mysql_server_init_done) _mysql_server_init_done = 1
 #endif
 
-PyObject *_mysql_MySQLError;
- PyObject *_mysql_Warning;
- PyObject *_mysql_Error;
- PyObject *_mysql_DatabaseError;
- PyObject *_mysql_InterfaceError; 
- PyObject *_mysql_DataError;
- PyObject *_mysql_OperationalError; 
- PyObject *_mysql_IntegrityError; 
- PyObject *_mysql_InternalError; 
- PyObject *_mysql_ProgrammingError;
- PyObject *_mysql_NotSupportedError;
-PyObject *_mysql_error_map;
+extern PyObject *_mysql_MySQLError;
+extern PyObject *_mysql_Warning;
+extern PyObject *_mysql_Error;
+extern PyObject *_mysql_DatabaseError;
+extern PyObject *_mysql_InterfaceError; 
+extern PyObject *_mysql_DataError;
+extern PyObject *_mysql_OperationalError; 
+extern PyObject *_mysql_IntegrityError; 
+extern PyObject *_mysql_InternalError; 
+extern PyObject *_mysql_ProgrammingError;
+extern PyObject *_mysql_NotSupportedError;
+extern PyObject *_mysql_error_map;
 
 extern PyObject *
 _mysql_Exception(_mysql_ConnectionObject *c);
@@ -85,3 +94,9 @@
 	_mysql_ResultObject *self,
 	PyObject *args,
 	PyObject *kwargs);
+
+extern int
+_mysql_FieldObject_Initialize(
+	_mysql_FieldObject *self,
+	PyObject *args,
+	PyObject *kwargs);
--- a/_mysql_exceptions.py	Tue Feb 27 00:58:49 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,103 +0,0 @@
-"""_mysql_exceptions: Exception classes for _mysql and MySQLdb.
-
-These classes are dictated by the DB API v2.0:
-
-    http://www.python.org/topics/database/DatabaseAPI-2.0.html
-"""
-
-from exceptions import Exception, StandardError, Warning
-from MySQLdb.constants import ER
-
-class MySQLError(StandardError):
-    
-    """Exception related to operation with MySQL."""
-
-
-class Warning(Warning, MySQLError):
-
-    """Exception raised for important warnings like data truncations
-    while inserting, etc."""
-
-class Error(MySQLError):
-
-    """Exception that is the base class of all other error exceptions
-    (not Warning)."""
-
-
-class InterfaceError(Error):
-
-    """Exception raised for errors that are related to the database
-    interface rather than the database itself."""
-
-
-class DatabaseError(Error):
-
-    """Exception raised for errors that are related to the
-    database."""
-
-
-class DataError(DatabaseError):
-
-    """Exception raised for errors that are due to problems with the
-    processed data like division by zero, numeric value out of range,
-    etc."""
-
-
-class OperationalError(DatabaseError):
-
-    """Exception raised for errors that are related to the database's
-    operation and not necessarily under the control of the programmer,
-    e.g. an unexpected disconnect occurs, the data source name is not
-    found, a transaction could not be processed, a memory allocation
-    error occurred during processing, etc."""
-
-
-class IntegrityError(DatabaseError):
-
-    """Exception raised when the relational integrity of the database
-    is affected, e.g. a foreign key check fails, duplicate key,
-    etc."""
-
-
-class InternalError(DatabaseError):
-
-    """Exception raised when the database encounters an internal
-    error, e.g. the cursor is not valid anymore, the transaction is
-    out of sync, etc."""
-
-
-class ProgrammingError(DatabaseError):
-
-    """Exception raised for programming errors, e.g. table not found
-    or already exists, syntax error in the SQL statement, wrong number
-    of parameters specified, etc."""
-
-
-class NotSupportedError(DatabaseError):
-
-    """Exception raised in case a method or database API was used
-    which is not supported by the database, e.g. requesting a
-    .rollback() on a connection that does not support transaction or
-    has transactions turned off."""
-
-
-error_map = {}
-
-def _map_error(exc, *errors):
-    for error in errors:
-        error_map[error] = exc
-
-_map_error(ProgrammingError, ER.DB_CREATE_EXISTS, ER.SYNTAX_ERROR,
-           ER.PARSE_ERROR, ER.NO_SUCH_TABLE, ER.WRONG_DB_NAME, ER.WRONG_TABLE_NAME,
-           ER.FIELD_SPECIFIED_TWICE, ER.INVALID_GROUP_FUNC_USE, ER.UNSUPPORTED_EXTENSION,
-           ER.TABLE_MUST_HAVE_COLUMNS, ER.CANT_DO_THIS_DURING_AN_TRANSACTION)
-_map_error(DataError, ER.WARN_DATA_TRUNCATED, ER.WARN_NULL_TO_NOTNULL,
-           ER.WARN_DATA_OUT_OF_RANGE, ER.NO_DEFAULT, ER.PRIMARY_CANT_HAVE_NULL,
-           ER.DATA_TOO_LONG, ER.DATETIME_FUNCTION_OVERFLOW)
-_map_error(IntegrityError, ER.DUP_ENTRY, ER.NO_REFERENCED_ROW,
-           ER.NO_REFERENCED_ROW_2, ER.ROW_IS_REFERENCED, ER.ROW_IS_REFERENCED_2,
-           ER.CANNOT_ADD_FOREIGN)
-_map_error(NotSupportedError, ER.WARNING_NOT_COMPLETE_ROLLBACK, ER.NOT_SUPPORTED_YET,
-           ER.FEATURE_DISABLED, ER.UNKNOWN_STORAGE_ENGINE)
-
-del Exception, StandardError, _map_error, ER
--- a/_mysql_results.c	Tue Feb 27 00:58:49 2007 +0000
+++ b/_mysql_results.c	Fri Mar 14 23:06:29 2008 +0000
@@ -156,7 +156,46 @@
 	Py_XDECREF(d);
 	return NULL;
 }
-	
+
+static char _mysql_ResultObject_fields__doc__[] =
+"Returns the sequence of 7-tuples required by the DB-API for\n\
+the Cursor.description attribute.\n\
+";
+
+static PyObject *
+_mysql_ResultObject_fields(
+	_mysql_ResultObject *self,
+	PyObject *args)
+{
+	PyObject *arglist=NULL, *kwarglist=NULL;
+	PyObject *fields=NULL;
+	_mysql_FieldObject *field=NULL;
+	unsigned int i, n;
+	if (!PyArg_ParseTuple(args, "")) return NULL;
+	check_result_connection(self);
+	kwarglist = PyDict_New();
+	if (!kwarglist) goto error;
+	n = mysql_num_fields(self->result);
+	if (!(fields = PyTuple_New(n))) return NULL;
+	for (i=0; i<n; i++) {
+		arglist = Py_BuildValue("(Oi)", self, i);
+		if (!arglist) goto error;
+		field = MyAlloc(_mysql_FieldObject, _mysql_FieldObject_Type);
+		if (!field) goto error;
+		if (_mysql_FieldObject_Initialize(field, arglist, kwarglist))
+			goto error;
+		Py_DECREF(arglist);
+		PyTuple_SET_ITEM(fields, i, (PyObject *) field);
+	}
+	Py_DECREF(kwarglist);
+	return fields;
+  error:
+	Py_XDECREF(arglist);
+	Py_XDECREF(kwarglist);
+	Py_XDECREF(fields);
+	return NULL;
+}
+		
 static char _mysql_ResultObject_field_flags__doc__[] =
 "Returns a tuple of field flags, one for each column in the result.\n\
 " ;
@@ -554,6 +593,12 @@
 		_mysql_ResultObject_describe__doc__
 	},
 	{
+		"fields",
+		(PyCFunction)_mysql_ResultObject_fields,
+		METH_VARARGS,
+		_mysql_ResultObject_fields__doc__
+	},
+	{
 		"fetch_row",
 		(PyCFunction)_mysql_ResultObject_fetch_row,
 		METH_VARARGS | METH_KEYWORDS,
--- a/dbapi20.py	Tue Feb 27 00:58:49 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,853 +0,0 @@
-#!/usr/bin/env python
-''' Python DB API 2.0 driver compliance unit test suite. 
-    
-    This software is Public Domain and may be used without restrictions.
-
- "Now we have booze and barflies entering the discussion, plus rumours of
-  DBAs on drugs... and I won't tell you what flashes through my mind each
-  time I read the subject line with 'Anal Compliance' in it.  All around
-  this is turning out to be a thoroughly unwholesome unit test."
-
-    -- Ian Bicking
-'''
-
-__rcs_id__  = '$Id$'
-__version__ = '$Revision$'[11:-2]
-__author__ = 'Stuart Bishop <[email protected]>'
-
-import unittest
-import time
-
-# $Log$
-# Revision 1.1.2.1  2006/02/25 03:44:32  adustman
-# Generic DB-API unit test module
-#
-# Revision 1.10  2003/10/09 03:14:14  zenzen
-# Add test for DB API 2.0 optional extension, where database exceptions
-# are exposed as attributes on the Connection object.
-#
-# Revision 1.9  2003/08/13 01:16:36  zenzen
-# Minor tweak from Stefan Fleiter
-#
-# Revision 1.8  2003/04/10 00:13:25  zenzen
-# Changes, as per suggestions by M.-A. Lemburg
-# - Add a table prefix, to ensure namespace collisions can always be avoided
-#
-# Revision 1.7  2003/02/26 23:33:37  zenzen
-# Break out DDL into helper functions, as per request by David Rushby
-#
-# Revision 1.6  2003/02/21 03:04:33  zenzen
-# Stuff from Henrik Ekelund:
-#     added test_None
-#     added test_nextset & hooks
-#
-# Revision 1.5  2003/02/17 22:08:43  zenzen
-# Implement suggestions and code from Henrik Eklund - test that cursor.arraysize
-# defaults to 1 & generic cursor.callproc test added
-#
-# Revision 1.4  2003/02/15 00:16:33  zenzen
-# Changes, as per suggestions and bug reports by M.-A. Lemburg,
-# Matthew T. Kromer, Federico Di Gregorio and Daniel Dittmar
-# - Class renamed
-# - Now a subclass of TestCase, to avoid requiring the driver stub
-#   to use multiple inheritance
-# - Reversed the polarity of buggy test in test_description
-# - Test exception heirarchy correctly
-# - self.populate is now self._populate(), so if a driver stub
-#   overrides self.ddl1 this change propogates
-# - VARCHAR columns now have a width, which will hopefully make the
-#   DDL even more portible (this will be reversed if it causes more problems)
-# - cursor.rowcount being checked after various execute and fetchXXX methods
-# - Check for fetchall and fetchmany returning empty lists after results
-#   are exhausted (already checking for empty lists if select retrieved
-#   nothing
-# - Fix bugs in test_setoutputsize_basic and test_setinputsizes
-#
-
-class DatabaseAPI20Test(unittest.TestCase):
-    ''' Test a database self.driver for DB API 2.0 compatibility.
-        This implementation tests Gadfly, but the TestCase
-        is structured so that other self.drivers can subclass this 
-        test case to ensure compiliance with the DB-API. It is 
-        expected that this TestCase may be expanded in the future
-        if ambiguities or edge conditions are discovered.
-
-        The 'Optional Extensions' are not yet being tested.
-
-        self.drivers should subclass this test, overriding setUp, tearDown,
-        self.driver, connect_args and connect_kw_args. Class specification
-        should be as follows:
-
-        import dbapi20 
-        class mytest(dbapi20.DatabaseAPI20Test):
-           [...] 
-
-        Don't 'import DatabaseAPI20Test from dbapi20', or you will
-        confuse the unit tester - just 'import dbapi20'.
-    '''
-
-    # The self.driver module. This should be the module where the 'connect'
-    # method is to be found
-    driver = None
-    connect_args = () # List of arguments to pass to connect
-    connect_kw_args = {} # Keyword arguments for connect
-    table_prefix = 'dbapi20test_' # If you need to specify a prefix for tables
-
-    ddl1 = 'create table %sbooze (name varchar(20))' % table_prefix
-    ddl2 = 'create table %sbarflys (name varchar(20))' % table_prefix
-    xddl1 = 'drop table %sbooze' % table_prefix
-    xddl2 = 'drop table %sbarflys' % table_prefix
-
-    lowerfunc = 'lower' # Name of stored procedure to convert string->lowercase
-        
-    # Some drivers may need to override these helpers, for example adding
-    # a 'commit' after the execute.
-    def executeDDL1(self,cursor):
-        cursor.execute(self.ddl1)
-
-    def executeDDL2(self,cursor):
-        cursor.execute(self.ddl2)
-
-    def setUp(self):
-        ''' self.drivers should override this method to perform required setup
-            if any is necessary, such as creating the database.
-        '''
-        pass
-
-    def tearDown(self):
-        ''' self.drivers should override this method to perform required cleanup
-            if any is necessary, such as deleting the test database.
-            The default drops the tables that may be created.
-        '''
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            for ddl in (self.xddl1,self.xddl2):
-                try: 
-                    cur.execute(ddl)
-                    con.commit()
-                except self.driver.Error: 
-                    # Assume table didn't exist. Other tests will check if
-                    # execute is busted.
-                    pass
-        finally:
-            con.close()
-
-    def _connect(self):
-        try:
-            return self.driver.connect(
-                *self.connect_args,**self.connect_kw_args
-                )
-        except AttributeError:
-            self.fail("No connect method found in self.driver module")
-
-    def test_connect(self):
-        con = self._connect()
-        con.close()
-
-    def test_apilevel(self):
-        try:
-            # Must exist
-            apilevel = self.driver.apilevel
-            # Must equal 2.0
-            self.assertEqual(apilevel,'2.0')
-        except AttributeError:
-            self.fail("Driver doesn't define apilevel")
-
-    def test_threadsafety(self):
-        try:
-            # Must exist
-            threadsafety = self.driver.threadsafety
-            # Must be a valid value
-            self.failUnless(threadsafety in (0,1,2,3))
-        except AttributeError:
-            self.fail("Driver doesn't define threadsafety")
-
-    def test_paramstyle(self):
-        try:
-            # Must exist
-            paramstyle = self.driver.paramstyle
-            # Must be a valid value
-            self.failUnless(paramstyle in (
-                'qmark','numeric','named','format','pyformat'
-                ))
-        except AttributeError:
-            self.fail("Driver doesn't define paramstyle")
-
-    def test_Exceptions(self):
-        # Make sure required exceptions exist, and are in the
-        # defined heirarchy.
-        self.failUnless(issubclass(self.driver.Warning,StandardError))
-        self.failUnless(issubclass(self.driver.Error,StandardError))
-        self.failUnless(
-            issubclass(self.driver.InterfaceError,self.driver.Error)
-            )
-        self.failUnless(
-            issubclass(self.driver.DatabaseError,self.driver.Error)
-            )
-        self.failUnless(
-            issubclass(self.driver.OperationalError,self.driver.Error)
-            )
-        self.failUnless(
-            issubclass(self.driver.IntegrityError,self.driver.Error)
-            )
-        self.failUnless(
-            issubclass(self.driver.InternalError,self.driver.Error)
-            )
-        self.failUnless(
-            issubclass(self.driver.ProgrammingError,self.driver.Error)
-            )
-        self.failUnless(
-            issubclass(self.driver.NotSupportedError,self.driver.Error)
-            )
-
-    def test_ExceptionsAsConnectionAttributes(self):
-        # OPTIONAL EXTENSION
-        # Test for the optional DB API 2.0 extension, where the exceptions
-        # are exposed as attributes on the Connection object
-        # I figure this optional extension will be implemented by any
-        # driver author who is using this test suite, so it is enabled
-        # by default.
-        con = self._connect()
-        drv = self.driver
-        self.failUnless(con.Warning is drv.Warning)
-        self.failUnless(con.Error is drv.Error)
-        self.failUnless(con.InterfaceError is drv.InterfaceError)
-        self.failUnless(con.DatabaseError is drv.DatabaseError)
-        self.failUnless(con.OperationalError is drv.OperationalError)
-        self.failUnless(con.IntegrityError is drv.IntegrityError)
-        self.failUnless(con.InternalError is drv.InternalError)
-        self.failUnless(con.ProgrammingError is drv.ProgrammingError)
-        self.failUnless(con.NotSupportedError is drv.NotSupportedError)
-
-
-    def test_commit(self):
-        con = self._connect()
-        try:
-            # Commit must work, even if it doesn't do anything
-            con.commit()
-        finally:
-            con.close()
-
-    def test_rollback(self):
-        con = self._connect()
-        # If rollback is defined, it should either work or throw
-        # the documented exception
-        if hasattr(con,'rollback'):
-            try:
-                con.rollback()
-            except self.driver.NotSupportedError:
-                pass
-    
-    def test_cursor(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-        finally:
-            con.close()
-
-    def test_cursor_isolation(self):
-        con = self._connect()
-        try:
-            # Make sure cursors created from the same connection have
-            # the documented transaction isolation level
-            cur1 = con.cursor()
-            cur2 = con.cursor()
-            self.executeDDL1(cur1)
-            cur1.execute("insert into %sbooze values ('Victoria Bitter')" % (
-                self.table_prefix
-                ))
-            cur2.execute("select name from %sbooze" % self.table_prefix)
-            booze = cur2.fetchall()
-            self.assertEqual(len(booze),1)
-            self.assertEqual(len(booze[0]),1)
-            self.assertEqual(booze[0][0],'Victoria Bitter')
-        finally:
-            con.close()
-
-    def test_description(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.executeDDL1(cur)
-            self.assertEqual(cur.description,None,
-                'cursor.description should be none after executing a '
-                'statement that can return no rows (such as DDL)'
-                )
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            self.assertEqual(len(cur.description),1,
-                'cursor.description describes too many columns'
-                )
-            self.assertEqual(len(cur.description[0]),7,
-                'cursor.description[x] tuples must have 7 elements'
-                )
-            self.assertEqual(cur.description[0][0].lower(),'name',
-                'cursor.description[x][0] must return column name'
-                )
-            self.assertEqual(cur.description[0][1],self.driver.STRING,
-                'cursor.description[x][1] must return column type. Got %r'
-                    % cur.description[0][1]
-                )
-
-            # Make sure self.description gets reset
-            self.executeDDL2(cur)
-            self.assertEqual(cur.description,None,
-                'cursor.description not being set to None when executing '
-                'no-result statements (eg. DDL)'
-                )
-        finally:
-            con.close()
-
-    def test_rowcount(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.executeDDL1(cur)
-            self.assertEqual(cur.rowcount,-1,
-                'cursor.rowcount should be -1 after executing no-result '
-                'statements'
-                )
-            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
-                self.table_prefix
-                ))
-            self.failUnless(cur.rowcount in (-1,1),
-                'cursor.rowcount should == number or rows inserted, or '
-                'set to -1 after executing an insert statement'
-                )
-            cur.execute("select name from %sbooze" % self.table_prefix)
-            self.failUnless(cur.rowcount in (-1,1),
-                'cursor.rowcount should == number of rows returned, or '
-                'set to -1 after executing a select statement'
-                )
-            self.executeDDL2(cur)
-            self.assertEqual(cur.rowcount,-1,
-                'cursor.rowcount not being reset to -1 after executing '
-                'no-result statements'
-                )
-        finally:
-            con.close()
-
-    lower_func = 'lower'
-    def test_callproc(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            if self.lower_func and hasattr(cur,'callproc'):
-                r = cur.callproc(self.lower_func,('FOO',))
-                self.assertEqual(len(r),1)
-                self.assertEqual(r[0],'FOO')
-                r = cur.fetchall()
-                self.assertEqual(len(r),1,'callproc produced no result set')
-                self.assertEqual(len(r[0]),1,
-                    'callproc produced invalid result set'
-                    )
-                self.assertEqual(r[0][0],'foo',
-                    'callproc produced invalid results'
-                    )
-        finally:
-            con.close()
-
-    def test_close(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-        finally:
-            con.close()
-
-        # cursor.execute should raise an Error if called after connection
-        # closed
-        self.assertRaises(self.driver.Error,self.executeDDL1,cur)
-
-        # connection.commit should raise an Error if called after connection'
-        # closed.'
-        self.assertRaises(self.driver.Error,con.commit)
-
-        # connection.close should raise an Error if called more than once
-        self.assertRaises(self.driver.Error,con.close)
-
-    def test_execute(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self._paraminsert(cur)
-        finally:
-            con.close()
-
-    def _paraminsert(self,cur):
-        self.executeDDL1(cur)
-        cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
-            self.table_prefix
-            ))
-        self.failUnless(cur.rowcount in (-1,1))
-
-        if self.driver.paramstyle == 'qmark':
-            cur.execute(
-                'insert into %sbooze values (?)' % self.table_prefix,
-                ("Cooper's",)
-                )
-        elif self.driver.paramstyle == 'numeric':
-            cur.execute(
-                'insert into %sbooze values (:1)' % self.table_prefix,
-                ("Cooper's",)
-                )
-        elif self.driver.paramstyle == 'named':
-            cur.execute(
-                'insert into %sbooze values (:beer)' % self.table_prefix, 
-                {'beer':"Cooper's"}
-                )
-        elif self.driver.paramstyle == 'format':
-            cur.execute(
-                'insert into %sbooze values (%%s)' % self.table_prefix,
-                ("Cooper's",)
-                )
-        elif self.driver.paramstyle == 'pyformat':
-            cur.execute(
-                'insert into %sbooze values (%%(beer)s)' % self.table_prefix,
-                {'beer':"Cooper's"}
-                )
-        else:
-            self.fail('Invalid paramstyle')
-        self.failUnless(cur.rowcount in (-1,1))
-
-        cur.execute('select name from %sbooze' % self.table_prefix)
-        res = cur.fetchall()
-        self.assertEqual(len(res),2,'cursor.fetchall returned too few rows')
-        beers = [res[0][0],res[1][0]]
-        beers.sort()
-        self.assertEqual(beers[0],"Cooper's",
-            'cursor.fetchall retrieved incorrect data, or data inserted '
-            'incorrectly'
-            )
-        self.assertEqual(beers[1],"Victoria Bitter",
-            'cursor.fetchall retrieved incorrect data, or data inserted '
-            'incorrectly'
-            )
-
-    def test_executemany(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.executeDDL1(cur)
-            largs = [ ("Cooper's",) , ("Boag's",) ]
-            margs = [ {'beer': "Cooper's"}, {'beer': "Boag's"} ]
-            if self.driver.paramstyle == 'qmark':
-                cur.executemany(
-                    'insert into %sbooze values (?)' % self.table_prefix,
-                    largs
-                    )
-            elif self.driver.paramstyle == 'numeric':
-                cur.executemany(
-                    'insert into %sbooze values (:1)' % self.table_prefix,
-                    largs
-                    )
-            elif self.driver.paramstyle == 'named':
-                cur.executemany(
-                    'insert into %sbooze values (:beer)' % self.table_prefix,
-                    margs
-                    )
-            elif self.driver.paramstyle == 'format':
-                cur.executemany(
-                    'insert into %sbooze values (%%s)' % self.table_prefix,
-                    largs
-                    )
-            elif self.driver.paramstyle == 'pyformat':
-                cur.executemany(
-                    'insert into %sbooze values (%%(beer)s)' % (
-                        self.table_prefix
-                        ),
-                    margs
-                    )
-            else:
-                self.fail('Unknown paramstyle')
-            self.failUnless(cur.rowcount in (-1,2),
-                'insert using cursor.executemany set cursor.rowcount to '
-                'incorrect value %r' % cur.rowcount
-                )
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            res = cur.fetchall()
-            self.assertEqual(len(res),2,
-                'cursor.fetchall retrieved incorrect number of rows'
-                )
-            beers = [res[0][0],res[1][0]]
-            beers.sort()
-            self.assertEqual(beers[0],"Boag's",'incorrect data retrieved')
-            self.assertEqual(beers[1],"Cooper's",'incorrect data retrieved')
-        finally:
-            con.close()
-
-    def test_fetchone(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-
-            # cursor.fetchone should raise an Error if called before
-            # executing a select-type query
-            self.assertRaises(self.driver.Error,cur.fetchone)
-
-            # cursor.fetchone should raise an Error if called after
-            # executing a query that cannnot return rows
-            self.executeDDL1(cur)
-            self.assertRaises(self.driver.Error,cur.fetchone)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            self.assertEqual(cur.fetchone(),None,
-                'cursor.fetchone should return None if a query retrieves '
-                'no rows'
-                )
-            self.failUnless(cur.rowcount in (-1,0))
-
-            # cursor.fetchone should raise an Error if called after
-            # executing a query that cannnot return rows
-            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
-                self.table_prefix
-                ))
-            self.assertRaises(self.driver.Error,cur.fetchone)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            r = cur.fetchone()
-            self.assertEqual(len(r),1,
-                'cursor.fetchone should have retrieved a single row'
-                )
-            self.assertEqual(r[0],'Victoria Bitter',
-                'cursor.fetchone retrieved incorrect data'
-                )
-            self.assertEqual(cur.fetchone(),None,
-                'cursor.fetchone should return None if no more rows available'
-                )
-            self.failUnless(cur.rowcount in (-1,1))
-        finally:
-            con.close()
-
-    samples = [
-        'Carlton Cold',
-        'Carlton Draft',
-        'Mountain Goat',
-        'Redback',
-        'Victoria Bitter',
-        'XXXX'
-        ]
-
-    def _populate(self):
-        ''' Return a list of sql commands to setup the DB for the fetch
-            tests.
-        '''
-        populate = [
-            "insert into %sbooze values ('%s')" % (self.table_prefix,s) 
-                for s in self.samples
-            ]
-        return populate
-
-    def test_fetchmany(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-
-            # cursor.fetchmany should raise an Error if called without
-            #issuing a query
-            self.assertRaises(self.driver.Error,cur.fetchmany,4)
-
-            self.executeDDL1(cur)
-            for sql in self._populate():
-                cur.execute(sql)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            r = cur.fetchmany()
-            self.assertEqual(len(r),1,
-                'cursor.fetchmany retrieved incorrect number of rows, '
-                'default of arraysize is one.'
-                )
-            cur.arraysize=10
-            r = cur.fetchmany(3) # Should get 3 rows
-            self.assertEqual(len(r),3,
-                'cursor.fetchmany retrieved incorrect number of rows'
-                )
-            r = cur.fetchmany(4) # Should get 2 more
-            self.assertEqual(len(r),2,
-                'cursor.fetchmany retrieved incorrect number of rows'
-                )
-            r = cur.fetchmany(4) # Should be an empty sequence
-            self.assertEqual(len(r),0,
-                'cursor.fetchmany should return an empty sequence after '
-                'results are exhausted'
-            )
-            self.failUnless(cur.rowcount in (-1,6))
-
-            # Same as above, using cursor.arraysize
-            cur.arraysize=4
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            r = cur.fetchmany() # Should get 4 rows
-            self.assertEqual(len(r),4,
-                'cursor.arraysize not being honoured by fetchmany'
-                )
-            r = cur.fetchmany() # Should get 2 more
-            self.assertEqual(len(r),2)
-            r = cur.fetchmany() # Should be an empty sequence
-            self.assertEqual(len(r),0)
-            self.failUnless(cur.rowcount in (-1,6))
-
-            cur.arraysize=6
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            rows = cur.fetchmany() # Should get all rows
-            self.failUnless(cur.rowcount in (-1,6))
-            self.assertEqual(len(rows),6)
-            self.assertEqual(len(rows),6)
-            rows = [r[0] for r in rows]
-            rows.sort()
-          
-            # Make sure we get the right data back out
-            for i in range(0,6):
-                self.assertEqual(rows[i],self.samples[i],
-                    'incorrect data retrieved by cursor.fetchmany'
-                    )
-
-            rows = cur.fetchmany() # Should return an empty list
-            self.assertEqual(len(rows),0,
-                'cursor.fetchmany should return an empty sequence if '
-                'called after the whole result set has been fetched'
-                )
-            self.failUnless(cur.rowcount in (-1,6))
-
-            self.executeDDL2(cur)
-            cur.execute('select name from %sbarflys' % self.table_prefix)
-            r = cur.fetchmany() # Should get empty sequence
-            self.assertEqual(len(r),0,
-                'cursor.fetchmany should return an empty sequence if '
-                'query retrieved no rows'
-                )
-            self.failUnless(cur.rowcount in (-1,0))
-
-        finally:
-            con.close()
-
-    def test_fetchall(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            # cursor.fetchall should raise an Error if called
-            # without executing a query that may return rows (such
-            # as a select)
-            self.assertRaises(self.driver.Error, cur.fetchall)
-
-            self.executeDDL1(cur)
-            for sql in self._populate():
-                cur.execute(sql)
-
-            # cursor.fetchall should raise an Error if called
-            # after executing a a statement that cannot return rows
-            self.assertRaises(self.driver.Error,cur.fetchall)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            rows = cur.fetchall()
-            self.failUnless(cur.rowcount in (-1,len(self.samples)))
-            self.assertEqual(len(rows),len(self.samples),
-                'cursor.fetchall did not retrieve all rows'
-                )
-            rows = [r[0] for r in rows]
-            rows.sort()
-            for i in range(0,len(self.samples)):
-                self.assertEqual(rows[i],self.samples[i],
-                'cursor.fetchall retrieved incorrect rows'
-                )
-            rows = cur.fetchall()
-            self.assertEqual(
-                len(rows),0,
-                'cursor.fetchall should return an empty list if called '
-                'after the whole result set has been fetched'
-                )
-            self.failUnless(cur.rowcount in (-1,len(self.samples)))
-
-            self.executeDDL2(cur)
-            cur.execute('select name from %sbarflys' % self.table_prefix)
-            rows = cur.fetchall()
-            self.failUnless(cur.rowcount in (-1,0))
-            self.assertEqual(len(rows),0,
-                'cursor.fetchall should return an empty list if '
-                'a select query returns no rows'
-                )
-            
-        finally:
-            con.close()
-    
-    def test_mixedfetch(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.executeDDL1(cur)
-            for sql in self._populate():
-                cur.execute(sql)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            rows1  = cur.fetchone()
-            rows23 = cur.fetchmany(2)
-            rows4  = cur.fetchone()
-            rows56 = cur.fetchall()
-            self.failUnless(cur.rowcount in (-1,6))
-            self.assertEqual(len(rows23),2,
-                'fetchmany returned incorrect number of rows'
-                )
-            self.assertEqual(len(rows56),2,
-                'fetchall returned incorrect number of rows'
-                )
-
-            rows = [rows1[0]]
-            rows.extend([rows23[0][0],rows23[1][0]])
-            rows.append(rows4[0])
-            rows.extend([rows56[0][0],rows56[1][0]])
-            rows.sort()
-            for i in range(0,len(self.samples)):
-                self.assertEqual(rows[i],self.samples[i],
-                    'incorrect data retrieved or inserted'
-                    )
-        finally:
-            con.close()
-
-    def help_nextset_setUp(self,cur):
-        ''' Should create a procedure called deleteme
-            that returns two result sets, first the 
-	    number of rows in booze then "name from booze"
-        '''
-        raise NotImplementedError,'Helper not implemented'
-        #sql="""
-        #    create procedure deleteme as
-        #    begin
-        #        select count(*) from booze
-        #        select name from booze
-        #    end
-        #"""
-        #cur.execute(sql)
-
-    def help_nextset_tearDown(self,cur):
-        'If cleaning up is needed after nextSetTest'
-        raise NotImplementedError,'Helper not implemented'
-        #cur.execute("drop procedure deleteme")
-
-    def test_nextset(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            if not hasattr(cur,'nextset'):
-                return
-
-            try:
-                self.executeDDL1(cur)
-                sql=self._populate()
-                for sql in self._populate():
-                    cur.execute(sql)
-
-                self.help_nextset_setUp(cur)
-
-                cur.callproc('deleteme')
-                numberofrows=cur.fetchone()
-                assert numberofrows[0]== len(self.samples)
-                assert cur.nextset()
-                names=cur.fetchall()
-                assert len(names) == len(self.samples)
-                s=cur.nextset()
-                assert s == None,'No more return sets, should return None'
-            finally:
-                self.help_nextset_tearDown(cur)
-
-        finally:
-            con.close()
-
-    def test_nextset(self):
-        raise NotImplementedError,'Drivers need to override this test'
-
-    def test_arraysize(self):
-        # Not much here - rest of the tests for this are in test_fetchmany
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.failUnless(hasattr(cur,'arraysize'),
-                'cursor.arraysize must be defined'
-                )
-        finally:
-            con.close()
-
-    def test_setinputsizes(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            cur.setinputsizes( (25,) )
-            self._paraminsert(cur) # Make sure cursor still works
-        finally:
-            con.close()
-
-    def test_setoutputsize_basic(self):
-        # Basic test is to make sure setoutputsize doesn't blow up
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            cur.setoutputsize(1000)
-            cur.setoutputsize(2000,0)
-            self._paraminsert(cur) # Make sure the cursor still works
-        finally:
-            con.close()
-
-    def test_setoutputsize(self):
-        # Real test for setoutputsize is driver dependant
-        raise NotImplementedError,'Driver need to override this test'
-
-    def test_None(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.executeDDL1(cur)
-            cur.execute('insert into %sbooze values (NULL)' % self.table_prefix)
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            r = cur.fetchall()
-            self.assertEqual(len(r),1)
-            self.assertEqual(len(r[0]),1)
-            self.assertEqual(r[0][0],None,'NULL value not returned as None')
-        finally:
-            con.close()
-
-    def test_Date(self):
-        d1 = self.driver.Date(2002,12,25)
-        d2 = self.driver.DateFromTicks(time.mktime((2002,12,25,0,0,0,0,0,0)))
-        # Can we assume this? API doesn't specify, but it seems implied
-        # self.assertEqual(str(d1),str(d2))
-
-    def test_Time(self):
-        t1 = self.driver.Time(13,45,30)
-        t2 = self.driver.TimeFromTicks(time.mktime((2001,1,1,13,45,30,0,0,0)))
-        # Can we assume this? API doesn't specify, but it seems implied
-        # self.assertEqual(str(t1),str(t2))
-
-    def test_Timestamp(self):
-        t1 = self.driver.Timestamp(2002,12,25,13,45,30)
-        t2 = self.driver.TimestampFromTicks(
-            time.mktime((2002,12,25,13,45,30,0,0,0))
-            )
-        # Can we assume this? API doesn't specify, but it seems implied
-        # self.assertEqual(str(t1),str(t2))
-
-    def test_Binary(self):
-        b = self.driver.Binary('Something')
-        b = self.driver.Binary('')
-
-    def test_STRING(self):
-        self.failUnless(hasattr(self.driver,'STRING'),
-            'module.STRING must be defined'
-            )
-
-    def test_BINARY(self):
-        self.failUnless(hasattr(self.driver,'BINARY'),
-            'module.BINARY must be defined.'
-            )
-
-    def test_NUMBER(self):
-        self.failUnless(hasattr(self.driver,'NUMBER'),
-            'module.NUMBER must be defined.'
-            )
-
-    def test_DATETIME(self):
-        self.failUnless(hasattr(self.driver,'DATETIME'),
-            'module.DATETIME must be defined.'
-            )
-
-    def test_ROWID(self):
-        self.failUnless(hasattr(self.driver,'ROWID'),
-            'module.ROWID must be defined.'
-            )
-
--- a/metadata.cfg	Tue Feb 27 00:58:49 2007 +0000
+++ b/metadata.cfg	Fri Mar 14 23:06:29 2008 +0000
@@ -45,10 +45,10 @@
         Topic :: Database
         Topic :: Database :: Database Engines/Servers
 py_modules:
-        _mysql_exceptions
         MySQLdb.converters
         MySQLdb.connections
         MySQLdb.cursors
+        MySQLdb.exceptions
         MySQLdb.release
         MySQLdb.times
         MySQLdb.constants.CR
--- a/setup.py	Tue Feb 27 00:58:49 2007 +0000
+++ b/setup.py	Fri Mar 14 23:06:29 2008 +0000
@@ -20,6 +20,7 @@
             '_mysql.c',
             '_mysql_connections.c',
             '_mysql_results.c',
+            '_mysql_fields.c',
             ],
         **options),
     ]
--- a/test_MySQLdb_capabilities.py	Tue Feb 27 00:58:49 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-#!/usr/bin/env python
-import test_capabilities
-import unittest
-import MySQLdb
-import warnings
-
-warnings.filterwarnings('error')
-
-class test_MySQLdb(test_capabilities.DatabaseTest):
-
-    db_module = MySQLdb
-    connect_args = ()
-    connect_kwargs = dict(db='test', read_default_file='~/.my.cnf',
-                          charset='utf8', sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")
-    create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8"
-    leak_test = False
-    
-    def quote_identifier(self, ident):
-        return "`%s`" % ident
-
-    def test_TIME(self):
-        from datetime import timedelta
-        def generator(row,col):
-            return timedelta(0, row*8000)
-        self.check_data_integrity(
-                 ('col1 TIME',),
-                 generator)
-
-    def test_TINYINT(self):
-        # Number data
-        def generator(row,col):
-            v = (row*row) % 256
-            if v > 127:
-                v = v-256
-            return v
-        self.check_data_integrity(
-            ('col1 TINYINT',),
-            generator)
-        
-    def test_stored_procedures(self):
-        db = self.connection
-        c = self.cursor
-        self.create_table(('pos INT', 'tree CHAR(20)'))
-        c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table,
-                      list(enumerate('ash birch cedar larch pine'.split())))
-        db.commit()
-        
-        c.execute("""
-        CREATE PROCEDURE test_sp(IN t VARCHAR(255))
-        BEGIN
-            SELECT pos FROM %s WHERE tree = t;
-        END
-        """ % self.table)
-        db.commit()
-
-        c.callproc('test_sp', ('larch',))
-        rows = c.fetchall()
-        self.assertEquals(len(rows), 1)
-        self.assertEquals(rows[0][0], 3)
-        c.nextset()
-        
-        c.execute("DROP PROCEDURE test_sp")
-        c.execute('drop table %s' % (self.table))
-
-    def test_small_CHAR(self):
-        # Character data
-        def generator(row,col):
-            i = (row*col+62)%256
-            if i == 62: return ''
-            if i == 63: return None
-            return chr(i)
-        self.check_data_integrity(
-            ('col1 char(1)','col2 char(1)'),
-            generator)
-        
-if __name__ == '__main__':
-    if test_MySQLdb.leak_test:
-        import gc
-        gc.enable()
-        gc.set_debug(gc.DEBUG_LEAK)
-    unittest.main()
-    print '''"Huh-huh, he said 'unit'." -- Butthead'''
--- a/test_MySQLdb_dbapi20.py	Tue Feb 27 00:58:49 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-#!/usr/bin/env python
-import dbapi20
-import unittest
-import MySQLdb
-
-class test_MySQLdb(dbapi20.DatabaseAPI20Test):
-    driver = MySQLdb
-    connect_args = ()
-    connect_kw_args = dict(db='test',
-                           read_default_file='~/.my.cnf',
-                           charset='utf8',
-                           sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")
-
-    def test_setoutputsize(self): pass
-    def test_setoutputsize_basic(self): pass
-    def test_nextset(self): pass
-
-    """The tests on fetchone and fetchall and rowcount bogusly
-    test for an exception if the statement cannot return a
-    result set. MySQL always returns a result set; it's just that
-    some things return empty result sets."""
-    
-    def test_fetchall(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            # cursor.fetchall should raise an Error if called
-            # without executing a query that may return rows (such
-            # as a select)
-            self.assertRaises(self.driver.Error, cur.fetchall)
-
-            self.executeDDL1(cur)
-            for sql in self._populate():
-                cur.execute(sql)
-
-            # cursor.fetchall should raise an Error if called
-            # after executing a a statement that cannot return rows
-##             self.assertRaises(self.driver.Error,cur.fetchall)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            rows = cur.fetchall()
-            self.failUnless(cur.rowcount in (-1,len(self.samples)))
-            self.assertEqual(len(rows),len(self.samples),
-                'cursor.fetchall did not retrieve all rows'
-                )
-            rows = [r[0] for r in rows]
-            rows.sort()
-            for i in range(0,len(self.samples)):
-                self.assertEqual(rows[i],self.samples[i],
-                'cursor.fetchall retrieved incorrect rows'
-                )
-            rows = cur.fetchall()
-            self.assertEqual(
-                len(rows),0,
-                'cursor.fetchall should return an empty list if called '
-                'after the whole result set has been fetched'
-                )
-            self.failUnless(cur.rowcount in (-1,len(self.samples)))
-
-            self.executeDDL2(cur)
-            cur.execute('select name from %sbarflys' % self.table_prefix)
-            rows = cur.fetchall()
-            self.failUnless(cur.rowcount in (-1,0))
-            self.assertEqual(len(rows),0,
-                'cursor.fetchall should return an empty list if '
-                'a select query returns no rows'
-                )
-            
-        finally:
-            con.close()
-                
-    def test_fetchone(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-
-            # cursor.fetchone should raise an Error if called before
-            # executing a select-type query
-            self.assertRaises(self.driver.Error,cur.fetchone)
-
-            # cursor.fetchone should raise an Error if called after
-            # executing a query that cannnot return rows
-            self.executeDDL1(cur)
-##             self.assertRaises(self.driver.Error,cur.fetchone)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            self.assertEqual(cur.fetchone(),None,
-                'cursor.fetchone should return None if a query retrieves '
-                'no rows'
-                )
-            self.failUnless(cur.rowcount in (-1,0))
-
-            # cursor.fetchone should raise an Error if called after
-            # executing a query that cannnot return rows
-            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
-                self.table_prefix
-                ))
-##             self.assertRaises(self.driver.Error,cur.fetchone)
-
-            cur.execute('select name from %sbooze' % self.table_prefix)
-            r = cur.fetchone()
-            self.assertEqual(len(r),1,
-                'cursor.fetchone should have retrieved a single row'
-                )
-            self.assertEqual(r[0],'Victoria Bitter',
-                'cursor.fetchone retrieved incorrect data'
-                )
-##             self.assertEqual(cur.fetchone(),None,
-##                 'cursor.fetchone should return None if no more rows available'
-##                 )
-            self.failUnless(cur.rowcount in (-1,1))
-        finally:
-            con.close()
-
-    # Same complaint as for fetchall and fetchone
-    def test_rowcount(self):
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            self.executeDDL1(cur)
-##             self.assertEqual(cur.rowcount,-1,
-##                 'cursor.rowcount should be -1 after executing no-result '
-##                 'statements'
-##                 )
-            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
-                self.table_prefix
-                ))
-##             self.failUnless(cur.rowcount in (-1,1),
-##                 'cursor.rowcount should == number or rows inserted, or '
-##                 'set to -1 after executing an insert statement'
-##                 )
-            cur.execute("select name from %sbooze" % self.table_prefix)
-            self.failUnless(cur.rowcount in (-1,1),
-                'cursor.rowcount should == number of rows returned, or '
-                'set to -1 after executing a select statement'
-                )
-            self.executeDDL2(cur)
-##             self.assertEqual(cur.rowcount,-1,
-##                 'cursor.rowcount not being reset to -1 after executing '
-##                 'no-result statements'
-##                 )
-        finally:
-            con.close()
-
-    def test_callproc(self):
-        pass # performed in test_MySQL_capabilities
-
-    def help_nextset_setUp(self,cur):
-        ''' Should create a procedure called deleteme
-            that returns two result sets, first the 
-	    number of rows in booze then "name from booze"
-        '''
-        sql="""
-           create procedure deleteme()
-           begin
-               select count(*) from %(tp)sbooze;
-               select name from %(tp)sbooze;
-           end
-        """ % dict(tp=self.table_prefix)
-        cur.execute(sql)
-
-    def help_nextset_tearDown(self,cur):
-        'If cleaning up is needed after nextSetTest'
-        cur.execute("drop procedure deleteme")
-
-    def test_nextset(self):
-        from warnings import warn
-        con = self._connect()
-        try:
-            cur = con.cursor()
-            if not hasattr(cur,'nextset'):
-                return
-
-            try:
-                self.executeDDL1(cur)
-                sql=self._populate()
-                for sql in self._populate():
-                    cur.execute(sql)
-
-                self.help_nextset_setUp(cur)
-
-                cur.callproc('deleteme')
-                numberofrows=cur.fetchone()
-                assert numberofrows[0]== len(self.samples)
-                assert cur.nextset()
-                names=cur.fetchall()
-                assert len(names) == len(self.samples)
-                s=cur.nextset()
-                if s:
-                    empty = cur.fetchall()
-                    self.assertEquals(len(empty), 0,
-                                      "non-empty result set after other result sets")
-                    #warn("Incompatibility: MySQL returns an empty result set for the CALL itself",
-                    #     Warning)
-                #assert s == None,'No more return sets, should return None'
-            finally:
-                self.help_nextset_tearDown(cur)
-
-        finally:
-            con.close()
-
-    
-if __name__ == '__main__':
-    unittest.main()
-    print '''"Huh-huh, he said 'unit'." -- Butthead'''
--- a/test_capabilities.py	Tue Feb 27 00:58:49 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,289 +0,0 @@
-#!/usr/bin/env python -O
-""" Script to test database capabilities and the DB-API interface
-    for functionality and memory leaks.
-
-    Adapted from a script by M-A Lemburg.
-    
-"""
-from time import time
-import array
-import unittest
-
-
-class DatabaseTest(unittest.TestCase):
-
-    db_module = None
-    connect_args = ()
-    connect_kwargs = dict()
-    create_table_extra = ''
-    rows = 10
-    debug = False
-    
-    def setUp(self):
-        import gc
-        db = self.db_module.connect(*self.connect_args, **self.connect_kwargs)
-        self.connection = db
-        self.cursor = db.cursor()
-        self.BLOBText = ''.join([chr(i) for i in range(256)] * 100);
-        self.BLOBUText = u''.join([unichr(i) for i in range(16384)])
-        self.BLOBBinary = self.db_module.Binary(''.join([chr(i) for i in range(256)] * 16))
-
-    leak_test = True
-    
-    def tearDown(self):
-        if self.leak_test:
-            import gc
-            del self.cursor
-            orphans = gc.collect()
-            self.failIf(orphans, "%d orphaned objects found after deleting cursor" % orphans)
-            
-            del self.connection
-            orphans = gc.collect()
-            self.failIf(orphans, "%d orphaned objects found after deleting connection" % orphans)
-            
-    def table_exists(self, name):
-        try:
-            self.cursor.execute('select * from %s where 1=0' % name)
-        except:
-            return False
-        else:
-            return True
-
-    def quote_identifier(self, ident):
-        return '"%s"' % ident
-    
-    def new_table_name(self):
-        i = id(self.cursor)
-        while True:
-            name = self.quote_identifier('tb%08x' % i)
-            if not self.table_exists(name):
-                return name
-            i = i + 1
-
-    def create_table(self, columndefs):
-
-        """ Create a table using a list of column definitions given in
-            columndefs.
-        
-            generator must be a function taking arguments (row_number,
-            col_number) returning a suitable data object for insertion
-            into the table.
-
-        """
-        self.table = self.new_table_name()
-        self.cursor.execute('CREATE TABLE %s (%s) %s' % 
-                            (self.table,
-                             ',\n'.join(columndefs),
-                             self.create_table_extra))
-
-    def check_data_integrity(self, columndefs, generator):
-        # insert
-        self.create_table(columndefs)
-        insert_statement = ('INSERT INTO %s VALUES (%s)' % 
-                            (self.table,
-                             ','.join(['%s'] * len(columndefs))))
-        data = [ [ generator(i,j) for j in range(len(columndefs)) ]
-                 for i in range(self.rows) ]
-        if self.debug:
-            print data
-        self.cursor.executemany(insert_statement, data)
-        self.connection.commit()
-        # verify
-        self.cursor.execute('select * from %s' % self.table)
-        l = self.cursor.fetchall()
-        if self.debug:
-            print l
-        self.assertEquals(len(l), self.rows)
-        try:
-            for i in range(self.rows):
-                for j in range(len(columndefs)):
-                    self.assertEquals(l[i][j], generator(i,j))
-        finally:
-            if not self.debug:
-                self.cursor.execute('drop table %s' % (self.table))
-
-    def test_transactions(self):
-        columndefs = ( 'col1 INT', 'col2 VARCHAR(255)')
-        def generator(row, col):
-            if col == 0: return row
-            else: return ('%i' % (row%10))*255
-        self.create_table(columndefs)
-        insert_statement = ('INSERT INTO %s VALUES (%s)' % 
-                            (self.table,
-                             ','.join(['%s'] * len(columndefs))))
-        data = [ [ generator(i,j) for j in range(len(columndefs)) ]
-                 for i in range(self.rows) ]
-        self.cursor.executemany(insert_statement, data)
-        # verify
-        self.connection.commit()
-        self.cursor.execute('select * from %s' % self.table)
-        l = self.cursor.fetchall()
-        self.assertEquals(len(l), self.rows)
-        for i in range(self.rows):
-            for j in range(len(columndefs)):
-                self.assertEquals(l[i][j], generator(i,j))
-        delete_statement = 'delete from %s where col1=%%s' % self.table
-        self.cursor.execute(delete_statement, (0,))
-        self.cursor.execute('select col1 from %s where col1=%s' % \
-                            (self.table, 0))
-        l = self.cursor.fetchall()
-        self.failIf(l, "DELETE didn't work")
-        self.connection.rollback()
-        self.cursor.execute('select col1 from %s where col1=%s' % \
-                            (self.table, 0))
-        l = self.cursor.fetchall()
-        self.failUnless(len(l) == 1, "ROLLBACK didn't work")
-        self.cursor.execute('drop table %s' % (self.table))
-
-    def test_truncation(self):
-        columndefs = ( 'col1 INT', 'col2 VARCHAR(255)')
-        def generator(row, col):
-            if col == 0: return row
-            else: return ('%i' % (row%10))*((255-self.rows/2)+row)
-        self.create_table(columndefs)
-        insert_statement = ('INSERT INTO %s VALUES (%s)' % 
-                            (self.table,
-                             ','.join(['%s'] * len(columndefs))))
-
-        try:
-            self.cursor.execute(insert_statement, (0, '0'*256))
-        except Warning:
-            if self.debug: print self.cursor.messages
-        except self.connection.DataError:
-            pass
-        else:
-            self.fail("Over-long column did not generate warnings/exception with single insert")
-
-        self.connection.rollback()
-        
-        try:
-            for i in range(self.rows):
-                data = []
-                for j in range(len(columndefs)):
-                    data.append(generator(i,j))
-                self.cursor.execute(insert_statement,tuple(data))
-        except Warning:
-            if self.debug: print self.cursor.messages
-        except self.connection.DataError:
-            pass
-        else:
-            self.fail("Over-long columns did not generate warnings/exception with execute()")
-
-        self.connection.rollback()
-        
-        try:
-            data = [ [ generator(i,j) for j in range(len(columndefs)) ]
-                     for i in range(self.rows) ]
-            self.cursor.executemany(insert_statement, data)
-        except Warning:
-            if self.debug: print self.cursor.messages
-        except self.connection.DataError:
-            pass
-        else:
-            self.fail("Over-long columns did not generate warnings/exception with executemany()")
-
-        self.connection.rollback()
-        self.cursor.execute('drop table %s' % (self.table))
-
-    def test_CHAR(self):
-        # Character data
-        def generator(row,col):
-            return ('%i' % ((row+col) % 10)) * 255
-        self.check_data_integrity(
-            ('col1 char(255)','col2 char(255)'),
-            generator)
-
-    def test_INT(self):
-        # Number data
-        def generator(row,col):
-            return row*row
-        self.check_data_integrity(
-            ('col1 INT',),
-            generator)
-
-    def test_DECIMAL(self):
-        # DECIMAL
-        def generator(row,col):
-            from decimal import Decimal
-            return Decimal("%d.%02d" % (row, col))
-        self.check_data_integrity(
-            ('col1 DECIMAL(5,2)',),
-            generator)
-
-    def test_DATE(self):
-        ticks = time()
-        def generator(row,col):
-            return self.db_module.DateFromTicks(ticks+row*86400-col*1313)
-        self.check_data_integrity(
-                 ('col1 DATE',),
-                 generator)
-
-    def test_TIME(self):
-        ticks = time()
-        def generator(row,col):
-            return self.db_module.TimeFromTicks(ticks+row*86400-col*1313)
-        self.check_data_integrity(
-                 ('col1 TIME',),
-                 generator)
-
-    def test_DATETIME(self):
-        ticks = time()
-        def generator(row,col):
-            return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313)
-        self.check_data_integrity(
-                 ('col1 DATETIME',),
-                 generator)
-
-    def test_TIMESTAMP(self):
-        ticks = time()
-        def generator(row,col):
-            return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313)
-        self.check_data_integrity(
-                 ('col1 TIMESTAMP',),
-                 generator)
-
-    def test_fractional_TIMESTAMP(self):
-        ticks = time()
-        def generator(row,col):
-            return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313+row*0.7*col/3.0)
-        self.check_data_integrity(
-                 ('col1 TIMESTAMP',),
-                 generator)
-
-    def test_LONG(self):
-        def generator(row,col):
-            if col == 0:
-                return row
-            else:
-                return self.BLOBUText # 'BLOB Text ' * 1024
-        self.check_data_integrity(
-                 ('col1 INT','col2 LONG'),
-                 generator)
-
-    def test_TEXT(self):
-        def generator(row,col):
-            return self.BLOBUText # 'BLOB Text ' * 1024
-        self.check_data_integrity(
-                 ('col2 TEXT',),
-                 generator)
-
-    def test_LONG_BYTE(self):
-        def generator(row,col):
-            if col == 0:
-                return row
-            else:
-                return self.BLOBBinary # 'BLOB\000Binary ' * 1024
-        self.check_data_integrity(
-                 ('col1 INT','col2 LONG BYTE'),
-                 generator)
-
-    def test_BLOB(self):
-        def generator(row,col):
-            if col == 0:
-                return row
-            else:
-                return self.BLOBBinary # 'BLOB\000Binary ' * 1024
-        self.check_data_integrity(
-                 ('col1 INT','col2 BLOB'),
-                 generator)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/capabilities.py	Fri Mar 14 23:06:29 2008 +0000
@@ -0,0 +1,289 @@
+#!/usr/bin/env python -O
+""" Script to test database capabilities and the DB-API interface
+    for functionality and memory leaks.
+
+    Adapted from a script by M-A Lemburg.
+    
+"""
+from time import time
+import array
+import unittest
+
+
+class DatabaseTest(unittest.TestCase):
+
+    db_module = None
+    connect_args = ()
+    connect_kwargs = dict()
+    create_table_extra = ''
+    rows = 10
+    debug = False
+    
+    def setUp(self):
+        import gc
+        db = self.db_module.connect(*self.connect_args, **self.connect_kwargs)
+        self.connection = db
+        self.cursor = db.cursor()
+        self.BLOBText = ''.join([chr(i) for i in range(256)] * 100);
+        self.BLOBUText = u''.join([unichr(i) for i in range(16384)])
+        self.BLOBBinary = self.db_module.Binary(''.join([chr(i) for i in range(256)] * 16))
+
+    leak_test = True
+    
+    def tearDown(self):
+        if self.leak_test:
+            import gc
+            del self.cursor
+            orphans = gc.collect()
+            self.failIf(orphans, "%d orphaned objects found after deleting cursor" % orphans)
+            
+            del self.connection
+            orphans = gc.collect()
+            self.failIf(orphans, "%d orphaned objects found after deleting connection" % orphans)
+            
+    def table_exists(self, name):
+        try:
+            self.cursor.execute('select * from %s where 1=0' % name)
+        except:
+            return False
+        else:
+            return True
+
+    def quote_identifier(self, ident):
+        return '"%s"' % ident
+    
+    def new_table_name(self):
+        i = id(self.cursor)
+        while True:
+            name = self.quote_identifier('tb%08x' % i)
+            if not self.table_exists(name):
+                return name
+            i = i + 1
+
+    def create_table(self, columndefs):
+
+        """ Create a table using a list of column definitions given in
+            columndefs.
+        
+            generator must be a function taking arguments (row_number,
+            col_number) returning a suitable data object for insertion
+            into the table.
+
+        """
+        self.table = self.new_table_name()
+        self.cursor.execute('CREATE TABLE %s (%s) %s' % 
+                            (self.table,
+                             ',\n'.join(columndefs),
+                             self.create_table_extra))
+
+    def check_data_integrity(self, columndefs, generator):
+        # insert
+        self.create_table(columndefs)
+        insert_statement = ('INSERT INTO %s VALUES (%s)' % 
+                            (self.table,
+                             ','.join(['%s'] * len(columndefs))))
+        data = [ [ generator(i,j) for j in range(len(columndefs)) ]
+                 for i in range(self.rows) ]
+        if self.debug:
+            print data
+        self.cursor.executemany(insert_statement, data)
+        self.connection.commit()
+        # verify
+        self.cursor.execute('select * from %s' % self.table)
+        l = self.cursor.fetchall()
+        if self.debug:
+            print l
+        self.assertEquals(len(l), self.rows)
+        try:
+            for i in range(self.rows):
+                for j in range(len(columndefs)):
+                    self.assertEquals(l[i][j], generator(i,j))
+        finally:
+            if not self.debug:
+                self.cursor.execute('drop table %s' % (self.table))
+
+    def test_transactions(self):
+        columndefs = ( 'col1 INT', 'col2 VARCHAR(255)')
+        def generator(row, col):
+            if col == 0: return row
+            else: return ('%i' % (row%10))*255
+        self.create_table(columndefs)
+        insert_statement = ('INSERT INTO %s VALUES (%s)' % 
+                            (self.table,
+                             ','.join(['%s'] * len(columndefs))))
+        data = [ [ generator(i,j) for j in range(len(columndefs)) ]
+                 for i in range(self.rows) ]
+        self.cursor.executemany(insert_statement, data)
+        # verify
+        self.connection.commit()
+        self.cursor.execute('select * from %s' % self.table)
+        l = self.cursor.fetchall()
+        self.assertEquals(len(l), self.rows)
+        for i in range(self.rows):
+            for j in range(len(columndefs)):
+                self.assertEquals(l[i][j], generator(i,j))
+        delete_statement = 'delete from %s where col1=%%s' % self.table
+        self.cursor.execute(delete_statement, (0,))
+        self.cursor.execute('select col1 from %s where col1=%s' % \
+                            (self.table, 0))
+        l = self.cursor.fetchall()
+        self.failIf(l, "DELETE didn't work")
+        self.connection.rollback()
+        self.cursor.execute('select col1 from %s where col1=%s' % \
+                            (self.table, 0))
+        l = self.cursor.fetchall()
+        self.failUnless(len(l) == 1, "ROLLBACK didn't work")
+        self.cursor.execute('drop table %s' % (self.table))
+
+    def test_truncation(self):
+        columndefs = ( 'col1 INT', 'col2 VARCHAR(255)')
+        def generator(row, col):
+            if col == 0: return row
+            else: return ('%i' % (row%10))*((255-self.rows/2)+row)
+        self.create_table(columndefs)
+        insert_statement = ('INSERT INTO %s VALUES (%s)' % 
+                            (self.table,
+                             ','.join(['%s'] * len(columndefs))))
+
+        try:
+            self.cursor.execute(insert_statement, (0, '0'*256))
+        except Warning:
+            if self.debug: print self.cursor.messages
+        except self.connection.DataError:
+            pass
+        else:
+            self.fail("Over-long column did not generate warnings/exception with single insert")
+
+        self.connection.rollback()
+        
+        try:
+            for i in range(self.rows):
+                data = []
+                for j in range(len(columndefs)):
+                    data.append(generator(i,j))
+                self.cursor.execute(insert_statement,tuple(data))
+        except Warning:
+            if self.debug: print self.cursor.messages
+        except self.connection.DataError:
+            pass
+        else:
+            self.fail("Over-long columns did not generate warnings/exception with execute()")
+
+        self.connection.rollback()
+        
+        try:
+            data = [ [ generator(i,j) for j in range(len(columndefs)) ]
+                     for i in range(self.rows) ]
+            self.cursor.executemany(insert_statement, data)
+        except Warning:
+            if self.debug: print self.cursor.messages
+        except self.connection.DataError:
+            pass
+        else:
+            self.fail("Over-long columns did not generate warnings/exception with executemany()")
+
+        self.connection.rollback()
+        self.cursor.execute('drop table %s' % (self.table))
+
+    def test_CHAR(self):
+        # Character data
+        def generator(row,col):
+            return ('%i' % ((row+col) % 10)) * 255
+        self.check_data_integrity(
+            ('col1 char(255)','col2 char(255)'),
+            generator)
+
+    def test_INT(self):
+        # Number data
+        def generator(row,col):
+            return row*row
+        self.check_data_integrity(
+            ('col1 INT',),
+            generator)
+
+    def test_DECIMAL(self):
+        # DECIMAL
+        def generator(row,col):
+            from decimal import Decimal
+            return Decimal("%d.%02d" % (row, col))
+        self.check_data_integrity(
+            ('col1 DECIMAL(5,2)',),
+            generator)
+
+    def test_DATE(self):
+        ticks = time()
+        def generator(row,col):
+            return self.db_module.DateFromTicks(ticks+row*86400-col*1313)
+        self.check_data_integrity(
+                 ('col1 DATE',),
+                 generator)
+
+    def test_TIME(self):
+        ticks = time()
+        def generator(row,col):
+            return self.db_module.TimeFromTicks(ticks+row*86400-col*1313)
+        self.check_data_integrity(
+                 ('col1 TIME',),
+                 generator)
+
+    def test_DATETIME(self):
+        ticks = time()
+        def generator(row,col):
+            return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313)
+        self.check_data_integrity(
+                 ('col1 DATETIME',),
+                 generator)
+
+    def test_TIMESTAMP(self):
+        ticks = time()
+        def generator(row,col):
+            return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313)
+        self.check_data_integrity(
+                 ('col1 TIMESTAMP',),
+                 generator)
+
+    def test_fractional_TIMESTAMP(self):
+        ticks = time()
+        def generator(row,col):
+            return self.db_module.TimestampFromTicks(ticks+row*86400-col*1313+row*0.7*col/3.0)
+        self.check_data_integrity(
+                 ('col1 TIMESTAMP',),
+                 generator)
+
+    def test_LONG(self):
+        def generator(row,col):
+            if col == 0:
+                return row
+            else:
+                return self.BLOBUText # 'BLOB Text ' * 1024
+        self.check_data_integrity(
+                 ('col1 INT','col2 LONG'),
+                 generator)
+
+    def test_TEXT(self):
+        def generator(row,col):
+            return self.BLOBUText # 'BLOB Text ' * 1024
+        self.check_data_integrity(
+                 ('col2 TEXT',),
+                 generator)
+
+    def test_LONG_BYTE(self):
+        def generator(row,col):
+            if col == 0:
+                return row
+            else:
+                return self.BLOBBinary # 'BLOB\000Binary ' * 1024
+        self.check_data_integrity(
+                 ('col1 INT','col2 LONG BYTE'),
+                 generator)
+
+    def test_BLOB(self):
+        def generator(row,col):
+            if col == 0:
+                return row
+            else:
+                return self.BLOBBinary # 'BLOB\000Binary ' * 1024
+        self.check_data_integrity(
+                 ('col1 INT','col2 BLOB'),
+                 generator)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dbapi20.py	Fri Mar 14 23:06:29 2008 +0000
@@ -0,0 +1,853 @@
+#!/usr/bin/env python
+''' Python DB API 2.0 driver compliance unit test suite. 
+    
+    This software is Public Domain and may be used without restrictions.
+
+ "Now we have booze and barflies entering the discussion, plus rumours of
+  DBAs on drugs... and I won't tell you what flashes through my mind each
+  time I read the subject line with 'Anal Compliance' in it.  All around
+  this is turning out to be a thoroughly unwholesome unit test."
+
+    -- Ian Bicking
+'''
+
+__rcs_id__  = '$Id$'
+__version__ = '$Revision$'[11:-2]
+__author__ = 'Stuart Bishop <[email protected]>'
+
+import unittest
+import time
+
+# $Log$
+# Revision 1.1.2.1  2006/02/25 03:44:32  adustman
+# Generic DB-API unit test module
+#
+# Revision 1.10  2003/10/09 03:14:14  zenzen
+# Add test for DB API 2.0 optional extension, where database exceptions
+# are exposed as attributes on the Connection object.
+#
+# Revision 1.9  2003/08/13 01:16:36  zenzen
+# Minor tweak from Stefan Fleiter
+#
+# Revision 1.8  2003/04/10 00:13:25  zenzen
+# Changes, as per suggestions by M.-A. Lemburg
+# - Add a table prefix, to ensure namespace collisions can always be avoided
+#
+# Revision 1.7  2003/02/26 23:33:37  zenzen
+# Break out DDL into helper functions, as per request by David Rushby
+#
+# Revision 1.6  2003/02/21 03:04:33  zenzen
+# Stuff from Henrik Ekelund:
+#     added test_None
+#     added test_nextset & hooks
+#
+# Revision 1.5  2003/02/17 22:08:43  zenzen
+# Implement suggestions and code from Henrik Eklund - test that cursor.arraysize
+# defaults to 1 & generic cursor.callproc test added
+#
+# Revision 1.4  2003/02/15 00:16:33  zenzen
+# Changes, as per suggestions and bug reports by M.-A. Lemburg,
+# Matthew T. Kromer, Federico Di Gregorio and Daniel Dittmar
+# - Class renamed
+# - Now a subclass of TestCase, to avoid requiring the driver stub
+#   to use multiple inheritance
+# - Reversed the polarity of buggy test in test_description
+# - Test exception heirarchy correctly
+# - self.populate is now self._populate(), so if a driver stub
+#   overrides self.ddl1 this change propogates
+# - VARCHAR columns now have a width, which will hopefully make the
+#   DDL even more portible (this will be reversed if it causes more problems)
+# - cursor.rowcount being checked after various execute and fetchXXX methods
+# - Check for fetchall and fetchmany returning empty lists after results
+#   are exhausted (already checking for empty lists if select retrieved
+#   nothing
+# - Fix bugs in test_setoutputsize_basic and test_setinputsizes
+#
+
+class DatabaseAPI20Test(unittest.TestCase):
+    ''' Test a database self.driver for DB API 2.0 compatibility.
+        This implementation tests Gadfly, but the TestCase
+        is structured so that other self.drivers can subclass this 
+        test case to ensure compiliance with the DB-API. It is 
+        expected that this TestCase may be expanded in the future
+        if ambiguities or edge conditions are discovered.
+
+        The 'Optional Extensions' are not yet being tested.
+
+        self.drivers should subclass this test, overriding setUp, tearDown,
+        self.driver, connect_args and connect_kw_args. Class specification
+        should be as follows:
+
+        import dbapi20 
+        class mytest(dbapi20.DatabaseAPI20Test):
+           [...] 
+
+        Don't 'import DatabaseAPI20Test from dbapi20', or you will
+        confuse the unit tester - just 'import dbapi20'.
+    '''
+
+    # The self.driver module. This should be the module where the 'connect'
+    # method is to be found
+    driver = None
+    connect_args = () # List of arguments to pass to connect
+    connect_kw_args = {} # Keyword arguments for connect
+    table_prefix = 'dbapi20test_' # If you need to specify a prefix for tables
+
+    ddl1 = 'create table %sbooze (name varchar(20))' % table_prefix
+    ddl2 = 'create table %sbarflys (name varchar(20))' % table_prefix
+    xddl1 = 'drop table %sbooze' % table_prefix
+    xddl2 = 'drop table %sbarflys' % table_prefix
+
+    lowerfunc = 'lower' # Name of stored procedure to convert string->lowercase
+        
+    # Some drivers may need to override these helpers, for example adding
+    # a 'commit' after the execute.
+    def executeDDL1(self,cursor):
+        cursor.execute(self.ddl1)
+
+    def executeDDL2(self,cursor):
+        cursor.execute(self.ddl2)
+
+    def setUp(self):
+        ''' self.drivers should override this method to perform required setup
+            if any is necessary, such as creating the database.
+        '''
+        pass
+
+    def tearDown(self):
+        ''' self.drivers should override this method to perform required cleanup
+            if any is necessary, such as deleting the test database.
+            The default drops the tables that may be created.
+        '''
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            for ddl in (self.xddl1,self.xddl2):
+                try: 
+                    cur.execute(ddl)
+                    con.commit()
+                except self.driver.Error: 
+                    # Assume table didn't exist. Other tests will check if
+                    # execute is busted.
+                    pass
+        finally:
+            con.close()
+
+    def _connect(self):
+        try:
+            return self.driver.connect(
+                *self.connect_args,**self.connect_kw_args
+                )
+        except AttributeError:
+            self.fail("No connect method found in self.driver module")
+
+    def test_connect(self):
+        con = self._connect()
+        con.close()
+
+    def test_apilevel(self):
+        try:
+            # Must exist
+            apilevel = self.driver.apilevel
+            # Must equal 2.0
+            self.assertEqual(apilevel,'2.0')
+        except AttributeError:
+            self.fail("Driver doesn't define apilevel")
+
+    def test_threadsafety(self):
+        try:
+            # Must exist
+            threadsafety = self.driver.threadsafety
+            # Must be a valid value
+            self.failUnless(threadsafety in (0,1,2,3))
+        except AttributeError:
+            self.fail("Driver doesn't define threadsafety")
+
+    def test_paramstyle(self):
+        try:
+            # Must exist
+            paramstyle = self.driver.paramstyle
+            # Must be a valid value
+            self.failUnless(paramstyle in (
+                'qmark','numeric','named','format','pyformat'
+                ))
+        except AttributeError:
+            self.fail("Driver doesn't define paramstyle")
+
+    def test_Exceptions(self):
+        # Make sure required exceptions exist, and are in the
+        # defined heirarchy.
+        self.failUnless(issubclass(self.driver.Warning,StandardError))
+        self.failUnless(issubclass(self.driver.Error,StandardError))
+        self.failUnless(
+            issubclass(self.driver.InterfaceError,self.driver.Error)
+            )
+        self.failUnless(
+            issubclass(self.driver.DatabaseError,self.driver.Error)
+            )
+        self.failUnless(
+            issubclass(self.driver.OperationalError,self.driver.Error)
+            )
+        self.failUnless(
+            issubclass(self.driver.IntegrityError,self.driver.Error)
+            )
+        self.failUnless(
+            issubclass(self.driver.InternalError,self.driver.Error)
+            )
+        self.failUnless(
+            issubclass(self.driver.ProgrammingError,self.driver.Error)
+            )
+        self.failUnless(
+            issubclass(self.driver.NotSupportedError,self.driver.Error)
+            )
+
+    def test_ExceptionsAsConnectionAttributes(self):
+        # OPTIONAL EXTENSION
+        # Test for the optional DB API 2.0 extension, where the exceptions
+        # are exposed as attributes on the Connection object
+        # I figure this optional extension will be implemented by any
+        # driver author who is using this test suite, so it is enabled
+        # by default.
+        con = self._connect()
+        drv = self.driver
+        self.failUnless(con.Warning is drv.Warning)
+        self.failUnless(con.Error is drv.Error)
+        self.failUnless(con.InterfaceError is drv.InterfaceError)
+        self.failUnless(con.DatabaseError is drv.DatabaseError)
+        self.failUnless(con.OperationalError is drv.OperationalError)
+        self.failUnless(con.IntegrityError is drv.IntegrityError)
+        self.failUnless(con.InternalError is drv.InternalError)
+        self.failUnless(con.ProgrammingError is drv.ProgrammingError)
+        self.failUnless(con.NotSupportedError is drv.NotSupportedError)
+
+
+    def test_commit(self):
+        con = self._connect()
+        try:
+            # Commit must work, even if it doesn't do anything
+            con.commit()
+        finally:
+            con.close()
+
+    def test_rollback(self):
+        con = self._connect()
+        # If rollback is defined, it should either work or throw
+        # the documented exception
+        if hasattr(con,'rollback'):
+            try:
+                con.rollback()
+            except self.driver.NotSupportedError:
+                pass
+    
+    def test_cursor(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+        finally:
+            con.close()
+
+    def test_cursor_isolation(self):
+        con = self._connect()
+        try:
+            # Make sure cursors created from the same connection have
+            # the documented transaction isolation level
+            cur1 = con.cursor()
+            cur2 = con.cursor()
+            self.executeDDL1(cur1)
+            cur1.execute("insert into %sbooze values ('Victoria Bitter')" % (
+                self.table_prefix
+                ))
+            cur2.execute("select name from %sbooze" % self.table_prefix)
+            booze = cur2.fetchall()
+            self.assertEqual(len(booze),1)
+            self.assertEqual(len(booze[0]),1)
+            self.assertEqual(booze[0][0],'Victoria Bitter')
+        finally:
+            con.close()
+
+    def test_description(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.executeDDL1(cur)
+            self.assertEqual(cur.description,None,
+                'cursor.description should be none after executing a '
+                'statement that can return no rows (such as DDL)'
+                )
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            self.assertEqual(len(cur.description),1,
+                'cursor.description describes too many columns'
+                )
+            self.assertEqual(len(cur.description[0]),7,
+                'cursor.description[x] tuples must have 7 elements'
+                )
+            self.assertEqual(cur.description[0][0].lower(),'name',
+                'cursor.description[x][0] must return column name'
+                )
+            self.assertEqual(cur.description[0][1],self.driver.STRING,
+                'cursor.description[x][1] must return column type. Got %r'
+                    % cur.description[0][1]
+                )
+
+            # Make sure self.description gets reset
+            self.executeDDL2(cur)
+            self.assertEqual(cur.description,None,
+                'cursor.description not being set to None when executing '
+                'no-result statements (eg. DDL)'
+                )
+        finally:
+            con.close()
+
+    def test_rowcount(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.executeDDL1(cur)
+            self.assertEqual(cur.rowcount,-1,
+                'cursor.rowcount should be -1 after executing no-result '
+                'statements'
+                )
+            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
+                self.table_prefix
+                ))
+            self.failUnless(cur.rowcount in (-1,1),
+                'cursor.rowcount should == number or rows inserted, or '
+                'set to -1 after executing an insert statement'
+                )
+            cur.execute("select name from %sbooze" % self.table_prefix)
+            self.failUnless(cur.rowcount in (-1,1),
+                'cursor.rowcount should == number of rows returned, or '
+                'set to -1 after executing a select statement'
+                )
+            self.executeDDL2(cur)
+            self.assertEqual(cur.rowcount,-1,
+                'cursor.rowcount not being reset to -1 after executing '
+                'no-result statements'
+                )
+        finally:
+            con.close()
+
+    lower_func = 'lower'
+    def test_callproc(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            if self.lower_func and hasattr(cur,'callproc'):
+                r = cur.callproc(self.lower_func,('FOO',))
+                self.assertEqual(len(r),1)
+                self.assertEqual(r[0],'FOO')
+                r = cur.fetchall()
+                self.assertEqual(len(r),1,'callproc produced no result set')
+                self.assertEqual(len(r[0]),1,
+                    'callproc produced invalid result set'
+                    )
+                self.assertEqual(r[0][0],'foo',
+                    'callproc produced invalid results'
+                    )
+        finally:
+            con.close()
+
+    def test_close(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+        finally:
+            con.close()
+
+        # cursor.execute should raise an Error if called after connection
+        # closed
+        self.assertRaises(self.driver.Error,self.executeDDL1,cur)
+
+        # connection.commit should raise an Error if called after connection'
+        # closed.'
+        self.assertRaises(self.driver.Error,con.commit)
+
+        # connection.close should raise an Error if called more than once
+        self.assertRaises(self.driver.Error,con.close)
+
+    def test_execute(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self._paraminsert(cur)
+        finally:
+            con.close()
+
+    def _paraminsert(self,cur):
+        self.executeDDL1(cur)
+        cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
+            self.table_prefix
+            ))
+        self.failUnless(cur.rowcount in (-1,1))
+
+        if self.driver.paramstyle == 'qmark':
+            cur.execute(
+                'insert into %sbooze values (?)' % self.table_prefix,
+                ("Cooper's",)
+                )
+        elif self.driver.paramstyle == 'numeric':
+            cur.execute(
+                'insert into %sbooze values (:1)' % self.table_prefix,
+                ("Cooper's",)
+                )
+        elif self.driver.paramstyle == 'named':
+            cur.execute(
+                'insert into %sbooze values (:beer)' % self.table_prefix, 
+                {'beer':"Cooper's"}
+                )
+        elif self.driver.paramstyle == 'format':
+            cur.execute(
+                'insert into %sbooze values (%%s)' % self.table_prefix,
+                ("Cooper's",)
+                )
+        elif self.driver.paramstyle == 'pyformat':
+            cur.execute(
+                'insert into %sbooze values (%%(beer)s)' % self.table_prefix,
+                {'beer':"Cooper's"}
+                )
+        else:
+            self.fail('Invalid paramstyle')
+        self.failUnless(cur.rowcount in (-1,1))
+
+        cur.execute('select name from %sbooze' % self.table_prefix)
+        res = cur.fetchall()
+        self.assertEqual(len(res),2,'cursor.fetchall returned too few rows')
+        beers = [res[0][0],res[1][0]]
+        beers.sort()
+        self.assertEqual(beers[0],"Cooper's",
+            'cursor.fetchall retrieved incorrect data, or data inserted '
+            'incorrectly'
+            )
+        self.assertEqual(beers[1],"Victoria Bitter",
+            'cursor.fetchall retrieved incorrect data, or data inserted '
+            'incorrectly'
+            )
+
+    def test_executemany(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.executeDDL1(cur)
+            largs = [ ("Cooper's",) , ("Boag's",) ]
+            margs = [ {'beer': "Cooper's"}, {'beer': "Boag's"} ]
+            if self.driver.paramstyle == 'qmark':
+                cur.executemany(
+                    'insert into %sbooze values (?)' % self.table_prefix,
+                    largs
+                    )
+            elif self.driver.paramstyle == 'numeric':
+                cur.executemany(
+                    'insert into %sbooze values (:1)' % self.table_prefix,
+                    largs
+                    )
+            elif self.driver.paramstyle == 'named':
+                cur.executemany(
+                    'insert into %sbooze values (:beer)' % self.table_prefix,
+                    margs
+                    )
+            elif self.driver.paramstyle == 'format':
+                cur.executemany(
+                    'insert into %sbooze values (%%s)' % self.table_prefix,
+                    largs
+                    )
+            elif self.driver.paramstyle == 'pyformat':
+                cur.executemany(
+                    'insert into %sbooze values (%%(beer)s)' % (
+                        self.table_prefix
+                        ),
+                    margs
+                    )
+            else:
+                self.fail('Unknown paramstyle')
+            self.failUnless(cur.rowcount in (-1,2),
+                'insert using cursor.executemany set cursor.rowcount to '
+                'incorrect value %r' % cur.rowcount
+                )
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            res = cur.fetchall()
+            self.assertEqual(len(res),2,
+                'cursor.fetchall retrieved incorrect number of rows'
+                )
+            beers = [res[0][0],res[1][0]]
+            beers.sort()
+            self.assertEqual(beers[0],"Boag's",'incorrect data retrieved')
+            self.assertEqual(beers[1],"Cooper's",'incorrect data retrieved')
+        finally:
+            con.close()
+
+    def test_fetchone(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+
+            # cursor.fetchone should raise an Error if called before
+            # executing a select-type query
+            self.assertRaises(self.driver.Error,cur.fetchone)
+
+            # cursor.fetchone should raise an Error if called after
+            # executing a query that cannnot return rows
+            self.executeDDL1(cur)
+            self.assertRaises(self.driver.Error,cur.fetchone)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            self.assertEqual(cur.fetchone(),None,
+                'cursor.fetchone should return None if a query retrieves '
+                'no rows'
+                )
+            self.failUnless(cur.rowcount in (-1,0))
+
+            # cursor.fetchone should raise an Error if called after
+            # executing a query that cannnot return rows
+            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
+                self.table_prefix
+                ))
+            self.assertRaises(self.driver.Error,cur.fetchone)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            r = cur.fetchone()
+            self.assertEqual(len(r),1,
+                'cursor.fetchone should have retrieved a single row'
+                )
+            self.assertEqual(r[0],'Victoria Bitter',
+                'cursor.fetchone retrieved incorrect data'
+                )
+            self.assertEqual(cur.fetchone(),None,
+                'cursor.fetchone should return None if no more rows available'
+                )
+            self.failUnless(cur.rowcount in (-1,1))
+        finally:
+            con.close()
+
+    samples = [
+        'Carlton Cold',
+        'Carlton Draft',
+        'Mountain Goat',
+        'Redback',
+        'Victoria Bitter',
+        'XXXX'
+        ]
+
+    def _populate(self):
+        ''' Return a list of sql commands to setup the DB for the fetch
+            tests.
+        '''
+        populate = [
+            "insert into %sbooze values ('%s')" % (self.table_prefix,s) 
+                for s in self.samples
+            ]
+        return populate
+
+    def test_fetchmany(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+
+            # cursor.fetchmany should raise an Error if called without
+            #issuing a query
+            self.assertRaises(self.driver.Error,cur.fetchmany,4)
+
+            self.executeDDL1(cur)
+            for sql in self._populate():
+                cur.execute(sql)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            r = cur.fetchmany()
+            self.assertEqual(len(r),1,
+                'cursor.fetchmany retrieved incorrect number of rows, '
+                'default of arraysize is one.'
+                )
+            cur.arraysize=10
+            r = cur.fetchmany(3) # Should get 3 rows
+            self.assertEqual(len(r),3,
+                'cursor.fetchmany retrieved incorrect number of rows'
+                )
+            r = cur.fetchmany(4) # Should get 2 more
+            self.assertEqual(len(r),2,
+                'cursor.fetchmany retrieved incorrect number of rows'
+                )
+            r = cur.fetchmany(4) # Should be an empty sequence
+            self.assertEqual(len(r),0,
+                'cursor.fetchmany should return an empty sequence after '
+                'results are exhausted'
+            )
+            self.failUnless(cur.rowcount in (-1,6))
+
+            # Same as above, using cursor.arraysize
+            cur.arraysize=4
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            r = cur.fetchmany() # Should get 4 rows
+            self.assertEqual(len(r),4,
+                'cursor.arraysize not being honoured by fetchmany'
+                )
+            r = cur.fetchmany() # Should get 2 more
+            self.assertEqual(len(r),2)
+            r = cur.fetchmany() # Should be an empty sequence
+            self.assertEqual(len(r),0)
+            self.failUnless(cur.rowcount in (-1,6))
+
+            cur.arraysize=6
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            rows = cur.fetchmany() # Should get all rows
+            self.failUnless(cur.rowcount in (-1,6))
+            self.assertEqual(len(rows),6)
+            self.assertEqual(len(rows),6)
+            rows = [r[0] for r in rows]
+            rows.sort()
+          
+            # Make sure we get the right data back out
+            for i in range(0,6):
+                self.assertEqual(rows[i],self.samples[i],
+                    'incorrect data retrieved by cursor.fetchmany'
+                    )
+
+            rows = cur.fetchmany() # Should return an empty list
+            self.assertEqual(len(rows),0,
+                'cursor.fetchmany should return an empty sequence if '
+                'called after the whole result set has been fetched'
+                )
+            self.failUnless(cur.rowcount in (-1,6))
+
+            self.executeDDL2(cur)
+            cur.execute('select name from %sbarflys' % self.table_prefix)
+            r = cur.fetchmany() # Should get empty sequence
+            self.assertEqual(len(r),0,
+                'cursor.fetchmany should return an empty sequence if '
+                'query retrieved no rows'
+                )
+            self.failUnless(cur.rowcount in (-1,0))
+
+        finally:
+            con.close()
+
+    def test_fetchall(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            # cursor.fetchall should raise an Error if called
+            # without executing a query that may return rows (such
+            # as a select)
+            self.assertRaises(self.driver.Error, cur.fetchall)
+
+            self.executeDDL1(cur)
+            for sql in self._populate():
+                cur.execute(sql)
+
+            # cursor.fetchall should raise an Error if called
+            # after executing a a statement that cannot return rows
+            self.assertRaises(self.driver.Error,cur.fetchall)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            rows = cur.fetchall()
+            self.failUnless(cur.rowcount in (-1,len(self.samples)))
+            self.assertEqual(len(rows),len(self.samples),
+                'cursor.fetchall did not retrieve all rows'
+                )
+            rows = [r[0] for r in rows]
+            rows.sort()
+            for i in range(0,len(self.samples)):
+                self.assertEqual(rows[i],self.samples[i],
+                'cursor.fetchall retrieved incorrect rows'
+                )
+            rows = cur.fetchall()
+            self.assertEqual(
+                len(rows),0,
+                'cursor.fetchall should return an empty list if called '
+                'after the whole result set has been fetched'
+                )
+            self.failUnless(cur.rowcount in (-1,len(self.samples)))
+
+            self.executeDDL2(cur)
+            cur.execute('select name from %sbarflys' % self.table_prefix)
+            rows = cur.fetchall()
+            self.failUnless(cur.rowcount in (-1,0))
+            self.assertEqual(len(rows),0,
+                'cursor.fetchall should return an empty list if '
+                'a select query returns no rows'
+                )
+            
+        finally:
+            con.close()
+    
+    def test_mixedfetch(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.executeDDL1(cur)
+            for sql in self._populate():
+                cur.execute(sql)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            rows1  = cur.fetchone()
+            rows23 = cur.fetchmany(2)
+            rows4  = cur.fetchone()
+            rows56 = cur.fetchall()
+            self.failUnless(cur.rowcount in (-1,6))
+            self.assertEqual(len(rows23),2,
+                'fetchmany returned incorrect number of rows'
+                )
+            self.assertEqual(len(rows56),2,
+                'fetchall returned incorrect number of rows'
+                )
+
+            rows = [rows1[0]]
+            rows.extend([rows23[0][0],rows23[1][0]])
+            rows.append(rows4[0])
+            rows.extend([rows56[0][0],rows56[1][0]])
+            rows.sort()
+            for i in range(0,len(self.samples)):
+                self.assertEqual(rows[i],self.samples[i],
+                    'incorrect data retrieved or inserted'
+                    )
+        finally:
+            con.close()
+
+    def help_nextset_setUp(self,cur):
+        ''' Should create a procedure called deleteme
+            that returns two result sets, first the 
+	    number of rows in booze then "name from booze"
+        '''
+        raise NotImplementedError,'Helper not implemented'
+        #sql="""
+        #    create procedure deleteme as
+        #    begin
+        #        select count(*) from booze
+        #        select name from booze
+        #    end
+        #"""
+        #cur.execute(sql)
+
+    def help_nextset_tearDown(self,cur):
+        'If cleaning up is needed after nextSetTest'
+        raise NotImplementedError,'Helper not implemented'
+        #cur.execute("drop procedure deleteme")
+
+    def test_nextset(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            if not hasattr(cur,'nextset'):
+                return
+
+            try:
+                self.executeDDL1(cur)
+                sql=self._populate()
+                for sql in self._populate():
+                    cur.execute(sql)
+
+                self.help_nextset_setUp(cur)
+
+                cur.callproc('deleteme')
+                numberofrows=cur.fetchone()
+                assert numberofrows[0]== len(self.samples)
+                assert cur.nextset()
+                names=cur.fetchall()
+                assert len(names) == len(self.samples)
+                s=cur.nextset()
+                assert s == None,'No more return sets, should return None'
+            finally:
+                self.help_nextset_tearDown(cur)
+
+        finally:
+            con.close()
+
+    def test_nextset(self):
+        raise NotImplementedError,'Drivers need to override this test'
+
+    def test_arraysize(self):
+        # Not much here - rest of the tests for this are in test_fetchmany
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.failUnless(hasattr(cur,'arraysize'),
+                'cursor.arraysize must be defined'
+                )
+        finally:
+            con.close()
+
+    def test_setinputsizes(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            cur.setinputsizes( (25,) )
+            self._paraminsert(cur) # Make sure cursor still works
+        finally:
+            con.close()
+
+    def test_setoutputsize_basic(self):
+        # Basic test is to make sure setoutputsize doesn't blow up
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            cur.setoutputsize(1000)
+            cur.setoutputsize(2000,0)
+            self._paraminsert(cur) # Make sure the cursor still works
+        finally:
+            con.close()
+
+    def test_setoutputsize(self):
+        # Real test for setoutputsize is driver dependant
+        raise NotImplementedError,'Driver need to override this test'
+
+    def test_None(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.executeDDL1(cur)
+            cur.execute('insert into %sbooze values (NULL)' % self.table_prefix)
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            r = cur.fetchall()
+            self.assertEqual(len(r),1)
+            self.assertEqual(len(r[0]),1)
+            self.assertEqual(r[0][0],None,'NULL value not returned as None')
+        finally:
+            con.close()
+
+    def test_Date(self):
+        d1 = self.driver.Date(2002,12,25)
+        d2 = self.driver.DateFromTicks(time.mktime((2002,12,25,0,0,0,0,0,0)))
+        # Can we assume this? API doesn't specify, but it seems implied
+        # self.assertEqual(str(d1),str(d2))
+
+    def test_Time(self):
+        t1 = self.driver.Time(13,45,30)
+        t2 = self.driver.TimeFromTicks(time.mktime((2001,1,1,13,45,30,0,0,0)))
+        # Can we assume this? API doesn't specify, but it seems implied
+        # self.assertEqual(str(t1),str(t2))
+
+    def test_Timestamp(self):
+        t1 = self.driver.Timestamp(2002,12,25,13,45,30)
+        t2 = self.driver.TimestampFromTicks(
+            time.mktime((2002,12,25,13,45,30,0,0,0))
+            )
+        # Can we assume this? API doesn't specify, but it seems implied
+        # self.assertEqual(str(t1),str(t2))
+
+    def test_Binary(self):
+        b = self.driver.Binary('Something')
+        b = self.driver.Binary('')
+
+    def test_STRING(self):
+        self.failUnless(hasattr(self.driver,'STRING'),
+            'module.STRING must be defined'
+            )
+
+    def test_BINARY(self):
+        self.failUnless(hasattr(self.driver,'BINARY'),
+            'module.BINARY must be defined.'
+            )
+
+    def test_NUMBER(self):
+        self.failUnless(hasattr(self.driver,'NUMBER'),
+            'module.NUMBER must be defined.'
+            )
+
+    def test_DATETIME(self):
+        self.failUnless(hasattr(self.driver,'DATETIME'),
+            'module.DATETIME must be defined.'
+            )
+
+    def test_ROWID(self):
+        self.failUnless(hasattr(self.driver,'ROWID'),
+            'module.ROWID must be defined.'
+            )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_MySQLdb_capabilities.py	Fri Mar 14 23:06:29 2008 +0000
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+import capabilities
+import unittest
+import MySQLdb
+import warnings
+
+warnings.filterwarnings('error')
+
+class test_MySQLdb(capabilities.DatabaseTest):
+
+    db_module = MySQLdb
+    connect_args = ()
+    connect_kwargs = dict(db='test', read_default_file='~/.my.cnf',
+                          charset='utf8', sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")
+    create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8"
+    leak_test = False
+    
+    def quote_identifier(self, ident):
+        return "`%s`" % ident
+
+    def test_TIME(self):
+        from datetime import timedelta
+        def generator(row,col):
+            return timedelta(0, row*8000)
+        self.check_data_integrity(
+                 ('col1 TIME',),
+                 generator)
+
+    def test_TINYINT(self):
+        # Number data
+        def generator(row,col):
+            v = (row*row) % 256
+            if v > 127:
+                v = v-256
+            return v
+        self.check_data_integrity(
+            ('col1 TINYINT',),
+            generator)
+        
+    def test_stored_procedures(self):
+        db = self.connection
+        c = self.cursor
+        try:
+            self.create_table(('pos INT', 'tree CHAR(20)'))
+            c.executemany("INSERT INTO %s (pos,tree) VALUES (%%s,%%s)" % self.table,
+                          list(enumerate('ash birch cedar larch pine'.split())))
+            db.commit()
+            
+            c.execute("""
+            CREATE PROCEDURE test_sp(IN t VARCHAR(255))
+            BEGIN
+                SELECT pos FROM %s WHERE tree = t;
+            END
+            """ % self.table)
+            db.commit()
+    
+            c.callproc('test_sp', ('larch',))
+            rows = c.fetchall()
+            self.assertEquals(len(rows), 1)
+            self.assertEquals(rows[0][0], 3)
+            c.nextset()
+        finally:
+            c.execute("DROP PROCEDURE IF EXISTS test_sp")
+            c.execute('drop table %s' % (self.table))
+
+    def test_small_CHAR(self):
+        # Character data
+        def generator(row,col):
+            i = (row*col+62)%256
+            if i == 62: return ''
+            if i == 63: return None
+            return chr(i)
+        self.check_data_integrity(
+            ('col1 char(1)','col2 char(1)'),
+            generator)
+        
+if __name__ == '__main__':
+    if test_MySQLdb.leak_test:
+        import gc
+        gc.enable()
+        gc.set_debug(gc.DEBUG_LEAK)
+    unittest.main()
+    print '''"Huh-huh, he said 'unit'." -- Butthead'''
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_MySQLdb_dbapi20.py	Fri Mar 14 23:06:29 2008 +0000
@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+import dbapi20
+import unittest
+import MySQLdb
+
+class test_MySQLdb(dbapi20.DatabaseAPI20Test):
+    driver = MySQLdb
+    connect_args = ()
+    connect_kw_args = dict(db='test',
+                           read_default_file='~/.my.cnf',
+                           charset='utf8',
+                           sql_mode="ANSI,STRICT_TRANS_TABLES,TRADITIONAL")
+
+    def test_setoutputsize(self): pass
+    def test_setoutputsize_basic(self): pass
+    def test_nextset(self): pass
+
+    """The tests on fetchone and fetchall and rowcount bogusly
+    test for an exception if the statement cannot return a
+    result set. MySQL always returns a result set; it's just that
+    some things return empty result sets."""
+    
+    def test_fetchall(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            # cursor.fetchall should raise an Error if called
+            # without executing a query that may return rows (such
+            # as a select)
+            self.assertRaises(self.driver.Error, cur.fetchall)
+
+            self.executeDDL1(cur)
+            for sql in self._populate():
+                cur.execute(sql)
+
+            # cursor.fetchall should raise an Error if called
+            # after executing a a statement that cannot return rows
+##             self.assertRaises(self.driver.Error,cur.fetchall)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            rows = cur.fetchall()
+            self.failUnless(cur.rowcount in (-1,len(self.samples)))
+            self.assertEqual(len(rows),len(self.samples),
+                'cursor.fetchall did not retrieve all rows'
+                )
+            rows = [r[0] for r in rows]
+            rows.sort()
+            for i in range(0,len(self.samples)):
+                self.assertEqual(rows[i],self.samples[i],
+                'cursor.fetchall retrieved incorrect rows'
+                )
+            rows = cur.fetchall()
+            self.assertEqual(
+                len(rows),0,
+                'cursor.fetchall should return an empty list if called '
+                'after the whole result set has been fetched'
+                )
+            self.failUnless(cur.rowcount in (-1,len(self.samples)))
+
+            self.executeDDL2(cur)
+            cur.execute('select name from %sbarflys' % self.table_prefix)
+            rows = cur.fetchall()
+            self.failUnless(cur.rowcount in (-1,0))
+            self.assertEqual(len(rows),0,
+                'cursor.fetchall should return an empty list if '
+                'a select query returns no rows'
+                )
+            
+        finally:
+            con.close()
+                
+    def test_fetchone(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+
+            # cursor.fetchone should raise an Error if called before
+            # executing a select-type query
+            self.assertRaises(self.driver.Error,cur.fetchone)
+
+            # cursor.fetchone should raise an Error if called after
+            # executing a query that cannnot return rows
+            self.executeDDL1(cur)
+##             self.assertRaises(self.driver.Error,cur.fetchone)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            self.assertEqual(cur.fetchone(),None,
+                'cursor.fetchone should return None if a query retrieves '
+                'no rows'
+                )
+            self.failUnless(cur.rowcount in (-1,0))
+
+            # cursor.fetchone should raise an Error if called after
+            # executing a query that cannnot return rows
+            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
+                self.table_prefix
+                ))
+##             self.assertRaises(self.driver.Error,cur.fetchone)
+
+            cur.execute('select name from %sbooze' % self.table_prefix)
+            r = cur.fetchone()
+            self.assertEqual(len(r),1,
+                'cursor.fetchone should have retrieved a single row'
+                )
+            self.assertEqual(r[0],'Victoria Bitter',
+                'cursor.fetchone retrieved incorrect data'
+                )
+##             self.assertEqual(cur.fetchone(),None,
+##                 'cursor.fetchone should return None if no more rows available'
+##                 )
+            self.failUnless(cur.rowcount in (-1,1))
+        finally:
+            con.close()
+
+    # Same complaint as for fetchall and fetchone
+    def test_rowcount(self):
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            self.executeDDL1(cur)
+##             self.assertEqual(cur.rowcount,-1,
+##                 'cursor.rowcount should be -1 after executing no-result '
+##                 'statements'
+##                 )
+            cur.execute("insert into %sbooze values ('Victoria Bitter')" % (
+                self.table_prefix
+                ))
+##             self.failUnless(cur.rowcount in (-1,1),
+##                 'cursor.rowcount should == number or rows inserted, or '
+##                 'set to -1 after executing an insert statement'
+##                 )
+            cur.execute("select name from %sbooze" % self.table_prefix)
+            self.failUnless(cur.rowcount in (-1,1),
+                'cursor.rowcount should == number of rows returned, or '
+                'set to -1 after executing a select statement'
+                )
+            self.executeDDL2(cur)
+##             self.assertEqual(cur.rowcount,-1,
+##                 'cursor.rowcount not being reset to -1 after executing '
+##                 'no-result statements'
+##                 )
+        finally:
+            con.close()
+
+    def test_callproc(self):
+        pass # performed in test_MySQL_capabilities
+
+    def help_nextset_setUp(self,cur):
+        ''' Should create a procedure called deleteme
+            that returns two result sets, first the 
+	    number of rows in booze then "name from booze"
+        '''
+        sql="""
+           create procedure deleteme()
+           begin
+               select count(*) from %(tp)sbooze;
+               select name from %(tp)sbooze;
+           end
+        """ % dict(tp=self.table_prefix)
+        cur.execute(sql)
+
+    def help_nextset_tearDown(self,cur):
+        'If cleaning up is needed after nextSetTest'
+        cur.execute("drop procedure deleteme")
+
+    def test_nextset(self):
+        from warnings import warn
+        con = self._connect()
+        try:
+            cur = con.cursor()
+            if not hasattr(cur,'nextset'):
+                return
+
+            try:
+                self.executeDDL1(cur)
+                sql=self._populate()
+                for sql in self._populate():
+                    cur.execute(sql)
+
+                self.help_nextset_setUp(cur)
+
+                cur.callproc('deleteme')
+                numberofrows=cur.fetchone()
+                assert numberofrows[0]== len(self.samples)
+                assert cur.nextset()
+                names=cur.fetchall()
+                assert len(names) == len(self.samples)
+                s=cur.nextset()
+                if s:
+                    empty = cur.fetchall()
+                    self.assertEquals(len(empty), 0,
+                                      "non-empty result set after other result sets")
+                    #warn("Incompatibility: MySQL returns an empty result set for the CALL itself",
+                    #     Warning)
+                #assert s == None,'No more return sets, should return None'
+            finally:
+                self.help_nextset_tearDown(cur)
+
+        finally:
+            con.close()
+
+    
+if __name__ == '__main__':
+    unittest.main()
+    print '''"Huh-huh, he said 'unit'." -- Butthead'''