Connection.py 13.3 KB
Newer Older
Jim Fulton's avatar
alpha1  
Jim Fulton committed
1
##############################################################################
2 3 4 5 6 7 8
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
Jim Fulton's avatar
alpha1  
Jim Fulton committed
9 10 11 12 13
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
Jim Fulton's avatar
alpha1  
Jim Fulton committed
84 85 86
##############################################################################
"""Database connection support

87 88
$Id: Connection.py,v 1.10 1999/06/24 20:05:50 jim Exp $"""
__version__='$Revision: 1.10 $'[11:-2]
Jim Fulton's avatar
alpha1  
Jim Fulton committed
89

Jim Fulton's avatar
Jim Fulton committed
90
from cPickleCache import PickleCache
91
from POSException import ConflictError, ExportError
Jim Fulton's avatar
alpha1  
Jim Fulton committed
92 93
from cStringIO import StringIO
from cPickle import Unpickler, Pickler
94
from ExtensionClass import Base
95
import Transaction, string, ExportImport, sys, traceback
96 97

ExtensionKlass=Base.__class__
Jim Fulton's avatar
alpha1  
Jim Fulton committed
98 99 100 101

class HelperClass: pass
ClassType=type(HelperClass)

102
class Connection(ExportImport.ExportImport):
Jim Fulton's avatar
alpha1  
Jim Fulton committed
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
    """Object managers for individual object space.

    An object space is a version of collection of objects.  In a
    multi-threaded application, each thread get's it's own object
    space.

    The Connection manages movement of objects in and out of object storage.
    """

    def __init__(self, storage, version='', cache_size=400,
                 cache_deactivate_after=60):
        """Create a new Connection"""
        self._storage=storage
        self.new_oid=storage.new_oid
        self._version=version
Jim Fulton's avatar
Jim Fulton committed
118
        self._cache=cache=PickleCache(self, cache_size, cache_deactivate_after)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
119
        self._incrgc=cache.incrgc
120 121 122
        self._invalidated=d={}
        self._invalid=d.has_key
        self._committed=[]
Jim Fulton's avatar
alpha1  
Jim Fulton committed
123 124 125 126 127 128 129 130 131 132 133 134 135

    def _breakcr(self):
        try: del self._cache
        except: pass
        try: del self._incrgc
        except: pass

    def __getitem__(self, oid,
                    tt=type(()), ct=type(HelperClass)):
        cache=self._cache
        if cache.has_key(oid): return cache[oid]

        __traceback_info__=oid
Jim Fulton's avatar
Jim Fulton committed
136
        p, serial = self._storage.load(oid, self._version)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
137 138 139 140 141 142
        file=StringIO(p)
        unpickler=Unpickler(file)
        unpickler.persistent_load=self._persistent_load

        object = unpickler.load()

Jim Fulton's avatar
Jim Fulton committed
143
        klass, args = object
144 145 146 147 148

        if type(klass) is tt:
            module, name = klass
            klass=self._db._classFactory(self, module, name)
        
Jim Fulton's avatar
Jim Fulton committed
149 150 151
        if (args is None or
            not args and not hasattr(klass,'__getinitargs__')):
            object=klass.__basicnew__()
Jim Fulton's avatar
alpha1  
Jim Fulton committed
152
        else:
Jim Fulton's avatar
Jim Fulton committed
153
            object=apply(klass,args)
154 155
            if klass is not ExtensionKlass:
                object.__dict__.clear()
Jim Fulton's avatar
alpha1  
Jim Fulton committed
156

Jim Fulton's avatar
Jim Fulton committed
157 158 159 160
        object._p_oid=oid
        object._p_jar=self
        object._p_changed=None
        object._p_serial=serial
Jim Fulton's avatar
alpha1  
Jim Fulton committed
161 162

        cache[oid]=object
163
        if oid=='\0\0\0\0\0\0\0\0': self._root_=object # keep a ref
Jim Fulton's avatar
alpha1  
Jim Fulton committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
        return object

    def _persistent_load(self,oid,
                        d={'__builtins__':{}},
                        tt=type(()), st=type(''), ct=type(HelperClass)):

        __traceback_info__=oid

        cache=self._cache

        if type(oid) is tt:
            # Quick instance reference.  We know all we need to know
            # to create the instance wo hitting the db, so go for it!
            oid, klass = oid
            if cache.has_key(oid): return cache[oid]
