Skip to content

Commit 7c540cf

Browse files
committed
More polish
1 parent c6ecaa4 commit 7c540cf

File tree

1 file changed

+30
-29
lines changed

1 file changed

+30
-29
lines changed

src/Equinox.CosmosStore/CosmosStoreLinq.fs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
6577
module 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
163166
module 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&lt;Events.Index&gt;.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

Comments
 (0)