[Zope-DB] ZSQL Caching

Andy McKay andym@ActiveState.com
Tue, 30 Oct 2001 21:48:10 -0800


This is a multi-part message in MIME format.

------=_NextPart_000_0630_01C1618C.916781C0
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

We were discussing ZSQL caching and work, and we wanted to debug the
contents of our cache, so I through together a quick method that cleans up
the cache a little and adds a Cache tab. There you can see the contents of
the cache and flush it.

For quick debugging we found it quite useful.

Patch and new source for ZRDB/DA.py and viewCache.dtml attached.

Cheers.
--
  Andy McKay

------=_NextPart_000_0630_01C1618C.916781C0
Content-Type: text/plain;
	name="ZSQLDiff.txt"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="ZSQLDiff.txt"

--- f:\lib\python\Shared\DC\ZRDB\DA.py	Wed Oct 03 11:48:37 2001=0D
+++ l:\lib\python\Shared\DC\ZRDB\DA.py	Tue Oct 23 16:08:38 2001=0D
@@ -155,6 +155,7 @@=0D
          'help':('ZSQLMethods','Z-SQL-Method_Test.stx')},=0D
         {'label':'Advanced', 'action':'manage_advancedForm',=0D
          'help':('ZSQLMethods','Z-SQL-Method_Advanced.stx')},=0D
+        {'label':'Cache', 'action':'manage_viewCache',},=0D
         )=0D
         +AccessControl.Role.RoleManager.manage_options=0D
         +OFS.SimpleItem.Item.manage_options=0D
