Added:
trunk/configomat.py (contents, props changed)
trunk/doc/configomat.rst
Modified:
trunk/doc/Makefile
trunk/orapig.py
Log:
Added array type support to orapig, and added configomat.
configomat doc is at http://markharrison.net/orapig/configomat.html
Added: trunk/configomat.py
==============================================================================
--- (empty file)
+++ trunk/configomat.py Tue Sep 2 00:16:01 2008
@@ -0,0 +1,366 @@
+#!/usr/anim/bin/pypix
+"""
+print p4configs.depots
+
+for d in p4config.depots:
+ print p4configs.depot[d]
+ print p4configs.depot[d]['p4port']
+
+to use a generated module:
+
+ import myconfig
+ print myconfig.keys
+ for i in myconfig.keys:
+ print myconfig.data
+"""
+
+import sys
+import re
+import optparse
+import cx_Oracle
+import getpass
+
+def pr(s):
+ global pr_output
+ pr_output.write(s+'\n')
+
+#-----------------------------------------------------------------------
+# Tdat -- container for everything we know about the table
+#-----------------------------------------------------------------------
+class Tdat:
+ """table data: everything we need to know about a table"""
+ def dump(self):
+ dd=self
+ print ' table=%s'%(dd.table)
+ print 'singular=%s'%(dd.singular)
+ print ' pk=%s'%(dd.pk)
+ print ' tabcom=%s'%(dd.tabcom)
+ print ' cols=%s'%(dd.cols)
+ print ' cols2=%s'%(dd.cols2)
+ print ' colcom=%s'%(dd.colcom)
+ print ' pkdata=%s'%(dd.pkdata)
+ print ' cdata=%s'%(dd.cdata)
+
+#-----------------------------------------------------------------------
+# getdata -- populate the Tdat from the database
+#-----------------------------------------------------------------------
+
+def getdata(connstr, tblname):
+ """get the data out of the database"""
+
+ conn = cx_Oracle.connect(connstr)
+ curs = conn.cursor()
+ curs.arraysize=50
+ dd=Tdat()
+
+ #---------------------------------------
+ # 1. table name
+ #---------------------------------------
+ dd.table=tblname
+ dd.singular=tblname.rstrip('s')
+
+ #---------------------------------------
+ # 2. primary key
+ #---------------------------------------
+ curs.execute(
+ """select column_name
+ from all_cons_columns a
+ join all_constraints c on a.constraint_name = c.constraint_name
+ where c.table_name = :1 and c.constraint_type = 'P'""",
+ [dd.table.upper()])
+ dd.pk=curs.fetchone()[0].lower()
+
+ #---------------------------------------
+ # 3. column names
+ #---------------------------------------
+ curs.execute(
+ """select column_name from user_tab_columns
+ where table_name = :1 order by column_id""",
+ [dd.table.upper()])
+ dd.cols = [i[0].lower() for i in curs]
+ dd.cols2=[i for i in dd.cols]
+ dd.cols2.remove(dd.pk)
+
+ #---------------------------------------
+ # 4. table comment
+ #---------------------------------------
+ curs.execute(
+ """select comments from user_tab_comments where table_name = :1""",
+ [dd.table.upper()])
+ dd.tabcom=curs.fetchone()[0]
+
+ #---------------------------------------
+ # 5. column comments
+ #---------------------------------------
+ curs.execute("""select column_name, comments from user_col_comments
+ where table_name = :1""",
+ [dd.table.upper()])
+ dd.colcom={}
+ for i in curs:
+ dd.colcom[i[0].lower()]=i[1]
+
+ #---------------------------------------
+ # 6. all rows of data
+ #---------------------------------------
+
+ dd.cdata={}
+ dd.pkdata=[]
+ curs.execute("select %s,%s from %s order by %s"%
+ (dd.pk,','.join(dd.cols2),dd.table,dd.pk))
+ for r in curs:
+ pk=r[0]
+ dd.pkdata.append(pk)
+ cols=r[1:]
+ dd.cdata[pk]=cols
+
+ conn.close()
+ return dd
+
+#-----------------------------------------------------------------------
+# gen_python -- dump into python data structure
+#-----------------------------------------------------------------------
+def gen_python(dd):
+ """generate python data"""
+ pr('"""')
+ pr('%s.py -- baked in parameters'%(dd.table))
+ pr('autogenerated by configomat.py')
+ pr('')
+ pr(' table name : %s'%(dd.table))
+ pr(' primary key : %s'%(dd.pk))
+ pr(' description : %s'%(dd.tabcom))
+ pr('')
+ pr(' columns:')
+ for c in dd.cols:
+ pr(' %15s - %s'%(c,dd.colcom[c]))
+ pr('"""')
+ pr('')
+
+ pr("#-------------------------------------")
+ pr("# all keys")
+ pr("#-------------------------------------")
+ # TODO: print table or keys???
+ #pr('%s=['%(dd.table))
+ pr('keys=[')
+ for r in dd.pkdata:
+ pr(" '%s',"%(r))
+ pr(']')
+ pr('')
+
+ # TODO: print singular or data???
+ #pr('%s={'%(dd.singular))
+ pr('data={')
+ for pkd in dd.pkdata:
+ pr(" '%s':{"%(pkd))
+ for (cname,cdata) in zip(dd.cols2,dd.cdata[pkd]):
+ pr(" '%s':'%s',"%(cname,cdata))
+ pr(" },")
+ pr('}')
+
+#-----------------------------------------------------------------------
+# gperf helper functions
+#-----------------------------------------------------------------------
+COLS=80
+def fb1():
+ """print a flowerbox horizontal line"""
+ pr('/*%s*/'%('-'*(COLS-4)))
+
+def fb(s):
+ """flowerbox a C comment"""
+ pr('/* %-74s */'%(s))
+
+def cquote(x):
+ """enquote a string with c rules"""
+ return str(x).replace('\\','\\\\').replace('"','\\"')
+
+#-----------------------------------------------------------------------
+# gen_gperf -- dump into gperf data structure
+#-----------------------------------------------------------------------
+def gen_gperf(dd):
+ """generate gperf data"""
+ fb1()
+ fb('%s.gperf -- baked in parameters'%(dd.table))
+ fb('autogenerated by configomat.py')
+ fb('')
+ fb(' table name : %s'%(dd.table))
+ fb(' primary key : %s'%(dd.pk))
+ fb(' description : %s'%(dd.tabcom))
+ fb('')
+ fb(' columns:')
+ for c in dd.cols:
+ fb(' %15s - %s'%(c,dd.colcom[c]))
+ fb('')
+ fb('to compile:')
+ fb(' gperf -t --ignore-case --output=foo.c foo.gperf')
+ fb('')
+ fb('usage: TODO cleanup')
+ fb(' char* mytype = mimetype("jpg")')
+ fb('')
+ fb('notes:')
+ fb(' returns NULL if the thing is not found.')
+ fb1()
+
+ #---------------------------------------
+ pr('%includes')
+ pr('%%define lookup-function-name %s_lookup'%(dd.table))
+ pr('%%define hash-function-name %s_hash'%(dd.table))
+ pr('struct %s_tbl {'%(dd.table))
+ pr(' char *%s; /* PK */'%(dd.pk))
+ for c in dd.cols2:
+ pr(' char *%s;'%(c))
+ pr('}')
+ pr('%%')
+ for pk in dd.pkdata:
+ line='"%s"'%(pk)
+ for c in dd.cdata[pk]:
+ line+=',"%s"'%(cquote(c))
+ pr(line)
+ pr('%%')
+ pr('')
+
+ #---------------------------------------
+ pr('char *%s_keys[] = {'%(dd.table))
+ #pr(' "mpg-test",)
+ for pk in dd.pkdata:
+ pr(' "%s",'%(cquote(pk)))
+ pr('};')
+
+ #---------------------------------------
+ pr('')
+ fb1()
+ fb('%s -- primary interface to this module'%(dd.table))
+ fb1()
+ pr('struct %s_tbl *%s_get(char *key)'%(dd.table,dd.table))
+ pr('{')
+ pr(' int len = strlen(key);')
+ pr(' if (len == 0)')
+ pr(' return NULL;')
+ pr(' struct %s_tbl *v = %s_lookup(key, len);'%(dd.table,dd.table))
+ pr(' return v;')
+ pr('}')
+ pr('')
+ pr('#include <stdio.h>')
+
+ #---------------------------------------
+ for c in dd.cols2:
+ pr('')
+ pr('char *%s_get%s(char *key)'%(dd.table,c))
+ pr('{')
+ pr(' struct %s_tbl *v = %s_get(key);'%(dd.table,dd.table))
+ pr(' return v ? v->%s : NULL;'%(c))
+ pr('}')
+
+ #---------------------------------------
+ tt=dd.table
+ pr('')
+ pr('#ifdef TEST')
+ pr('#include <stdio.h>')
+ pr('int main()')
+ pr('{')
+ pr(' int i;')
+ pr(' char *k;')
+ pr(' struct %s_tbl *v;'%(dd.table))
+ pr(' for (i = 0; i < sizeof(%s_keys)/sizeof(*%s_keys); ++i)
{'%(tt,tt))
+ pr(' k = %s_keys[i];'%(tt))
+ pr(' printf("%s\\n", k);')
+ for c in dd.cols2:
+ pr(' printf(" %12s : %%s\\n", %s_get%s(k));'% (c,tt,c))
+ pr(' printf("\\n");')
+ pr(' }')
+ pr(' printf("nomatch case:\\n");')
+ pr(' v = %s_get("");'%(dd.table))
+ pr(' printf(" %p\\n", v);')
+ pr(' printf("match case:\\n");')
+ pr(' v = %s_get(%s_keys[0]);'%(dd.table,dd.table))
+ pr(' printf(" %p\\n", v);')
+ pr(' return 0;')
+ pr('}')
+ pr('#endif')
+
+#-----------------------------------------------------------------------
+# gen_header -- generate C/C++ header file
+#-----------------------------------------------------------------------
+def gen_header(dd,fname):
+ """generate header file"""
+
+ guard = '_configomat_'+fname.replace('.','_')
+ hh=open(fname,"w")
+ print >>hh,('/* %s -- C/C++ decls for table %s */'%(fname, dd.table))
+ print >>hh,('/* autogenerated by configomat.py */')
+ print >>hh,('')
+ print >>hh,('#ifndef %s'%(guard))
+ print >>hh,('#define %s'%(guard))
+ print >>hh,('')
+
+ #---------------------------------------
+ print >>hh,('struct %s_tbl {'%(dd.table))
+ print >>hh,(' char *%s; /* PK */'%(dd.pk))
+ for c in dd.cols2:
+ print >>hh,(' char *%s;'%(c))
+ print >>hh,('}')
+ print >>hh,('')
+
+ #---------------------------------------
+ print >>hh,('extern struct %s_tbl *%s_get(char
*key);'%(dd.table,dd.table))
+ for c in dd.cols2:
+ print >>hh,('extern char *%s_get%s(char *key);'%(dd.table,c))
+ print >>hh,('')
+ print >>hh,('#endif /* %s */'%(guard))
+
+ hh.close()
+
+#-----------------------------------------------------------------------
+# main -- you know what it does
+#-----------------------------------------------------------------------
+def main():
+ p = optparse.OptionParser(usage="usage: %prog options pkg...")
+ p.add_option("-C","--conn",action="store",type="string",dest="conn",
+ help="Database connection string")
+
p.add_option("-O","--output",action="store",type="string",dest="output",
+ help="output file name")
+ p.add_option("","--lang",action="store",type="string",dest="lang",
+ help="language binding",default='python')
+
p.add_option("-P","--pass",action="store",type="string",dest="password",
+ help="Database password")
+ p.add_option("","--dump",action="store_true",dest="dump",
+ help="dump the parsed table",default=False)
+ p.add_option("-t","--table",action="store",type="string",dest="table",
+ help="database table to digest")
+
p.add_option("-H","--header",action="store",type="string",dest="header",
+ help="generate C/C++ header file")
+ (opts,args) = p.parse_args()
+
+ # password handling, connection string reconstruction
+ oraconnre = re.compile("^([^/@]*)(/([^@]*))?(@(.*))?$")
+ user,password,host=oraconnre.match(opts.conn).groups()[0::2]
+ if opts.password:
+ password=opts.password
+ if not password:
+ password=getpass.getpass()
+ connstr="%s/%s"%(user,password)
+ if host:
+ connstr+="@%s" % host
+
+ # set output
+ global pr_output
+ if opts.output:
+ pr_output=open(opts.output,"w")
+ else:
+ pr_output=sys.stdout
+
+ dd = getdata(connstr, opts.table)
+ if opts.dump==True:
+ dd.dump()
+ elif opts.lang=='python':
+ gen_python(dd)
+ elif opts.lang=='gperf':
+ gen_gperf(dd)
+ else:
+ sys.stderr.write("unknown --lang: %s\n"%(opts.lang))
+ sys.exit(1)
+ if opts.header is not None:
+ gen_header(dd,opts.header)
+
+ sys.exit(0)
+
+if __name__=='__main__':
+ main()
Modified: trunk/doc/Makefile
==============================================================================
--- trunk/doc/Makefile (original)
+++ trunk/doc/Makefile Tue Sep 2 00:16:01 2008
@@ -1,7 +1,9 @@
all:
rst2html.py --stylesheet=orapig.css orapig.rst >orapig.html
+ rst2html.py --stylesheet=orapig.css configomat.rst >configomat.html
#rst2latex.py orapig.rst >orapig.latex
+ #rst2latex.py configomat.rst >configomat.latex
clean:
- rm -f orapig.html
- #rm -f orapig.latex
+ rm -f orapig.html configomat.html
+ rm -f orapig.latex configomat.latex
Added: trunk/doc/configomat.rst
==============================================================================
--- (empty file)
+++ trunk/doc/configomat.rst Tue Sep 2 00:16:01 2008
@@ -0,0 +1,316 @@
+.. image:: orapig.png
+
+OraPIG Configomat -- Configuration Generator
+==============================================
+
+OraPIG Configomat takes configuration data stored in a
+database file and converts it into Python or C/C++
+data structures. This data can be included in application
+programs to allow access to the global configuration
+without having to connect to the database.
+This provides the best of both worlds:
+
+- Configuration data can be stored in the database
+
+ - centralized location makes it easy to find
+ - it's easy to edit using your database tools
+ - language-independent configuration format
+ - configuration information accessible within the database
+
+- The data is also easily accessible by programs which do not
+ access the database. This has several potential advantages:
+
+ - speed. a small program might spend more time connecting to the
+ database than actually doing its work.
+
+ - independence. the program does not need to be able to connect
+ to the database in order to run nor include a database interface
+ if it is not needed for any other purpose.
+
+ - bootstrapability. a common configuration parameter is the
+ database connection string, which you want to be able to
+ get without having to connect to the database.
+
+diagram::
+
+ . database table --> python module --> python applications
+ . --> C/C++ module --> C/C++ applications
+
+Specifying the Configuration Table
+==================================
+
+Just about any table can be converted by the configomat.
+
+- The table must have a primary consisting of a single column.
+- Table and column comments will be carried forward into the generated
+ code. Document your work!
+
+Let's look at an example table called "netservices". It's a
+configuration table for some hypothetical network service
+and has these columns::
+
+ name : service name. this is the primary key.
+ host : hostname.
+ port : ip port.
+ admin : email of administrative contact.
+ description : description of the service.
+
+and four rows of data::
+
+ table: netservices
+
+ name | host | port | admin | description
+ ----------+--------+------+-------+-----------------------------
+ xmap | ajax | 2305 | steve | shared xmap validator
+ qman | ajax | 1999 | bob | queue manager
+ thumbs01 | xerxes | 1022 | bob | primary thumbnail server
+ thumbs02 | ajax | 1022 | bob | secondary thumbnail server
+
+Generating the Interfaces
+=========================
+
+The simplest commands to generate the Python and C/C++ interfaces are::
+
+ configomat --conn=orapig/orapig --table=netservices --lang=python
+ configomat --conn=orapig/orapig --table=netservices --lang=gperf
+
+The only required parameters are the database connection string
+and the table name. Full command line detail appear below.
+
+Contents of the Python Module
+=============================
+
+There are three things in the generated python module:
+
+- generated documentation on the module, extracted from the oracle
+ table and column comments.
+
+- **keys**, a list of all the primary key data in the table
+
+- **data**, a dictionary of dictionaries representing the data in the
+ table. Access the data as data[keyname][columname].
+
+All data from the table is represented as strings, regardless of
+the column type in the table.
+
+Using the Python Interface
+==========================
+
+To use the file, just import it in the usual manner::
+
+ >>> import netservices
+
+**keys** is a list of all the keys. This is the data in the table's
+primary key::
+
+ >>> netservices.keys
+ ['qman', 'thumbs01', 'thumbs02', 'xmap']
+
+**data** is a dictonary indexed by the elements in keys::
+
+ >>> from pprint import pprint
+ >>> pprint(netservices.data)
+ {'qman': {'admin': 'bob',
+ 'description': 'queue manager',
+ 'host': 'ajax',
+ 'port': '1999'},
+ 'thumbs01': {'admin': 'bob',
+ 'description': 'primary thumbnail server',
+ 'host': 'xerxes',
+ 'port': '1022'},
+ 'thumbs02': {'admin': 'bob',
+ 'description': 'secondary thumbnail server',
+ 'host': 'ajax',
+ 'port': '1022'},
+ 'xmap': {'admin': 'steve',
+ 'description': 'shared xmap validator',
+ 'host': 'ajax',
+ 'port': '2305'}}
+
+You can access a particular configuration record by subscripting with
+one the key names::
+
+ >>> pprint(netservices.data['xmap'])
+ {'admin': 'steve',
+ 'description': 'shared xmap validator',
+ 'host': 'ajax',
+ 'port': '2305'}
+
+and access a particular field by subscripting again with the column name::
+
+ >>> netservices.data['xmap']['port']
+ '2305'
+
+Contents of the C/C++ Module
+============================
+
+- A struct called *table*\ _tbl. It has a char* member for every
+ column in the table.
+
+- A statically initalized array of strings called *tablename*\ _keys which
+ contains the key data.
+
+- A perfect hash function *tablename*\ _lookup() that maps the keys to
+ struct *table*\ _tbl pointers.
+
+- A set of generated functions *tablename*\ _get\ *colname* () that will
+ return that column's data for the specified key.
+
+- A test main() to verify correct operation and serve as an example
+ of how to access the data.
+
+All data from the table is represented as strings, regardless of
+the column type in the table.
+
+The configomat uses gnu gperf program, details below.
+
+Using the C/C++ Interface
+=========================
+
+Include the generated header file::
+
+ #include "netservices.h"
+
+netservices_keys is an array of all the keys::
+
+ int i;
+ int nkeys = sizeof(netservices_keys[0]) / sizeof(netservices_keys);
+ for (i = 0; i < nkeys; ++i)
+ printf("%d. %s\n", i, netservices_keys[i]);
+
+output::
+
+ 1. qman
+ 2. thumbs01
+ 3. thumbs02
+ 4. xmap
+
+The functions netservices_gethost() and netservices_getport() will
+return the host and port parameters for the given key. Similar
+functions exist for each of the columns. They take one parameter
+which is the key, and return either a string or NULL if the key
+does not exist::
+
+ char *host = netservices_gethost("qman");
+ char *port = netservices_getport("arglebarg"); /*does not exist*/
+ if (host != NULL)
+ printf("host: %s\n", host);
+ printf("port: %p\n", port);
+
+output::
+
+ host: ajax
+ port: (nil)
+
+To compile the test program, pass -DTEST to the compiler.
+
+Running Configomat
+==================
+
+The usage of OraPIG Configomat is::
+
+ configomat [options]
+
+The command line options are::
+
+--help, -h show this help message and exit
+--conn=CONN, -C CONN database connection string (required)
+--table=TABLENAME generate bindings for this table (required)
+--lang=LANG language binding (currently gperf or python)
+--output=OUTPUT, -O OUTPUT output file, defaults to stdout
+--pass=PASS, -P PASS database password (not implemented)
+--header=file, -H file generate C/C++ header file
+
+--dump dump parsed data and exit (for debugging)
+
+Example Makefile
+================
+
+Here are some example Makefile entries::
+
+ config: netservices.py netservices.o
+
+ netservices.py:
+ configomat.py --conn=orapig/orapig --table=netservices -O
netservices.py
+
+ netservices.o:
+ configomat.py --conn=orapig/orapig --table=netservices --lang=gperf
-O netservices.gperf -H netservices.h
+ gperf -t --ignore-case --output=netservices.c netservices.gperf
+ $(CC) $(CFLAGS) -c netservices.c
+
+ test:
+ configomat.py --conn=orapig/orapig --table=netservices --lang=gperf
-O netservices.gperf -H netservices.h
+ gperf -t --ignore-case --output=netservices.c netservices.gperf
+ $(CC) $(CFLAGS) -DTEST netservices.c -o netservices-test
+ ./netservices-test
+
+ clean:
+ rm -f netservices.py netservices.pyc
+ rm -f netservices.gperf netservices.h netservices.c netservices.o
netservices-test
+
+Gperf Note
+==========
+
+The C/C++ bindingings use the Gnu perfect hash function generator gperf.
+Details, docs and downloads available here:
+
+- http://www.gnu.org/software/gperf
+
+Administrivia
+=============
+
+Configomat is part of OraPIG, the Oracle Python Interface Generator.
+
+:Version:
+ 1.0
+:Download:
+ http://code.google.com/p/orapig
+:Documentation:
+ http://markharrison.net/orapig
+:Author:
+ Mark Harrison (m...@pixar.com)
+:License:
+ Copyright 2008 Pixar, available under a BSD license.
+:Support:
+ Send questions to the cx_Oracle mailing list. There's a
+ link at: http://python.net/crew/atuining/cx_Oracle
+:Dependencies:
+ cx_Oracle, the Python interface for Oracle:
+ http://python.net/crew/atuining/cx_Oracle
+ The C/C++ language binding uses gperf, the gnu perfect hash generator:
+ http://www.gnu.org/software/gperf
+:Installation:
+ Configomat is a standalone script. Edit the #! line and put in
+ an appropriate path.
+:OraPIG Motto:
+ "There's a Snake in my Oracle!"
+
+This has been tested with Python 2.3 and 2.4, and Oracle 10.2.1.
+
+Thanks
+======
+
+- to Anthony Tuininga (Mr. cx_Oracle) for doing such a fine job on
cx_Oracle.
+- to the smart and ever-helpful denizens of cx-oracle-users
+ and comp.databases.oracle.misc.
+- to colleague Mike Sundy who inspired the need for this program.
+- Douglas C. Schmidt and the rest of the gperf contributors.
+- Joy Sikorski, who taught me how to draw a cute pig. Visit her
+ site and unlock your creativity! http://www.joysikorski.com
+
+FAQs
+====
+
+1. question
+
+ answer
+
+TODO
+====
+
+- allow overriding of column names to avoid reserved words
+ in the target language.
+
+- Generate reST documentation for the package.
+
+- look at the gperf C++ flag.
Modified: trunk/orapig.py
==============================================================================
--- trunk/orapig.py (original)
+++ trunk/orapig.py Tue Sep 2 00:16:01 2008
@@ -92,37 +92,41 @@
def getarrayparms(self,owner,objname,package_name):
"""get the list of parameters of array type"""
rv={}
- self.curs.execute("""
- select a.argument_name, b.data_type
- from all_arguments a, all_arguments b
- where a.object_name=:objname and b.object_name=:objname
- and a.position=b.position
- and a.data_type='PL/SQL TABLE'
- and b.data_type!='PL/SQL TABLE'
- and a.owner=:owner
- and b.owner=:owner
- and a.package_name=:package_name
- and
b.package_name=:package_name""",objname=objname,package_name=package_name,owner=owner)
+ self.curs.execute("""select argument_name, data_type from
all_arguments
+ where package_name=:package_name
+ and owner=:owner
+ and object_name=:objname
+ order by
sequence""",objname=objname,package_name=package_name,owner=owner)
+
+ current_arg = ""
+ pick_next = False
for row in self.curs:
- rv[row[0].lower()]=row[1]
+ if row[1] == "PL/SQL TABLE":
+ current_arg = row[0]
+ pick_next = True
+ if row[0] is None and pick_next:
+ rv[current_arg.lower()]=row[1]
+ pick_next = False
return rv
def getarrayindices(self,owner,objname,package_name):
"""get the list of parameters of array type"""
rv={}
- self.curs.execute("""
- select a.position, b.data_type
- from all_arguments a, all_arguments b
- where a.object_name=:objname and b.object_name=:objname
- and a.position=b.position
- and a.data_type='PL/SQL TABLE'
- and b.data_type!='PL/SQL TABLE'
- and a.owner=:owner
- and b.owner=:owner
- and a.package_name=:package_name
- and
b.package_name=:package_name""",objname=objname.upper(),package_name=package_name,owner=owner)
+ self.curs.execute("""select argument_name, data_type, position
from all_arguments
+ where package_name=:package_name
+ and owner=:owner
+ and object_name=:objname
+ order by
sequence""",objname=objname,package_name=package_name,owner=owner)
+
+ position = 0
+ pick_next = False
for row in self.curs:
- rv[row[0]] = row[1]
+ if row[1] == "PL/SQL TABLE":
+ position = row[2]
+ pick_next = True
+ if row[0] is None and pick_next:
+ rv[position]=row[1]
+ pick_next = False
return rv
def hasoutputparms(self,owner,objname,package_name):