[Checkins] SVN: relstorage/branches/1.1-jarn/ Use an outputtypehandler to send BLOBs inline when possible.

Shane Hathaway shane at hathawaymix.org
Fri Aug 29 19:59:08 EDT 2008


Log message for revision 90605:
  Use an outputtypehandler to send BLOBs inline when possible.
  
  This change is based on a patch by Helge Tesdal.
  

Changed:
  U   relstorage/branches/1.1-jarn/relstorage/adapters/oracle.py
  U   relstorage/branches/1.1-jarn/setup.py

-=-
Modified: relstorage/branches/1.1-jarn/relstorage/adapters/oracle.py
===================================================================
--- relstorage/branches/1.1-jarn/relstorage/adapters/oracle.py	2008-08-29 23:54:25 UTC (rev 90604)
+++ relstorage/branches/1.1-jarn/relstorage/adapters/oracle.py	2008-08-29 23:59:07 UTC (rev 90605)
@@ -23,8 +23,68 @@
 from common import Adapter
 
 log = logging.getLogger("relstorage.adapters.oracle")
+use_inline_lobs = (cx_Oracle.version >= '5.0')
 
 
+def inline(value):
+    """Handle an Oracle LOB by returning its byte stream.
+
+    Returns other objects unchanged.
+    """
+    if isinstance(value, cx_Oracle.LOB):
+        return value.read()
+    return value
+
+
+if use_inline_lobs:
+    def execute_lob_stmt(cursor, stmt, args=(), default=None):
+        """Execute a statement and return one row with all LOBs inline.
+
+        Returns the value of the default parameter if the result was empty.
+        """
+        try:
+            cursor.execute(stmt, args)
+            for row in cursor:
+                return row
+        except cx_Oracle.DatabaseError, e:
+            # ORA-01406: fetched column value was truncated
+            if not e.args[0].endswith(' 1406'):
+                raise
+            del cursor.outputtypehandler
+            try:
+                # Execute the query, but alter it slightly without
+                # changing its meaning, so that the query cache
+                # will see it as a statement that has to be compiled
+                # with different output type parameters.
+                cursor.execute(stmt + ' ', args)
+            finally:
+                cursor.outputtypehandler = lob_handler
+            for row in cursor:
+                return tuple(map(inline, row))
+        return default
+else:
+    def execute_lob_stmt(cursor, stmt, args=(), default=None):
+        """Execute a statement and return one row with all LOBs inline.
+
+        Returns the value of the default parameter if the result was empty.
+        """
+        cursor.execute(stmt, args)
+        for row in cursor:
+            return tuple(map(inline, row))
+        return default
+
+
+def lob_handler(cursor, name, defaultType, size, precision, scale):
+    """cx_Oracle outputtypehandler that causes Oracle to send BLOBs inline.
+
+    Note that if a BLOB in the result is too large, Oracle generates an
+    error indicating truncation.  The execute_lob_stmt() function works
+    around this.
+    """
+    if defaultType == cx_Oracle.BLOB:
+        return cursor.var(cx_Oracle.LONG_BINARY)
+
+
 class OracleAdapter(Adapter):
     """Oracle adapter for RelStorage."""
 
@@ -99,7 +159,13 @@
             params = ()
 
         try:
-            cursor.execute(stmt, params)
+            if use_inline_lobs:
+                del cursor.outputtypehandler
+            try:
+                cursor.execute(stmt, params)
+            finally:
+                if use_inline_lobs:
+                    cursor.outputtypehandler = lob_handler
         except:
             log.warning("script statement failed: %r; parameters: %r",
                 stmt, params)
@@ -290,6 +356,8 @@
             conn = cx_Oracle.connect(*self._params, **kw)
             cursor = conn.cursor()
             cursor.arraysize = self._arraysize
+            if use_inline_lobs:
+                cursor.outputtypehandler = lob_handler
             if transaction_mode:
                 cursor.execute("SET TRANSACTION %s" % transaction_mode)
             return conn, cursor
@@ -370,34 +438,27 @@
 
         oid is an integer.  Returns (None, None) if object does not exist.
         """
-        cursor.execute("""
+        stmt = """
         SELECT state, tid
         FROM current_object
             JOIN object_state USING(zoid, tid)
         WHERE zoid = :1
-        """, (oid,))
-        for state, tid in cursor:
-            if state is not None:
-                state = state.read()
-            # else this object's creation has been undone
-            return state, tid
-        return None, None
+        """
+        return execute_lob_stmt(cursor, stmt, (oid,), default=(None, None))
 
     def load_revision(self, cursor, oid, tid):
         """Returns the pickle for an object on a particular transaction.
 
         Returns None if no such state exists.
         """
-        cursor.execute("""
+        stmt = """
         SELECT state
         FROM object_state
         WHERE zoid = :1
             AND tid = :2
-        """, (oid, tid))
-        for (state,) in cursor:
-            if state is not None:
-                return state.read()
-        return None
+        """
+        (state,) = execute_lob_stmt(cursor, stmt, (oid, tid), default=(None,))
+        return state
 
     def exists(self, cursor, oid):
         """Returns a true value if the given object exists."""
@@ -420,13 +481,8 @@
                     AND tid < :tid
             )
         """
-        cursor.execute(stmt, {'oid': oid, 'tid': tid})
-        for state, tid in cursor:
-            if state is not None:
-                state = state.read()
-            # else this object's creation has been undone
-            return state, tid
-        return None, None
+        return execute_lob_stmt(cursor, stmt, {'oid': oid, 'tid': tid},
+             default=(None, None))
 
     def get_object_tid_after(self, cursor, oid, tid):
         """Returns the tid of the next change after an object revision.
@@ -577,10 +633,7 @@
             JOIN current_object ON (temp_store.zoid = current_object.zoid)
         WHERE temp_store.prev_tid != current_object.tid
         """
-        cursor.execute(stmt)
-        for oid, prev_tid, attempted_prev_tid, data in cursor:
-            return oid, prev_tid, attempted_prev_tid, data.read()
-        return None
+        return execute_lob_stmt(cursor, stmt)
 
     def move_from_temp(self, cursor, tid):
         """Moved the temporarily stored objects to permanent storage.

Modified: relstorage/branches/1.1-jarn/setup.py
===================================================================
--- relstorage/branches/1.1-jarn/setup.py	2008-08-29 23:54:25 UTC (rev 90604)
+++ relstorage/branches/1.1-jarn/setup.py	2008-08-29 23:59:07 UTC (rev 90605)
@@ -27,7 +27,7 @@
 with RelStorage.
 """
 
-VERSION = "1.1c1"
+VERSION = "1.1jarn2"
 
 classifiers = """\
 Development Status :: 4 - Beta



More information about the Checkins mailing list