Fauna .NET Driver 0.1.0-beta
 
Loading...
Searching...
No Matches
QuerySourceDsl.cs
Go to the documentation of this file.
2using Fauna.Mapping;
4using Fauna.Util;
5using System.Diagnostics;
6using System.Runtime.CompilerServices;
7using System.Linq.Expressions;
9
10namespace Fauna.Linq;
11
12public partial class QuerySource<T>
13{
14 private Query Query { get => Pipeline.Query; }
15 private MappingContext MappingCtx { get => Ctx.MappingCtx; }
16 private LookupTable Lookup { get => Ctx.LookupTable; }
17
18 // Composition methods
19
21 {
22 RequireQueryMode();
23 return Chain<T>(q: QH.MethodCall(Query, "distinct"));
24 }
25
27 {
28 RequireQueryMode();
29 return Chain<T>(q: QH.MethodCall(Query, "order"));
30 }
31
32 public IQuerySource<T> OrderBy<K>(Expression<Func<T, K>> keySelector)
33 {
34 RequireQueryMode();
35 return Chain<T>(q: QH.MethodCall(Query, "order", SubQuery(keySelector)));
36 }
37
38 public IQuerySource<T> OrderByDescending<K>(Expression<Func<T, K>> keySelector)
39 {
40 RequireQueryMode();
41 return Chain<T>(q: QH.MethodCall(Query, "order", QH.FnCall("desc", SubQuery(keySelector))));
42 }
43
45 {
46 RequireQueryMode();
47 return Chain<T>(q: QH.MethodCall(Query, "order", QH.Expr("desc(x => x)")));
48 }
49
51 Chain<T>(q: QH.MethodCall(Query, "reverse"));
52
53 public IQuerySource<R> Select<R>(Expression<Func<T, R>> selector)
54 {
55 var pl = SelectCall(Query, selector);
56 return new QuerySource<R>(Ctx, pl);
57 }
58
59 public IQuerySource<T> Skip(int count) =>
60 Chain<T>(q: QH.MethodCall(Query, "drop", QH.Const(count)));
61
62 public IQuerySource<T> Take(int count) =>
63 Chain<T>(q: QH.MethodCall(Query, "take", QH.Const(count)));
64
65 public IQuerySource<T> Where(Expression<Func<T, bool>> predicate) =>
66 Chain<T>(q: WhereCall(Query, predicate));
67
68 // Terminal result methods
69
70 public bool All(Expression<Func<T, bool>> predicate) => Execute<bool>(AllImpl(predicate));
71 public Task<bool> AllAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
72 ExecuteAsync<bool>(AllImpl(predicate), cancel);
73 private Pipeline AllImpl(Expression<Func<T, bool>> predicate)
74 {
75 RequireQueryMode("All");
76 return CopyPipeline(
77 mode: PipelineMode.Scalar,
78 q: QH.MethodCall(Query, "every", SubQuery(predicate)),
79 ety: typeof(bool));
80 }
81
82 public bool Any() => Execute<bool>(AnyImpl(null));
83 public Task<bool> AnyAsync(CancellationToken cancel = default) =>
84 ExecuteAsync<bool>(AnyImpl(null), cancel);
85 public bool Any(Expression<Func<T, bool>> predicate) => Execute<bool>(AnyImpl(predicate));
86 public Task<bool> AnyAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
87 ExecuteAsync<bool>(AnyImpl(predicate), cancel);
88 private Pipeline AnyImpl(Expression<Func<T, bool>>? predicate) =>
89 CopyPipeline(
90 mode: PipelineMode.Scalar,
91 q: QH.MethodCall(MaybeWhereCall(Query, predicate), "nonEmpty"),
92 ety: typeof(bool));
93
94 public int Count() => Execute<int>(CountImpl(null));
95 public Task<int> CountAsync(CancellationToken cancel = default) =>
96 ExecuteAsync<int>(CountImpl(null), cancel);
97 public int Count(Expression<Func<T, bool>> predicate) => Execute<int>(CountImpl(predicate));
98 public Task<int> CountAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
99 ExecuteAsync<int>(CountImpl(predicate), cancel);
100 private Pipeline CountImpl(Expression<Func<T, bool>>? predicate) =>
101 CopyPipeline(
102 mode: PipelineMode.Scalar,
103 q: QH.MethodCall(MaybeWhereCall(Query, predicate), "count"),
104 ety: typeof(int));
105
106 public T First() => Execute<T>(FirstImpl(null));
107 public Task<T> FirstAsync(CancellationToken cancel = default) =>
108 ExecuteAsync<T>(FirstImpl(null), cancel);
109 public T First(Expression<Func<T, bool>> predicate) => Execute<T>(FirstImpl(predicate));
110 public Task<T> FirstAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
111 ExecuteAsync<T>(FirstImpl(predicate), cancel);
112 private Pipeline FirstImpl(Expression<Func<T, bool>>? predicate) =>
113 CopyPipeline(
114 mode: PipelineMode.Scalar,
115 q: QH.MethodCall(AbortIfEmpty(MaybeWhereCall(Query, predicate)), "first"));
116
117 public T? FirstOrDefault() => Execute<T?>(FirstOrDefaultImpl(null));
118 public Task<T?> FirstOrDefaultAsync(CancellationToken cancel = default) =>
119 ExecuteAsync<T?>(FirstOrDefaultImpl(null), cancel);
120 public T? FirstOrDefault(Expression<Func<T, bool>> predicate) => Execute<T?>(FirstOrDefaultImpl(predicate));
121 public Task<T?> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
122 ExecuteAsync<T?>(FirstOrDefaultImpl(predicate), cancel);
123 private Pipeline FirstOrDefaultImpl(Expression<Func<T, bool>>? predicate) =>
124 CopyPipeline(
125 mode: PipelineMode.Scalar,
126 q: QH.MethodCall(MaybeWhereCall(Query, predicate), "first"),
127 ety: typeof(T),
128 enull: true);
129
130 public T Last() => Execute<T>(LastImpl(null));
131 public Task<T> LastAsync(CancellationToken cancel = default) =>
132 ExecuteAsync<T>(LastImpl(null), cancel);
133 public T Last(Expression<Func<T, bool>> predicate) => Execute<T>(LastImpl(predicate));
134 public Task<T> LastAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
135 ExecuteAsync<T>(LastImpl(predicate), cancel);
136 private Pipeline LastImpl(Expression<Func<T, bool>>? predicate) =>
137 CopyPipeline(
138 mode: PipelineMode.Scalar,
139 q: QH.MethodCall(AbortIfEmpty(MaybeWhereCall(Query, predicate)), "last"));
140
141 public T? LastOrDefault() => Execute<T?>(LastOrDefaultImpl(null));
142 public Task<T?> LastOrDefaultAsync(CancellationToken cancel = default) =>
143 ExecuteAsync<T?>(LastOrDefaultImpl(null), cancel);
144 public T? LastOrDefault(Expression<Func<T, bool>> predicate) => Execute<T?>(LastOrDefaultImpl(predicate));
145 public Task<T?> LastOrDefaultAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
146 ExecuteAsync<T?>(LastOrDefaultImpl(predicate), cancel);
147 private Pipeline LastOrDefaultImpl(Expression<Func<T, bool>>? predicate) =>
148 CopyPipeline(
149 mode: PipelineMode.Scalar,
150 q: QH.MethodCall(MaybeWhereCall(Query, predicate), "last"),
151 ety: typeof(T),
152 enull: true);
153
154 public long LongCount() => Execute<long>(LongCountImpl(null));
155 public Task<long> LongCountAsync(CancellationToken cancel = default) =>
156 ExecuteAsync<long>(LongCountImpl(null), cancel);
157 public long LongCount(Expression<Func<T, bool>> predicate) => Execute<long>(LongCountImpl(predicate));
158 public Task<long> LongCountAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
159 ExecuteAsync<long>(LongCountImpl(predicate), cancel);
160 private Pipeline LongCountImpl(Expression<Func<T, bool>>? predicate) =>
161 CopyPipeline(
162 mode: PipelineMode.Scalar,
163 q: QH.MethodCall(MaybeWhereCall(Query, predicate), "count"),
164 ety: typeof(long));
165
166 private static readonly Query _maxReducer = QH.Expr("(a, b) => if (a >= b) a else b");
167
168 public T Max() => Execute<T>(MaxImpl<T>(null));
169 public Task<T> MaxAsync(CancellationToken cancel = default) =>
170 ExecuteAsync<T>(MaxImpl<T>(null), cancel);
171 public R Max<R>(Expression<Func<T, R>> selector) => Execute<R>(MaxImpl(selector));
172 public Task<R> MaxAsync<R>(Expression<Func<T, R>> selector, CancellationToken cancel = default) =>
173 ExecuteAsync<R>(MaxImpl(selector), cancel);
174 private Pipeline MaxImpl<R>(Expression<Func<T, R>>? selector, [CallerMemberName] string callerName = "")
175 {
176 RequireQueryMode(callerName);
177 return CopyPipeline(
178 mode: PipelineMode.Scalar,
179 q: QH.MethodCall(MaybeMap(AbortIfEmpty(Query), selector), "reduce", _maxReducer),
180 ety: typeof(R));
181 }
182
183 private static readonly Query _minReducer = QH.Expr("(a, b) => if (a <= b) a else b");
184
185 public T Min() => Execute<T>(MinImpl<T>(null));
186 public Task<T> MinAsync(CancellationToken cancel = default) => ExecuteAsync<T>(MinImpl<T>(null), cancel);
187 public R Min<R>(Expression<Func<T, R>> selector) => Execute<R>(MinImpl(selector));
188 public Task<R> MinAsync<R>(Expression<Func<T, R>> selector, CancellationToken cancel = default) =>
189 ExecuteAsync<R>(MinImpl(selector), cancel);
190 private Pipeline MinImpl<R>(Expression<Func<T, R>>? selector, [CallerMemberName] string callerName = "")
191 {
192 RequireQueryMode(callerName);
193 return CopyPipeline(
194 mode: PipelineMode.Scalar,
195 q: QH.MethodCall(MaybeMap(AbortIfEmpty(Query), selector), "reduce", _minReducer),
196 ety: typeof(R));
197 }
198
199 public T Single() => Execute<T>(SingleImpl(null));
200 public Task<T> SingleAsync(CancellationToken cancel = default) => ExecuteAsync<T>(SingleImpl(null), cancel);
201 public T Single(Expression<Func<T, bool>> predicate) => Execute<T>(SingleImpl(predicate));
202 public Task<T> SingleAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
203 ExecuteAsync<T>(SingleImpl(predicate), cancel);
204 private Pipeline SingleImpl(Expression<Func<T, bool>>? predicate) =>
205 CopyPipeline(
206 mode: PipelineMode.Scalar,
207 q: QH.MethodCall(AbortIfEmpty(Singularize(MaybeWhereCall(Query, predicate))), "first"));
208
209 public T SingleOrDefault() => Execute<T>(SingleOrDefaultImpl(null));
210 public Task<T> SingleOrDefaultAsync(CancellationToken cancel = default) => ExecuteAsync<T>(SingleOrDefaultImpl(null), cancel);
211 public T SingleOrDefault(Expression<Func<T, bool>> predicate) => Execute<T>(SingleOrDefaultImpl(predicate));
212 public Task<T> SingleOrDefaultAsync(Expression<Func<T, bool>> predicate, CancellationToken cancel = default) =>
213 ExecuteAsync<T>(SingleOrDefaultImpl(predicate), cancel);
214 private Pipeline SingleOrDefaultImpl(Expression<Func<T, bool>>? predicate) =>
215 CopyPipeline(
216 mode: PipelineMode.Scalar,
217 q: QH.MethodCall(Singularize(MaybeWhereCall(Query, predicate)), "first"),
218 ety: typeof(T),
219 enull: true);
220
221 private static readonly Query _sumReducer = QH.Expr("(a, b) => a + b");
222
223 public int Sum(Expression<Func<T, int>> selector) => Execute<int>(SumImpl<int>(selector));
224 public Task<int> SumAsync(Expression<Func<T, int>> selector, CancellationToken cancel = default) =>
225 ExecuteAsync<int>(SumImpl<int>(selector), cancel);
226 public long Sum(Expression<Func<T, long>> selector) => Execute<long>(SumImpl<long>(selector));
227 public Task<long> SumAsync(Expression<Func<T, long>> selector, CancellationToken cancel = default) =>
228 ExecuteAsync<long>(SumImpl<long>(selector), cancel);
229 public double Sum(Expression<Func<T, double>> selector) => Execute<double>(SumImpl<double>(selector));
230 public Task<double> SumAsync(Expression<Func<T, double>> selector, CancellationToken cancel = default) =>
231 ExecuteAsync<double>(SumImpl<double>(selector), cancel);
232 private Pipeline SumImpl<R>(Expression<Func<T, R>> selector)
233 {
234 RequireQueryMode("Sum");
235 var seed = (typeof(R) == typeof(int) || typeof(R) == typeof(long)) ?
236 QH.Expr("0") :
237 QH.Expr("0.0");
238 var mapped = QH.MethodCall(Query, "map", SubQuery(selector));
239 return CopyPipeline(
240 mode: PipelineMode.Scalar,
241 q: QH.MethodCall(mapped, "fold", seed, _sumReducer),
242 ety: typeof(R));
243 }
244
245 // helpers
246
247 private void RequireQueryMode([CallerMemberName] string callerName = "")
248 {
249 if (Pipeline.Mode != PipelineMode.Query)
250 {
251 throw IQuerySource.Fail(
252 callerName,
253 $"Query is not pure: Earlier `Select` could not be translated to pure FQL.");
254 }
255 }
256
257 private R Execute<R>(Pipeline pl)
258 {
259 try
260 {
261 var res = ExecuteAsync<R>(pl);
262 res.Wait();
263 return res.Result;
264 }
265 catch (AggregateException ex)
266 {
267 throw TranslateException(ex.InnerExceptions.First());
268 }
269 }
270
271 private async Task<R> ExecuteAsync<R>(Pipeline pl, CancellationToken cancel = default)
272 {
273 try
274 {
275 return await pl.GetExec(Ctx).Result<R>(queryOptions: null, cancel: cancel);
276 }
277 catch (AggregateException ex)
278 {
279 throw TranslateException(ex.InnerExceptions.First());
280 }
281 }
282
283 private QuerySource<R> Chain<R>(
284 PipelineMode? mode = null,
285 Query? q = null,
286 IDeserializer? deser = null,
287 Type? ety = null,
288 bool enull = false,
289 LambdaExpression? proj = null) =>
290 new QuerySource<R>(Ctx, CopyPipeline(mode, q, deser, ety, enull, proj));
291
292 private Pipeline CopyPipeline(
293 PipelineMode? mode = null,
294 Query? q = null,
295 IDeserializer? deser = null,
296 Type? ety = null,
297 bool enull = false,
298 LambdaExpression? proj = null)
299 {
300 if (deser is not null) Debug.Assert(ety is not null);
301
302 var mode0 = mode ?? Pipeline.Mode;
303 var q0 = q ?? Pipeline.Query;
304
305 // if ety is not null, reset deser and proj if not provided.
306 var (ety0, enull0, deser0, proj0) = ety is not null ?
307 (ety, enull, deser, proj) :
308 (Pipeline.ElemType,
309 Pipeline.ElemNullable,
310 Pipeline.ElemDeserializer,
311 proj ?? Pipeline.ProjectExpr);
312
313 return new Pipeline(mode0, q0, ety0, enull0, deser0, proj0);
314 }
315
316 // There is a bug in abort data deserialization if the abort
317 // value is a string. Work around it by using an array.
318 // FIXME(matt) remove workaround and use a string
319 private Query AbortIfEmpty(Query setq) =>
320 QH.Expr(@"({ let s = (").Concat(setq).Concat(@")
321 if (s.isEmpty()) abort(['empty'])
322 s
323 })");
324
325 private Query Singularize(Query setq) =>
326 QH.Expr(@"({
327 let s = (").Concat(setq).Concat(@").take(2).toArray()
328 if (s.length > 1) abort(['not single'])
329 s.take(1)
330 })");
331
332 private Exception TranslateException(Exception ex) =>
333 ex switch
334 {
335 AbortException aex =>
336 aex.GetData<List<string>>()?.First() switch
337 {
338 "empty" => new InvalidOperationException("Empty set"),
339 "not single" => new InvalidOperationException("Set contains more than one element"),
340 _ => aex,
341 },
342 _ => ex
343 };
344
345 private Query MaybeWhereCall(Query callee, Expression? predicate, [CallerMemberName] string callerName = "") =>
346 predicate is null ? callee : WhereCall(callee, predicate, callerName);
347
348 private Query MaybeMap(Query setq, Expression? selector) =>
349 selector is null ? setq : QH.MethodCall(setq, "map", SubQuery(selector));
350
351 private Query SubQuery(Expression expr) =>
352 new SubQuerySwitch(Ctx.LookupTable).Apply(expr);
353
354 private Query WhereCall(Query callee, Expression predicate, [CallerMemberName] string callerName = "")
355 {
356 RequireQueryMode(callerName);
357 return QH.MethodCall(callee, "where", SubQuery(predicate));
358 }
359
360 private Pipeline SelectCall(Query callee, Expression proj, [CallerMemberName] string callerName = "")
361 {
362 var lambda = Expressions.UnwrapLambda(proj);
363 Debug.Assert(lambda is not null, $"lambda is {proj.NodeType}");
364 Debug.Assert(lambda.Parameters.Count() == 1);
365
366 // there is already a projection wired up, so tack on to its mapping lambda
367 if (Pipeline.Mode == PipelineMode.Project)
368 {
369 Debug.Assert(Pipeline.ProjectExpr is not null);
370 var prev = Pipeline.ProjectExpr;
371 var pbody = Expression.Invoke(lambda, new Expression[] { prev.Body });
372 var plambda = Expression.Lambda(pbody, prev.Parameters);
373
374 return CopyPipeline(proj: plambda);
375 }
376
377 Debug.Assert(Pipeline.Mode == PipelineMode.Query);
378
379 var lparam = lambda.Parameters.First()!;
380 var analysis = new ProjectionAnalysisVisitor(MappingCtx, lparam);
381 analysis.Visit(lambda.Body);
382
383 // select is a simple field access which we can translate directly to FQL.
384 // TODO(matt) translate more cases to pure FQL
385 if (lambda.Body is MemberExpression mexpr && mexpr.Expression == lparam)
386 {
387 Debug.Assert(!analysis.Escapes);
388 var info = MappingCtx.GetInfo(lparam.Type);
389 var access = analysis.Accesses.First();
390 var field = Lookup.FieldLookup(access, lparam);
391 Debug.Assert(field is not null);
392
393 return CopyPipeline(
394 q: QH.MethodCall(callee, "map", QH.Expr($".{field.Name}")),
395 deser: field.Deserializer,
396 ety: field.Type);
397 }
398
399 if (analysis.Escapes)
400 {
401 return CopyPipeline(mode: PipelineMode.Project, proj: lambda);
402 }
403 else
404 {
405 var accesses = analysis.Accesses.OrderBy(f => f.Name).ToArray();
406 var fields = accesses.Select(a => Lookup.FieldLookup(a, lparam)!);
407
408 // projection query fragment
409 var accs = fields.Select(f => QH.Expr($"x.{f.Name}"));
410 var pquery = QH.Expr("x => ").Concat(QH.Array(accs));
411
412 // projected field deserializer
413 var deser = new ProjectionDeserializer(fields.Select(f => f.Deserializer));
414 var ety = typeof(object?[]);
415
416 // build mapping lambda expression
417 var pparam = Expression.Parameter(typeof(object?[]), "x");
418 var rewriter = new ProjectionRewriteVisitor(lparam, accesses, pparam);
419 var pbody = rewriter.Visit(lambda.Body);
420 var plambda = Expression.Lambda(pbody, pparam);
421
422 return CopyPipeline(
423 q: QH.MethodCall(callee, "map", pquery),
424 mode: PipelineMode.Project,
425 deser: deser,
426 ety: ety,
427 proj: plambda);
428 }
429 }
430}
Fauna.Linq.IntermediateQueryHelpers QH
Represents an exception that occurs when the FQL abort function is called. This exception captures th...
object? GetData()
Retrieves the deserialized data associated with the abort operation as an object.
Task< int > CountAsync(CancellationToken cancel=default)
Task< T?> FirstOrDefaultAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
Task< bool > AllAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
Task< long > SumAsync(Expression< Func< T, long > > selector, CancellationToken cancel=default)
Task< T > SingleOrDefaultAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
Task< T > FirstAsync(CancellationToken cancel=default)
Task< T > FirstAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
double Sum(Expression< Func< T, double > > selector)
T SingleOrDefault(Expression< Func< T, bool > > predicate)
Task< T > MinAsync(CancellationToken cancel=default)
bool Any(Expression< Func< T, bool > > predicate)
Task< T?> FirstOrDefaultAsync(CancellationToken cancel=default)
Task< int > SumAsync(Expression< Func< T, int > > selector, CancellationToken cancel=default)
Task< int > CountAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
IQuerySource< T > OrderByDescending< K >(Expression< Func< T, K > > keySelector)
R Min< R >(Expression< Func< T, R > > selector)
Task< T > LastAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
T? FirstOrDefault(Expression< Func< T, bool > > predicate)
T First(Expression< Func< T, bool > > predicate)
Task< T > LastAsync(CancellationToken cancel=default)
IQuerySource< T > Reverse()
Task< R > MinAsync< R >(Expression< Func< T, R > > selector, CancellationToken cancel=default)
IQuerySource< T > Skip(int count)
Task< T > SingleOrDefaultAsync(CancellationToken cancel=default)
IQuerySource< T > Where(Expression< Func< T, bool > > predicate)
IQuerySource< T > OrderDescending()
bool All(Expression< Func< T, bool > > predicate)
Task< R > MaxAsync< R >(Expression< Func< T, R > > selector, CancellationToken cancel=default)
IQuerySource< T > Order()
long LongCount(Expression< Func< T, bool > > predicate)
Task< T?> LastOrDefaultAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
int Sum(Expression< Func< T, int > > selector)
IQuerySource< T > Take(int count)
Task< T > SingleAsync(CancellationToken cancel=default)
T Single(Expression< Func< T, bool > > predicate)
IQuerySource< T > Distinct()
Task< long > LongCountAsync(CancellationToken cancel=default)
Task< double > SumAsync(Expression< Func< T, double > > selector, CancellationToken cancel=default)
Task< T?> LastOrDefaultAsync(CancellationToken cancel=default)
Task< T > SingleAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
T? LastOrDefault(Expression< Func< T, bool > > predicate)
Task< bool > AnyAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
T Last(Expression< Func< T, bool > > predicate)
IQuerySource< T > OrderBy< K >(Expression< Func< T, K > > keySelector)
Task< T > MaxAsync(CancellationToken cancel=default)
Task< long > LongCountAsync(Expression< Func< T, bool > > predicate, CancellationToken cancel=default)
long Sum(Expression< Func< T, long > > selector)
IQuerySource< R > Select< R >(Expression< Func< T, R > > selector)
Task< bool > AnyAsync(CancellationToken cancel=default)
R Max< R >(Expression< Func< T, R > > selector)
int Count(Expression< Func< T, bool > > predicate)
MappingInfo GetInfo(Type ty)
Represents the abstract base class for constructing FQL queries.
Definition Query.cs:11
Definition Client.cs:9