@@ -98,36 +98,64 @@ public QueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintProvid
98
98
FilterExpression ? primaryFilter = GetFilter ( Array . Empty < QueryExpression > ( ) , hasManyRelationship . LeftType ) ;
99
99
FilterExpression ? secondaryFilter = GetFilter ( filtersInSecondaryScope , hasManyRelationship . RightType ) ;
100
100
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 ) ;
102
108
103
- return LogicalExpression . Compose ( LogicalOperator . And , inverseFilter , primaryFilter , secondaryFilter ) ;
109
+ return LogicalExpression . Compose ( LogicalOperator . And , inverseFilter , secondaryFilter ) ;
104
110
}
105
111
106
112
private static FilterExpression GetInverseRelationshipFilter < TId > ( [ DisallowNull ] TId primaryId , HasManyAttribute relationship ,
107
- RelationshipAttribute inverseRelationship )
113
+ RelationshipAttribute inverseRelationship , FilterExpression ? primaryFilter )
108
114
{
109
115
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 ) ;
112
118
}
113
119
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 )
116
122
{
117
123
AttrAttribute idAttribute = GetIdAttribute ( relationship . LeftType ) ;
118
124
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 ;
119
128
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 ) ! ;
121
146
}
122
147
123
148
private static HasExpression GetInverseHasManyRelationshipFilter < TId > ( [ DisallowNull ] TId primaryId , HasManyAttribute relationship ,
124
- HasManyAttribute inverseRelationship )
149
+ HasManyAttribute inverseRelationship , FilterExpression ? primaryFilter )
125
150
{
151
+ // many-to-many. This one is easy, we can just push into the sub-condition of has().
152
+
126
153
AttrAttribute idAttribute = GetIdAttribute ( relationship . LeftType ) ;
127
154
var idChain = new ResourceFieldChainExpression ( ImmutableArray . Create < ResourceFieldAttribute > ( idAttribute ) ) ;
128
155
var idComparison = new ComparisonExpression ( ComparisonOperator . Equals , idChain , new LiteralConstantExpression ( primaryId ) ) ;
129
156
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 ) ;
131
159
}
132
160
133
161
/// <inheritdoc />
0 commit comments