Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cross-database-references.txt 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. =========================
  2. Cross-Database References
  3. =========================
  4. Persistent references to objects in different databases within a
  5. multi-database are allowed.
  6. Lets set up a multi-database with 2 databases:
  7. >>> import ZODB.tests.util, transaction, persistent
  8. >>> databases = {}
  9. >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
  10. >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2')
  11. And create a persistent object in the first database:
  12. >>> tm = transaction.TransactionManager()
  13. >>> conn1 = db1.open(transaction_manager=tm)
  14. >>> p1 = MyClass()
  15. >>> conn1.root()['p'] = p1
  16. >>> tm.commit()
  17. First, we get a connection to the second database. We get the second
  18. connection using the first connection's `get_connection` method. This
  19. is important. When using multiple databases, we need to make sure we
  20. use a consistent set of connections so that the objects in the
  21. connection caches are connected in a consistent manner.
  22. >>> conn2 = conn1.get_connection('2')
  23. Now, we'll create a second persistent object in the second database.
  24. We'll have a reference to the first object:
  25. >>> p2 = MyClass()
  26. >>> conn2.root()['p'] = p2
  27. >>> p2.p1 = p1
  28. >>> tm.commit()
  29. Now, let's open a separate connection to database 2. We use it to
  30. read `p2`, use `p2` to get to `p1`, and verify that it is in database 1:
  31. >>> conn = db2.open()
  32. >>> p2x = conn.root()['p']
  33. >>> p1x = p2x.p1
  34. >>> p2x is p2, p2x._p_oid == p2._p_oid, p2x._p_jar.db() is db2
  35. (False, True, True)
  36. >>> p1x is p1, p1x._p_oid == p1._p_oid, p1x._p_jar.db() is db1
  37. (False, True, True)
  38. It isn't valid to create references outside a multi database:
  39. >>> db3 = ZODB.tests.util.DB()
  40. >>> conn3 = db3.open(transaction_manager=tm)
  41. >>> p3 = MyClass()
  42. >>> conn3.root()['p'] = p3
  43. >>> tm.commit()
  44. >>> p2.p3 = p3
  45. >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  46. Traceback (most recent call last):
  47. ...
  48. InvalidObjectReference:
  49. ('Attempt to store an object from a foreign database connection',
  50. <Connection at ...>,
  51. <ZODB.tests.testcrossdatabasereferences.MyClass...>)
  52. >>> tm.abort()
  53. Databases for new objects
  54. -------------------------
  55. Objects are normally added to a database by making them reachable from
  56. an object already in the database. This is unambiguous when there is
  57. only one database. With multiple databases, it is not so clear what
  58. happens. Consider:
  59. >>> p4 = MyClass()
  60. >>> p1.p4 = p4
  61. >>> p2.p4 = p4
  62. In this example, the new object is reachable from both `p1` in database
  63. 1 and `p2` in database 2. If we commit, which database should `p4` end up
  64. in? This sort of ambiguity could lead to subtle bugs. For that reason,
  65. an error is generated if we commit changes when new objects are
  66. reachable from multiple databases:
  67. >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  68. Traceback (most recent call last):
  69. ...
  70. InvalidObjectReference:
  71. ("A new object is reachable from multiple databases. Won't try to
  72. guess which one was correct!",
  73. <Connection at ...>,
  74. <ZODB.tests.testcrossdatabasereferences.MyClass...>)
  75. >>> tm.abort()
  76. To resolve this ambiguity, we can commit before an object becomes
  77. reachable from multiple databases.
  78. >>> p4 = MyClass()
  79. >>> p1.p4 = p4
  80. >>> tm.commit()
  81. >>> p2.p4 = p4
  82. >>> tm.commit()
  83. >>> p4._p_jar.db().database_name
  84. '1'
  85. This doesn't work with a savepoint:
  86. >>> p5 = MyClass()
  87. >>> p1.p5 = p5
  88. >>> s = tm.savepoint()
  89. >>> p2.p5 = p5
  90. >>> tm.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  91. Traceback (most recent call last):
  92. ...
  93. InvalidObjectReference:
  94. ("A new object is reachable from multiple databases. Won't try to guess
  95. which one was correct!",
  96. <Connection at ...>,
  97. <ZODB.tests.testcrossdatabasereferences.MyClass...>)
  98. >>> tm.abort()
  99. (Maybe it should.)
  100. We can disambiguate this situation by using the connection add method
  101. to explicitly say what database an object belongs to:
  102. >>> p5 = MyClass()
  103. >>> p1.p5 = p5
  104. >>> p2.p5 = p5
  105. >>> conn1.add(p5)
  106. >>> tm.commit()
  107. >>> p5._p_jar.db().database_name
  108. '1'
  109. This the most explicit and thus the best way, when practical, to avoid
  110. the ambiguity.
  111. Dissallowing implicit cross-database references
  112. -----------------------------------------------
  113. The database contructor accepts a xrefs keyword argument that defaults
  114. to True. If False is passed, the implicit cross database references
  115. are disallowed. (Note that currently, implicit cross references are
  116. the only kind of cross references allowed.)
  117. >>> databases = {}
  118. >>> db1 = ZODB.tests.util.DB(databases=databases, database_name='1')
  119. >>> db2 = ZODB.tests.util.DB(databases=databases, database_name='2',
  120. ... xrefs=False)
  121. In this example, we allow cross-references from db1 to db2, but not
  122. the other way around.
  123. >>> c1 = db1.open()
  124. >>> c2 = c1.get_connection('2')
  125. >>> c1.root.x = c2.root()
  126. >>> transaction.commit()
  127. >>> c2.root.x = c1.root()
  128. >>> transaction.commit() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
  129. Traceback (most recent call last):
  130. ...
  131. InvalidObjectReference:
  132. ("Database '2' doesn't allow implicit cross-database references",
  133. <Connection at ...>,
  134. {'x': {}})
  135. >>> transaction.abort()
  136. NOTE
  137. ----
  138. This implementation is incomplete. It allows creating and using
  139. cross-database references, however, there are a number of facilities
  140. missing:
  141. cross-database garbage collection
  142. Garbage collection is done on a database by database basis.
  143. If an object on a database only has references to it from other
  144. databases, then the object will be garbage collected when its
  145. database is packed. The cross-database references to it will be
  146. broken.
  147. cross-database undo
  148. Undo is only applied to a single database. Fixing this for
  149. multiple databases is going to be extremely difficult. Undo
  150. currently poses consistency problems, so it is not (or should not
  151. be) widely used.
  152. Cross-database aware (tolerant) export/import
  153. The export/import facility needs to be aware, at least, of cross-database
  154. references.