Hi,
I'm attempting to do some universal filtering using a custom Query class. In this case, I'm trying to filter out all items marked as archived in two related classes. I'm having some trouble adding the required filters to the query at all the right levels.
I'm using Flask 0.9, SQLAlchemy 0.8 and PostgreSQL 9.1.5
Let's call the two classes Parent and Child, which are inheriting from Archivable:
class Archivable(object):
@declared_attr
def is_archived(cls):
return Column('is_archived', types.Boolean, nullable=False, default=False, index=True)
class Parent(base, Archivable):
__tablename__ = 'parent'
id = Column('id', types.BigInteger, primary_key=True, nullable=False)
is_archived =
class Child(base, Archivable):
__tablename__ = 'child'
id = Column('id', types.BigInteger, primary_key=True, nullable=False)
parent_id = Column('id', types.BigInteger, ForeignKey('parent.id'), nullable=False) parent = relationship('Parent', primaryjoin='Child.parent_id==Parent.id',
backref='children')
Somewhere in my code I am calling:
parent = db.session.query(Parent).filter(Parent.id == 1234).options(joinedload('children')).first()
This is resulting in a query of the form:
SELECT anon_1.*, child.*
FROM (
SELECT parent.*
FROM parent
LIMIT 1) AS anon_1
LEFT OUTER JOIN child ON child.parent_id = parent.id
which is fine.
When I try and use a custom query class to access the query and filter it however, I only seem to be able to access elements of the inner subquery. self._entities for instance only shows a single _MapperEntity Mapper|Parent|parent, self.whereclause is a BooleanClauseList of
parent.id = 1234. If I try and inject my filters at this stage using the following:
class NoArchivesQuery(Query):
def __iter__(self):
return Query.__iter__(self._add_archive_filter())
def from_self(self, *ent):
return Query.from_self(self._add_archive_filter(), *ent)
def _add_archive_filter(self):
entities = self._entities
for entity in entities:
if entity.entity_zero and hasattr(entity.entity_zero, 'class_'):
tables_involved_in_the_query.add(entity.entity_zero.class_.__table__)
filter_crits = []
for table in tables_involved_in_the_query:
if hasattr(table.c, "is_archived"):
filter_crits.append(or_(table.c.is_archived == None, table.c.is_archived == False))
if filter_crits:
return self.enable_assertions(False).filter(*filter_crits)
I can get as far as
SELECT anon_1.*, child.*
FROM (
SELECT parent.*
FROM parent
WHERE parent.id = 1234 AND (parent.is_archived IS NULL OR parent.is_archived = false) LIMIT 1) AS anon_1
LEFT OUTER JOIN child ON child.parent_id = parent.id
But this does not filter the children, and so I get all archived children back. What I would like to get back is more along the lines of:
SELECT anon_1.*, child.*
FROM (
SELECT parent.*
FROM parent
WHERE parent.id = 1234 AND (parent.is_archived IS NULL OR parent.is_archived = false) LIMIT 1) AS anon_1
LEFT OUTER JOIN child ON child.parent_id = parent.id AND (child.is_archived IS NULL OR child.is_archived = false)
Is that sort of manipulation of a joinedload possible at this level? Do I need to look somewhere else in the query processing pipeline? Or is it really not feasible?
Thanks,
Mick