Commit c117040f authored by Tim Peters's avatar Tim Peters

Introduced new exception ConnectionStateError, as a subclass of POSError,

raised when an operation on a Connection can't be performed because the
Connection is in "a wrong state".

Connection.close() now raises this exception if the connection is
currently joined to a transaction (most obviously, if the transaction
the connection is joined to has modified objects waiting for commit
or abort).

Also changed other appropriate instances of RuntimeError to raise
ConnectionStateError instead (e.g., trying to load an object from a
closed connection).
parent 8a0b33b6
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Database connection support """Database connection support
$Id: Connection.py,v 1.150 2004/04/16 14:19:11 jeremy Exp $""" $Id: Connection.py,v 1.151 2004/04/16 19:07:00 tim_one Exp $"""
import logging import logging
import sys import sys
...@@ -31,7 +31,8 @@ import transaction ...@@ -31,7 +31,8 @@ import transaction
from ZODB.ConflictResolution import ResolvedSerial from ZODB.ConflictResolution import ResolvedSerial
from ZODB.ExportImport import ExportImport from ZODB.ExportImport import ExportImport
from ZODB.POSException \ from ZODB.POSException \
import ConflictError, ReadConflictError, InvalidObjectReference import ConflictError, ReadConflictError, InvalidObjectReference, \
ConnectionStateError
from ZODB.TmpStore import TmpStore from ZODB.TmpStore import TmpStore
from ZODB.utils import oid_repr, z64, positive_id from ZODB.utils import oid_repr, z64, positive_id
from ZODB.serialize import ObjectWriter, ConnectionObjectReader, myhasattr from ZODB.serialize import ObjectWriter, ConnectionObjectReader, myhasattr
...@@ -316,11 +317,11 @@ class Connection(ExportImport, object): ...@@ -316,11 +317,11 @@ class Connection(ExportImport, object):
object does not exist as of the current transaction, but object does not exist as of the current transaction, but
existed in the past. It may even exist again in the existed in the past. It may even exist again in the
future, if the transaction that removed it is undone. future, if the transaction that removed it is undone.
- `RuntimeError`: if the connection is closed. - `ConnectionStateError`: if the connection is closed.
""" """
if self._storage is None: if self._storage is None:
# XXX Should this be a ZODB-specific exception? # XXX Should this be a ZODB-specific exception?
raise RuntimeError("The database connection is closed") raise ConnectionStateError("The database connection is closed")
obj = self._cache.get(oid, None) obj = self._cache.get(oid, None)
if obj is not None: if obj is not None:
...@@ -365,11 +366,10 @@ class Connection(ExportImport, object): ...@@ -365,11 +366,10 @@ class Connection(ExportImport, object):
- `TypeError`: if obj is not a persistent object. - `TypeError`: if obj is not a persistent object.
- `InvalidObjectReference`: if obj is already associated - `InvalidObjectReference`: if obj is already associated
with another connection. with another connection.
- `RuntimeError`: if the connection is closed. - `ConnectionStateError`: if the connection is closed.
""" """
if self._storage is None: if self._storage is None:
# XXX Should this be a ZODB-specific exception? raise ConnectionStateError("The database connection is closed")
raise RuntimeError("The database connection is closed")
marker = object() marker = object()
oid = getattr(obj, "_p_oid", marker) oid = getattr(obj, "_p_oid", marker)
...@@ -532,6 +532,12 @@ class Connection(ExportImport, object): ...@@ -532,6 +532,12 @@ class Connection(ExportImport, object):
onCloseCallback() are invoked and the cache is scanned for onCloseCallback() are invoked and the cache is scanned for
old objects. old objects.
""" """
if not self._needs_to_join:
# We're currently joined to a transaction.
raise ConnectionStateError("Cannot close a connection joined to "
"a transaction")
if self._cache is not None: if self._cache is not None:
self._cache.incrgc() # This is a good time to do some GC self._cache.incrgc() # This is a good time to do some GC
...@@ -671,14 +677,12 @@ class Connection(ExportImport, object): ...@@ -671,14 +677,12 @@ class Connection(ExportImport, object):
def getVersion(self): def getVersion(self):
if self._storage is None: if self._storage is None:
# XXX Should this be a ZODB-specific exception? raise ConnectionStateError("The database connection is closed")
raise RuntimeError("The database connection is closed")
return self._version return self._version
def isReadOnly(self): def isReadOnly(self):
if self._storage is None: if self._storage is None:
# XXX Should this be a ZODB-specific exception? raise ConnectionStateError("The database connection is closed")
raise RuntimeError("The database connection is closed")
return self._storage.isReadOnly() return self._storage.isReadOnly()
def invalidate(self, tid, oids): def invalidate(self, tid, oids):
...@@ -776,7 +780,7 @@ class Connection(ExportImport, object): ...@@ -776,7 +780,7 @@ class Connection(ExportImport, object):
msg = ("Shouldn't load state for %s " msg = ("Shouldn't load state for %s "
"when the connection is closed" % oid_repr(oid)) "when the connection is closed" % oid_repr(oid))
self._log.error(msg) self._log.error(msg)
raise RuntimeError(msg) raise ConnectionStateError(msg)
try: try:
self._setstate(obj) self._setstate(obj)
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""ZODB-defined exceptions """ZODB-defined exceptions
$Id: POSException.py,v 1.23 2004/02/27 00:31:53 faassen Exp $""" $Id: POSException.py,v 1.24 2004/04/16 19:07:00 tim_one Exp $"""
from ZODB.utils import oid_repr, serial_repr from ZODB.utils import oid_repr, serial_repr
...@@ -268,7 +268,7 @@ class ExportError(POSError): ...@@ -268,7 +268,7 @@ class ExportError(POSError):
"""An export file doesn't have the right format.""" """An export file doesn't have the right format."""
class Unsupported(POSError): class Unsupported(POSError):
"""An feature that is unsupported bt the storage was used.""" """A feature was used that is not supported by the storage."""
class InvalidObjectReference(POSError): class InvalidObjectReference(POSError):
"""An object contains an invalid reference to another object. """An object contains an invalid reference to another object.
...@@ -281,3 +281,13 @@ class InvalidObjectReference(POSError): ...@@ -281,3 +281,13 @@ class InvalidObjectReference(POSError):
XXX The exception ought to have a member that is the invalid object. XXX The exception ought to have a member that is the invalid object.
""" """
class ConnectionStateError(POSError):
"""A Connection isn't in the required state for an operation.
o An operation such as a load is attempted on a closed connection.
o An attempt to close a connection is made while the connection is
still joined to a transaction (for example, a transaction is in
progress, with uncommitted modifications in the connection).
"""
...@@ -236,12 +236,48 @@ class UserMethodTests(unittest.TestCase): ...@@ -236,12 +236,48 @@ class UserMethodTests(unittest.TestCase):
>>> cn.get(p64(0)) >>> cn.get(p64(0))
Traceback (most recent call last): Traceback (most recent call last):
... ...
RuntimeError: The database connection is closed ConnectionStateError: The database connection is closed
>>> p = Persistent() >>> p = Persistent()
>>> cn.add(p) >>> cn.add(p)
Traceback (most recent call last): Traceback (most recent call last):
... ...
RuntimeError: The database connection is closed ConnectionStateError: The database connection is closed
"""
def test_close_with_pending_changes(self):
r"""doctest to ensure close() w/ pending changes complains
>>> import transaction
Just opening and closing is fine.
>>> db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>")
>>> cn = db.open()
>>> cn.close()
Opening, making a change, committing, and closing is fine.
>>> cn = db.open()
>>> cn.root()['a'] = 1
>>> transaction.commit()
>>> cn.close()
Opening, making a change, committing, and aborting is fine.
>>> cn = db.open()
>>> cn.root()['a'] = 1
>>> transaction.abort()
>>> cn.close()
But trying to close with a change pending complains.
>>> cn = db.open()
>>> cn.root()['a'] = 1
>>> cn.close()
Traceback (most recent call last):
...
ConnectionStateError: Cannot close a connection joined to a transaction
This leaves the connection as it was, so we can still commit
the change.
>>> transaction.commit()
>>> cn.close()
""" """
def test_onCloseCallbacks(self): def test_onCloseCallbacks(self):
...@@ -298,7 +334,7 @@ class UserMethodTests(unittest.TestCase): ...@@ -298,7 +334,7 @@ class UserMethodTests(unittest.TestCase):
>>> cn.isReadOnly() >>> cn.isReadOnly()
Traceback (most recent call last): Traceback (most recent call last):
... ...
RuntimeError: The database connection is closed ConnectionStateError: The database connection is closed
An expedient way to create a read-only storage: An expedient way to create a read-only storage:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment