--- base.py.orig	2008-09-08 14:54:02.000000000 -0400
+++ base.py	2008-09-19 11:00:48.000000000 -0400
@@ -7,6 +7,7 @@
 import os
 import datetime
 import time
+import string
 
 # Oracle takes client-side character set encoding from the environment.
 os.environ['NLS_LANG'] = '.UTF8'
@@ -251,11 +252,12 @@
             else:
                 conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
                 self.connection = Database.connect(conn_string, **self.options)
+                
             cursor = FormatStylePlaceholderCursor(self.connection)
             # Set oracle date to ansi date format.  This only needs to execute
             # once when we create a new connection.
-            cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD' "
-                           "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
+            cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'"
+                "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'")
             try:
                 self.oracle_version = int(self.connection.version.split('.')[0])
                 # There's no way for the DatabaseOperations class to know the
@@ -280,6 +282,9 @@
         cursor.arraysize = 100
         return cursor
 
+    def commit(self):
+            self.connection.commit()
+
 
 class OracleParam(object):
     """
@@ -314,10 +319,12 @@
     charset = 'utf-8'
 
     def _format_params(self, params):
+        if params is None:
+            params = []
         if isinstance(params, dict):
             result = {}
             for key, value in params.items():
-                result[smart_str(key, self.charset)] = OracleParam(param, self.charset)
+                result[smart_str(key, self.charset)] = OracleParam(value, self.charset)
             return result
         else:
             return tuple([OracleParam(p, self.charset, True) for p in params])
@@ -333,10 +340,11 @@
             for key, value in iterator:
                 if value.input_size: sizes[key] = value.input_size
         if isinstance(sizes, dict):
-            self.setinputsizes(**sizes)
+            return self.setinputsizes(**sizes)
         else:
-            self.setinputsizes(*sizes)
-
+            return self.setinputsizes(*sizes)
+            
+            
     def _param_generator(self, params):
         if isinstance(params, dict):
             return dict([(k, p.smart_str) for k, p in params.iteritems()])
@@ -344,10 +352,7 @@
             return [p.smart_str for p in params]
 
     def execute(self, query, params=None):
-        if params is None:
-            params = []
-        else:
-            params = self._format_params(params)
+        params = self._format_params(params)
         args = [(':arg%d' % i) for i in range(len(params))]
         # cx_Oracle wants no trailing ';' for SQL statements.  For PL/SQL, it
         # it does want a trailing ';' but not a trailing '/'.  However, these
@@ -356,6 +361,8 @@
         if query.endswith(';') or query.endswith('/'):
             query = query[:-1]
         query = smart_str(query, self.charset) % tuple(args)
+        #print 'query', query, [x.smart_str for x in params]
+        #print
         self._guess_input_sizes([params])
         try:
             return Database.Cursor.execute(self, query, self._param_generator(params))
@@ -388,6 +395,72 @@
                 e = IntegrityError(e.message)
             raise e
 
+
+    def callproc(self, name, params=None, keys=None):
+        """
+        Call a PL/SQL procedure with the given name.
+        The list of parameters must contain one entry for each argument that the procedure expects.
+        The result is a modified copy of the input sequence.
+        Input parameters are left untouched; 
+        output and input/output parameters are replaced with possibly new values,
+        and must be initialised as Oracle variables;
+        ex: arg1 = cursor.var(cx_Oracle.STRING), arg1.setvalue('this is an input value').
+        An optional list of corresponding keys will return a dictionnary instead of a list.
+        """
+        if isinstance(params, dict):
+            raise('callproc: "params" must be a list, not a dictionnary')
+        params  = self._format_params(params)
+        # "keys" is a list of corresponding keys to return a dictionnary of results
+        if keys:
+            if isinstance(keys, list):
+                if len(keys) != len(params):
+                    raise('callproc: the number of keys differs from the number of params')
+            else:
+                raise('callproc: "keys" must be a list, not a dictionnary')
+        results = self._guess_input_sizes([params])
+        try:
+            Database.Cursor.execute( self
+                , "begin %s(%s); end;" % (name, 
+                    string.join([":arg%d"%i for i in range(len(params))], ','))
+                , self._param_generator(params)
+            )
+        except DatabaseError, e:
+            # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400.
+            if e.message.code == 1400 and type(e) != IntegrityError:
+                e = IntegrityError(e.message)
+            raise e
+        if isinstance(keys, list):
+            return dict([(keys[i], results[i].getvalue()) for i in range(len(keys))])
+        else:
+            return tuple([result.getvalue() for result in results])
+
+
+    def callfunc(self, name, returnType, params=None):
+        """
+        Call a function with the given name.
+        The return type must be a Oracle type; ex: cx_Oracle.STRING
+        The sequence of parameters must contain one entry for each argument that the function expects. 
+        The result of the call is the return value of the function.
+        """
+        if isinstance(params, dict):
+            raise('callfunc: parameters must be in a list, not a dictionnary')
+        if 'cx_Oracle.' not in str(returnType):
+            raise('callfunc: returnType must be a Oracle type')
+        params = self._format_params([self.var(returnType)]+params)
+        results = self._guess_input_sizes([params])
+        try:
+            Database.Cursor.execute(self
+                , "begin :arg0 := %s(%s); end;" % (name, 
+                    string.join([":arg%d"%(i+1) for i in range(len(params)-1)], ','))
+                , self._param_generator(params)
+            )
+        except DatabaseError, e:
+            # cx_Oracle <= 4.4.0 wrongly raises a DatabaseError for ORA-01400.
+            if e.message.code == 1400 and type(e) != IntegrityError:
+                e = IntegrityError(e.message)
+            raise e
+        return results[0].getvalue()
+            
     def fetchone(self):
         row = Database.Cursor.fetchone(self)
         if row is None:
