changeset 57:9ea2b0e9302e MySQLdb

The pure Python SQL-to-Python conversion code. TODO: There should be a way to register plugins in the module and in the connection.
author adustman
date Sat, 28 Feb 2009 04:06:44 +0000
parents 89b07ce2a788
children 6732437eb2ac
files MySQLdb/converters.py MySQLdb/cursors.py
diffstat 2 files changed, 78 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/MySQLdb/converters.py	Fri Feb 27 19:46:55 2009 +0000
+++ b/MySQLdb/converters.py	Sat Feb 28 04:06:44 2009 +0000
@@ -125,6 +125,13 @@
     set: Set_to_sql,
     str: object_to_quoted_sql, # default
 
+    }
+
+# This is for MySQL column types that can be converted directly
+# into Python types without having to look at metadata (flags,
+# character sets, etc.). This should always be used as the last
+# resort.
+simple_sql_to_python_conversions = {
     FIELD_TYPE.TINY: int,
     FIELD_TYPE.SHORT: int,
     FIELD_TYPE.LONG: int,
@@ -139,21 +146,62 @@
     FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter,
     FIELD_TYPE.DATETIME: datetime_or_None,
     FIELD_TYPE.TIME: timedelta_or_None,
-    FIELD_TYPE.DATE: date_or_None,
-    FIELD_TYPE.BLOB: [
-        (FLAG.BINARY, str),
-        ],
-    FIELD_TYPE.STRING: [
-        (FLAG.BINARY, str),
-        ],
-    FIELD_TYPE.VAR_STRING: [
-        (FLAG.BINARY, str),
-        ],
-    FIELD_TYPE.VARCHAR: [
-        (FLAG.BINARY, str),
-        ],
+    FIELD_TYPE.DATE: date_or_None,   
     }
 
+# Converter plugin protocol
+# Each plugin is passed a cursor object and a field object.
+# The plugin returns a single value:
+# A callable that given an SQL value, returns a Python object.
+# This can be as simple as int or str, etc. If the plugin
+# returns None, this plugin will be ignored and the next plugin
+# on the stack will be checked.
+
+def filter_NULL(f):
+    def _filter_NULL(o):
+        if o is None: return o
+        return f(o)
+    _filter_NULL.__name__ = f.__name__
+    return _filter_NULL
+
+def sql_to_python_last_resort_plugin(cursor, field):
+    return str
+
+def simple_sql_to_python_plugin(cursor, field):
+    return simple_sql_to_python_conversions.get(field.type, None)
+
+character_types = [
+    FIELD_TYPE.BLOB, 
+    FIELD_TYPE.STRING,
+    FIELD_TYPE.VAR_STRING,
+    FIELD_TYPE.VARCHAR,
+    ]
+
+def character_sql_to_python_plugin(cursor, field):
+    if field.type not in character_types:
+        return None
+    if field.flags & FLAG.BINARY:
+        return str
+    
+    charset = cursor.connection.character_set_name()
+    def char_to_unicode(s):
+        return s.decode(charset)
+    
+    return char_to_unicode
+
+sql_to_python_plugins = [
+    character_sql_to_python_plugin,
+    simple_sql_to_python_plugin,
+    sql_to_python_last_resort_plugin,
+    ]
+
+def lookup_converter(cursor, field):
+    for plugin in sql_to_python_plugins:
+        f = plugin(cursor, field)
+        if f:
+            return filter_NULL(f)
+    return None # this should never happen
+
 
 
 
--- a/MySQLdb/cursors.py	Fri Feb 27 19:46:55 2009 +0000
+++ b/MySQLdb/cursors.py	Sat Feb 28 04:06:44 2009 +0000
@@ -125,8 +125,16 @@
     
     def _do_get_result(self):
         """Get the result from the last query."""
+        from MySQLdb.converters import lookup_converter
         connection = self._get_db()
         self._result = self._get_result()
+        if self._result:
+            self.sql_to_python = [ 
+                lookup_converter(self, f)
+                for f in self._result.fields()
+            ]
+        else:
+            self.sql_to_python = []
         self.rowcount = connection.affected_rows()
         self.rownumber = 0
         self.description = self._result and self._result.describe() or None
@@ -184,6 +192,7 @@
             del traceback
             self.messages.append((exc, value))
             self.errorhandler(self, exc, value)
+            
         self._executed = query
         if not self._defer_warnings:
             self._warning_check()
@@ -313,7 +322,14 @@
         """Low-level fetch_row wrapper."""
         if not self._result:
             return ()
-        return self._result.fetch_row(size, self._fetch_type)
+        # unfortunately it is necessary to wrap these generators up as tuples
+        # as the rows are expected to be subscriptable.
+        return tuple(
+            ( 
+                tuple( ( f(x) for f, x in zip(self.sql_to_python, row) ) )
+                for row in self._result.fetch_row(size, self._fetch_type)
+            )
+        )
 
     def __iter__(self):
         return iter(self.fetchone, None)