| 
16 | 16 | 
 
  | 
17 | 17 | import doctest  | 
18 | 18 | import time  | 
 | 19 | +import warnings  | 
 | 20 | +import gc  | 
19 | 21 | 
 
  | 
20 | 22 | from persistent import Persistent  | 
21 | 23 | from persistent.mapping import PersistentMapping  | 
22 | 24 | from ZODB import DB  | 
23 |  | -from ZODB.POSException import ConflictError, StorageError  | 
 | 25 | +from ZODB.POSException import ConflictError, StorageError, POSKeyError, \  | 
 | 26 | +                              ReadConflictError  | 
24 | 27 | from ZODB.serialize import referencesf  | 
25 | 28 | from ZODB.tests.MinPO import MinPO  | 
26 | 29 | from ZODB.tests.MTStorage import TestThread  | 
@@ -528,6 +531,109 @@ def checkPackOnlyOneObject(self):  | 
528 | 531 |         eq(pobj.getoid(), oid2)  | 
529 | 532 |         eq(pobj.value, 11)  | 
530 | 533 | 
 
  | 
 | 534 | +    def checkPackVSConnectionGet(self):  | 
 | 535 | +        # verify behaviour of Connection.get vs simultaneous pack:  | 
 | 536 | +        #  | 
 | 537 | +        # For deleted objects, in normal circumstances, get should raise POSKeyError.  | 
 | 538 | +        # However when a pack was run simultaneously with packtime going after  | 
 | 539 | +        # connection view of the database, for storages that do not implement  | 
 | 540 | +        # IMVCCStorage natively, get should raise ReadConflictError instead.  | 
 | 541 | +        #  | 
 | 542 | +        # IMVCCStorage storages are not affected, since they natively provide  | 
 | 543 | +        # isolation, via e.g. RDBMS in case of RelStorage. The isolation  | 
 | 544 | +        # property provided by RDBMS guarantees that connection view of the  | 
 | 545 | +        # database is not affected by other changes - e.g. pack - until  | 
 | 546 | +        # connection's transaction is complete.  | 
 | 547 | +        db = DB(self._storage)  | 
 | 548 | +        eq = self.assertEqual  | 
 | 549 | +        raises = self.assertRaises  | 
 | 550 | + | 
 | 551 | +        # connA is main connection through which database changes are made  | 
 | 552 | +        # @at0 - start  | 
 | 553 | +        tmA   = transaction.TransactionManager()  | 
 | 554 | +        connA = db.open(transaction_manager=tmA)  | 
 | 555 | +        rootA = connA.root()  | 
 | 556 | +        rootA[0] = None  | 
 | 557 | +        tmA.commit()  | 
 | 558 | +        at0 = rootA._p_serial  | 
 | 559 | + | 
 | 560 | +        # conn0 is "current" db connection that observes database as of @at0 state  | 
 | 561 | +        tm0   = transaction.TransactionManager()  | 
 | 562 | +        conn0 = db.open(transaction_manager=tm0)  | 
 | 563 | + | 
 | 564 | +        # @at1 - new object is added to database and linked to root  | 
 | 565 | +        rootA[0] = objA = MinPO(1)  | 
 | 566 | +        tmA.commit()  | 
 | 567 | +        oid = objA._p_oid  | 
 | 568 | +        at1 = objA._p_serial  | 
 | 569 | + | 
 | 570 | +        # conn1 is "current" db connection that observes database as of @at1 state  | 
 | 571 | +        tm1   = transaction.TransactionManager()  | 
 | 572 | +        conn1 = db.open(transaction_manager=tm1)  | 
 | 573 | + | 
 | 574 | +        # @at2 - object value is modified  | 
 | 575 | +        objA.value = 2  | 
 | 576 | +        tmA.commit()  | 
 | 577 | +        at2 = objA._p_serial  | 
 | 578 | + | 
 | 579 | +        # ---- before pack ----  | 
 | 580 | + | 
 | 581 | +        # conn0.get(oid) -> POSKeyError (as of @at0 object was not yet created)  | 
 | 582 | +        errGetNoObject = POSKeyError  | 
 | 583 | +        if (not ZODB.interfaces.IMVCCStorage.providedBy(self._storage)) and \  | 
 | 584 | +           (not ZODB.interfaces.IStorageLastPack.providedBy(self._storage)):  | 
 | 585 | +            warnings.warn("FIXME %s does not implement lastPack" %  | 
 | 586 | +                            type(self._storage), DeprecationWarning)  | 
 | 587 | +            errGetNoObject = ReadConflictError  | 
 | 588 | +        raises(errGetNoObject, conn0.get, oid)  | 
 | 589 | + | 
 | 590 | +        # conn1.get(oid) -> obj(1)  | 
 | 591 | +        obj1 = conn1.get(oid)  | 
 | 592 | +        eq(obj1._p_oid, oid)  | 
 | 593 | +        eq(obj1.value, 1)  | 
 | 594 | + | 
 | 595 | +        # --- after pack to latest db head ----  | 
 | 596 | +        db.pack(time.time()+1)  | 
 | 597 | + | 
 | 598 | +        # IMVCCStorage - as before  | 
 | 599 | +        #  | 
 | 600 | +        # !IMVCCStorage:  | 
 | 601 | +        # conn0.get(oid) -> ReadConflictError  | 
 | 602 | +        # conn1.get(oid) -> ReadConflictError  | 
 | 603 | +        #  | 
 | 604 | +        # ( conn1: the pack removes obj@at1 revision, which results in conn1  | 
 | 605 | +        #   finding the object in non-existent/deleted state, traditionally this  | 
 | 606 | +        #   is reported as ReadConflictError for conn1's transaction to be  | 
 | 607 | +        #   restarted.  | 
 | 608 | +        #  | 
 | 609 | +        #   conn0: obj@at0 never existed, but after pack@at2 it is  | 
 | 610 | +        #   indistinguishable whether obj@at0 revision was removed, or it never  | 
 | 611 | +        #   existed -> ReadConflictError too, similarly to conn1 )  | 
 | 612 | +        conn0.cacheMinimize()  | 
 | 613 | +        conn1.cacheMinimize()  | 
 | 614 | +        del obj1  | 
 | 615 | +        gc.collect()  | 
 | 616 | +        if ZODB.interfaces.IMVCCStorage.providedBy(self._storage):  | 
 | 617 | +            raises(POSKeyError, conn0.get, oid)  | 
 | 618 | +            obj1 = conn1.get(oid)  | 
 | 619 | +            eq(obj1._p_oid, oid)  | 
 | 620 | +            eq(obj1.value, 1)  | 
 | 621 | + | 
 | 622 | +        else:  | 
 | 623 | +            # !IMVCCStorage  | 
 | 624 | +            raises(ReadConflictError, conn0.get, oid)  | 
 | 625 | +            raises(ReadConflictError, conn1.get, oid)  | 
 | 626 | + | 
 | 627 | +        # connA stays ok  | 
 | 628 | +        connA.cacheMinimize()  | 
 | 629 | +        objA_ = connA.get(oid)  | 
 | 630 | +        self.assertIs(objA_, objA)  | 
 | 631 | +        eq(objA_.value, 2)  | 
 | 632 | + | 
 | 633 | +        # end  | 
 | 634 | +        self._sanity_check()  | 
 | 635 | +        db.close()  | 
 | 636 | + | 
531 | 637 | class PackableStorageWithOptionalGC(PackableStorage):  | 
532 | 638 | 
 
  | 
533 | 639 |     def checkPackAllRevisionsNoGC(self):  | 
 | 
0 commit comments