fsprojects / FSharp.AWS.DynamoDB

F# wrapper API for AWS DynamoDB
MIT License
58 stars 18 forks source link

Question on Combining Quotation Predicates #62

Closed alexmorask closed 1 year ago

alexmorask commented 1 year ago

Hi there,

I'm relatively new to F#, so I assume I'm just thinking about this problem incorrectly but would love to get some feedback if possible.

I'm trying to take in optional query parameters from a request and use them to build up the Expr<T' -> bool> necessary to call the TableContext.ScanAsync method.

To build up that Expr<T' -> bool>, I'm using quotation slicing:

// Given the simplified record below
type Item = {
    Text : string
    Number : int
}

// Build the expressions based on incoming query parameters
// textQueryParam: string
// numQueryParam: int
let textQuery = <@ fun (item: Item) -> item.Text = textQueryParam @>
let numQuery = <@ fun (item: Item) -> item.Number = numQueryParam @>

let finalQuery = <@ fun (item: Item) -> (%textQuery) item && (%numQuery) item @>

However, this results in a Supplied expression is not a valid conditional. exception.

If I don't use quotation slicing, for instance, if I just do:

let finalQuery = <@ fun (item: Item) -> item.Text = textQueryParam && item.Number = numQueryParam @>

then it expectedly works just fine.

The only reason I'm trying to use slicing is that, in the real application, the query strings are optional - so I'd like to be able to dynamically construct the Expr<Item -> bool> based on which query strings are actually provided by the client and come in as Some.

My apologies if I'm just misunderstanding a language feature rather than anything with the FSharp.AWS.DynamoDB package, but any direction would be greatly appreciated. Thanks in advance.

samritchie commented 1 year ago

Hi @alexmorask, in all honesty I've never used quotation splicing before. However, operations possible in a filter expression are fairly limited (to what we can easily translate to a Dynamo API call).

If I'm correctly understanding how it works, your spliced query is actually equivalent to:

let finalQuery = <@ fun (item: Item) -> (fun (i: Item) -> i.Text = textQueryParam) item && (fun (i: Item) -> i.Number = numQueryParam) item @>

I can't think of any easy way to extract logic from functions within the expression tree and flatten into a single expression. Is it possible to instead do something like:

let finalQuery = <@ fun (item: Item) -> (textQueryParam.IsNone ||  item.Text = textQueryParam.Value) && (numQueryParam.IsNone || item.Number = numQueryParam.Value) @>
alexmorask commented 1 year ago

Hi @samritchie,

That actually worked perfectly. Thanks a ton for the help!