179 180 181 182 183 184 185 186 187 188

            if type(klass) is tt:
                module, name = klass
                try: klass=self._db._classFactory(self, module, name)
                except:
                    # Eek, we couldn't get the class. Hm.
                    # Maybe their's more current data in the
                    # object's actual record!
                    return self[oid]
            
Jim Fulton's avatar
Jim Fulton committed
189 190 191 192
            object=klass.__basicnew__()
            object._p_oid=oid
            object._p_jar=self
            object._p_changed=None
Jim Fulton's avatar
alpha1  
Jim Fulton committed
193 194
            
            cache[oid]=object
195

Jim Fulton's avatar
alpha1  
Jim Fulton committed
196 197 198
            return object

        if cache.has_key(oid): return cache[oid]
199
        return self[oid]
Jim Fulton's avatar
alpha1  
Jim Fulton committed
200 201 202 203 204 205 206 207

    def _planToStore(self,object,stackp):
        oid=object._p_oid
        if oid is None or object._p_jar is not self:
            oid = self.new_oid()
            object._p_jar=self
            object._p_oid=oid
            stackp(object)
208
            
Jim Fulton's avatar
alpha1  
Jim Fulton committed
209 210
        elif object._p_changed:
            stackp(object)
211

Jim Fulton's avatar
alpha1  
Jim Fulton committed
212 213 214 215 216 217 218 219
        return oid

    def _setDB(self, odb=None):
        """Begin a new transaction.

        Any objects modified since the last transaction are invalidated.
        """     
        self._db=odb
220
        self._cache.invalidate(self._invalidated)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
221 222 223

        return self

Jim Fulton's avatar
Jim Fulton committed
224 225 226 227 228 229 230
    def abort(self, object, transaction):
        """Abort the object in the transaction.

        This just deactivates the thing.
        """
        del object._p_changed

Jim Fulton's avatar
alpha1  
Jim Fulton committed
231 232 233 234 235 236 237
    def close(self):
        self._incrgc()
        self._db._closeConnection(self)
        del self._db

    def commit(self, object, transaction):
        oid=object._p_oid
238 239
        invalid=self._invalid
        if invalid(oid) or invalid(None): raise ConflictError, oid
Jim Fulton's avatar
alpha1  
Jim Fulton committed
240 241 242 243 244 245
        self._invalidating.append(oid)
        plan=self._planToStore
        stack=[]
        stackup=stack.append
        topoid=plan(object,stackup)
        version=self._version
246

Jim Fulton's avatar
alpha1  
Jim Fulton committed
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
        if stack:
            # Create a special persistent_id that passes T and the subobject
            # stack along:
            def persistent_id(object,self=self,stackup=stackup):
                if (not hasattr(object, '_p_oid') or
                    type(object) is ClassType): return None

                oid=object._p_oid

                if oid is None or object._p_jar is not self:
                    oid = self.new_oid()
                    object._p_jar=self
                    object._p_oid=oid
                    stackup(object)

262 263 264 265 266 267 268 269 270 271 272
                klass=object.__class__

                if klass is ExtensionKlass: return oid
                
                if hasattr(klass, '__getinitargs__'): return oid

                try: module=klass.__module__
                except: module=''
                if module: klass=module, klass.__name__
                
                return oid, klass
Jim Fulton's avatar
alpha1  
Jim Fulton committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287

            file=StringIO()
            seek=file.seek
            pickler=Pickler(file,1)
            pickler.persistent_id=persistent_id
            dbstore=self._storage.store
            file=file.getvalue
            cache=self._cache
            dump=pickler.dump
            clear_memo=pickler.clear_memo

            while stack:
                object=stack[-1]
                del stack[-1]
                oid=object._p_oid
288 289
                try: serial=object._p_serial
                except: serial='\0'*8
290
                if invalid(oid): raise ConflictError, oid
291 292 293
                klass = object.__class__

                if klass is ExtensionKlass:
