Commit 1a888ac3 authored by Jim Fulton's avatar Jim Fulton

Added an option to disallow cross-database references.

Include extra data in InvalidObjectReference exceptions to make
debugging easier.
parent a8346702
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
Maximum size of the ZEO blob cache, in bytes. If not set, then Maximum size of the ZEO blob cache, in bytes. If not set, then
the cache size isn't checked and the blob directory will the cache size isn't checked and the blob directory will
grow without bound. grow without bound.
This option is ignored if shared_blob_dir is true. This option is ignored if shared_blob_dir is true.
</description> </description>
</key> </key>
...@@ -125,7 +125,6 @@ ...@@ -125,7 +125,6 @@
size. This option is ignored if shared_blob_dir is true. size. This option is ignored if shared_blob_dir is true.
</description> </description>
</key> </key>
<key name="storage" default="1"> <key name="storage" default="1">
<description> <description>
The name of the storage that the client wants to use. If the The name of the storage that the client wants to use. If the
...@@ -272,12 +271,13 @@ ...@@ -272,12 +271,13 @@
</description> </description>
</key> </key>
<key name="historical-timeout" datatype="time-interval" <key name="historical-timeout" datatype="time-interval"
default="5m"/> default="5m">
<description> <description>
The minimum interval that an unused historical connection should be The minimum interval that an unused historical connection should be
kept. kept.
</description> </description>
<key name="database-name" default="unnamed"/> </key>
<key name="database-name">
<description> <description>
When multidatabases are in use, this is the name given to this When multidatabases are in use, this is the name given to this
database in the collection. The name must be unique across all database in the collection. The name must be unique across all
...@@ -288,6 +288,14 @@ ...@@ -288,6 +288,14 @@
their own config files, using the "databases" parameter of a DB their own config files, using the "databases" parameter of a DB
constructor. constructor.
</description> </description>
</key>
<key name="allow-implicit-cross-references" datatype="boolean">
<description>
If set to false, implicit cross references (the only kind
currently possible) are disallowed.
</description>
</key>
</sectiontype> </sectiontype>
<sectiontype name="blobstorage" datatype=".BlobStorage" <sectiontype name="blobstorage" datatype=".BlobStorage"
...@@ -301,7 +309,5 @@ ...@@ -301,7 +309,5 @@
</sectiontype> </sectiontype>
</component> </component>
...@@ -58,11 +58,13 @@ It isn't valid to create references outside a multi database: ...@@ -58,11 +58,13 @@ It isn't valid to create references outside a multi database:
>>> tm.commit() >>> tm.commit()
>>> p2.p3 = p3 >>> p2.p3 = p3
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
... ...
InvalidObjectReference: InvalidObjectReference:
Attempt to store an object from a foreign database connection ('Attempt to store an object from a foreign database connection',
<Connection at ...>,
<ZODB.tests.testcrossdatabasereferences.MyClass...>)
>>> tm.abort() >>> tm.abort()
...@@ -84,11 +86,14 @@ in? This sort of ambiguity could lead to subtle bugs. For that reason, ...@@ -84,11 +86,14 @@ in? This sort of ambiguity could lead to subtle bugs. For that reason,
an error is generated if we commit changes when new objects are an error is generated if we commit changes when new objects are
reachable from multiple databases: reachable from multiple databases:
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
... ...
InvalidObjectReference: A new object is reachable from multiple InvalidObjectReference:
databases. Won't try to guess which one was correct! ("A new object is reachable from multiple databases. Won't try to
guess which one was correct!",
<Connection at ...>,
<ZODB.tests.testcrossdatabasereferences.MyClass...>)
>>> tm.abort() >>> tm.abort()
...@@ -109,11 +114,14 @@ This doesn't work with a savepoint: ...@@ -109,11 +114,14 @@ This doesn't work with a savepoint:
>>> p1.p5 = p5 >>> p1.p5 = p5
>>> s = tm.savepoint() >>> s = tm.savepoint()
>>> p2.p5 = p5 >>> p2.p5 = p5
>>> tm.commit() # doctest: +NORMALIZE_WHITESPACE >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
... ...
InvalidObjectReference: A new object is reachable from multiple InvalidObjectReference:
databases. Won't try to guess which one was correct! ("A new object is reachable from multiple databases. Won't try to guess
which one was correct!",
<Connection at ...>,
<ZODB.tests.testcrossdatabasereferences.MyClass...>)
>>> tm.abort() >>> tm.abort()
...@@ -133,6 +141,37 @@ to explicitly say what database an object belongs to: ...@@ -133,6 +141,37 @@ to explicitly say what database an object belongs to:
This the most explicit and thus the best way, when practical, to avoid This the most explicit and thus the best way, when practical, to avoid
the ambiguity. the ambiguity.
Dissallowing implicit cross-database references
-----------------------------------------------
The database contructor accepts a xrefs keyword argument that defaults
to True. If False is passed, the implicit cross database references
are disallowed. (Note that currently, implicit cross references are
the only kind of cross references allowed.)
>>> databases = {}
>>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
>>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2',
... xrefs=False)
In this example, we allow cross-references from db1 to db2, but not
the other way around.
>>> c1 = db1.open()
>>> c2 = c1.get_connection('2')
>>> c1.root.x = c2.root()
>>> transaction.commit()
>>> c2.root.x = c1.root()
>>> transaction.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last):
...
InvalidObjectReference:
("Database '2' doesn't allow implicit cross-database references",
<Connection at ...>,
{'x': {}})
>>> transaction.abort()
NOTE NOTE
---- ----
......
...@@ -186,6 +186,7 @@ class ObjectWriter: ...@@ -186,6 +186,7 @@ class ObjectWriter:
>>> from ZODB.tests.util import P >>> from ZODB.tests.util import P
>>> class DummyJar: >>> class DummyJar:
... xrefs = True
... def new_oid(self): ... def new_oid(self):
... return 42 ... return 42
... def db(self): ... def db(self):
...@@ -229,11 +230,13 @@ class ObjectWriter: ...@@ -229,11 +230,13 @@ class ObjectWriter:
If the jar doesn't match that of the writer, an error is raised: If the jar doesn't match that of the writer, an error is raised:
>>> bob._p_jar = DummyJar() >>> bob._p_jar = DummyJar()
>>> writer.persistent_id(bob) # doctest: +NORMALIZE_WHITESPACE >>> writer.persistent_id(bob)
... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
... ...
InvalidObjectReference: Attempt to store an object from a InvalidObjectReference:
foreign database connection ('Attempt to store an object from a foreign database connection',
<ZODB.serialize.DummyJar instance at ...>, P(bob))
Constructor arguments used by __new__(), as returned by Constructor arguments used by __new__(), as returned by
__getnewargs__(), can affect memory allocation, but may also __getnewargs__(), can affect memory allocation, but may also
...@@ -322,8 +325,13 @@ class ObjectWriter: ...@@ -322,8 +325,13 @@ class ObjectWriter:
oid = obj._p_oid = self._jar.new_oid() oid = obj._p_oid = self._jar.new_oid()
obj._p_jar = self._jar obj._p_jar = self._jar
self._stack.append(obj) self._stack.append(obj)
elif obj._p_jar is not self._jar: elif obj._p_jar is not self._jar:
if not self._jar.db().xrefs:
raise InvalidObjectReference(
"Database %r doesn't allow implicit cross-database "
"references" % self._jar.db().database_name,
self._jar, obj)
try: try:
otherdb = obj._p_jar.db() otherdb = obj._p_jar.db()
...@@ -334,14 +342,14 @@ class ObjectWriter: ...@@ -334,14 +342,14 @@ class ObjectWriter:
if self._jar.db().databases.get(database_name) is not otherdb: if self._jar.db().databases.get(database_name) is not otherdb:
raise InvalidObjectReference( raise InvalidObjectReference(
"Attempt to store an object from a foreign " "Attempt to store an object from a foreign "
"database connection" "database connection", self._jar, obj,
) )
if self._jar.get_connection(database_name) is not obj._p_jar: if self._jar.get_connection(database_name) is not obj._p_jar:
raise InvalidObjectReference( raise InvalidObjectReference(
"Attempt to store a reference to an object from " "Attempt to store a reference to an object from "
"a separate connection to the same database or " "a separate connection to the same database or "
"multidatabase" "multidatabase", self._jar, obj,
) )
# OK, we have an object from another database. # OK, we have an object from another database.
...@@ -350,9 +358,9 @@ class ObjectWriter: ...@@ -350,9 +358,9 @@ class ObjectWriter:
if obj._p_jar._implicitlyAdding(oid): if obj._p_jar._implicitlyAdding(oid):
raise InvalidObjectReference( raise InvalidObjectReference(
"A new object is reachable from multiple databases. " "A new object is reachable from multiple databases. "
"Won't try to guess which one was correct!" "Won't try to guess which one was correct!",
self._jar, obj,
) )
klass = type(obj) klass = type(obj)
if hasattr(klass, '__getnewargs__'): if hasattr(klass, '__getnewargs__'):
......
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