Last active
July 21, 2020 13:15
-
-
Save Luiz-Monad/c10851b84a3c21c755e3a6603aeeb7a8 to your computer and use it in GitHub Desktop.
graphql-less query creator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Controller.Selector | |
open System | |
open log4net | |
open System.Linq | |
open System.Web.Http | |
open Microsoft.FSharp.Linq | |
open Microsoft.FSharp.Quotations | |
open Backend.Helpers | |
open Backend.TypeHelpers | |
open Backend.Model.Selection | |
open Backend.ExpressionHelpers | |
open Backend.Extension.DbContextModule | |
module SelectorModule = | |
exception InvalidColumnExn of string | |
let DataIdColumn = "Id" | |
//Generic column. | |
[<AbstractClass>] | |
type Column<'T> () = | |
abstract member map : 'T Linq.IQueryable Expr -> 'T Linq.IQueryable Expr | |
static member create ( templateType : Type ) column ( constructorParam : obj ) = | |
let p = typeof<'T>.GetProperty column | |
InvalidColumnExn column |> raiseWhen ( isNull p ) | |
let gt = templateType.GetGenericTypeDefinition () | |
let g = gt.MakeGenericType ( typeof<'T>, p.PropertyType ) | |
Activator.CreateInstance ( g, constructorParam ) :?> Column<'T> | |
//Filter typed for the column. | |
type Filter<'T, 'K when 'K : comparison> ( param ) = | |
inherit Column<'T> () | |
override this.map source = | |
let ( column, oValue : obj, operator ) = param | |
let value = prepareValue oValue | |
let finalPredicate = | |
let col = quotedProperty<'T, 'K> column | |
let like = <@ fun x y -> ( x :> obj :?> string ).Contains( y :> obj :?> string ) @> | |
match operator with | |
| LessThan -> <@ fun record -> (%col) record < value @> | |
| LessOrEqualThan -> <@ fun record -> (%col) record <= value @> | |
| Equal -> <@ fun record -> (%col) record = value @> | |
| GreaterThan -> <@ fun record -> (%col) record > value @> | |
| GreaterOrEqualThan -> <@ fun record -> (%col) record >= value @> | |
| Like -> <@ fun record -> (%like) ( (%col) record ) value @> | |
<@ query { | |
for entity in (%source) do | |
where ( (%finalPredicate) entity ) | |
select entity } @> | |
//Process filtering. | |
let processFiltering selector source = | |
match selector with | |
| { Filters = filters } when isNotNull filters -> | |
let col source ( column, value, operator ) = | |
let p = box ( column, value, operator ) | |
let fn = Column<'T>.create typeof<Filter<_, _>> column p | |
fn.map source | |
filters |> List.fold col source | |
| _ -> source | |
//Filter typed for the column. | |
type Sorter<'T, 'K when 'K : comparison> ( param ) = | |
inherit Column<'T> () | |
override this.map source = | |
let ( column, order, first ) = param | |
let col = quotedProperty<'T, 'K> column | |
match ( order, first ) with | |
| Ascending, true -> <@ query { | |
for item in (%source) do | |
sortBy ( (%col) item ) | |
thenBy ( (%col) item ) } @> | |
| Descending, true -> <@ query { | |
for item in (%source) do | |
sortByDescending ( (%col) item ) } @> | |
| Ascending, false -> <@ query { | |
for item in (%source) do | |
thenBy ( (%col) item ) } @> | |
| Descending, false -> <@ query { | |
for item in (%source) do | |
thenByDescending ( (%col) item ) } @> | |
//Process sorting. | |
let processSorting selector source = | |
let col source ( column, order, first ) = | |
let p = box ( column, order, first ) | |
let fn = Column<'T>.create typeof<Sorter<_, _>> column p | |
fn.map source | |
match selector with | |
| { Columns = columns } when isNotNull columns && columns.Length > 0 -> | |
let item ( ix, ( col, order ) ) = ( col, order, ix = 0 ) | |
columns |> List.indexed |> List.map item |> List.fold col source | |
| _ -> | |
col source ( DataIdColumn, Descending, true ) | |
//Process paginating. | |
let processPaging selector source = | |
match selector with | |
| pager when isNotNull pager -> | |
let ct = pager.RecordCount |> max 10 | |
let id = pager.PageNumber |> max 1 | |
let takeCount = ct | |
let skipCount = (id - 1) * ct | |
let createQuery src = | |
<@ query { | |
for entity in (%src) do | |
select entity | |
skip skipCount | |
take takeCount } @> | |
source |> createQuery | |
| _ -> source | |
//Process key search. | |
let processKey id source = | |
processFiltering | |
<| { Filters = [ ( DataIdColumn, box id, Equal ) ] } | |
<| source | |
//Process paginating. | |
let processTotal source = | |
<@ query { | |
for entity in (%source) do | |
count } @> | |
//Process selector modes. | |
let processTotalSelector selector = | |
processFiltering selector >> | |
processTotal >> | |
runQueryValue | |
//Process selector modes. | |
let processSelector selector = | |
let noLazy = Seq.toList >> Seq.ofList | |
match selector with | |
| AllData -> | |
runQuery >> noLazy | |
| Key k -> | |
processKey k >> | |
runQuery >> noLazy | |
| Selector s -> | |
processFiltering s.Filter >> | |
processSorting s.Sorter >> | |
processPaging s.Paginator >> | |
runQuery >> noLazy | |
| Total _ -> | |
fun _ -> Seq.empty | |
[<AutoOpen>] | |
module SelectorControllerModule = | |
open SelectorModule | |
//Add the trait to the controller. | |
let selectorController dataAccess controller = | |
let read = function | |
| Some ( Total t ) -> dataAccess () |> processTotalSelector t |> Totals | |
| Some s -> dataAccess () |> processSelector s |> Records | |
| None -> Seq.empty |> Records | |
controller read | |
//Deep clone the selector, used for type punning. | |
let inline selectorCopy o = | |
o |> whenSome selectorDeepCopy | |
// copyright Luiz Felipe Eugenio Stangarlin, circa 2015 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment