changeset 387:e3dd9e4467d1

first-pass metadata collection working
author catherine@DellZilla
date Tue, 06 Oct 2009 15:09:46 -0400
parents 846a59c10cf4 (diff) 0f019dfb795f (current diff)
children 841573c48676
files sqlpython/mysqlpy.py sqlpython/schemagroup.py sqlpython/sqlpyPlus.py sqlpython/sqlpython.py
diffstat 6 files changed, 110 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Thu Aug 13 14:33:55 2009 -0400
+++ b/.hgtags	Tue Oct 06 15:09:46 2009 -0400
@@ -15,3 +15,4 @@
 09bb5789e72de737998e31027873ae1422dd37a0 nogerald branch; 1.6.5
 f65f74c948bca2d22a6aab5964a42076ea8e70f2 1.6.6
 477f0bf652b2f754dd72671e04460f7f2743eae4 1.6.7
+9fc0da48b2c41576c7ab40a3260988ce1d7919da 1.6.8
--- a/setup.py	Thu Aug 13 14:33:55 2009 -0400
+++ b/setup.py	Tue Oct 06 15:09:46 2009 -0400
@@ -9,7 +9,7 @@
 Operating System :: OS Independent""".splitlines()
 
 setup(name="sqlpython",
-      version="1.6.7",
+      version="1.6.8",
       description="Command-line interface to Oracle",
       long_description="Customizable alternative to Oracle's SQL*PLUS command-line interface",
       author="Luca Canali",
--- a/sqlpython/mysqlpy.py	Thu Aug 13 14:33:55 2009 -0400
+++ b/sqlpython/mysqlpy.py	Tue Oct 06 15:09:46 2009 -0400
@@ -1,5 +1,5 @@
 #!/usr/bin/python
-# MySqlPy V1.6.7
+# MySqlPy V1.6.8
 # Author: Luca.Canali@cern.ch
 # 
 #
@@ -14,9 +14,9 @@
 
 class mysqlpy(sqlpyPlus):
     '''
-MySqlPy V1.6.7 - 'sqlplus in python'
+MySqlPy V1.6.8 - 'sqlplus in python'
 Author: Luca.Canali@cern.ch
-Rev: 1.6.7, 07-Jul-09
+Rev: 1.6.8, 02-Sep-09
 
 Companion of SqlPython, a python module that reproduces Oracle's command line within python
 and sqlpyPlus. Major contributions by Catherine Devlin, http://catherinedevlin.blogspot.com
@@ -211,4 +211,7 @@
         sys.argv = [sys.argv[0]]  # the --test argument upsets unittest.main()
         unittest.main()
     else:
+        #import cProfile, pstats
+        #cProfile.run('run()', 'stats.txt')
         run()
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sqlpython/schemagroup.py	Tue Oct 06 15:09:46 2009 -0400
@@ -0,0 +1,61 @@
+import gerald, re, datetime, threading
+
+def get_schemagroup(rdbms, connstr, connection, user):
+    gerald_connstring = connstr.split('/?mode=')[0].replace('//','/')
+    if rdbms == 'oracle':
+        childtype = OracleSchemaGroup            
+    grp = childtype(gerald_connstring, connection, user)
+    return grp
+
+def gerald_connection_string(sqlalchemy_connection_string):
+    return sqlalchemy_connection_string.split('/?mode=')[0].replace('//','/')
+    
+class RefreshThread(threading.Thread):
+    def __init__(self, schemagroup, owner):
+        threading.Thread.__init__(self)
+        self.schemagroup = schemagroup
+        self.owner = owner
+        self.schema = self.schemagroup.schemas.get(self.owner)
+    def run(self):
+        if (not self.schema) or (self.schema.is_stale()):
+            self.schema = self.schemagroup.childtype(self.owner, self.schemagroup)
+        else:
+            self.schema.refreshed = self.schema.time()
+        self.schemagroup.schemas[self.owner] = self.schema
+            
+class RefreshGroupThread(threading.Thread):
+    def __init__(self, schemas):
+        threading.Thread.__init__(self)
+        self.schemas = schemas
+        self.daemon = True
+    def run(self):
+        self.schemas.refresh()                    
+        
+class SchemaDict(dict):
+    schema_types = {'oracle': gerald.OracleSchema}
+    def __init__(self, dct, rdbms, user, connection, connection_string):
+        dict.__init__(self, dct)
+        self.child_type = self.schema_types[rdbms]
+        self.user = user
+        self.connection = connection
+        self.gerald_connection_string = gerald_connection_string(connection_string)
+        self.refresh_thread = RefreshGroupThread(self)
+        self.complete = False
+        # do we need a second thread for a second run?
+    def refresh(self):
+        curs = self.connection.cursor()
+        curs.execute('SELECT sysdate FROM dual')
+        current_database_time = curs.fetchone()[0]
+        curs.execute('''SELECT   owner, MAX(last_ddl_time)
+                        FROM     all_objects
+                        GROUP BY owner
+                        -- sort :username to top
+                        ORDER BY REPLACE(owner, :username, 'A'), owner''',
+                     {'username': self.user.upper()})
+        for (owner, last_ddl_time) in curs.fetchall():
+            if (owner not in self) or (self[owner].refreshed < last_ddl_time):
+                self[owner] = self.child_type(owner, self.gerald_connection_string)
+                self[owner].refreshed = current_database_time
+                # what if a user's last object is deleted?
+        self.complete = True
+        
\ No newline at end of file
--- a/sqlpython/sqlpyPlus.py	Thu Aug 13 14:33:55 2009 -0400
+++ b/sqlpython/sqlpyPlus.py	Tue Oct 06 15:09:46 2009 -0400
@@ -1429,7 +1429,7 @@
         'WINDOW',               
         'WINDOW GROUP',         
         'XML SCHEMA')
-    
+        
     @options([make_option('-l', '--long', action='store_true', help='long descriptions'),
               make_option('-a', '--all', action='store_true', help="all schemas' objects"),
               make_option('-t', '--timesort', action='store_true', help="Sort by last_ddl_time"),              
@@ -1439,32 +1439,37 @@
         Lists objects as through they were in an {object_type}/{object_name} UNIX
         directory structure.  `*` and `%` may be used as wildcards.
         '''
-        clauses = {'owner': '', 'moreColumns': '',
-                   'source': metaqueries['ls'][self.rdbms],
-                   'where': self.ls_where_clause(arg, opts)}
-        if opts.long:
-            clauses['moreColumns'] = ', status, last_ddl_time'
-        if opts.all:
-            clauses['owner'] = "owner || '.' ||"
-
-        # 'Normal' sort order is DATE DESC (maybe), object type ASC, object name ASC
-        sortdirection = (hasattr(opts, 'reverse') and opts.reverse and 'DESC') or 'ASC'
-        orderby = 'object_type %s, object_name %s' % (sortdirection, sortdirection)
-        if hasattr(opts, 'timesort') and opts.timesort:
-            if hasattr(opts, 'reverse') and opts.reverse:
-                direction = 'DESC'
+        seek = '^%s$' % (arg.replace('*', '.*').replace('?','.'). \
+                         replace('%', '.*'))
+        schemas = self.connections[self.connection_number]['schemas']
+        result = []
+        username = self.connections[self.connection_number]['user'].upper()
+        for (schema_name, schema) in schemas.items():
+            if opts.all or schema_name == username:
+                for (name, obj) in schema.schema.items():
+                    if hasattr(obj, 'type'):
+                        dbtype = obj.type
+                    else:
+                        dbtype = str(type(obj)).rstrip("'>").split('.')[-1]
+                    if opts.all:
+                        name = '%s.%s' % (schema_name, name)
+                    descriptor = '%s/%s' % (dbtype, name)
+                    descriptor = descriptor.upper()
+                    if (not arg) or \
+                       re.search(seek, descriptor, re.IGNORECASE) or \
+                       re.search(seek, name, re.IGNORECASE) or \
+                       re.search(seek, dbtype, re.IGNORECASE):
+                        result.append(descriptor)
+                        # if opts.long: status, last_ddl_time
+        if not schemas.complete:
+            if opts.all:
+                qualifier = 'may be '
             else:
-                direction = 'ASC'
-            orderby = 'last_ddl_time %s, %s' % (direction, orderby)
-        clauses['orderby'] = orderby    
-        statement = '''
-            SELECT object_type || '/' || %(owner)s object_name AS name %(moreColumns)s 
-            FROM   (%(source)s) source
-            %(where)s
-            ORDER BY %(orderby)s;''' % clauses
-        self.do_select(self.parsed(statement, 
-                                   terminator=arg.parsed.terminator or ';', 
-                                   suffix=arg.parsed.suffix))
+                qualifier = ''
+            self.perror('Metadata discovery still in progress - results %sincomplete' % qualifier)        
+        if result:
+            result.sort(reverse=bool(opts.reverse))
+            self.poutput('\n'.join(result))
         
     @options([make_option('-i', '--ignore-case', dest='ignorecase', action='store_true', help='Case-insensitive search')])        
     def do_grep(self, arg, opts):
--- a/sqlpython/sqlpython.py	Thu Aug 13 14:33:55 2009 -0400
+++ b/sqlpython/sqlpython.py	Tue Oct 06 15:09:46 2009 -0400
@@ -1,7 +1,7 @@
 #
 # SqlPython V1.6.7
 # Author: Luca.Canali@cern.ch, Apr 2006
-# Rev 7-Jul-09
+# Rev 2-Sep-09
 
 # A python module to reproduce Oracle's command line 'sqlplus-like' within python
 # Intended to allow easy customizations and extentions 
@@ -9,8 +9,8 @@
 # See also http://twiki.cern.ch/twiki/bin/view/PSSGroup/SqlPython
 
 import cmd2,getpass,binascii,cx_Oracle,re,os,platform
-import sqlalchemy, pyparsing
-__version__ = '1.6.7'    
+import sqlalchemy, pyparsing, schemagroup
+__version__ = '1.6.8'    
 
 class Parser(object):
     comment_def = "--" + ~ ('-' + pyparsing.CaselessKeyword('begin')) + pyparsing.ZeroOrMore(pyparsing.CharsNotIn("\n"))    
@@ -98,9 +98,14 @@
     def url_connect(self, arg):
         eng = sqlalchemy.create_engine(arg) 
         self.conn = eng.connect().connection
+        user = eng.url.username or ''
+        rdbms = eng.url.drivername
         conn  = {'conn': self.conn, 'prompt': self.prompt, 'dbname': eng.url.database,
-                 'rdbms': eng.url.drivername, 'user': eng.url.username or '', 
-                 'eng': eng, 'connect_string': arg}
+                 'rdbms': rdbms, 'user': user, 'eng': eng, 
+                 'schemas': schemagroup.get_schemagroup(rdbms, arg,
+                                                    self.conn, user)}
+        s = conn['schemas']
+        s.refresh()
         return conn
     def ora_connect(self, arg):
         modeval = 0