@@ -98,36 +98,64 @@ public QueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintProvid
9898 FilterExpression ? primaryFilter = GetFilter ( Array . Empty < QueryExpression > ( ) , hasManyRelationship . LeftType ) ;
9999 FilterExpression ? secondaryFilter = GetFilter ( filtersInSecondaryScope , hasManyRelationship . RightType ) ;
100100
101- FilterExpression inverseFilter = GetInverseRelationshipFilter ( primaryId , hasManyRelationship , inverseRelationship ) ;
101+ if ( primaryFilter != null )
102+ {
103+ // This would hide total resource count on secondary to-one endpoint, but at least not crash anymore.
104+ // return null;
105+ }
106+
107+ FilterExpression inverseFilter = GetInverseRelationshipFilter ( primaryId , hasManyRelationship , inverseRelationship , primaryFilter ) ;
102108
103- return LogicalExpression . Compose ( LogicalOperator . And , inverseFilter , primaryFilter , secondaryFilter ) ;
109+ return LogicalExpression . Compose ( LogicalOperator . And , inverseFilter , secondaryFilter ) ;
104110 }
105111
106112 private static FilterExpression GetInverseRelationshipFilter < TId > ( [ DisallowNull ] TId primaryId , HasManyAttribute relationship ,
107- RelationshipAttribute inverseRelationship )
113+ RelationshipAttribute inverseRelationship , FilterExpression ? primaryFilter )
108114 {
109115 return inverseRelationship is HasManyAttribute hasManyInverseRelationship
110- ? GetInverseHasManyRelationshipFilter ( primaryId , relationship , hasManyInverseRelationship )
111- : GetInverseHasOneRelationshipFilter ( primaryId , relationship , ( HasOneAttribute ) inverseRelationship ) ;
116+ ? GetInverseHasManyRelationshipFilter ( primaryId , relationship , hasManyInverseRelationship , primaryFilter )
117+ : GetInverseHasOneRelationshipFilter ( primaryId , relationship , ( HasOneAttribute ) inverseRelationship , primaryFilter ) ;
112118 }
113119
114- private static ComparisonExpression GetInverseHasOneRelationshipFilter < TId > ( [ DisallowNull ] TId primaryId , HasManyAttribute relationship ,
115- HasOneAttribute inverseRelationship )
120+ private static FilterExpression GetInverseHasOneRelationshipFilter < TId > ( [ DisallowNull ] TId primaryId , HasManyAttribute relationship ,
121+ HasOneAttribute inverseRelationship , FilterExpression ? primaryFilter )
116122 {
117123 AttrAttribute idAttribute = GetIdAttribute ( relationship . LeftType ) ;
118124 var idChain = new ResourceFieldChainExpression ( ImmutableArray . Create < ResourceFieldAttribute > ( inverseRelationship , idAttribute ) ) ;
125+ var idComparison = new ComparisonExpression ( ComparisonOperator . Equals , idChain , new LiteralConstantExpression ( primaryId ) ) ;
126+
127+ FilterExpression ? newPrimaryFilter = null ;
119128
120- return new ComparisonExpression ( ComparisonOperator . Equals , idChain , new LiteralConstantExpression ( primaryId ) ) ;
129+ if ( primaryFilter != null )
130+ {
131+ // many-to-one. This is the hard part. We can special-case for built-in has() and isType() usage, however third-party filters can't participate.
132+ // Because there is no way of indicating in an expression "this chain belongs to something related"; the parsers only know that.
133+ // For example, see the third-party SumExpression.Selector with SumFilterParser in test project.
134+
135+ // For example:
136+ // input: and(equals(isDeleted,'false'), has(books ,equals(author.name,'Mary Shelley')),isType( house,bigHouses,equals(floorCount,'3')))
137+ // output: and(equals(author.isDeleted,'false'),has(author.books,equals(author.name,'Mary Shelley')),isType(author.house,bigHouses,equals(floorCount,'3')))
138+ // ^ ^ ^! ^ ^!
139+ // Note how some chains are updated, while others (expressions on related types) are intentionally not.
140+
141+ var rewriter = new ChainInsertionFilterRewriter ( inverseRelationship ) ;
142+ newPrimaryFilter = ( FilterExpression ? ) rewriter . Visit ( primaryFilter , null ) ;
143+ }
144+
145+ return LogicalExpression . Compose ( LogicalOperator . And , idComparison , newPrimaryFilter ) ! ;
121146 }
122147
123148 private static HasExpression GetInverseHasManyRelationshipFilter < TId > ( [ DisallowNull ] TId primaryId , HasManyAttribute relationship ,
124- HasManyAttribute inverseRelationship )
149+ HasManyAttribute inverseRelationship , FilterExpression ? primaryFilter )
125150 {
151+ // many-to-many. This one is easy, we can just push into the sub-condition of has().
152+
126153 AttrAttribute idAttribute = GetIdAttribute ( relationship . LeftType ) ;
127154 var idChain = new ResourceFieldChainExpression ( ImmutableArray . Create < ResourceFieldAttribute > ( idAttribute ) ) ;
128155 var idComparison = new ComparisonExpression ( ComparisonOperator . Equals , idChain , new LiteralConstantExpression ( primaryId ) ) ;
129156
130- return new HasExpression ( new ResourceFieldChainExpression ( inverseRelationship ) , idComparison ) ;
157+ FilterExpression filter = LogicalExpression . Compose ( LogicalOperator . And , idComparison , primaryFilter ) ! ;
158+ return new HasExpression ( new ResourceFieldChainExpression ( inverseRelationship ) , filter ) ;
131159 }
132160
133161 /// <inheritdoc />
0 commit comments