from sqlalchemy import *
from sqlalchemy import exc as sa_exc
from sqlalchemy.orm import *
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import fixtures
from sqlalchemy import testing


class CompileTest(fixtures.ORMTest):
    """test various mapper compilation scenarios"""

    def teardown(self):
        clear_mappers()

    def test_with_polymorphic(self):
        metadata = MetaData(testing.db)

        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,
            with_polymorphic=('*', order_join),
            polymorphic_on=order_join.c.type,
            polymorphic_identity='order',
            properties={
                'orderproducts': relationship(OrderProduct, lazy='select', backref='order')}
            )

        mapper(Product, product,
            properties={
                'orderproducts': relationship(OrderProduct, lazy='select', backref='product')}
            )

        mapper(Employee, employee,
            properties={
                'orders': relationship(Order, lazy='select', 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.
        configure_mappers()

    def test_conflicting_backref_one(self):
        """test that conflicting backrefs raises an exception"""

        metadata = MetaData(testing.db)

        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,
            with_polymorphic=('*', order_join),
            polymorphic_on=order_join.c.type,
            polymorphic_identity='order',
            properties={
                'orderproducts': relationship(OrderProduct, lazy='select', backref='product')}
            )

        mapper(Product, product,
            properties={
                'orderproducts': relationship(OrderProduct, lazy='select', backref='product')}
            )

        mapper(OrderProduct, orderproduct)

        assert_raises_message(
            sa_exc.ArgumentError,
            "Error creating backref",
            configure_mappers
        )

    def test_misc_one(self):
        metadata = MetaData(testing.db)
        node_table = Table("node", metadata,
            Column('node_id', Integer, primary_key=True),
            Column('name_index', Integer, nullable=True),
            )
        node_name_table = Table("node_name", metadata,
            Column('node_name_id', Integer, primary_key=True),
            Column('node_id', Integer, ForeignKey('node.node_id')),
            Column('host_id', Integer, ForeignKey('host.host_id')),
            Column('name', String(64), nullable=False),
            )
        host_table = Table("host", metadata,
            Column('host_id', Integer, primary_key=True),
            Column('hostname', String(64), nullable=False,
        unique=True),
            )
        metadata.create_all()
        try:
            node_table.insert().execute(node_id=1, node_index=5)
            class Node(object):pass
            class NodeName(object):pass
            class Host(object):pass

            node_mapper = mapper(Node, node_table)
            host_mapper = mapper(Host, host_table)
            node_name_mapper = mapper(NodeName, node_name_table,
            properties = {
                'node' : relationship(Node, backref=backref('names')),
                'host' : relationship(Host),
                }
            )
            sess = create_session()
            assert sess.query(Node).get(1).names == []
        finally:
            metadata.drop_all()

    def test_conflicting_backref_two(self):
        meta = MetaData()

        a = Table('a', meta, Column('id', Integer, primary_key=True))
        b = Table('b', meta, Column('id', Integer, primary_key=True),
                                Column('a_id', Integer, ForeignKey('a.id')))

        class A(object):
            pass
        class B(object):
            pass

        mapper(A, a, properties={
            'b':relationship(B, backref='a')
        })
        mapper(B, b, properties={
            'a':relationship(A, backref='b')
        })

        assert_raises_message(
            sa_exc.ArgumentError,
            "Error creating backref",
            configure_mappers
        )

    def test_conflicting_backref_subclass(self):
        meta = MetaData()

        a = Table('a', meta, Column('id', Integer, primary_key=True))
        b = Table('b', meta, Column('id', Integer, primary_key=True),
                                Column('a_id', Integer, ForeignKey('a.id')))

        class A(object):
            pass
        class B(object):
            pass
        class C(B):
            pass

        mapper(A, a, properties={
            'b': relationship(B, backref='a'),
            'c': relationship(C, backref='a')
        })
        mapper(B, b)
        mapper(C, None, inherits=B)

        assert_raises_message(
            sa_exc.ArgumentError,
            "Error creating backref",
            configure_mappers
        )
