1+ using System ;
12using System . Collections . Generic ;
23using System . Collections . Immutable ;
34using System . Linq ;
5+ using System . Net ;
46using JetBrains . Annotations ;
57using JsonApiDotNetCore . Configuration ;
8+ using JsonApiDotNetCore . Errors ;
69using JsonApiDotNetCore . Queries . Expressions ;
710using JsonApiDotNetCore . Queries . Internal ;
811using JsonApiDotNetCore . Resources ;
912using JsonApiDotNetCore . Resources . Annotations ;
13+ using JsonApiDotNetCore . Serialization . Objects ;
1014
1115#pragma warning disable AV1551 // Method overload should call another overload
1216#pragma warning disable AV2310 // Code block should not contain inline comment
@@ -52,48 +56,23 @@ public NoSqlQueryLayerComposer(IEnumerable<IQueryConstraintProvider> constraintP
5256 /// <inheritdoc />
5357 public FilterExpression ? GetPrimaryFilterFromConstraintsForNoSql ( ResourceType primaryResourceType )
5458 {
55- return GetPrimaryFilterFromConstraints ( primaryResourceType ) ;
59+ return AssertFilterExpressionIsSimple ( GetPrimaryFilterFromConstraints ( primaryResourceType ) ) ;
5660 }
5761
5862 /// <inheritdoc />
5963 public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeFromConstraintsForNoSql ( ResourceType requestResourceType )
6064 {
6165 QueryLayer queryLayer = ComposeFromConstraints ( requestResourceType ) ;
62- IncludeExpression include = queryLayer . Include ?? IncludeExpression . Empty ;
6366
64- queryLayer . Include = IncludeExpression . Empty ;
65- queryLayer . Projection = null ;
66-
67- return ( queryLayer , include ) ;
68- }
69-
70- /// <inheritdoc />
71- public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeForGetByIdWithConstraintsForNoSql < TId > ( TId id , ResourceType primaryResourceType ,
72- TopFieldSelection fieldSelection )
73- where TId : notnull
74- {
75- QueryLayer queryLayer = ComposeForGetById ( id , primaryResourceType , fieldSelection ) ;
76- IncludeExpression include = queryLayer . Include ?? IncludeExpression . Empty ;
67+ IncludeExpression include = AssertIncludeExpressionIsSimple ( queryLayer . Include ) ;
7768
69+ queryLayer . Filter = AssertFilterExpressionIsSimple ( queryLayer . Filter ) ;
7870 queryLayer . Include = IncludeExpression . Empty ;
7971 queryLayer . Projection = null ;
8072
8173 return ( queryLayer , include ) ;
8274 }
8375
84- /// <inheritdoc />
85- public QueryLayer ComposeForGetByIdForNoSql < TId > ( TId id , ResourceType primaryResourceType )
86- where TId : notnull
87- {
88- return new QueryLayer ( primaryResourceType )
89- {
90- Filter = new ComparisonExpression ( ComparisonOperator . Equals ,
91- new ResourceFieldChainExpression ( primaryResourceType . Fields . Single ( field => field . Property . Name == nameof ( IIdentifiable < TId > . Id ) ) ) ,
92- new LiteralConstantExpression ( id . ToString ( ) ! ) ) ,
93- Include = IncludeExpression . Empty
94- } ;
95- }
96-
9776 /// <inheritdoc />
9877 public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeFromConstraintsForNoSql ( ResourceType requestResourceType , string propertyName ,
9978 string propertyValue , bool isIncluded )
@@ -104,27 +83,44 @@ public QueryLayer ComposeForGetByIdForNoSql<TId>(TId id, ResourceType primaryRes
10483 ComposeSecondaryResourceFilter ( requestResourceType , propertyName , propertyValue )
10584 } ;
10685
86+ // @formatter:off
87+
10788 // Get the query expressions from the request.
108- ExpressionInScope [ ] constraints = _constraintProviders . SelectMany ( provider => provider . GetConstraints ( ) ) . ToArray ( ) ;
89+ ExpressionInScope [ ] constraints = _constraintProviders
90+ . SelectMany ( provider => provider . GetConstraints ( ) )
91+ . ToArray ( ) ;
10992
11093 bool IsQueryLayerConstraint ( ExpressionInScope constraint )
11194 {
112- return constraint . Expression is not IncludeExpression && ( ! isIncluded || ( constraint . Scope is not null &&
113- constraint . Scope . Fields . Any ( field => field . PublicName == requestResourceType . PublicName ) ) ) ;
95+ return constraint . Expression is not IncludeExpression && ( ! isIncluded || IsResourceScoped ( constraint ) ) ;
96+ }
97+
98+ bool IsResourceScoped ( ExpressionInScope constraint )
99+ {
100+ return constraint . Scope is not null &&
101+ constraint . Scope . Fields . Any ( field => field . PublicName == requestResourceType . PublicName ) ;
114102 }
115103
116- IEnumerable < QueryExpression > requestQueryExpressions = constraints . Where ( IsQueryLayerConstraint ) . Select ( constraint => constraint . Expression ) ;
104+ QueryExpression [ ] requestQueryExpressions = constraints
105+ . Where ( IsQueryLayerConstraint )
106+ . Select ( constraint => constraint . Expression )
107+ . ToArray ( ) ;
117108
118- // Combine the secondary resource filter and request query expressions and
119- // create the query layer from the combined query expressions.
120- QueryExpression [ ] queryExpressions = secondaryResourceFilterExpressions . Concat ( requestQueryExpressions ) . ToArray ( ) ;
109+ FilterExpression [ ] requestFilterExpressions = requestQueryExpressions
110+ . OfType < FilterExpression > ( )
111+ . Select ( filterExpression => AssertFilterExpressionIsSimple ( filterExpression ) ! )
112+ . ToArray ( ) ;
113+
114+ FilterExpression [ ] combinedFilterExpressions = secondaryResourceFilterExpressions
115+ . Concat ( requestFilterExpressions )
116+ . ToArray ( ) ;
121117
122118 var queryLayer = new QueryLayer ( requestResourceType )
123119 {
124120 Include = IncludeExpression . Empty ,
125- Filter = GetFilter ( queryExpressions , requestResourceType ) ,
126- Sort = GetSort ( queryExpressions , requestResourceType ) ,
127- Pagination = GetPagination ( queryExpressions , requestResourceType )
121+ Filter = GetFilter ( combinedFilterExpressions , requestResourceType ) ,
122+ Sort = GetSort ( requestQueryExpressions , requestResourceType ) ,
123+ Pagination = GetPagination ( requestQueryExpressions , requestResourceType )
128124 } ;
129125
130126 // Retrieve the IncludeExpression from the constraints collection.
@@ -133,7 +129,13 @@ bool IsQueryLayerConstraint(ExpressionInScope constraint)
133129 // into a single expression.
134130 IncludeExpression include = isIncluded
135131 ? IncludeExpression . Empty
136- : constraints . Select ( constraint => constraint . Expression ) . OfType < IncludeExpression > ( ) . DefaultIfEmpty ( IncludeExpression . Empty ) . Single ( ) ;
132+ : AssertIncludeExpressionIsSimple ( constraints
133+ . Select ( constraint => constraint . Expression )
134+ . OfType < IncludeExpression > ( )
135+ . DefaultIfEmpty ( IncludeExpression . Empty )
136+ . Single ( ) ) ;
137+
138+ // @formatter:on
137139
138140 return ( queryLayer , include ) ;
139141 }
@@ -145,6 +147,35 @@ private static FilterExpression ComposeSecondaryResourceFilter(ResourceType reso
145147 new LiteralConstantExpression ( properyValue ) ) ;
146148 }
147149
150+ /// <inheritdoc />
151+ public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeForGetByIdWithConstraintsForNoSql < TId > ( TId id , ResourceType primaryResourceType ,
152+ TopFieldSelection fieldSelection )
153+ where TId : notnull
154+ {
155+ QueryLayer queryLayer = ComposeForGetById ( id , primaryResourceType , fieldSelection ) ;
156+
157+ IncludeExpression include = AssertIncludeExpressionIsSimple ( queryLayer . Include ) ;
158+
159+ queryLayer . Filter = AssertFilterExpressionIsSimple ( queryLayer . Filter ) ;
160+ queryLayer . Include = IncludeExpression . Empty ;
161+ queryLayer . Projection = null ;
162+
163+ return ( queryLayer , include ) ;
164+ }
165+
166+ /// <inheritdoc />
167+ public QueryLayer ComposeForGetByIdForNoSql < TId > ( TId id , ResourceType primaryResourceType )
168+ where TId : notnull
169+ {
170+ return new QueryLayer ( primaryResourceType )
171+ {
172+ Filter = new ComparisonExpression ( ComparisonOperator . Equals ,
173+ new ResourceFieldChainExpression ( primaryResourceType . Fields . Single ( field => field . Property . Name == nameof ( IIdentifiable < TId > . Id ) ) ) ,
174+ new LiteralConstantExpression ( id . ToString ( ) ! ) ) ,
175+ Include = IncludeExpression . Empty
176+ } ;
177+ }
178+
148179 public ( QueryLayer QueryLayer , IncludeExpression Include ) ComposeForUpdateForNoSql < TId > ( TId id , ResourceType primaryResourceType )
149180 where TId : notnull
150181 {
@@ -171,5 +202,127 @@ private static AttrAttribute GetIdAttribute(ResourceType resourceType)
171202 {
172203 return resourceType . GetAttributeByPropertyName ( nameof ( Identifiable < object > . Id ) ) ;
173204 }
205+
206+ private static FilterExpression ? AssertFilterExpressionIsSimple ( FilterExpression ? filterExpression )
207+ {
208+ if ( filterExpression is null )
209+ {
210+ return filterExpression ;
211+ }
212+
213+ var visitor = new FilterExpressionVisitor ( ) ;
214+
215+ return visitor . Visit ( filterExpression , null )
216+ ? filterExpression
217+ : throw new JsonApiException ( new ErrorObject ( HttpStatusCode . BadRequest )
218+ {
219+ Title = "Unsupported filter expression" ,
220+ Detail = "Navigation of to-one or to-many relationships is not supported."
221+ } ) ;
222+ }
223+
224+ private static IncludeExpression AssertIncludeExpressionIsSimple ( IncludeExpression ? includeExpression )
225+ {
226+ if ( includeExpression is null )
227+ {
228+ return IncludeExpression . Empty ;
229+ }
230+
231+ return includeExpression . Elements . Any ( element => element . Children . Any ( ) )
232+ ? throw new JsonApiException ( new ErrorObject ( HttpStatusCode . BadRequest )
233+ {
234+ Title = "Unsupported include expression" ,
235+ Detail = "Multi-level include expressions are not supported."
236+ } )
237+ : includeExpression ;
238+ }
239+
240+ private sealed class FilterExpressionVisitor : QueryExpressionVisitor < object ? , bool >
241+ {
242+ private bool _isSimpleFilterExpression = true ;
243+
244+ /// <inheritdoc />
245+ public override bool DefaultVisit ( QueryExpression expression , object ? argument )
246+ {
247+ return _isSimpleFilterExpression ;
248+ }
249+
250+ /// <inheritdoc />
251+ public override bool VisitComparison ( ComparisonExpression expression , object ? argument )
252+ {
253+ return expression . Left . Accept ( this , argument ) && expression . Right . Accept ( this , argument ) ;
254+ }
255+
256+ /// <inheritdoc />
257+ public override bool VisitResourceFieldChain ( ResourceFieldChainExpression expression , object ? argument )
258+ {
259+ _isSimpleFilterExpression &= expression . Fields . All ( IsFieldSupported ) ;
260+
261+ return _isSimpleFilterExpression ;
262+ }
263+
264+ private static bool IsFieldSupported ( ResourceFieldAttribute field )
265+ {
266+ return field switch
267+ {
268+ AttrAttribute => true ,
269+ HasManyAttribute hasMany when HasOwnsManyAttribute ( hasMany ) => true ,
270+ _ => false
271+ } ;
272+ }
273+
274+ private static bool HasOwnsManyAttribute ( ResourceFieldAttribute field )
275+ {
276+ return Attribute . GetCustomAttribute ( field . Property , typeof ( NoSqlOwnsManyAttribute ) ) is not null ;
277+ }
278+
279+ /// <inheritdoc />
280+ public override bool VisitLogical ( LogicalExpression expression , object ? argument )
281+ {
282+ return expression . Terms . All ( term => term . Accept ( this , argument ) ) ;
283+ }
284+
285+ /// <inheritdoc />
286+ public override bool VisitNot ( NotExpression expression , object ? argument )
287+ {
288+ return expression . Child . Accept ( this , argument ) ;
289+ }
290+
291+ /// <inheritdoc />
292+ public override bool VisitHas ( HasExpression expression , object ? argument )
293+ {
294+ return expression . TargetCollection . Accept ( this , argument ) && ( expression . Filter is null || expression . Filter . Accept ( this , argument ) ) ;
295+ }
296+
297+ /// <inheritdoc />
298+ public override bool VisitSortElement ( SortElementExpression expression , object ? argument )
299+ {
300+ return expression . TargetAttribute is null || expression . TargetAttribute . Accept ( this , argument ) ;
301+ }
302+
303+ /// <inheritdoc />
304+ public override bool VisitSort ( SortExpression expression , object ? argument )
305+ {
306+ return expression . Elements . All ( element => element . Accept ( this , argument ) ) ;
307+ }
308+
309+ /// <inheritdoc />
310+ public override bool VisitCount ( CountExpression expression , object ? argument )
311+ {
312+ return expression . TargetCollection . Accept ( this , argument ) ;
313+ }
314+
315+ /// <inheritdoc />
316+ public override bool VisitMatchText ( MatchTextExpression expression , object ? argument )
317+ {
318+ return expression . TargetAttribute . Accept ( this , argument ) ;
319+ }
320+
321+ /// <inheritdoc />
322+ public override bool VisitAny ( AnyExpression expression , object ? argument )
323+ {
324+ return expression . TargetAttribute . Accept ( this , argument ) ;
325+ }
326+ }
174327 }
175328}
0 commit comments