Hi everyone.

 I'm currently trying to migrate a legacy oracle database to web2py and
on the past weeks I stumbled upon several issues related to how the database
was originally designed and how web2py expects to interact with it. 

Among them:
* I need support for ref cursors
* I need to write some complex sql queries that rely on oracle's
  advanced functions
* A "bug" (??) on the web2py oracle's implementation (see
  http://tinyurl.com/kjs2pk)

I wanted to be able to interact freely between DAL objects and ad hocs
queries using web2py's ORM syntax. I came up with a subclassed version of SQLDB 
that
so far helped me with this and I wanted to share it with everyone hoping 
somebody
might find it useful.  This might be redundant, soon-to-be-or-already-obsolete 
(I have
high hopes on new DALs although I couldn't try it yet)or just a showcase of bad 
programming 
but since I already spent a few hours with it, I thought I can share :)

Put it simple, what I did was to create a decorator that, applied to a
method that returns a list of tuples, will convert the result to a SQLRows 
object mapped to
a "virtual" table.
This "virtual" table (one for manual queries, one for ref cursors) will
hold the results until a new ad-hoc query is executed. Of course you cannot use 
them to
update, delete or insert any records in the db, but if you have a special query 
and you
don't want to loose DAL's syntax when iteracting with it, this might help you. 
People using
a database other than oracle can make it work with minimal patching.
Following, a full example of a refcursor (in case you don't know what it
is), an ad-hoc query example. Finally, the proper code to make it work.


--- models/db.py

db.define_table("Food", db.Field("name", type='string', unique=True),
                        db.Field("fav_food", type='string'))
db.Food.insert(name='Mariano', fav_food='spaghettini carbonara')
db.Food.insert(name='Gregorio', fav_food='hot dogs')
db.Food.insert(name='Justina', fav_food='hot dogs')
db.commit()
 

--- store procedure returning a ref cursor:

create procedure test_food (food_name in food.fav_food%type,
                            rc_out out sys_refcursor)
is
begin
 open rc_out for
   select * from food where fav_food=food_name;

end test_food;
/

-- in action:
KAL> variable rc refcursor
KAL> begin test_food('hot dogs', :rc); end;
  2  /

PL/SQL procedure successfully completed.

KAL> print rc
         2 Gregorio hot dogs

         3 Justina hot dogs

-- accesing it from web2py itself (ipython) and getting SQLRows object:
In [8]: rows = db.executerc("test_food", ['hot dogs'])

In [9]: for row in rows: print(row.NAME, row.FAV_FOOD)
   ....: 
('Gregorio', 'hot dogs')
('Justina', 'hot dogs')

-- executing ad hoc queries with executesql and getting SQLRows object:
In [10]: rows = db.executesql("Select name, fav_food from food where
name like 'Mar%'")

In [11]: for row in rows: print row
   ...: 
<SQLStorage {'FAV_FOOD': 'spaghettini carbonara', 'NAME': 'Mariano'}>

In [12]: type(rows)
Out[12]: <class 'gluon.sql.SQLRows'>

-- my modified db.py
from gluon.sql import SQLDB, SQLRows

# not sure why I imported these but here they are
from cx_Oracle import BFILE, BINARY, BLOB, CLOB, DATETIME, FIXED_CHAR,
LOB 
from cx_Oracle import LONG_BINARY, LONG_STRING, NATIVE_FLOAT, NCLOB,
NUMBER
from cx_Oracle import STRING, TIMESTAMP

# this is the inverse version of SQL_DIALECTS in gluon/sql.py
# we're mapping oracle's datatypes to web2py's
oracle_dialect = dict(
    NUMBER='integer',
    STRING='string',
    DATETIME='datetime',
    BLOB='blob',
    CLOB='text',
    FIXED_CHAR='boolean',
    NATIVE_FLOAT='double',
    TIMESTAMP='datetime')

class mySQLDB(SQLDB):

    def __init__(self, uri='sqlite://dummy.db', pool_size=0, pools=0):
        super(mySQLDB, self).__init__(uri, pool_size, pools)

    def buildSQLRow(fnc):
        """Decorator to return a SQLRows object.
        It will receive the result of the query, transform the db native
        datatypes into ones understood by DAL and return the result"""

        def wrapper(self, *args):
            rt1, rt2, rt3 = fnc(self, *args)
            pargs = []
            try:
                self.pop(rt3)
                self['tables'].remove(rt3)
            except KeyError:
                pass
            for ev in rt2:
                pargs.append(SQLField(ev[0],
                                  oracle_dialect[ev[1].__name__]))
            self.define_table(rt3, *tuple(pargs), **dict(migrate=False))
            return SQLRows(self, rt1, *map(lambda x: "%s.%s" % (rt3,
x[0]),
                                     rt2))
        return wrapper

    @buildSQLRow
    def executesql(self, query, *args):
        """Execute a query and return the result. If you have two
columns with the 
        same name, make sure you create different aliases for them"""
        self['_lastsql'] = query
        self._cursor.execute(query, args)
        return self._cursor.fetchall(), self._cursor.description,
'manquery'

    @buildSQLRow
    def executerc(self, st_name, parameters):
        """Execute a procedure with a ref cursor as result.
        In the procedure, the ref cursor output parameter must be the
last one
        parameter and the only one out value
        Parameters:
            st_name: the name of the [package.]procedure to execute
            parameters: a list of parameters to pass to the procedure.
You
            don't need to pass a ref cursor variable as the method will
append
            it by default"""
        rc = self._connection.cursor()
        parameters.append(rc)
        self._cursor.callproc(st_name, parameters)
        return rc.fetchall(), rc.description, 'refcursor'

db = mySQLDB('oracle://kal/aldos...@myora')

# web2py models stuff...


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"web2py Web Framework" group.
To post to this group, send email to web2py@googlegroups.com
To unsubscribe from this group, send email to 
web2py+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/web2py?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to