]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Improvements to the error messages emitted when
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 16 Mar 2011 16:30:13 +0000 (12:30 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 16 Mar 2011 16:30:13 +0000 (12:30 -0400)
querying against column-only entities in conjunction
with (typically incorrectly) using loader options,
where the parent entity is not fully present.
[ticket:2069]

CHANGES
lib/sqlalchemy/orm/interfaces.py
test/orm/test_eager_relations.py
test/orm/test_mapper.py
test/orm/test_query.py

diff --git a/CHANGES b/CHANGES
index f31af1d914c91304143c6a4cdb2598ede28ff404..4242cd06e61876241c00f9d602e58e92c72756ee 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -47,6 +47,12 @@ CHANGES
     to state actually within the current flush.
     [ticket:2082]
 
+  - Improvements to the error messages emitted when
+    querying against column-only entities in conjunction
+    with (typically incorrectly) using loader options, 
+    where the parent entity is not fully present.  
+    [ticket:2069]
+
 - sql
   - Added a fully descriptive error message for the
     case where Column is subclassed and _make_proxy()
index 907dd4bf5f5269ed682f7a4be70c7e4140b5c5cc..8055aa504bb878a78c9191363e85c2cc2a075dbc 100644 (file)
@@ -422,7 +422,7 @@ class PropertyOption(MapperOption):
         state['key'] = tuple(ret)
         self.__dict__ = state
 
-    def _find_entity( self, query, mapper, raiseerr):
+    def _find_entity_prop_comparator(self, query, token, mapper, raiseerr):
         if mapperutil._is_aliased_class(mapper):
             searchfor = mapper
             isa = False
@@ -435,9 +435,27 @@ class PropertyOption(MapperOption):
                 return ent
         else:
             if raiseerr:
-                raise sa_exc.ArgumentError("Can't find entity %s in "
-                        "Query.  Current list: %r" % (searchfor,
-                        [str(m.path_entity) for m in query._entities]))
+                raise sa_exc.ArgumentError(
+                    "Can't find property '%s' on any entity "
+                    "specified in this Query." % (token,)
+                )
+            else:
+                return None
+
+    def _find_entity_basestring(self, query, token, raiseerr):
+        for ent in query._mapper_entities:
+            # return only the first _MapperEntity when searching
+            # based on string prop name.   Ideally object
+            # attributes are used to specify more exactly.
+            return ent
+        else:
+            if raiseerr:
+                raise sa_exc.ArgumentError(
+                    "Can't find property named '%s' on the first mapped "
+                    "entity in this Query. "
+                    "Consider using an attribute object instead of a "
+                    "string name to target a specific entity." % (token, )
+                )
             else:
                 return None
 
@@ -459,13 +477,13 @@ class PropertyOption(MapperOption):
                 sub_tokens = token.split(".", 1)
                 token = sub_tokens[0]
                 tokens.extendleft(sub_tokens[1:])
-
                 if not entity:
                     if current_path:
                         if current_path[1] == token:
                             current_path = current_path[2:]
                             continue
-                    entity = query._entity_zero()
+                    entity = self._find_entity_basestring(query, 
+                            token, raiseerr)
                     path_element = entity.path_entity
                     mapper = entity.mapper
                 mappers.append(mapper)
@@ -473,7 +491,6 @@ class PropertyOption(MapperOption):
                     prop = mapper.get_property(token)
                 else:
                     prop = None
-                key = token
             elif isinstance(token, PropComparator):
                 prop = token.property
                 if not entity:
@@ -482,14 +499,13 @@ class PropertyOption(MapperOption):
                                 prop.key]:
                             current_path = current_path[2:]
                             continue
-                    entity = self._find_entity(query,
-                            token.parententity, raiseerr)
+                    entity = self._find_entity_prop_comparator(query,
+                            prop.key, token.parententity, raiseerr)
                     if not entity:
                         return [], []
                     path_element = entity.path_entity
                     mapper = entity.mapper
                 mappers.append(prop.parent)
-                key = prop.key
             else:
                 raise sa_exc.ArgumentError('mapper option expects '
                         'string key or list of attributes')
index 095254b2787e7a7fc297d293f2ea1b9a5f7cd9a1..8fdb844b4e2d87f7846763882991b187b6f21b6f 100644 (file)
@@ -4,7 +4,7 @@ from test.lib.testing import eq_, is_, is_not_
 import sqlalchemy as sa
 from test.lib import testing
 from sqlalchemy.orm import joinedload, deferred, undefer, \
