Source code for relstorage.interfaces

# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2019 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Interfaces for top-level RelStorage components.

These interfaces aren't meant to be considered public, they exist to
serve as documentation and for validation of RelStorage internals.

"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from zope.interface import Interface
from zope.interface import Attribute

import ZODB.interfaces
from ZODB import POSException

# pylint:disable=inherit-non-class, no-self-argument, no-method-argument
# pylint:disable=too-many-ancestors
try:
    from zope.schema import Tuple
    from zope.schema import Object
    from zope.schema import Bool
    from zope.schema import Int
    from zope.interface.common.interfaces import IException
except ImportError: # pragma: no cover
    # We have nti.testing -> zope.schema as a test dependency; but we
    # don't have it as a hard-coded runtime dependency because we
    # don't want to force a version on consumers of RelStorage.
    class _Field(Attribute):
        __allowed_kw__ = ()
        def __init__(self, description, required=False, **kwargs):
            description = "%s (required? %s)" % (description, required)
            for k in self.__allowed_kw__:
                kwargs.pop(k, None)
            if kwargs:
                raise TypeError("Unexpected keyword arguments: %r" % (kwargs,))
            Attribute.__init__(self, description)

[docs] class Tuple(_Field): __allowed_kw__ = ('value_type', )
[docs] class Object(_Field): def __init__(self, schema, description=''): description = "%s (Must provide %s)" % (description, schema) super().__init__(description)
Bool = _Field Int = _Field class Factory(_Field): def __init__(self, schema, description='', **kwargs): description = "%s (Must implement %s)" % (description, schema) super().__init__(description, **kwargs) IException = Interface else: from zope.schema.interfaces import SchemaNotProvided as _SchemaNotProvided from zope.schema import Field as _Field
[docs] class Factory(_Field): def __init__(self, schema, **kw): self.schema = schema _Field.__init__(self, **kw) def _validate(self, value): super()._validate(value) if not self.schema.implementedBy(value): raise _SchemaNotProvided(self.schema, value).with_field_and_value(self, value)
__all__ = [ 'Tuple', 'Object', 'Bool', 'TID', 'OID', 'IException', 'Factory', 'IMVCCDatabaseCoordinator', 'IMVCCDatabaseViewer' ]
[docs] class OID(Int): """ A ZODB object identifier, represented as a 64-bit integer. """
[docs] class TID(Int): """ A ZODB transaction identifier, represented as a 64-bit integer. Traditionally, ZODB TIDs are created and derived using `persistent.timestamp.TimeStamp`, which is a reference to the current `time.time` value. """
### # Efficiently handling multiple views of a database # within a process. ###
[docs] class IMVCCDatabaseViewer(Interface): """ A component that has a consistent, point-in-time view of a database. This is implemented using an RDBMS connection (session) with ``REPEATABLE READ`` or higher isolation level. In the context of ZODB, this means that this view contains all the data for a particular transaction identifier (TID, also "revision" or "revid"; the contents of a particular persistent object's ``_p_serial``) and the transactions that come before it (lower TIDs), but not any newer (higher numbered) transactions that may exist. The highest available TID is updated between transactions. Viewers are associated with a :class:`IMVCCDatabaseCoordinator`. """ highest_visible_tid = TID( description=( """ The identifier of the most recent transaction viewable to this component. A value of ``None`` means that we have no idea what transactions even exist. """), required=False)
class IDetachableMVCCDatabaseViewer(IMVCCDatabaseViewer): """ An MVCC database viewer that can optionally be marked as detached from ongoing MVCC operations. Viewer may be detached when their coordinator projects it to become too expensive to keep them updated (for example, they have fallen too far behind the leading edge of the database, possibly due to sitting idle). Views are detached instead of destroying their ``highest_visible_tid`` because it's possible they may actively be working on their view of the database (for example, a long running read transaction --- this is exactly what ZODB historical connections become). Once detached, such a view cannot be updated to a more current state. Instead, it must be recreated and all of its cached state thrown away. Detached viewers are not counted in their coordinator's ``minimum_highest_visible_tid``. """ detached = Bool( description="Is this object detached?" )
[docs] class IMVCCDatabaseCoordinator(Interface): """ A component that handles tracking multiple `IMVCCDatabaseViewer` components fulfilling the same role. These components would be created by calling :meth:`ZODB.interfaces.IMVCCStorage.new_instance` on the `relstorage.interfaces.IRelStorage` owned by the `ZODB.interfaces.IDatabase` object. There will be one for each `ZODB.interfaces.IConnection` object in a pool. By tracking all existing components for a database within the same process, we can know what the maximum and minimum visible TIDs are within the process. When the minimum visible TID is incremented, we have an opportunity to take actions such as freeing data no longer needed (because it has been updated in a subsequent transaction and we now know the old states aren't visible to any current connections.) """ def register(viewer): """ Register the *viewer* to be tracked by this object. A matching call to :meth:`unregister` is expected. """ def unregister(viewer): """ Stop tracking the *viewer*. """ maximum_highest_visible_tid = TID( description=( """ Across all tracked components, report the current highest visible tid. This is the most recent transaction that can be seen in this process. """), required=False) minimum_highest_visible_tid = TID( description=( """ Across all tracked components, report the current minimum highest visible tid. This is the oldest transaction potentially being viewed in this process. """), required=False)
class IRelStorage( ZODB.interfaces.IMVCCAfterCompletionStorage, # IMVCCStorage <- IStorage ZODB.interfaces.IMultiCommitStorage, # mandatory in ZODB5, returns tid from tpc_finish. ZODB.interfaces.IStorageRestoreable, # tpc_begin(tid=) and restore() ZODB.interfaces.IStorageIteration, # iterator() ZODB.interfaces.IStorageCurrentRecordIteration, # record_iternext() ZODB.interfaces.ReadVerifyingStorage, # checkCurrentSerialInTransaction() IMVCCDatabaseViewer, ): """ The relational storage backend. These objects are not thread-safe. Instances may optionally implement some other interfaces, depending on their configuration. These include: - :class:`ZODB.interfaces.IBlobStorage` and :class:`ZODB.interfaces.IBlobStorage` if a ``blob-dir`` is configured. - :class:`ZODB.interfaces.IStorageUndoable` if ``keep-history`` is true. """ class POSKeyError(POSException.POSKeyError): """ A POSKeyError that records some extra information and prints it. """ extra = None def __init__(self, oid_bytes, extra=None, **kwargs): if extra is None: extra = kwargs self.extra = extra if extra: super().__init__(oid_bytes, extra) else: super().__init__(oid_bytes) def __str__(self): s = super().__str__() if self.extra: s = "%s (%r)" % (s, self.extra) return s