On Mar 22, 2010, at 3:48 PM, Kevin Wormington wrote: > That's getting closer. Now if I could just figure out how to get it to use > the parameters that I'm actually passing in engine.execute. It appears > ibm_db_sa botches the sql (to "CALL BILLING.subscriber_balance()") if use the > commented out cursor.callproc(statement, parameters).
well you're right there so figure out what is different about "parameters" versus your tuple there. a good candidate is, "parameters" is probably a list, not a tuple. > > > from sqlalchemy.ext.compiler import compiles > from sqlalchemy.interfaces import ConnectionProxy > from sqlalchemy.sql.expression import ClauseElement > from sqlalchemy import create_engine > from sqlalchemy import literal > > class procedure(ClauseElement): > supports_execution = True > _autocommit = False > _execution_options = {} > > def __init__(self, name, *args): > self.name = name > self.args = args > > @compiles(procedure) > def compile_procedure(element, compiler, **kw): > return "%s" % (element.name) > > class ProcedureProxy(ConnectionProxy): > > def cursor_execute(self, execute, cursor, statement, parameters, context, > executemany): > """Intercept low-level cursor execute() events.""" > if context and isinstance(context.compiled.statement, procedure): > engine.logger.info("calling stored procedure: %s", statement) > if hasattr(cursor, 'callproc'): > subid = 1000 > balance = 0 > result = cursor.callproc(statement, (subid,balance)) > # result = cursor.callproc(statement, parameters) > context.callproc_result = result > return result > else: > return cursor.execute(statement, parameters) > else: > return execute(cursor, statement, parameters, context) > > > engine = > create_engine('ibm_db_sa://db2inst1:somegreatpassw...@x.x.x.x:xxx/BILLING', > proxy=ProcedureProxy(), echo=True) > > subid=1000 > balance=0 > res = engine.execute(procedure("BILLING.subscriber_balance", subid, balance)) > print res.context.callproc_result > > > Michael Bayer wrote: >> On Mar 21, 2010, at 9:33 PM, Kevin Wormington wrote: >>> As a test I altered the compile_procedure and the call to cursor.callproc >>> and do get the values back from the stored procedure from the "print res" >>> before the return. I get exceptions about the cursor being close when I >>> try to access the ResultProxy object returned though. I think it's because >>> the callproc returns just the results and not a cursor/set. >> ah well, the result proxy closes the cursor when there's no >> cursor.description, i.e. that theres no results to fetch. >> The workarounds are getting uglier here, but you can do this: >> result = cursor.callproc(<stuff>) >> context.callproc_result = result >> then on the outside when you get your result, context is there: >> result = conn.execute(myprocedure...) >> print result.context.callproc_result >>> def compile_procedure(element, compiler, **kw): >>> return "%s" % (element.name) >>> >>> class ProcedureProxy(ConnectionProxy): >>> >>> def cursor_execute(self, execute, cursor, statement, parameters, context, >>> executemany): >>> """Intercept low-level cursor execute() events.""" >>> if context and isinstance(context.compiled.statement, procedure): >>> engine.logger.info("calling stored procedure: %s", statement) >>> if hasattr(cursor, 'callproc'): >>> subid = 1000 >>> balance = 0 >>> res = cursor.callproc(statement, (subid,balance)) >>> print res >>> return res >>> # return cursor.callproc(statement, parameters) >>> else: >>> return cursor.execute(statement, parameters) >>> else: >>> return execute(cursor, statement, parameters, context) >>> >>> >>> >>> Kevin Wormington wrote: >>>> I was able to get it working from just the ibm_db_dbi interface - the >>>> actual call has to be: >>>> cursor.callproc('BILLING.subscriber_balance',(subid,balance)) >>>> these both cause the sql errors: >>>> cursor.callproc('BILLING.subscriber_balance',(1000,0)) >>>> cursor.callproc('BILLING.subscriber_balance',(1000,balance)) >>>> The compiler.process(literal you suggested results in "CALL >>>> BILLING.subscriber_balance(?,?)(?,?)". I tried changing it to just >>>> return element.name and get just "CALL BILLING.subscriber_balance( )" >>>> sent to the DB. How can I get the return cursor.callproc(statement, >>>> parameters) to actually have the (subid,balance) in the parameters? >>>> Kevin >>>> Michael Bayer wrote: >>>>> On Mar 21, 2010, at 8:07 PM, Kevin Wormington wrote: >>>>> >>>>>> I just modified the compile to return just the procedure name and the >>>>>> cursor.callproc to send the statement and the two parameters as a tuple >>>>>> and the DB2 receives the correct SQL: >>>>>> >>>>>> CALL BILLING.subscriber_balance(?,?) >>>>>> >>>>>> But I get the following back from ibm_db_dbi: >>>>>> >>>>>> return cursor.callproc(statement, (1000,0)) >>>>>> File >>>>>> "/usr/local/lib/python2.6/dist-packages/ibm_db-1.0-py2.6-linux-i686.egg/ibm_db_dbi.py", >>>>>> line 973, in callproc >>>>>> result = self._callproc_helper(procname, parameters) >>>>>> File >>>>>> "/usr/local/lib/python2.6/dist-packages/ibm_db-1.0-py2.6-linux-i686.egg/ibm_db_dbi.py", >>>>>> line 951, in _callproc_helper >>>>>> raise _get_exception(inst) >>>>>> ibm_db_dbi.DatabaseError: ibm_db_dbi::DatabaseError: Describe Param >>>>>> Failed: [IBM][CLI Driver] CLI0150E Driver not capable. SQLSTATE=HYC00 >>>>>> SQLCODE=-99999 >>>>>> >>>>>> Looks like it doesn't like my values perhaps not being actual variables >>>>>> to bind. >>>>> heres how to get the binds to come through, in case thats the problem: >>>>> >>>>> from sqlalchemy import literal >>>>> >>>>> @compiles(procedure) >>>>> def compile_procedure(element, compiler, **kw): >>>>> return "%s(%s)" % (element.name, >>>>> ",".join(compiler.process(literal(expr)) for expr in element.args)) >>>>> >>>>> "literal" is basically the same as "bindparam". when you send it to the >>>>> compiler the value becomes part of the compiled's params, and the >>>>> generated string is "?" or whatever paramstyle is. >>>>> >>>>> usually in SQLA core we do the conversion to bindparam in the constructor >>>>> of the clause construct. >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>>> Kevin Wormington wrote: >>>>>>> 2010-03-21 18:40:49,656 INFO sqlalchemy.engine.base.Engine.0x...cb0c >>>>>>> calling stored procedure: BILLING.subscriber_balance(1000,?) >>>>>>> INFO:sqlalchemy.engine.base.Engine.0x...cb0c:calling stored procedure: >>>>>>> BILLING.subscriber_balance(1000,?) >>>>>>> It looks correct there but all that is getting logged is the statement >>>>>>> which has had all the arguments combined together by the compile. I >>>>>>> think that the 1000,? should remain as parameters to cursor.callproc >>>>>>> instead of everything going into statement. ibm_db_dbi.callproc is >>>>>>> probably adding the second set of () since it doesn't think there are >>>>>>> any arguments. Just not sure how to fix it. >>>>>>> Michael Bayer wrote: >>>>>>>> On Mar 21, 2010, at 7:34 PM, Kevin Wormington wrote: >>>>>>>> >>>>>>>>> From DB2 logs it appears that the following is what's getting sent to >>>>>>>>> DB2 and resulting in the error: >>>>>>>>> >>>>>>>>> CALL BILLING.subscriber_balance(1000,?)( ) >>>>>>>>> >>>>>>>>> >>>>>>>>> This is using: >>>>>>>>> >>>>>>>>> print engine.execute(procedure("BILLING.subscriber_balance", subid, >>>>>>>>> "?")) >>>>>>>>> >>>>>>>>> Any idea how I can get rid of the extra set of ()'s? >>>>>>>> what does the logging at the Python level say ? whatever goes into >>>>>>>> callproc() is the most you can control. that recipe is allowing you >>>>>>>> to control the string output exactly. >>>>>>>> >>>>>>>> >>>>>>>>> Thanks, >>>>>>>>> >>>>>>>>> Kevin >>>>>>>>> >>>>>>>>> Michael Bayer wrote: >>>>>>>>>> On Mar 21, 2010, at 12:50 PM, Kevin Wormington wrote: >>>>>>>>>>> Hi, >>>>>>>>>>> >>>>>>>>>>> I am using SQLAlchemy 0.5.8 with the ibm_db_sa (DB2) adapter and I >>>>>>>>>>> am wanting to add simple session.callproc support so that I can get >>>>>>>>>>> results from stored procedures that don't use a select or table >>>>>>>>>>> format. I haven't done any development on SA before but at first >>>>>>>>>>> glance it appears that I would just need to add the method in the >>>>>>>>>>> ibm dialect class and into engines/base.py to raise an exception >>>>>>>>>>> for other dialects that don't support it. Can it really be that >>>>>>>>>>> easy? Also, if I implement this will it be a possibility to get it >>>>>>>>>>> integrated into future releases? >>>>>>>>>> So here's the things i ask/state about this: >>>>>>>>>> 1. IBM's dialect works with 0.5 ? it says its only for 0.4. >>>>>>>>>> 2. the callproc requirement here assumes that there's absolutely no >>>>>>>>>> way to make this work using plain execute(). For example with >>>>>>>>>> cx_oracle you can use outparams with plain execute(). I don't know >>>>>>>>>> the details here for DB2, but if callproc() really provides >>>>>>>>>> functionality that is impossible otherwise, then yes we need to >>>>>>>>>> examine ways to call it. >>>>>>>>>> 3. the way this feature would work would be: >>>>>>>>>> from sqlalchemy import procedure >>>>>>>>>> result = engine.execute(procedure.name_of_my_procedure(arg1, arg2, >>>>>>>>>> ...)) >>>>>>>>>> or with plain text or such, you can do this: >>>>>>>>>> result = >>>>>>>>>> engine.connect().execution_options(callproc=True).execute("my >>>>>>>>>> procedure...") >>>>>>>>>> 4. execution_options is only availble in 0.6. Similarly, any "built >>>>>>>>>> in" version of this feature would be for 0.6. >>>>>>>>>> 5. I'd like to have IBM's dialect on 0.6. Two ways to do this >>>>>>>>>> would either be to write a new dialect from scratch using their >>>>>>>>>> DBAPI, or to just port their 0.4/0.5 dialect to 0.6. I'd prefer >>>>>>>>>> the latter, but I don't know the licensing details. If IBM were to >>>>>>>>>> give me the "green light" I'd just wrap their dialect into >>>>>>>>>> SQLAlchemy core where all the other ones are. >>>>>>>>>> 6. in 0.5 or any, the functionality of #3 can be achieved using >>>>>>>>>> public API points, here's a demo: >>>>>>>>>> from sqlalchemy.ext.compiler import compiles >>>>>>>>>> from sqlalchemy.interfaces import ConnectionProxy >>>>>>>>>> from sqlalchemy.sql.expression import ClauseElement >>>>>>>>>> from sqlalchemy import create_engine >>>>>>>>>> class procedure(ClauseElement): >>>>>>>>>> supports_execution = True >>>>>>>>>> _autocommit = False >>>>>>>>>> _execution_options = {} >>>>>>>>>> def __init__(self, name, *args): >>>>>>>>>> self.name = name >>>>>>>>>> self.args = args >>>>>>>>>> @compiles(procedure) >>>>>>>>>> def compile_procedure(element, compiler, **kw): >>>>>>>>>> return "%s(%s)" % (element.name, ",".join(str(expr) for expr in >>>>>>>>>> element.args)) >>>>>>>>>> class ProcedureProxy(ConnectionProxy): >>>>>>>>>> def cursor_execute(self, execute, cursor, statement, parameters, >>>>>>>>>> context, executemany): >>>>>>>>>> """Intercept low-level cursor execute() events.""" >>>>>>>>>> if context and isinstance(context.compiled.statement, >>>>>>>>>> procedure): >>>>>>>>>> engine.logger.info("calling stored procedure: %s", statement) >>>>>>>>>> if hasattr(cursor, 'callproc'): >>>>>>>>>> return cursor.callproc(statement, parameters) >>>>>>>>>> else: >>>>>>>>>> return cursor.execute(statement, parameters) >>>>>>>>>> else: >>>>>>>>>> return execute(cursor, statement, parameters, context) >>>>>>>>>> engine = create_engine('sqlite://', proxy=ProcedureProxy(), >>>>>>>>>> echo=True) >>>>>>>>>> print engine.execute(procedure("pragma table_info", "foo")) >>>>>>>>>> >>>>>>>>>>> Kevin >>>>>>>>>>> >>>>>>>>>>> -- >>>>>>>>>>> You received this message because you are subscribed to the Google >>>>>>>>>>> Groups "sqlalchemy" group. >>>>>>>>>>> To post to this group, send email to sqlalch...@googlegroups.com. >>>>>>>>>>> To unsubscribe from this group, send email to >>>>>>>>>>> sqlalchemy+unsubscr...@googlegroups.com. >>>>>>>>>>> For more options, visit this group at >>>>>>>>>>> http://groups.google.com/group/sqlalchemy?hl=en. >>>>>>>>>>> >>>>>>>>> -- >>>>>>>>> You received this message because you are subscribed to the Google >>>>>>>>> Groups "sqlalchemy" group. >>>>>>>>> To post to this group, send email to sqlalch...@googlegroups.com. >>>>>>>>> To unsubscribe from this group, send email to >>>>>>>>> sqlalchemy+unsubscr...@googlegroups.com. >>>>>>>>> For more options, visit this group at >>>>>>>>> http://groups.google.com/group/sqlalchemy?hl=en. >>>>>>>>> >>>>>> -- >>>>>> You received this message because you are subscribed to the Google >>>>>> Groups "sqlalchemy" group. >>>>>> To post to this group, send email to sqlalch...@googlegroups.com. >>>>>> To unsubscribe from this group, send email to >>>>>> sqlalchemy+unsubscr...@googlegroups.com. >>>>>> For more options, visit this group at >>>>>> http://groups.google.com/group/sqlalchemy?hl=en. >>>>>> >>> -- >>> You received this message because you are subscribed to the Google Groups >>> "sqlalchemy" group. >>> To post to this group, send email to sqlalch...@googlegroups.com. >>> To unsubscribe from this group, send email to >>> sqlalchemy+unsubscr...@googlegroups.com. >>> For more options, visit this group at >>> http://groups.google.com/group/sqlalchemy?hl=en. >>> > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To post to this group, send email to sqlalch...@googlegroups.com. > To unsubscribe from this group, send email to > sqlalchemy+unsubscr...@googlegroups.com. > For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. > -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.