[Ur] Capturing classes of queries/Dynamic SQL queries

Adam Chlipala adamc at csail.mit.edu
Wed Jun 25 14:10:22 EDT 2014


Your question inspired me to add one small parsing extension to Ur/Web!  
With that extension, available from the public Mercurial repo 
<http://hg.impredicative.com/urweb>, the following code gets the job done:

table images : { Id : int, Content : blob }
table tags : { Id : int, Tag : string }

datatype mode = Present | Absent
type condition = { Tag : string, Mode : mode }

type tag_query = sql_query [] [] [] [Id = int]

fun addCondition (c : condition) (q : tag_query) : tag_query =
     case c.Mode of
         Present => (SELECT I.Id AS Id
                     FROM ({{q}}) AS I
                       JOIN tags ON tags.Id = I.Id AND tags.Tag = {[c.Tag]})
       | Absent => (SELECT I.Id AS Id
                    FROM ({{q}}) AS I
                      LEFT JOIN tags ON tags.Id = I.Id AND tags.Tag = 
{[c.Tag]}
                    WHERE tags.Tag IS NULL)

fun withConditions (cs : list condition) : tag_query =
     List.foldl addCondition (SELECT images.Id AS Id FROM images) cs

fun main (cs : list condition) : transaction page =
     x <- queryX (withConditions cs) (fn r => <xml><li>{[r.Id]}</li></xml>);
     return <xml><body>
       {x}
     </body></xml>


On 06/21/2014 06:23 PM, orchidaceae phalaenopsis wrote:
> A bit stuck on something, hopefully someone can give some guidance!
>
> To perform a search in our application the user inputs a list of
> positive terms and a list of negative terms which will be turned into
> a SQL query. The general pattern can be inferred from this example:
>
> val query a b c d e =
>      (SELECT images.Id
>       FROM images
>         JOIN tags AS P1 ON P1.Id=images.Id
>         JOIN tags AS P2 ON P2.Id=images.Id
>         JOIN tags AS P3 ON P3.Id=images.Id
>         LEFT JOIN tags AS N1 ON N1.Id=images.Id AND N1.Tag={[d]}
>         LEFT JOIN tags AS N2 ON N2.Id=images.Id AND N2.Tag={[e]}
>       WHERE P1.Tag={[a]}
>         AND P2.Tag={[b]}
>         AND P3.Tag={[c]}
>         AND N1.Tag IS NULL
>         AND N2.Tag IS NULL)
>
> I would like to generate queries like this from lists containing
> positive and negative terms. It doesn't seem possible to do this
> directly though because there is no source of computed names (i.e. we
> can't have functions int -> Name).
>
> If I limit the possible searches to <n positive terms and <m negative
> terms then it would be possible to just hard code n*m queries. This is
> what we're doing right now and it's working fine but for a new feature
> we need to double the number of queries.
>
> If I pass in two records whose fields are used to generate the query
> with row-type metaprogramming techniques I think that I only need to
> hard code n+m ways of converting lists into records so I'll probably
> go with this solution - as long as this is the right way to do this.
> The types involved are pretty scary and difficult to put together so I
> wanted to be sure I wasn't doing something strange.
>
> I've started to work on the record solution and by inspecting the
> output of urweb with "-stop parse" I managed to correct the output a
> bit and get this accepted:
>
> val query' a =
>      sql_query {Rows =
>             sql_query1 [[]]
>                {Distinct = False,
>                 From = sql_inner_join
>                        (sql_from_table [#Images] images)
>                        (sql_from_table [#P1] tags)
>                        (sql_binary sql_eq
>                            (sql_field [#P1] [#Id])
>                            (sql_field [#Images] [#Id])),
>                 Where = sql_binary sql_eq
>                            (sql_field [#P1] [#Tag])
>                            (sql_inject a),
>                 GroupBy = sql_subset_all [(_ :: {{Type}})],
>                 Having = sql_inject True,
>                 SelectFields = sql_subset_all [(_ :: {{Type}})],
>                 SelectExps = {}},
>                 OrderBy = sql_order_by_Nil [(_ :: {Type})],
>                 Limit = sql_no_limit,
>             Offset = sql_no_offset}
>
> So would this be the right way to build our dynamic query? It's a
> shame not to use the nice built-in query syntax but it looks like I
> have to get one level lower. Thanks for any input on this.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.impredicative.com/pipermail/ur/attachments/20140625/ca6f44a7/attachment.html>


More information about the Ur mailing list