@@ -165,7 +166,8 @@=0D
         ('View management screens',=0D
          (=0D
         'manage_main', 'index_html',=0D
-        'manage_advancedForm', 'PrincipiaSearchSource', 'document_src'=0D
+        'manage_advancedForm', 'PrincipiaSearchSource', 'document_src',=0D
+        'manage_viewCache', 'size_of_cache', 'clean_cache', 'reset_cache',=
 'get_cache'=0D
         )),=0D
         ('Change Database Methods',=0D
          ('manage_edit','manage_advanced', 'manage_testForm','manage_test'=
,=0D
@@ -179,6 +181,7 @@=0D
         self.manage_edit(title, connection_id, arguments, template)=0D
     =0D
     manage_advancedForm=3DDTMLFile('dtml/advanced', globals())=0D
+    manage_viewCache=3DDTMLFile('dtml/viewCache', globals())=0D
 =0D
     test_url___roles__=3DNone=0D
     def test_url_(self):=0D
@@ -409,36 +416,92 @@=0D
     def _searchable_result_columns(self): return self._col=0D
 =0D
     def _cached_result(self, DB__, query):=0D
+        ''' get the cached sql, look up the result,=0D
+        if its not in the cache, add it in '''=0D
+        cache, tcache =3D self.__get_cache()=0D
+=0D
+        # if the cache size is half of the max,=0D
+        # clean, otherwise carry on=0D
+        if len(cache) > self.max_cache_ / 2:=0D
+            self.__clean_cache(cache, tcache)=0D
 =0D
-        # Try to fetch from cache=0D
-        if hasattr(self,'_v_cache'): cache=3Dself._v_cache=0D
-        else: cache=3Dself._v_cache=3D{}, Bucket()=0D
-        cache, tcache =3D cache=0D
-        max_cache=3Dself.max_cache_=0D
         now=3Dtime()=0D
         t=3Dnow-self.cache_time_=0D
-        if len(cache) > max_cache / 2:=0D
-            keys=3Dtcache.keys()=0D
-            keys.reverse()=0D
-            while keys and (len(keys) > max_cache or keys[-1] < t):=0D
-                key=3Dkeys[-1]=0D
-                q=3Dtcache[key]=0D
-                del tcache[key]=0D
-                if int(cache[q][0]) =3D=3D key:=0D
-                    del cache[q]=0D
-                del keys[-1]=0D
-                =0D
+=0D
+        # pull result out of cache based on=0D
+        # query=0D
         if cache.has_key(query):=0D
             k, r =3D cache[query]=0D
             if k > t: return r=0D
 =0D
         result=3Dapply(DB__.query, query)=0D
+=0D
+        # update cache if necessary=0D
         if self.cache_time_ > 0:=0D
             tcache[int(now)]=3Dquery=0D
             cache[query]=3D now, result=0D
 =0D
         return result=0D
 =0D
+    def __get_cache(self):=0D
+        ''' get the cached sql queries '''=0D
+        if hasattr(self,'_v_cache'): cache=3Dself._v_cache=0D
+        else: cache=3Dself._v_cache=3D{}, Bucket()=0D
+        return cache=0D
+=0D
+    def __clean_cache(self, cache, tcache):=0D
+        ''' clean the cache '''=0D
+        max_cache=3Dself.max_cache_=0D
+        now=3Dtime()=0D
+        t=3Dnow-self.cache_time_=0D
+=0D
+        keys=3Dtcache.keys()=0D
+        keys.reverse()=0D
+        while keys and (len(keys) > max_cache or keys[-1] < t):=0D
+            key=3Dkeys[-1]=0D
+            q=3Dtcache[key]=0D
+            del tcache[key]=0D
+            if int(cache[q][0]) =3D=3D key:=0D
+                del cache[q]=0D
+            del keys[-1]=0D
+=0D
+        return cache, tcache=0D
+=0D
+    def __reset_cache(self):=0D
+        self._v_cache=3D{}, Bucket()=0D
+=0D
+    def size_of_cache(self):=0D
+        ''' No of items in the cache '''=0D
+        return len(self.__get_cache()[0])=0D
+=0D
+    def clean_cache(self, REQUEST=3DNone):=0D
+        ''' Cleans the cache by removing only invalid objects '''=0D
+        c, t =3D self.__get_cache()=0D
+        self.__clean_cache(c, t)=0D
+        if REQUEST:=0D
+            return self.manage_viewCache(self, REQUEST, manage_tabs_messag=
e=3D'Cache cleaned')=0D
+=0D
+    def reset_cache(self, REQUEST=3DNone):=0D
+        ''' Removes all items from the cache '''=0D
+        self.__reset_cache()=0D
+        if REQUEST:=0D
+            return self.manage_viewCache(self, REQUEST, manage_tabs_messag=
e=3D'Cache reset')=0D
+=0D
+    def get_cache(self):=0D
+        ''' Shows the cache '''=0D
+        c =3D self.__get_cache()[0] # cache=0D
+        l =3D [] # easily presentable version...=0D
+        now=3Dtime()=0D
+=0D
+        for k, v in c.items():=0D
+            d =3D { =0D
+                'query':k[0], =0D
+                'expired': now > (self.cache_time_+int(v[0])), =0D
+                'secs_since_run':int(now-int(v[0])),=0D
+                }=0D
+            l.append(d)=0D
+        return l=0D
+=0D

------=_NextPart_000_0630_01C1618C.916781C0
Content-Type: text/plain;
	name="DA.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="DA.py"

###########################################################################=
###=0D
# =0D
# Zope Public License (ZPL) Version 1.0=0D
# -------------------------------------=0D
# =0D
# Copyright (c) Digital Creations.  All rights reserved.=0D
# =0D
# This license has been certified as Open Source(tm).=0D
# =0D
# Redistribution and use in source and binary forms, with or without=0D
# modification, are permitted provided that the following conditions are=0D
# met:=0D
# =0D
# 1. Redistributions in source code must retain the above copyright=0D
#    notice, this list of conditions, and the following disclaimer.=0D
# =0D
# 2. Redistributions in binary form must reproduce the above copyright=0D
#    notice, this list of conditions, and the following disclaimer in=0D
#    the documentation and/or other materials provided with the=0D
#    distribution.=0D
# =0D
# 3. Digital Creations requests that attribution be given to Zope=0D
#    in any manner possible. Zope includes a "Powered by Zope"=0D
#    button that is installed by default. While it is not a license=0D
#    violation to remove this button, it is requested that the=0D
#    attribution remain. A significant investment has been put=0D
#    into Zope, and this effort will continue if the Zope community=0D
#    continues to grow. This is one way to assure that growth.=0D
# =0D
# 4. All advertising materials and documentation mentioning=0D
#    features derived from or use of this software must display=0D
#    the following acknowledgement:=0D
# =0D
#      "This product includes software developed by Digital Creations=0D
#      for use in the Z Object Publishing Environment=0D
#      (http://www.zope.org/)."=0D
# =0D
#    In the event that the product being advertised includes an=0D
#    intact Zope distribution (with copyright and license included)=0D
#    then this clause is waived.=0D
# =0D
# 5. Names associated with Zope or Digital Creations must not be used to=0D
#    endorse or promote products derived from this software without=0D
#    prior written permission from Digital Creations.=0D
# =0D
# 6. Modified redistributions of any form whatsoever must retain=0D
#    the following acknowledgment:=0D
# =0D
#      "This product includes software developed by Digital Creations=0D
#      for use in the Z Object Publishing Environment=0D
#      (http://www.zope.org/)."=0D
# =0D
#    Intact (re-)distributions of any official Zope release do not=0D
#    require an external acknowledgement.=0D
# =0D
# 7. Modifications are encouraged but must be packaged separately as=0D
#    patches to official Zope releases.  Distributions that do not=0D
#    clearly separate the patches from the original work must be clearly=0D
#    labeled as unofficial distributions.  Modifications which do not=0D
#    carry the name Zope may be packaged in any form, as long as they=0D
#    conform to all of the clauses above.=0D
# =0D
# =0D
# Disclaimer=0D
# =0D
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY=0D
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE=0D
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR=0D
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS=0D
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,=0D
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT=0D
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF=0D
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND=0D
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,=0D
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT=0D
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF=0D
#   SUCH DAMAGE.=0D
# =0D
# =0D
# This software consists of contributions made by Digital Creations and=0D
# many individuals on behalf of Digital Creations.  Specific=0D
# attributions are listed in the accompanying credits file.=0D
# =0D
###########################################################################=
###=0D
__doc__=3D'''Generic Database adapter'''=0D
=0D
=0D
__version__=3D'$Revision: 1.103 $'[11:-2]=0D
=0D
import OFS.SimpleItem, Aqueduct, RDB, re=0D
import DocumentTemplate, marshal, md5, base64, Acquisition, os=0D
from Aqueduct import decodestring, parse=0D
from Aqueduct import custom_default_report, default_input_form=0D
from Globals import DTMLFile, MessageDialog=0D
from cStringIO import StringIO=0D
import sys, Globals, OFS.SimpleItem, AccessControl.Role=0D
from string import atoi, find, join, split, rstrip=0D
import DocumentTemplate, sqlvar, sqltest, sqlgroup=0D
from time import time=0D
from zlib import compress, decompress=0D
from DateTime.DateTime import DateTime=0D
md5new=3Dmd5.new=0D
import ExtensionClass=0D
import DocumentTemplate.DT_Util=0D
from cPickle import dumps, loads=0D
from Results import Results=0D
from App.Extensions import getBrain=0D
from AccessControl import getSecurityManager=0D
from AccessControl.DTML import RestrictedDTML=0D
from webdav.Resource import Resource=0D
from webdav.Lockable import ResourceLockedError=0D
try: from IOBTree import Bucket=0D
except: Bucket=3Dlambda:{}=0D
=0D
=0D
class nvSQL(DocumentTemplate.HTML):=0D
    # Non-validating SQL Template for use by SQLFiles.=0D
    commands=3D{}=0D
    for k, v in DocumentTemplate.HTML.commands.items(): commands[k]=3Dv=0D
    commands['sqlvar' ]=3Dsqlvar.SQLVar=0D
    commands['sqltest']=3Dsqltest.SQLTest=0D
    commands['sqlgroup' ]=3Dsqlgroup.SQLGroup=0D
=0D
    _proxy_roles=3D()=0D
=0D
=0D
class SQL(RestrictedDTML, ExtensionClass.Base, nvSQL):=0D
    # Validating SQL template for Zope SQL Methods.=0D
    pass=0D
=0D
=0D
class DA(=0D
    Aqueduct.BaseQuery,Acquisition.Implicit,=0D
    Globals.Persistent,=0D
    AccessControl.Role.RoleManager,=0D
    OFS.SimpleItem.Item,=0D
    Resource=0D
    ):=0D
    'Database Adapter'=0D
=0D
    _col=3DNone=0D
    max_rows_=3D1000=0D
    cache_time_=3D0=0D
    max_cache_=3D100=0D
    class_name_=3Dclass_file_=3D''=0D
    _zclass=3DNone=0D
    allow_simple_one_argument_traversal=3DNone=0D
    template_class=3DSQL=0D
    =0D
    manage_options=3D(=0D
        (=0D
        {'label':'Edit', 'action':'manage_main',=0D
         'help':('ZSQLMethods','Z-SQL-Method_Edit.stx')},=0D
        {'label':'Test', 'action':'manage_testForm',=0D
         'help':('ZSQLMethods','Z-SQL-Method_Test.stx')},=0D
        {'label':'Advanced', 'action':'manage_advancedForm',=0D
         'help':('ZSQLMethods','Z-SQL-Method_Advanced.stx')},=0D
        {'label':'Cache', 'action':'manage_viewCache',},=0D
        )=0D
        +AccessControl.Role.RoleManager.manage_options=0D
        +OFS.SimpleItem.Item.manage_options=0D
        )=0D
 =0D
    # Specify how individual operations add up to "permissions":=0D
    __ac_permissions__=3D(=0D
        ('View management screens',=0D
         (=0D
        'manage_main', 'index_html',=0D
        'manage_advancedForm', 'PrincipiaSearchSource', 'document_src',=0D
        'manage_viewCache', 'size_of_cache', 'clean_cache', 'reset_cache', =
'get_cache'=0D
        )),=0D
        ('Change Database Methods',=0D
         ('manage_edit','manage_advanced', 'manage_testForm','manage_test',=
=0D
          'manage_product_zclass_info', 'PUT')),=0D
        ('Use Database Methods', ('__call__',''), ('Anonymous','Manager')),=
=0D
        )=0D
   =0D
=0D
    def __init__(self, id, title, connection_id, arguments, template):=0D
        self.id=3Dstr(id)=0D
        self.manage_edit(title, connection_id, arguments, template)=0D
    =0D
    manage_advancedForm=3DDTMLFile('dtml/advanced', globals())=0D
    manage_viewCache=3DDTMLFile('dtml/viewCache', globals())=0D
=0D
    test_url___roles__=3DNone=0D
    def test_url_(self):=0D
        'Method for testing server connection information'=0D
        return 'PING'=0D
=0D
    _size_changes=3D{=0D
        'Bigger': (5,5),=0D
        'Smaller': (-5,-5),=0D
        'Narrower': (0,-5),=0D
        'Wider': (0,5),=0D
        'Taller': (5,0),=0D
        'Shorter': (-5,0),=0D
        }=0D
=0D
    def _er(self,title,connection_id,arguments,template,=0D
            SUBMIT,sql_pref__cols,sql_pref__rows,REQUEST):=0D
        dr,dc =3D self._size_changes[SUBMIT]=0D
        =0D
        rows=3Dmax(1,atoi(sql_pref__rows)+dr)=0D
        cols=3Dmax(40,atoi(sql_pref__cols)+dc)=0D
        e=3D(DateTime('GMT') + 365).rfc822()=0D
        resp=3DREQUEST['RESPONSE']=0D
        resp.setCookie('sql_pref__rows',str(rows),path=3D'/',expires=3De)=
=0D
        resp.setCookie('sql_pref__cols',str(cols),path=3D'/',expires=3De)=
=0D
        return self.manage_main(=0D
            self,REQUEST,=0D
            title=3Dtitle,=0D
            arguments_src=3Darguments,=0D
            connection_id=3Dconnection_id,=0D
            src=3Dtemplate,=0D
            sql_pref__cols=3Dcols,sql_pref__rows=3Drows)=0D
=0D
    def manage_edit(self,title,connection_id,arguments,template,=0D
                    SUBMIT=3D'Change',sql_pref__cols=3D'50', sql_pref__rows=
=3D'20',=0D
                    REQUEST=3DNone):=0D
        """Change database method  properties=0D
=0D
        The 'connection_id' argument is the id of a database connection=0D
        that resides in the current folder or in a folder above the=0D
        current folder.  The database should understand SQL.=0D
=0D
        The 'arguments' argument is a string containing an arguments=0D
        specification, as would be given in the SQL method cration form.=0D
=0D
        The 'template' argument is a string containing the source for the=
=0D
        SQL Template.=0D
        """=0D
=0D
        if self._size_changes.has_key(SUBMIT):=0D
            return self._er(title,connection_id,arguments,template,=0D
                            SUBMIT,sql_pref__cols,sql_pref__rows,REQUEST)=
=0D
=0D
        if self.wl_isLocked():=0D
            raise ResourceLockedError, 'SQL Method is locked via WebDAV'=0D
        =0D
        self.title=3Dstr(title)=0D
        self.connection_id=3Dstr(connection_id)=0D
        arguments=3Dstr(arguments)=0D
        self.arguments_src=3Darguments=0D
        self._arg=3Dparse(arguments)=0D
        template=3Dstr(template)=0D
        self.src=3Dtemplate=0D
        self.template=3Dt=3Dself.template_class(template)=0D
        t.cook()=0D
        self._v_cache=3D{}, Bucket()=0D
        if REQUEST:=0D
            if SUBMIT=3D=3D'Change and Test':=0D
                return self.manage_testForm(REQUEST)=0D
            message=3D'ZSQL Method content changed'=0D
            return self.manage_main(self, REQUEST, manage_tabs_message=3Dme=
ssage)=0D
        return ''=0D
=0D
=0D
    def manage_advanced(self, max_rows, max_cache, cache_time,=0D
                        class_name, class_file, direct=3DNone,=0D
                        REQUEST=3DNone, zclass=3D''):=0D
        """Change advanced properties=0D
=0D
        The arguments are:=0D
=0D
        max_rows -- The maximum number of rows to be returned from a query.=
=0D
=0D
        max_cache -- The maximum number of results to cache=0D
=0D
        cache_time -- The maximum amound of time to use a cached result.=0D
=0D
        class_name -- The name of a class that provides additional=0D
          attributes for result record objects. This class will be a=0D
          base class of the result record class.=0D
=0D
        class_file -- The name of the file containing the class=0D
          definition.=0D
=0D
        The class file normally resides in the 'Extensions'=0D
        directory, however, the file name may have a prefix of=0D
        'product.', indicating that it should be found in a product=0D
        directory.=0D
=0D
        For example, if the class file is: 'ACMEWidgets.foo', then an=0D
        attempt will first be made to use the file=0D
        'lib/python/Products/ACMEWidgets/Extensions/foo.py'. If this=0D
        failes, then the file 'Extensions/ACMEWidgets.foo.py' will be=0D
        used.=0D
  =0D
        """=0D
        # paranoid type checking=0D
        if type(max_rows) is not type(1):=0D
            max_rows=3Datoi(max_rows)=0D
        if type(max_cache) is not type(1):=0D
            max_cache=3Datoi(max_cache)=0D
        if type(cache_time) is not type(1):=0D
            cache_time=3Datoi(cache_time)            =0D
        class_name=3Dstr(class_name)=0D
        class_file=3Dstr(class_file)=0D
        =0D
        self.max_rows_ =3D max_rows=0D
        self.max_cache_, self.cache_time_ =3D max_cache, cache_time=0D
        self._v_cache=3D{}, Bucket()=0D
        self.class_name_, self.class_file_ =3D class_name, class_file=0D
        self._v_brain=3DgetBrain(self.class_file_, self.class_name_, 1)=0D
        self.allow_simple_one_argument_traversal=3Ddirect=0D
=0D
        if zclass:=0D
            for d in self.aq_acquire('_getProductRegistryData')('zclasses')=
:=0D
                if ("%s/%s" % (d.get('product'),d.get('id'))) =3D=3D zclass=
:=0D
                    self._zclass=3Dd['meta_class']=0D
                    break=0D
=0D
        =0D
        if REQUEST is not None:=0D
            m=3D"ZSQL Method advanced settings have been set"=0D
            return self.manage_advancedForm(self,REQUEST,manage_tabs_messag=
e=3Dm)=0D
##            return self.manage_editedDialog(REQUEST)=0D
=0D
    #def getFindContent(self):=0D
    #    """Return content for use by the Find machinery."""=0D
    #    return '%s\n%s' % (self.arguments_src, self.src)=0D
=0D
    def PrincipiaSearchSource(self):=0D
        """Return content for use by the Find machinery."""=0D
        return '%s\n%s' % (self.arguments_src, self.src)=0D
=0D
=0D
    # WebDAV / FTP support=0D
=0D
    default_content_type =3D 'text/plain'=0D
=0D
    def document_src(self, REQUEST=3DNone, RESPONSE=3DNone):=0D
        """Return unprocessed document source."""=0D
        if RESPONSE is not None:=0D
            RESPONSE.setHeader('Content-Type', 'text/plain')=0D
        return '<params>%s</params>\n%s' % (self.arguments_src, self.src)=
=0D
=0D
    def manage_FTPget(self):=0D
        """Get source for FTP download"""=0D
        self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')=0D
        return '<params>%s</params>\n%s' % (self.arguments_src, self.src)=
=0D
=0D
    def PUT(self, REQUEST, RESPONSE):=0D
        """Handle put requests"""=0D
        self.dav__init(REQUEST, RESPONSE)=0D
        self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=3D1)=0D
        body =3D REQUEST.get('BODY', '')=0D
        m =3D re.match('\s*<params>(.*)</params>\s*\n', body, re.I | re.S)=
=0D
        if m:=0D
            self.arguments_src =3D m.group(1)=0D
            self._arg=3Dparse(self.arguments_src)=0D
            body =3D body[m.end():]=0D
        template =3D body=0D
        self.src =3D template=0D
        self.template=3Dt=3Dself.template_class(template)=0D
        t.cook()=0D
        self._v_cache=3D{}, Bucket()=0D
        RESPONSE.setStatus(204)=0D
        return RESPONSE=0D
=0D
=0D
    def manage_testForm(self, REQUEST):=0D
        " "=0D
        input_src=3Ddefault_input_form(self.title_or_id(),=0D
                                     self._arg, 'manage_test',=0D
                                     '<dtml-var manage_tabs>')=0D
        return DocumentTemplate.HTML(input_src)(self, REQUEST, HTTP_REFERER=
=3D'')=0D
=0D
    def manage_test(self, REQUEST):=0D
        """Test an SQL method."""=0D
        # Try to render the query template first so that the rendered=0D
        # source will be available for the error message in case some=0D
        # error occurs...=0D
        try:    src=3Dself(REQUEST, src__=3D1)=0D
        except: src=3D"Could not render the query template!"=0D
        result=3D()=0D
        t=3Dv=3Dtb=3DNone=0D
        try:=0D
            try:=0D
                src, result=3Dself(REQUEST, test__=3D1)=0D
                if find(src,'\0'):=0D
                    src=3Djoin(split(src,'\0'),'\n'+'-'*60+'\n')=0D
                if result._searchable_result_columns():=0D
                    r=3Dcustom_default_report(self.id, result)=0D
                else:=0D
                    r=3D'This statement returned no results.'=0D
            except:=0D
                t, v, tb =3D sys.exc_info()=0D
                r=3D'<strong>Error, <em>%s</em>:</strong> %s' % (t, v)=0D
=0D
            report=3DDocumentTemplate.HTML(=0D
                '<html>\n'=0D
                '<BODY BGCOLOR=3D"#FFFFFF" LINK=3D"#000099" VLINK=3D"#55555=
5">\n'=0D
                '<dtml-var manage_tabs>\n<hr>\n%s\n\n'=0D
                '<hr><strong>SQL used:</strong><br>\n<pre>\n%s\n</pre>\n<hr=
>\n'=0D
                '</body></html>'=0D
                % (r,src))=0D
=0D
            report=3Dapply(report,(self,REQUEST),{self.id:result})=0D
=0D
            if tb is not None:=0D
                self.raise_standardErrorMessage(=0D
                    None, REQUEST, t, v, tb, None, report)=0D
=0D
            return report=0D
        =0D
        finally: tb=3DNone=0D
=0D
    def index_html(self, REQUEST):=0D
        """ """=0D
        REQUEST.RESPONSE.redirect("%s/manage_testForm" % REQUEST['URL1'])=
=0D
=0D
    def _searchable_arguments(self): return self._arg=0D
=0D
    def _searchable_result_columns(self): return self._col=0D
=0D
    def _cached_result(self, DB__, query):=0D
        ''' get the cached sql, look up the result,=0D
        if its not in the cache, add it in '''=0D
        cache, tcache =3D self.__get_cache()=0D
=0D
        # if the cache size is half of the max,=0D
        # clean, otherwise carry on=0D
        if len(cache) > self.max_cache_ / 2:=0D
            self.__clean_cache(cache, tcache)=0D
=0D
        now=3Dtime()=0D
        t=3Dnow-self.cache_time_=0D
=0D
        # pull result out of cache based on=0D
        # query=0D
        if cache.has_key(query):=0D
            k, r =3D cache[query]=0D
            if k > t: return r=0D
=0D
        result=3Dapply(DB__.query, query)=0D
=0D
        # update cache if necessary=0D
        if self.cache_time_ > 0:=0D
            tcache[int(now)]=3Dquery=0D
            cache[query]=3D now, result=0D
=0D
        return result=0D
=0D
    def __get_cache(self):=0D
        ''' get the cached sql queries '''=0D
        if hasattr(self,'_v_cache'): cache=3Dself._v_cache=0D
        else: cache=3Dself._v_cache=3D{}, Bucket()=0D
        return cache=0D
=0D
    def __clean_cache(self, cache, tcache):=0D
        ''' clean the cache '''=0D
        max_cache=3Dself.max_cache_=0D
        now=3Dtime()=0D
        t=3Dnow-self.cache_time_=0D
=0D
        keys=3Dtcache.keys()=0D
        keys.reverse()=0D
        while keys and (len(keys) > max_cache or keys[-1] < t):=0D
            key=3Dkeys[-1]=0D
            q=3Dtcache[key]=0D
            del tcache[key]=0D
            if int(cache[q][0]) =3D=3D key:=0D
                del cache[q]=0D
            del keys[-1]=0D
=0D
        return cache, tcache=0D
=0D
    def __reset_cache(self):=0D
        self._v_cache=3D{}, Bucket()=0D
=0D
    def size_of_cache(self):=0D
        ''' No of items in the cache '''=0D
        return len(self.__get_cache()[0])=0D
=0D
    def clean_cache(self, REQUEST=3DNone):=0D
        ''' Cleans the cache by removing only invalid objects '''=0D
        c, t =3D self.__get_cache()=0D
        self.__clean_cache(c, t)=0D
        if REQUEST:=0D
            return self.manage_viewCache(self, REQUEST, manage_tabs_message=
=3D'Cache cleaned')=0D
=0D
    def reset_cache(self, REQUEST=3DNone):=0D
        ''' Removes all items from the cache '''=0D
        self.__reset_cache()=0D
        if REQUEST:=0D
            return self.manage_viewCache(self, REQUEST, manage_tabs_message=
=3D'Cache reset')=0D
=0D
    def get_cache(self):=0D
        ''' Shows the cache '''=0D
        c =3D self.__get_cache()[0] # cache=0D
        l =3D [] # easily presentable version...=0D
        now=3Dtime()=0D
=0D
        for k, v in c.items():=0D
            d =3D { =0D
                'query':k[0], =0D
                'expired': now > (self.cache_time_+int(v[0])), =0D
                'secs_since_run':int(now-int(v[0])),=0D
                }=0D
            l.append(d)=0D
        return l=0D
=0D
    def __call__(self, REQUEST=3DNone, __ick__=3DNone, src__=3D0, test__=3D=
0, **kw):=0D
        """Call the database method=0D
=0D
        The arguments to the method should be passed via keyword=0D
        arguments, or in a single mapping object. If no arguments are=0D
        given, and if the method was invoked through the Web, then the=0D
        method will try to acquire and use the Web REQUEST object as=0D
        the argument mapping.=0D
=0D
        The returned value is a sequence of record objects.=0D
        """=0D
=0D
        if REQUEST is None:=0D
            if kw: REQUEST=3Dkw=0D
            else:=0D
                if hasattr(self, 'REQUEST'): REQUEST=3Dself.REQUEST=0D
                else: REQUEST=3D{}=0D
=0D
        try: dbc=3Dgetattr(self, self.connection_id)=0D
        except AttributeError:=0D
            raise AttributeError, (=0D
                "The database connection <em>%s</em> cannot be found." % (=
=0D
                self.connection_id))=0D
=0D
        try: DB__=3Ddbc()=0D
        except: raise 'Database Error', (=0D
            '%s is not connected to a database' % self.id)=0D
        =0D
        if hasattr(self, 'aq_parent'):=0D
            p=3Dself.aq_parent=0D
            if self._isBeingAccessedAsZClassDefinedInstanceMethod():=0D
                p=3Dp.aq_parent=0D
        else: p=3DNone=0D
=0D
        argdata=3Dself._argdata(REQUEST)=0D
        argdata['sql_delimiter']=3D'\0'=0D
        argdata['sql_quote__']=3Ddbc.sql_quote__=0D
=0D
        for k,v in kw.items(): argdata[k] =3D v=0D
=0D
        security=3DgetSecurityManager()=0D
        security.addContext(self)=0D
        try:     query=3Dapply(self.template, (p,), argdata)=0D
        finally: security.removeContext(self)=0D
=0D
        if src__: return query=0D
=0D
        if self.cache_time_ > 0 and self.max_cache_ > 0:=0D
            result=3Dself._cached_result(DB__, (query, self.max_rows_))=0D
        else: result=3DDB__.query(query, self.max_rows_)=0D
=0D
        if hasattr(self, '_v_brain'): brain=3Dself._v_brain=0D
        else:=0D
            brain=3Dself._v_brain=3DgetBrain(self.class_file_, self.class_n=
ame_)=0D
=0D
        zc=3Dself._zclass=0D
        if zc is not None: zc=3Dzc._zclass_=0D
=0D
        if type(result) is type(''):=0D
            f=3DStringIO()=0D
            f.write(result)=0D
            f.seek(0)=0D
            result=3DRDB.File(f,brain,p, zc)=0D
        else:=0D
            result=3DResults(result, brain, p, zc)=0D
        columns=3Dresult._searchable_result_columns()=0D
        if test__ and columns !=3D self._col: self._col=3Dcolumns=0D
=0D
        # If run in test mode, return both the query and results so=0D
        # that the template doesn't have to be rendered twice!=0D
        if test__: return query, result=0D
=0D
        return result=0D
=0D
    def da_has_single_argument(self): return len(self._arg)=3D=3D1=0D
=0D
    def __getitem__(self, key):=0D
        args=3Dself._arg=0D
        if self.allow_simple_one_argument_traversal and len(args)=3D=3D1:=
=0D
            results=3Dself({args.keys()[0]: key})=0D
            if results:=0D
                if len(results) > 1: raise KeyError, key=0D
            else: raise KeyError, key=0D
            r=3Dresults[0]=0D
            # if hasattr(self, 'aq_parent'): r=3Dr.__of__(self.aq_parent)=
=0D
            return r=0D
            =0D
        self._arg[key] # raise KeyError if not an arg=0D
        return Traverse(self,{},key)=0D
=0D
    def connectionIsValid(self):=0D
        return (hasattr(self, self.connection_id) and=0D
                hasattr(getattr(self, self.connection_id), 'connected'))=0D
=0D
    def connected(self):=0D
        return getattr(getattr(self, self.connection_id), 'connected')()=0D
=0D
=0D
    def manage_product_zclass_info(self):=0D
        r=3D[]=0D
        Z=3Dself._zclass=0D
        Z=3Dgetattr(Z, 'aq_self', Z)=0D
        for d in self.aq_acquire('_getProductRegistryData')('zclasses'):=0D
            z=3Dd['meta_class']=0D
            if hasattr(z._zclass_,'_p_deactivate'):=0D
                # Eek, persistent=0D
                continue=0D
            x=3D{}=0D
            x.update(d)=0D
            x['selected'] =3D (z is Z) and 'selected' or ''=0D
            del x['meta_class']=0D
            r.append(x)=0D
            =0D
        return r =0D
=0D
=0D
=0D
Globals.default__class_init__(DA)=0D
=0D
=0D
=0D
ListType=3Dtype([])=0D
class Traverse(ExtensionClass.Base):=0D
    """Helper class for 'traversing' searches during URL traversal=0D
    """=0D
    _r=3DNone=0D
    _da=3DNone=0D
=0D
    def __init__(self, da, args, name=3DNone):=0D
        self._da=3Dda=0D
        self._args=3Dargs=0D
        self._name=3Dname=0D
=0D
    def __bobo_traverse__(self, REQUEST, key):=0D
        name=3Dself._name=0D
        da=3Dself.__dict__['_da']=0D
        args=3Dself._args=0D
        if name:=0D
            if args.has_key(name):=0D
                v=3Dargs[name]=0D
                if type(v) is not ListType: v=3D[v]=0D
                v.append(key)=0D
                key=3Dv=0D
=0D
            args[name]=3Dkey=0D
=0D
            if len(args) < len(da._arg):            =0D
                return self.__class__(da, args)=0D
            key=3Dself # "consume" key=0D
=0D
        elif da._arg.has_key(key): return self.__class__(da, args, key)=0D
=0D
        results=3Dda(args)=0D
        if results:=0D
            if len(results) > 1:=0D
                try: return results[atoi(key)].__of__(da)=0D
                except: raise KeyError, key=0D
        else: raise KeyError, key=0D
        r=3Dresults[0]=0D
        # if hasattr(da, 'aq_parent'): r=3Dr.__of__(da.aq_parent)=0D
        self._r=3Dr=0D
=0D
        if key is self: return r=0D
=0D
        if hasattr(r,'__bobo_traverse__'):=0D
            try: return r.__bobo_traverse__(REQUEST, key)=0D
            except: pass=0D
=0D
        try: return getattr(r,key)=0D
        except AttributeError, v:=0D
            if str(v) !=3D key: raise AttributeError, v=0D
=0D
        return r[key]=0D
=0D
    def __getattr__(self, name):=0D
        r=3Dself.__dict__['_r']=0D
        if hasattr(r, name): return getattr(r,name)=0D
        return getattr(self.__dict__['_da'], name)=0D
=0D

------=_NextPart_000_0630_01C1618C.916781C0
Content-Type: application/octet-stream;
	name="viewCache.dtml"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="viewCache.dtml"

<dtml-var manage_page_header>=0D
<dtml-var manage_tabs>=0D
=0D
<p class=3D"form-help">This shows you the current contents of a ZSQL Method=
 cache.</p>=0D
<p>Cache size: <b><dtml-var size_of_cache></b> =0D
[ <a href=3D"clean_cache">Clean</a> ] =0D
[ <a href=3D"reset_cache">Reset</a> ] =0D
</p>=0D
=0D
<table cellpadding=3D"2" cellspacing=3D"0">=0D
    <tr class=3D"list-header"><td>Status</td><td>Query</td><td>Last run (se=
cs)</td></tr>=0D
<dtml-in get_cache>=0D
<dtml-with sequence-item mapping>=0D
    <tr class=3D"row-<dtml-if sequence-odd>hilite<dtml-else>normal</dtml-if=
>">=0D
        <td>=0D
            <dtml-if expired>Expired<dtml-else>Ok</dtml-if>=0D
        </td>=0D
        <td>&dtml-query;</td>=0D
        <td>&dtml-secs_since_run;</td>=0D
    </tr>=0D
</dtml-with>=0D
<dtml-else>=0D
    <tr class=3D"row-normal"><td colspan=3D"3">Cache is empty</td></tr>=0D
</dtml-in get_cache>=0D
</table>=0D
=0D
<dtml-var manage_page_footer>=0D
=0D

------=_NextPart_000_0630_01C1618C.916781C0--