summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-04-19 18:49:58 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-04-19 18:49:58 -0400
commit7303b59b00ef0f6f9332dd0362084e092c5d5acc (patch)
treea981d23bd38d8213d610a277f665af8fa50c4365 /test
parentc33d0378802abbc729de55ba205a4309e5d68f6b (diff)
downloadsqlalchemy-7303b59b00ef0f6f9332dd0362084e092c5d5acc.tar.gz
- The "primaryjoin" model has been stretched a bit further to allow
a join condition that is strictly from a single column to itself, translated through some kind of SQL function or expression. This is kind of experimental, but the first proof of concept is a "materialized path" join condition where a path string is compared to itself using "like". The :meth:`.Operators.like` operator has also been added to the list of valid operators to use in a primaryjoin condition. fixes #3029
Diffstat (limited to 'test')
-rw-r--r--test/orm/test_rel_fn.py51
-rw-r--r--test/orm/test_relationships.py150
2 files changed, 199 insertions, 2 deletions
diff --git a/test/orm/test_rel_fn.py b/test/orm/test_rel_fn.py
index c4d811d53..f0aa538f4 100644
--- a/test/orm/test_rel_fn.py
+++ b/test/orm/test_rel_fn.py
@@ -3,7 +3,7 @@ from sqlalchemy.testing import assert_raises_message, eq_, \
from sqlalchemy.testing import fixtures
from sqlalchemy.orm import relationships, foreign, remote
from sqlalchemy import MetaData, Table, Column, ForeignKey, Integer, \
- select, ForeignKeyConstraint, exc, func, and_
+ select, ForeignKeyConstraint, exc, func, and_, String
from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE, MANYTOMANY
@@ -119,6 +119,10 @@ class _JoinFixtures(object):
("composite_target.uid", "composite_target.oid")),
)
+ cls.purely_single_col = Table('purely_single_col', m,
+ Column('path', String)
+ )
+
def _join_fixture_overlapping_three_tables(self, **kw):
def _can_sync(*cols):
for c in cols:
@@ -440,6 +444,37 @@ class _JoinFixtures(object):
**kw
)
+ def _join_fixture_purely_single_o2m(self, **kw):
+ return relationships.JoinCondition(
+ self.purely_single_col,
+ self.purely_single_col,
+ self.purely_single_col,
+ self.purely_single_col,
+ support_sync=False,
+ primaryjoin=
+ self.purely_single_col.c.path.like(
+ remote(
+ foreign(
+ self.purely_single_col.c.path.concat('%')
+ )
+ )
+ )
+ )
+
+ def _join_fixture_purely_single_m2o(self, **kw):
+ return relationships.JoinCondition(
+ self.purely_single_col,
+ self.purely_single_col,
+ self.purely_single_col,
+ self.purely_single_col,
+ support_sync=False,
+ primaryjoin=
+ remote(self.purely_single_col.c.path).like(
+ foreign(self.purely_single_col.c.path.concat('%'))
+ )
+ )
+
+
def _assert_non_simple_warning(self, fn):
assert_raises_message(
exc.SAWarning,
@@ -829,6 +864,13 @@ class ColumnCollectionsTest(_JoinFixtures, fixtures.TestBase,
]
)
+ def test_determine_local_remote_pairs_purely_single_col_o2m(self):
+ joincond = self._join_fixture_purely_single_o2m()
+ eq_(
+ joincond.local_remote_pairs,
+ [(self.purely_single_col.c.path, self.purely_single_col.c.path)]
+ )
+
class DirectionTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
def test_determine_direction_compound_2(self):
joincond = self._join_fixture_compound_expression_2(
@@ -862,6 +904,13 @@ class DirectionTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
joincond = self._join_fixture_m2o()
is_(joincond.direction, MANYTOONE)
+ def test_determine_direction_purely_single_o2m(self):
+ joincond = self._join_fixture_purely_single_o2m()
+ is_(joincond.direction, ONETOMANY)
+
+ def test_determine_direction_purely_single_m2o(self):
+ joincond = self._join_fixture_purely_single_m2o()
+ is_(joincond.direction, MANYTOONE)
class DetermineJoinTest(_JoinFixtures, fixtures.TestBase, AssertsCompiledSQL):
__dialect__ = 'default'
diff --git a/test/orm/test_relationships.py b/test/orm/test_relationships.py
index ccd54284a..3d8287b75 100644
--- a/test/orm/test_relationships.py
+++ b/test/orm/test_relationships.py
@@ -8,7 +8,7 @@ from sqlalchemy.orm import mapper, relationship, relation, \
backref, create_session, configure_mappers, \
clear_mappers, sessionmaker, attributes,\
Session, composite, column_property, foreign,\
- remote, synonym, joinedload
+ remote, synonym, joinedload, subqueryload
from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE, MANYTOMANY
from sqlalchemy.testing import eq_, startswith_, AssertsCompiledSQL, is_
from sqlalchemy.testing import fixtures
@@ -231,6 +231,154 @@ class DependencyTwoParentTest(fixtures.MappedTest):
session.flush()
+class DirectSelfRefFKTest(fixtures.MappedTest, AssertsCompiledSQL):
+ """Tests the ultimate join condition, a single column
+ that points to itself, e.g. within a SQL function or similar.
+ The test is against a materialized path setup.
+
+ this is an **extremely** unusual case::
+
+ Entity
+ ------
+ path -------+
+ ^ |
+ +---------+
+
+ In this case, one-to-many and many-to-one are no longer accurate.
+ Both relationships return collections. I'm not sure if this is a good
+ idea.
+
+ """
+
+ __dialect__ = 'default'
+
+ @classmethod
+ def define_tables(cls, metadata):
+ Table('entity', metadata,
+ Column('path', String(100), primary_key=True)
+ )
+
+ @classmethod
+ def setup_classes(cls):
+ class Entity(cls.Basic):
+ def __init__(self, path):
+ self.path = path
+
+
+ def _descendants_fixture(self, data=True):
+ Entity = self.classes.Entity
+ entity = self.tables.entity
+
+ m = mapper(Entity, entity, properties={
+ "descendants": relationship(Entity,
+ primaryjoin=
+ remote(foreign(entity.c.path)).like(
+ entity.c.path.concat('/%')),
+ viewonly=True,
+ order_by=entity.c.path)
+ })
+ configure_mappers()
+ assert m.get_property("descendants").direction is ONETOMANY
+ if data:
+ return self._fixture()
+
+ def _anscestors_fixture(self, data=True):
+ Entity = self.classes.Entity
+ entity = self.tables.entity
+
+ m = mapper(Entity, entity, properties={
+ "anscestors": relationship(Entity,
+ primaryjoin=
+ entity.c.path.like(
+ remote(foreign(entity.c.path)).concat('/%')),
+ viewonly=True,
+ order_by=entity.c.path)
+ })
+ configure_mappers()
+ assert m.get_property("anscestors").direction is ONETOMANY
+ if data:
+ return self._fixture()
+
+ def _fixture(self):
+ Entity = self.classes.Entity
+ sess = Session()
+ sess.add_all([
+ Entity("/foo"),
+ Entity("/foo/bar1"),
+ Entity("/foo/bar2"),
+ Entity("/foo/bar2/bat1"),
+ Entity("/foo/bar2/bat2"),
+ Entity("/foo/bar3"),
+ Entity("/bar"),
+ Entity("/bar/bat1")
+ ])
+ return sess
+
+ def test_descendants_lazyload(self):
+ sess = self._descendants_fixture()
+ Entity = self.classes.Entity
+ e1 = sess.query(Entity).filter_by(path="/foo").first()
+ eq_(
+ [e.path for e in e1.descendants],
+ ["/foo/bar1", "/foo/bar2", "/foo/bar2/bat1",
+ "/foo/bar2/bat2", "/foo/bar3"]
+ )
+
+ def test_anscestors_lazyload(self):
+ sess = self._anscestors_fixture()
+ Entity = self.classes.Entity
+ e1 = sess.query(Entity).filter_by(path="/foo/bar2/bat1").first()
+ eq_(
+ [e.path for e in e1.anscestors],
+ ["/foo", "/foo/bar2"]
+ )
+
+ def test_descendants_joinedload(self):
+ sess = self._descendants_fixture()
+ Entity = self.classes.Entity
+ e1 = sess.query(Entity).filter_by(path="/foo").\
+ options(joinedload(Entity.descendants)).first()
+
+ eq_(
+ [e.path for e in e1.descendants],
+ ["/foo/bar1", "/foo/bar2", "/foo/bar2/bat1",
+ "/foo/bar2/bat2", "/foo/bar3"]
+ )
+
+ def test_descendants_subqueryload(self):
+ sess = self._descendants_fixture()
+ Entity = self.classes.Entity
+ e1 = sess.query(Entity).filter_by(path="/foo").\
+ options(subqueryload(Entity.descendants)).first()
+
+ eq_(
+ [e.path for e in e1.descendants],
+ ["/foo/bar1", "/foo/bar2", "/foo/bar2/bat1",
+ "/foo/bar2/bat2", "/foo/bar3"]
+ )
+
+ def test_anscestors_joinedload(self):
+ sess = self._anscestors_fixture()
+ Entity = self.classes.Entity
+ e1 = sess.query(Entity).filter_by(path="/foo/bar2/bat1").\
+ options(joinedload(Entity.anscestors)).first()
+ eq_(
+ [e.path for e in e1.anscestors],
+ ["/foo", "/foo/bar2"]
+ )
+
+ def test_plain_join_descendants(self):
+ self._descendants_fixture(data=False)
+ Entity = self.classes.Entity
+ sess = Session()
+ self.assert_compile(
+ sess.query(Entity).join(Entity.descendants, aliased=True),
+ "SELECT entity.path AS entity_path FROM entity JOIN entity AS "
+ "entity_1 ON entity_1.path LIKE (entity.path || :path_1)"
+ )
+
+
+
class CompositeSelfRefFKTest(fixtures.MappedTest):
"""Tests a composite FK where, in
the relationship(), one col points