294
                    # Yee Ha!
295 296 297 298 299
                    dict={}
                    dict.update(object.__dict__)
                    del dict['_p_jar']
                    args=object.__name__, object.__bases__, dict
                    state=None
Jim Fulton's avatar
alpha1  
Jim Fulton committed
300
                else:
301 302 303 304 305 306 307 308 309 310 311
                    if hasattr(klass, '__getinitargs__'):
                        args = object.__getinitargs__()
                        len(args) # XXX Assert it's a sequence
                    else:
                        args = None # New no-constructor protocol!

                    try: module=klass.__module__
                    except: module=''
                    if module: klass=module, klass.__name__
                    state=object.__getstate__()

Jim Fulton's avatar
alpha1  
Jim Fulton committed
312 313
                seek(0)
                clear_memo()
314
                dump((klass,args))
Jim Fulton's avatar
alpha1  
Jim Fulton committed
315 316
                dump(state)
                p=file()
Jim Fulton's avatar
Jim Fulton committed
317
                object._p_serial=dbstore(oid,serial,p,version,transaction)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
318
                object._p_changed=0
319 320 321 322 323
                try: cache[oid]=object
                except:
                    # Dang, I bet its wrapped:
                    if hasattr(object, 'aq_base'):
                        cache[oid]=object.aq_base
Jim Fulton's avatar
alpha1  
Jim Fulton committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

        return topoid

    def db(self): return self._db

    def getVersion(self): return self._version
        
    def invalidate(self, oid):
        """Invalidate a particular oid

        This marks the oid as invalid, but doesn't actually invalidate
        it.  The object data will be actually invalidated at certain
        transaction boundaries.
        """
        self._invalidated[oid]=1

    def modifiedInVersion(self, o):
        return self._db.modifiedInVersion(o._p_oid)

    def root(self): return self['\0\0\0\0\0\0\0\0']

    def setstate(self,object):
        # Note, we no longer mess with the object's state
        # flag, _p_changed.  This is the object's job.
        oid=object._p_oid
349 350
        invalid=self._invalid
        if invalid(oid) or invalid(None): raise ConflictError, oid
Jim Fulton's avatar
Jim Fulton committed
351
        p, serial = self._storage.load(oid, self._version)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
352 353 354
        file=StringIO(p)
        unpickler=Unpickler(file)
        unpickler.persistent_load=self._persistent_load
355 356 357 358 359 360 361
        try:
            unpickler.load()
            state = unpickler.load()
        except:
            t, v =sys.exc_info()[:2]
            raise
            
Jim Fulton's avatar
alpha1  
Jim Fulton committed
362 363 364 365 366
        if hasattr(object, '__setstate__'):
            object.__setstate__(state)
        else:
            d=object.__dict__
            for k,v in state.items(): d[k]=v
Jim Fulton's avatar
Jim Fulton committed
367
        object._p_serial=serial
Jim Fulton's avatar
alpha1  
Jim Fulton committed
368 369 370 371

    def tpc_abort(self, transaction):
        self._storage.tpc_abort(transaction)
        cache=self._cache
372 373
        cache.invalidate(self._invalidated)
        cache.invalidate(self._invalidating)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
374 375

    def tpc_begin(self, transaction):
376 377
        if self._invalid(None): # Some nitwit invalidated everything!
            raise ConflictError, oid 
Jim Fulton's avatar
alpha1  
Jim Fulton committed
378 379 380 381 382
        self._invalidating=[]
        self._storage.tpc_begin(transaction)

    def tpc_finish(self, transaction):
        self._storage.tpc_finish(transaction, self.tpc_finish_)
383
        self._cache.invalidate(self._invalidated)
Jim Fulton's avatar
alpha1  
Jim Fulton committed
384 385 386 387 388

    def tpc_finish_(self):
        invalidate=self._db.invalidate
        for oid in self._invalidating: invalidate(oid, self)

389 390 391 392 393
    def sync(self):
        get_transaction().abort()
        self._cache.invalidate(self._invalidated)
        

Jim Fulton's avatar
alpha1  
Jim Fulton committed
394 395 396 397
class tConnection(Connection):

    def close(self):
        self._breakcr()
398