]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
added check for conflicting backrefs + unit test
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Jun 2006 17:47:21 +0000 (17:47 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 19 Jun 2006 17:47:21 +0000 (17:47 +0000)
identified unit test where mapper properties must be set up before the surrogate mapper is created

CHANGES
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/properties.py
test/orm/alltests.py
test/orm/compile.py [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index c847502277fa3b744fbfa7a6ea84060bb3923982..f08ae12c8f6597d22ae4cd15d8e1f04a43f6aa42 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,7 @@ would just return a blank list or None, it now raises an exception.
 the given object was formerly attached to was garbage collected;
 otherwise still requires you explicitly remove the instance from 
 the previous Session.
+- fixes to mapper compilation, checking for more error conditions
 
 0.2.3
 - overhaul to mapper compilation to be deferred.  this allows mappers
index 2d118e9e56b4d4b65de1bfbaff37aebf6c58a21f..8c5d5d0768c101a337cf78b0ff6fbc2307d08d4a 100644 (file)
@@ -161,13 +161,13 @@ class Mapper(object):
         constructed in an arbitrary order, completing their relationships when they have all been established."""
         if self.__is_compiled:
             return self
-        #print "COMPILING!", self.class_key
+        #print "COMPILING!", self.class_key, "non primary: ", self.non_primary
         self.__is_compiled = True
         self._compile_extensions()
         self._compile_inheritance()
         self._compile_tables()
-        self._compile_selectable()
         self._compile_properties()
+        self._compile_selectable()
         self._initialize_properties()
         
         # compile some other mappers which have backrefs to this mapper
index a6125c89433556167b5938ce50e1dcd52caa19e8..4e096d99bd289f231b718492290a4c10fd94086a 100644 (file)
@@ -186,6 +186,13 @@ class PropertyLoader(mapper.MapperProperty):
         """template method for subclasses of PropertyLoader"""
         pass
     
+    def _get_target_class(self):
+        """return the target class of the relation, even if the property has not been initialized yet."""
+        if isinstance(self.argument, type):
+            return self.argument
+        else:
+            return self.argument.class_
+        
     def do_init(self, key, parent):
         import sqlalchemy.orm
         if isinstance(self.argument, type):
@@ -687,6 +694,9 @@ class BackRef(object):
             mapper._compile_property(self.key, relation);
         else:
             # else set one of us as the "backreference"
+            parent = prop.parent.primary_mapper()
+            if parent.class_ is not mapper.props[self.key]._get_target_class():
+                raise exceptions.ArgumentError("Backrefs do not match:  backref '%s' expects to connect to %s, but found a backref already connected to %s" % (self.key, str(parent.class_), str(mapper.props[self.key].mapper.class_)))
             if not mapper.props[self.key].is_backref:
                 prop.is_backref=True
                 prop._dependency_processor.is_backref=True
index 4c038f12217c5262e8fe17370ea3029b3dccbd3f..cfbfdb70b510f16ab6b197cb966dbe1a0a7ddd11 100644 (file)
@@ -19,6 +19,7 @@ def suite():
         'orm.poly_linked_list',
 
        'orm.entity',
+       'orm.compile',
        'orm.manytomany',
        'orm.onetoone',
        'orm.inheritance',
diff --git a/test/orm/compile.py b/test/orm/compile.py
new file mode 100644 (file)
index 0000000..c927ae5
--- /dev/null
@@ -0,0 +1,121 @@
+from sqlalchemy import *
+import testbase
+
+class CompileTest(testbase.AssertMixin):
+    """test various mapper compilation scenarios"""
+    def tearDownAll(self):
+        clear_mappers()
+        
+    def testone(self):
+        global metadata, order, employee, product, tax, orderproduct
+        metadata = BoundMetaData(engine)
+
+        order = Table('orders', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('employee_id', Integer, ForeignKey('employees.id'), nullable=False),
+            Column('type', Unicode(16)))
+
+        employee = Table('employees', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('name', Unicode(16), unique=True, nullable=False))
+
+        product = Table('products', metadata,
+            Column('id', Integer, primary_key=True),
+        )
+
+        orderproduct = Table('orderproducts', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('order_id', Integer, ForeignKey("orders.id"), nullable=False),
+            Column('product_id', Integer, ForeignKey("products.id"), nullable=False),
+        )
+
+        class Order(object):
+            pass
+
+        class Employee(object):
+            pass
+
+        class Product(object):
+            pass
+
+        class OrderProduct(object):
+            pass
+
+        order_join = order.select().alias('pjoin')
+
+        order_mapper = mapper(Order, order, 
+            select_table=order_join, 
+            polymorphic_on=order_join.c.type, 
+            polymorphic_identity='order',
+            properties={
+                'orderproducts': relation(OrderProduct, lazy=True, backref='order')}
+            )
+
+        mapper(Product, product,
+            properties={
+                'orderproducts': relation(OrderProduct, lazy=True, backref='product')}
+            )
+
+        mapper(Employee, employee,
+            properties={
+                'orders': relation(Order, lazy=True, backref='employee')})
+
+        mapper(OrderProduct, orderproduct)
+        
+        # this requires that the compilation of order_mapper's "surrogate mapper" occur after
+        # the initial setup of MapperProperty objects on the mapper.
+        class_mapper(Product).compile()
+
+    def testtwo(self):
+        """test that conflicting backrefs raises an exception"""
+        global metadata, order, employee, product, tax, orderproduct
+        metadata = BoundMetaData(engine)
+
+        order = Table('orders', metadata, 
+            Column('id', Integer, primary_key=True),
+            Column('type', Unicode(16)))
+
+        product = Table('products', metadata,
+            Column('id', Integer, primary_key=True),
+        )
+
+        orderproduct = Table('orderproducts', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('order_id', Integer, ForeignKey("orders.id"), nullable=False),
+            Column('product_id', Integer, ForeignKey("products.id"), nullable=False),
+        )
+
+        class Order(object):
+            pass
+
+        class Product(object):
+            pass
+
+        class OrderProduct(object):
+            pass
+
+        order_join = order.select().alias('pjoin')
+
+        order_mapper = mapper(Order, order, 
+            select_table=order_join, 
+            polymorphic_on=order_join.c.type, 
+            polymorphic_identity='order',
+            properties={
+                'orderproducts': relation(OrderProduct, lazy=True, backref='product')}
+            )
+
+        mapper(Product, product,
+            properties={
+                'orderproducts': relation(OrderProduct, lazy=True, backref='product')}
+            )
+
+        mapper(OrderProduct, orderproduct)
+
+        try:
+            class_mapper(Product).compile()
+            assert False
+        except exceptions.ArgumentError, e:
+            assert str(e).index("Backrefs do not match") > -1
+
+if __name__ == '__main__':
+    testbase.main()