@@ -62,6 +62,18 @@ type [<AbstractClass; Sealed>] Predicate =
6262 static member Where ( source : IQueryable < 'T >, indexSelector : Expression < Func < 'T , 'I >>, indexPredicate : Expression < Func < 'I , bool >>): IQueryable < 'T > =
6363 source.Where( indexSelector.Compose indexPredicate)
6464
65+ type [<NoComparison; NoEquality>] IProjection < 'T > =
66+ abstract ToAsyncEnumerable: unit -> TaskSeq < 'T >
67+ abstract Page: skip : int * take : int -> TaskSeq < 'T >
68+ abstract CountAsync: ct : CancellationToken -> Task < int >
69+
70+ type [<AbstractClass; Sealed>] ProjectionExtensions =
71+ [<Extension>] static member Count ( x : IProjection < 'M >): Async < int > = x.CountAsync |> Async.call
72+ [<Extension>] static member TryHead ( x : TaskSeq < 'T >) = TaskSeq.tryHead x |> Async.AwaitTask
73+ [<Extension>] static member TryHead ( x : IProjection < 'T >) = x.ToAsyncEnumerable() .TryHead()
74+ [<Extension>] static member All ( x : TaskSeq < 'T >) = TaskSeq.toArrayAsync x |> Async.AwaitTask
75+ [<Extension>] static member All ( x : IProjection < 'T >) = x.ToAsyncEnumerable() .All()
76+
6577module Internal =
6678 open Microsoft.Azure .Cosmos
6779 open Microsoft.Azure .Cosmos .Linq
@@ -142,23 +154,14 @@ module Internal =
142154 type Projection < 'T , 'M >( query , category , container , enum : IQueryable < 'T > -> TaskSeq < 'M >, count : IQueryable < 'T > -> CancellationToken -> Task < int >) =
143155 static member Create < 'P >( q , cat , c , log , hydrate : 'P -> 'M , logLevel ) =
144156 Projection< 'T, 'M>( q, cat, c, Query.enumAs< 'T, 'P> log c cat logLevel >> TaskSeq.map hydrate, AggregateOp.countAsync log c cat logLevel)
145- member _.ToAsyncEnumerable (): TaskSeq < 'M > = query |> enum
146- member _.Page ( skip , take ): TaskSeq < 'M > = query |> Query.offsetLimit ( skip, take) |> enum
147- member _.CountAsync : CancellationToken -> Task < int > = query |> count
157+ interface IProjection< 'M> with
158+ member _.ToAsyncEnumerable (): TaskSeq < 'M > = query |> enum
159+ member _.Page ( skip , take ): TaskSeq < 'M > = query |> Query.offsetLimit ( skip, take) |> enum
160+ member _.CountAsync cancellationToken : Task < int > = count query cancellationToken
148161 [<EditorBrowsable( EditorBrowsableState.Never) >] member val Query : IQueryable < 'T > = query
149162 [<EditorBrowsable( EditorBrowsableState.Never) >] member val Category : string = category
150163 [<EditorBrowsable( EditorBrowsableState.Never) >] member val Container : Container = container
151164
152- /// Represents a query projecting information values from an Index and/or Snapshots with a view to rendering the items and/or a count
153- type Query < 'T , 'M >( inner : Internal.Projection < 'T , 'M >) =
154- member _.ToAsyncEnumerable (): TaskSeq < 'M > = inner.ToAsyncEnumerable()
155- member _.Page ( skip , take ): TaskSeq < 'M > = inner.Page( skip, take)
156- member _.CountAsync ( ct : CancellationToken ): Task < int > = inner.CountAsync ct
157- member _.Count (): Async < int > = inner.CountAsync |> Async.call
158- member x.TryHead () = x.ToAsyncEnumerable() |> TaskSeq.tryHead |> Async.AwaitTaskCorrect
159- member x.All () = x.ToAsyncEnumerable() |> TaskSeq.toArrayAsync |> Async.AwaitTaskCorrect
160- [<EditorBrowsable( EditorBrowsableState.Never) >] member val Inner = inner
161-
162165/// Helpers for Querying Indices and Projecting Snapshot data based on well-known aspects of Equinox.CosmosStore's storage schema
163166module Index =
164167
@@ -210,7 +213,7 @@ type SnAndSnap() =
210213 bind ( nameof Unchecked.defaultof< SnAndSnap>. d, uExpression.Compose( dataExpression) .InlineParam( param)) |])
211214 Expression.Lambda< Func< 'T, SnAndSnap>>( body, [| param |])
212215 // a very ugly workaround for not being able to write query.Select<Item<'I>, SnAndSnap>(fun x -> { p = x.p; D = x.u[0].D; d = x.u[0].d })
213- static member ProjectStreamNameAndRawSnapshot ( snapshotUnfoldExpression ) : Expression < Func < Index.Item < 'I >, SnAndSnap >> =
216+ static member Project snapshotUnfoldExpression: Expression < Func < Index.Item < 'I >, SnAndSnap >> =
214217 let pExpression item = Expression.PropertyOrField( item, nameof Unchecked.defaultof< Index.Item< 'I>>. p)
215218 SnAndSnap.CreateItemQueryLambda< Index.Item< 'I>, Index.Unfold< 'I>>( pExpression, snapshotUnfoldExpression, ( fun x -> x.format), ( fun x -> x.data))
216219
@@ -234,26 +237,24 @@ type IndexContext<'I>(container, categoryName, log, [<O; D null>]?queryLogLevel)
234237
235238 /// Runs the query; yields the StreamName from the TOP 1 result
236239 member x.TryGetStreamNameAsync ( query : IQueryable < Index.Item < 'I >>, ct , [<O; D null >] ? logLevel ): Task < FsCodec.StreamName option > =
237- x.TryScalarAsync< string, FsCodec.StreamName>( query.Select _. p , ct, ?logLevel = logLevel)
240+ x.TryScalarAsync< string, FsCodec.StreamName>( query.Select( fun x -> x.p ) , ct, ?logLevel = logLevel)
238241
239242 /// Runs the query; yields the StreamName from the TOP 1 result
240243 member x.TryGetStreamName ( query : IQueryable < Index.Item < 'I >>): Async < FsCodec.StreamName option > =
241244 ( fun ct -> x.TryGetStreamNameAsync( query, ct)) |> Async.call
242245
243- /// Query the items, grabbing the Stream name, snapshot and encoding from the snapshot identified by `selectSnapshotUnfold`, mapping to a result via `hydrate`
244- member x.Hydrate < 'T >( query : IQueryable < Index.Item < 'I >>, selectSnapshotUnfold : Expression < Func < Index.Item < 'I >, Index.Unfold < 'I >>>, render : SnAndSnap -> 'T , [<O; D null >] ? logLevel ) =
245- let logLevel = defaultArg logLevel x.QueryLogLevel
246- let projection = query.Select( SnAndSnap.ProjectStreamNameAndRawSnapshot< 'I> selectSnapshotUnfold)
247- Internal.Projection.Create( projection, categoryName, container, log, render, logLevel) |> Query< SnAndSnap, 'T>
248-
249- /// Runs the query, rendering from the StreamName of each result
250- member x.HydrateStreamName < 'T >( query : IQueryable < Index.Item < 'I >>, render : FsCodec.StreamName -> 'T , [<O; D null >] ? logLevel ) =
246+ /// Query the items, projecting as T1, which gets bound to T2, which is then rendered as T
247+ member x.Project < 'T1 , 'T2 , 'T >( query : IQueryable < 'T1 >, render : 'T2 -> 'T , [<O; D null >] ? logLevel ) =
251248 let logLevel = defaultArg logLevel x.QueryLogLevel
252- Internal.Projection.Create( query.Select _. p, categoryName, container, log, render, logLevel) |> Query< string, 'T>
249+ Internal.Projection.Create< 'T2>( query, categoryName, container, log, render, logLevel) :> IProjection< 'T>
250+ /// Query the items, projecting as T1, which gets bound to T2, which is then rendered as T
251+ member x.Project < 'T1 , 'T2 , 'T >( query : IQueryable < Index.Item < 'I >>, projection : Expression < Func < Index.Item < 'I >, 'T1 >>, render : 'T2 -> 'T , [<O; D null >] ? logLevel ) =
252+ x.Project< 'T1, 'T2, 'T>( query.Select projection, render, ?logLevel = logLevel)
253253
254- type Query < 'I > =
254+ /// Query the items, grabbing the Stream name, snapshot and encoding from the snapshot identified by `selectSnapshotUnfold`, mapping to a result via `hydrate`
255+ member x.ProjectStreamNameAndSnapshot < 'T >( query : IQueryable < Index.Item < 'I >>, selectSnapshotUnfold : Expression < Func < Index.Item < 'I >, Index.Unfold < 'I >>>, render : SnAndSnap -> 'T , [<O; D null >] ? logLevel ) =
256+ x.Project< SnAndSnap, SnAndSnap, 'T>( query, SnAndSnap.Project< 'I> selectSnapshotUnfold, render, ?logLevel = logLevel)
255257
256- /// Helper to make F# consumption code more terse (the F# compiler generates Expression trees only when a function is passed to a `member`)
257- /// Example: `Query<Events.Index>.Predicate(fun e -> e.name = name)`
258- /// See https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/linq-to-sql for the list of supported constructs
259- static member Predicate expr : Expression < Func < 'I , bool >> = expr
258+ /// Runs the query, rendering from the StreamName of each result
259+ member x.ProjectStreamName < 'T >( query : IQueryable < Index.Item < 'I >>, render : FsCodec.StreamName -> 'T , [<O; D null >] ? logLevel ) =
260+ x.Project< string, FsCodec.StreamName, 'T>( query, _. p, render, ?logLevel = logLevel)
0 commit comments