-    joinedload_all, backref
+    joinedload_all, backref, eagerload
 from sqlalchemy import Integer, String, Date, ForeignKey, and_, select, \
     func
 from test.lib.schema import Table, Column
@@ -332,7 +332,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
         mapper(Item, items, properties = dict(
                 keywords = relationship(Keyword, secondary=item_keywords, lazy='select',
                                     order_by=keywords.c.id)))
-
         q = create_session().query(Item)
 
         def go():
@@ -342,6 +341,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
 
         self.assert_sql_count(testing.db, go, 1)
 
+
     @testing.resolve_artifact_names
     def test_cyclical(self):
         """A circular eager relationship breaks the cycle with a lazy loader"""
index e26097116c6748414806b90731dfc524f360fcef..552b631c244f89ac9c8e3dfcbeeeb7a5b274c306 100644 (file)
@@ -1493,8 +1493,8 @@ class DeepOptionsTest(_fixtures.FixtureTest):
 
         assert_raises_message(
             sa.exc.ArgumentError,
-            r"Can't find entity Mapper\|Order\|orders in Query.  "
-            r"Current list: \['Mapper\|User\|users'\]",
+            "Can't find property 'items' on any entity "
+            "specified in this Query.",
             sess.query(User).options, sa.orm.joinedload(Order.items))
 
         # joinedload "keywords" on items.  it will lazy load "orders", then
index 8e94f56c933af29964d85e18cfb68d5b7a7a7c27..6a7e7d6246404a8d77a123d884e82caa2761ad03 100644 (file)
@@ -2137,3 +2137,74 @@ class OptionsTest(QueryTest):
         opt = self._option_fixture(Address.user, User.addresses)
         self._assert_path_result(opt, q, [], [])
 
+class OptionsNoPropTest(_base.MappedTest):
+    """test the error messages emitted when using property
+    options in conjunection with column-only entities.
+    
+    """
+
+    @testing.resolve_artifact_names
+    def test_option_with_mapper_using_basestring(self):
+        self._assert_option([Item], 'keywords')
+
+    @testing.resolve_artifact_names
+    def test_option_with_mapper_using_PropCompatator(self):
+        self._assert_option([Item], Item.keywords)
+
+    @testing.resolve_artifact_names
+    def test_option_with_mapper_then_column_using_basestring(self):
+        self._assert_option([Item, Item.id], 'keywords')
+
+    @testing.resolve_artifact_names
+    def test_option_with_mapper_then_column_using_PropComparator(self):
+        self._assert_option([Item, Item.id], Item.keywords)
+
+    @testing.resolve_artifact_names
+    def test_option_with_column_then_mapper_using_basestring(self):
+        self._assert_option([Item.id, Item], 'keywords')
+
+    @testing.resolve_artifact_names
+    def test_option_with_column_then_mapper_using_PropComparator(self):
+        self._assert_option([Item.id, Item], Item.keywords)
+
+    @testing.resolve_artifact_names
+    def test_option_with_column_using_basestring(self):
+        message = \
+            "Can't find property named 'keywords' on the first mapped "\
+            "entity in this Query. Consider using an attribute object "\
+            "instead of a string name to target a specific entity."
+        self._assert_eager_with_just_column_exception(Item.id,
+                'keywords', message)
+
+    @testing.resolve_artifact_names
+    def test_option_with_column_using_PropComparator(self):
+        message = \
+            "Can't find property 'keywords' on any entity specified "\
+            "in this Query\."
+        self._assert_eager_with_just_column_exception(Item.id,
+                Item.keywords, message)
+
+    @classmethod
+    def define_tables(cls, metadata):
+        pass
+
+    @classmethod
+    @testing.resolve_artifact_names
+    def setup_mappers(cls):
+        mapper(Keyword, keywords)
+        mapper(Item, items,
+               properties=dict(keywords=relationship(Keyword,
+               secondary=item_keywords)))
+
+    @testing.resolve_artifact_names
+    def _assert_option(self, entity_list, option):
+        q = create_session().query(*entity_list).\
+                            options(eagerload(option))
+        key = ('loaderstrategy', (class_mapper(Item), 'keywords'))
+        assert key in q._attributes
+
+    def _assert_eager_with_just_column_exception(self, column,
+            eager_option, message):
+        assert_raises_message(sa.exc.ArgumentError, message,
+                              create_session().query(column).options,
+                              eagerload(eager_option))