changeset 993:10114d7b7477

SELECT DISTINCT; eta expansion during Cjrization
author Adam Chlipala <adamc@hcoop.net>
date Tue, 06 Oct 2009 15:39:27 -0400
parents b825d843b22d
children 7932d577cf78
files demo/more/versioned.ur demo/more/versioned.urp demo/more/versioned.urs demo/more/versioned1.ur demo/more/versioned1.urp demo/more/versioned1.urs lib/ur/basis.urs lib/ur/top.ur lib/ur/top.urs src/cjrize.sml src/elisp/urweb-mode.el src/mono_env.sig src/monoize.sml src/urweb.grm src/urweb.lex
diffstat 15 files changed, 260 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demo/more/versioned.ur	Tue Oct 06 15:39:27 2009 -0400
@@ -0,0 +1,114 @@
+functor Make(M : sig
+                 con key :: {Type}
+                 con data :: {Type}
+                 constraint key ~ data
+                 constraint [When] ~ (key ++ data)
+
+                 val key : $(map sql_injectable key)
+                 val data : $(map (fn t => {Inj : sql_injectable_prim t,
+                                            Eq : eq t}) data)
+
+                 val keyFolder : folder key
+                 val dataFolder : folder data
+             end) = struct
+    con all = [When = time] ++ M.key ++ map option M.data
+    table t : all
+
+    val keys = List.mapQuery (SELECT DISTINCT t.{{M.key}} FROM t) (fn r => r.T)
+
+    con dmeta = fn t => {Inj : sql_injectable_prim t,
+                         Eq : eq t}
+
+    fun keyRecd (r : $(M.key ++ M.data)) =
+        map2 [sql_injectable] [id] [sql_exp [] [] []]
+             (fn [t] => @sql_inject)
+             [_] M.keyFolder M.key (r --- M.data)
+
+    fun insert r = dml (Basis.insert t
+                                     ({When = (SQL CURRENT_TIMESTAMP)}
+                                          ++ keyRecd r
+                                          ++ map2 [dmeta] [id]
+                                          [fn t => sql_exp [] [] [] (option t)]
+                                          (fn [t] x v => @sql_inject (@sql_option_prim x.Inj)
+                                                          (Some v))
+                                          [_] M.dataFolder M.data (r --- M.key)))
+
+    fun keyExp (r : $M.key) : sql_exp [T = all] [] [] bool =
+        foldR2 [sql_injectable] [id] [fn before => after :: {Type} -> [before ~ after]
+                                         => sql_exp [T = before ++ after] [] [] bool]
+               (fn [nm :: Name] [t :: Type] [before :: {Type}] [[nm] ~ before]
+                                (inj : sql_injectable t) (v : t)
+                   (e : after :: {Type} -> [before ~ after]
+                    => sql_exp [T = before ++ after] [] [] bool)
+                   [after :: {Type}] [[nm = t] ++ before ~ after] =>
+                   (SQL t.{nm} = {[v]} AND {e [[nm = t] ++ after] !}))
+               (fn [after :: {Type}] [[] ~ after] => (SQL TRUE))
+               [_] M.keyFolder M.key r
+               [_] !
+
+    fun current k =
+        let
+            fun current' timeOpt r =
+                let
+                    val complete = foldR [option] [fn ts => option $ts]
+                                   (fn [nm :: Name] [v :: Type] [r :: {Type}] [[nm] ~ r]
+                                                    v r =>
+                                       case (v, r) of
+                                           (Some v, Some r) => Some ({nm = v} ++ r)
+                                         | _ => None)
+                                   (Some {}) [_] M.dataFolder r
+                in
+                    case complete of
+                        Some r => return (Some r)
+                      | None =>
+                        let
+                            val filter = case timeOpt of
+                                             None => (WHERE TRUE)
+                                           | Some time => (WHERE t.When < {[time]})
+                        in
+                            ro <- oneOrNoRows (SELECT t.When, t.{{map option M.data}}
+                                               FROM t
+                                               WHERE {filter}
+                                                 AND {keyExp k}
+                                               ORDER BY t.When DESC
+                                               LIMIT 1);
+                            case ro of
+                                None => return None
+                              | Some r' =>
+                                let
+                                    val r = map2 [option] [option] [option]
+                                            (fn [t ::: Type] old new =>
+                                                case old of
+                                                    None => new
+                                                  | Some _ => old)
+                                            [_] M.dataFolder r (r'.T -- #When)
+                                in
+                                    current' (Some r'.T.When) r
+                                end
+                        end
+                end
+        in
+            current' None (map0 [option] (fn [t :: Type] => None : option t) [_] M.dataFolder)
+        end
+
+    fun update r =
+        cur <- current (r --- M.data);
+        case cur of
+            None => error <xml>Tried to update nonexistent key</xml>
+          | Some cur =>
+            let
+                val r' = map3 [dmeta] [id] [id] [fn t => sql_exp [] [] [] (option t)]
+                              (fn [t] (meta : dmeta t) old new =>
+                                  @sql_inject (@sql_option_prim meta.Inj)
+                                   (if @@eq [_] meta.Eq old new then
+                                        None
+                                    else
+                                        Some new))
+                              [_] M.dataFolder M.data cur (r --- M.key)
+                val r' = {When = (SQL CURRENT_TIMESTAMP)}
+                             ++ keyRecd r
+                             ++ r'
+            in
+                dml (Basis.insert t r')
+            end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demo/more/versioned.urp	Tue Oct 06 15:39:27 2009 -0400
@@ -0,0 +1,4 @@
+
+$/option
+$/list
+versioned
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demo/more/versioned.urs	Tue Oct 06 15:39:27 2009 -0400
@@ -0,0 +1,19 @@
+functor Make(M : sig
+                 con key :: {Type}
+                 con data :: {Type}
+                 constraint key ~ data
+                 constraint [When] ~ (key ++ data)
+
+                 val key : $(map sql_injectable key)
+                 val data : $(map (fn t => {Inj : sql_injectable_prim t,
+                                            Eq : eq t}) data)
+
+                 val keyFolder : folder key
+                 val dataFolder : folder data
+             end) : sig
+    val insert : $(M.key ++ M.data) -> transaction unit
+    val update : $(M.key ++ M.data) -> transaction unit
+
+    val keys : transaction (list $M.key)
+    val current : $M.key -> transaction (option $M.data)
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demo/more/versioned1.ur	Tue Oct 06 15:39:27 2009 -0400
@@ -0,0 +1,62 @@
+open Versioned.Make(struct
+                        con key = [Id = int]
+                        con data = [Nam = string, ShoeSize = int]
+
+                        val key = {Id = _}
+                        val data = {Nam = {Inj = _,
+                                           Eq = _},
+                                    ShoeSize = {Inj = _,
+                                                Eq = _}}
+                    end)
+
+fun expandKey k =
+    name <- source "";
+    shoeSize <- source "";
+    return {Key = k, Nam = name, ShoeSize = shoeSize}
+
+fun main () =
+    ks0 <- keys;
+    ks0 <- List.mapM (fn r => expandKey r.Id) ks0;
+    ks <- source ks0;
+
+    id <- source "";
+    name <- source "";
+    shoeSize <- source "";
+
+    return <xml><body>
+      <dyn signal={ks <- signal ks;
+                   return (List.mapX (fn kr => <xml><div>
+                     {[kr.Key]}:
+                     <ctextbox source={kr.Nam}/>
+                     <ctextbox size={5} source={kr.ShoeSize}/>
+                     <button value="Latest" onclick={ro <- rpc (current {Id = kr.Key});
+                                                     case ro of
+                                                         None => alert "Can't get it!"
+                                                       | Some r =>
+                                                         set kr.Nam r.Nam;
+                                                         set kr.ShoeSize (show r.ShoeSize)}/>
+                     <button value="Update" onclick={name <- get kr.Nam;
+                                                     shoeSize <- get kr.ShoeSize;
+                                                     rpc (update {Id = kr.Key,
+                                                                  Nam = name,
+                                                                  ShoeSize = readError shoeSize})
+                                                    }/>
+                   </div></xml>) ks)}/>
+
+      <h2>Add one:</h2>
+
+      <table>
+        <tr><th>Id:</th> <td><ctextbox size={5} source={id}/></td></tr>
+        <tr><th>Name:</th> <td><ctextbox source={name}/></td></tr>
+        <tr><th>Shoe size:</th> <td><ctextbox size={5} source={shoeSize}/></td></tr>
+        <tr><th><button value="Add" onclick={id <- get id;
+                                             name <- get name;
+                                             shoeSize <- get shoeSize;
+                                             rpc (insert {Id = readError id, Nam = name,
+                                                          ShoeSize = readError shoeSize});
+
+                                             cur <- get ks;
+                                             kr <- expandKey (readError id);
+                                             set ks (kr :: cur)}/></th></tr>
+      </table>
+    </body></xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demo/more/versioned1.urp	Tue Oct 06 15:39:27 2009 -0400
@@ -0,0 +1,6 @@
+debug
+library versioned
+database dbname=test
+sql versioned1.sql
+
+versioned1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demo/more/versioned1.urs	Tue Oct 06 15:39:27 2009 -0400
@@ -0,0 +1,1 @@
+val main : unit -> transaction page
--- a/lib/ur/basis.urs	Tue Oct 06 13:11:03 2009 -0400
+++ b/lib/ur/basis.urs	Tue Oct 06 15:39:27 2009 -0400
@@ -291,7 +291,8 @@
                  -> grouped ::: {{Type}}
                  -> selectedFields ::: {{Type}}
                  -> selectedExps ::: {Type}
-                 -> {From : sql_from_items tables,
+                 -> {Distinct : bool,
+                     From : sql_from_items tables,
                      Where : sql_exp tables [] [] bool,
                      GroupBy : sql_subset tables grouped,
                      Having : sql_exp grouped tables [] bool,
--- a/lib/ur/top.ur	Tue Oct 06 13:11:03 2009 -0400
+++ b/lib/ur/top.ur	Tue Oct 06 15:39:27 2009 -0400
@@ -92,6 +92,12 @@
 fun txt [t] [ctx ::: {Unit}] [use ::: {Type}] (_ : show t) (v : t) =
     cdata (show v)
 
+fun map0 [K] [tf :: K -> Type] (f : t :: K -> tf t) [r :: {K}] (fl : folder r) =
+    fl [fn r :: {K} => $(map tf r)]
+    (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc =>
+        acc ++ {nm = f [t]})
+    {}
+
 fun mp [K] [tf1 :: K -> Type] [tf2 :: K -> Type] (f : t ::: K -> tf1 t -> tf2 t) [r :: {K}] (fl : folder r) =
     fl [fn r :: {K} => $(map tf1 r) -> $(map tf2 r)]
     (fn [nm :: Name] [t :: K] [rest :: {K}] [[nm] ~ rest] acc r =>
--- a/lib/ur/top.urs	Tue Oct 06 13:11:03 2009 -0400
+++ b/lib/ur/top.urs	Tue Oct 06 15:39:27 2009 -0400
@@ -45,6 +45,9 @@
 val txt : t ::: Type -> ctx ::: {Unit} -> use ::: {Type} -> show t -> t
           -> xml ctx use []
 
+val map0 : K --> tf :: (K -> Type)
+           -> (t :: K -> tf t)
+           -> r :: {K} -> folder r -> $(map tf r)
 val mp : K --> tf1 :: (K -> Type) -> tf2 :: (K -> Type)
          -> (t ::: K -> tf1 t -> tf2 t)
          -> r :: {K} -> folder r -> $(map tf1 r) -> $(map tf2 r)
--- a/src/cjrize.sml	Tue Oct 06 13:11:03 2009 -0400
+++ b/src/cjrize.sml	Tue Oct 06 15:39:27 2009 -0400
@@ -520,9 +520,14 @@
                                           in
                                               ((ax, dom) :: args, t, e)
                                           end
-                                        | (L'.TFun _, _) =>
-                                          (ErrorMsg.errorAt loc "Function isn't explicit at code generation";
-                                           ([], tAll, eAll))
+                                        | (L'.TFun (dom, ran), _) =>
+                                          let
+                                              val e = MonoEnv.liftExpInExp 0 eAll
+                                              val e = (L.EApp (e, (L.ERel 0, loc)), loc)
+                                              val (args, t, e) = unravel (ran, e)
+                                          in
+                                              (("x", dom) :: args, t, e)
+                                          end
                                         | _ => ([], tAll, eAll)
 
                                   val (args, ran, e) = unravel (t, e)
--- a/src/elisp/urweb-mode.el	Tue Oct 06 13:11:03 2009 -0400
+++ b/src/elisp/urweb-mode.el	Tue Oct 06 15:39:27 2009 -0400
@@ -144,7 +144,7 @@
   "A regexp that matches any non-SQL keywords of Ur/Web.")
 
 (defconst urweb-sql-keywords-regexp
-  (urweb-syms-re "SELECT" "FROM" "AS" "WHERE" "SQL" "GROUP" "ORDER" "BY"
+  (urweb-syms-re "SELECT" "DISTINCT" "FROM" "AS" "WHERE" "SQL" "GROUP" "ORDER" "BY"
                  "HAVING" "LIMIT" "OFFSET" "ALL" "UNION" "INTERSECT" "EXCEPT"
                  "TRUE" "FALSE" "AND" "OR" "NOT" "COUNT" "AVG" "SUM" "MIN" "MAX"
                  "ASC" "DESC" "INSERT" "INTO" "VALUES" "UPDATE" "SET" "DELETE"
--- a/src/mono_env.sig	Tue Oct 06 13:11:03 2009 -0400
+++ b/src/mono_env.sig	Tue Oct 06 15:39:27 2009 -0400
@@ -48,5 +48,7 @@
     val declBinds : env -> Mono.decl -> env
     val patBinds : env -> Mono.pat -> env
     val patBindsN : Mono.pat -> int
+
+    val liftExpInExp : int -> Mono.exp -> Mono.exp
                                                  
 end
--- a/src/monoize.sml	Tue Oct 06 13:11:03 2009 -0400
+++ b/src/monoize.sml	Tue Oct 06 15:39:27 2009 -0400
@@ -1771,6 +1771,7 @@
             let
                 fun sc s = (L'.EPrim (Prim.String s), loc)
                 val s = (L'.TFfi ("Basis", "string"), loc)
+                val b = (L'.TFfi ("Basis", "bool"), loc)
                 val un = (L'.TRecord [], loc)
                 fun gf s = (L'.EField ((L'.ERel 0, loc), s), loc)
 
@@ -1806,7 +1807,8 @@
                                         (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) sexps
                     in
                         ((L'.EAbs ("r",
-                                   (L'.TRecord [("From", s),
+                                   (L'.TRecord [("Distinct", b),
+                                                ("From", s),
                                                 ("Where", s),
                                                 ("GroupBy", un),
                                                 ("Having", s),
@@ -1815,6 +1817,22 @@
                                     loc),
                                    s,
                                    strcat [sc "SELECT ",
+                                           (L'.ECase (gf "Distinct",
+                                                      [((L'.PCon (L'.Enum,
+                                                                  L'.PConFfi {mod = "Basis",
+                                                                              datatyp = "bool",
+                                                                              con = "True",
+                                                                              arg = NONE},
+                                                                  NONE), loc),
+                                                        (L'.EPrim (Prim.String "DISTINCT "), loc)),
+                                                       ((L'.PCon (L'.Enum,
+                                                                  L'.PConFfi {mod = "Basis",
+                                                                              datatyp = "bool",
+                                                                              con = "False",
+                                                                              arg = NONE},
+                                                                  NONE), loc),
+                                                        (L'.EPrim (Prim.String ""), loc))],
+                                                      {disc = b, result = s}), loc),
                                            strcatComma (map (fn (x, t) =>
                                                                 strcat [
                                                                 (L'.EField (gf "SelectExps", x), loc),
--- a/src/urweb.grm	Tue Oct 06 13:11:03 2009 -0400
+++ b/src/urweb.grm	Tue Oct 06 15:39:27 2009 -0400
@@ -208,7 +208,7 @@
  | NOTAGS of string 
  | BEGIN_TAG of string | END_TAG of string
 
- | SELECT | FROM | AS | CWHERE | GROUP | ORDER | BY | HAVING
+ | SELECT | DISTINCT | FROM | AS | CWHERE | GROUP | ORDER | BY | HAVING
  | UNION | INTERSECT | EXCEPT
  | LIMIT | OFFSET | ALL
  | TRUE | FALSE | CAND | OR | NOT
@@ -314,6 +314,7 @@
 
  | query of exp
  | query1 of exp
+ | dopt of exp
  | tables of con list * exp
  | fitem of con list * exp
  | tname of con
@@ -625,7 +626,7 @@
 commaOpt:                               ()
         | COMMA                         ()
 
-pkopt  :                                (EVar (["Basis"], "no_primary_key", Infer), ErrorMsg.dummySpan)
+pkopt  :                                (EVar (["Basis"], "no_primary_key", Infer), dummy)
        | PRIMARY KEY tnames             (let
                                              val loc = s (PRIMARYleft, tnamesright)
 
@@ -1410,8 +1411,12 @@
                                          in
                                              (EApp ((EVar (["Basis"], "sql_query", Infer), loc), re), loc)
                                          end)
-                
-query1 : SELECT select FROM tables wopt gopt hopt
+
+dopt   :                                (EVar (["Basis"], "False", Infer), dummy)
+       | DISTINCT                       (EVar (["Basis"], "True", Infer),
+                                         s (DISTINCTleft, DISTINCTright))
+
+query1 : SELECT dopt select FROM tables wopt gopt hopt
                                         (let
                                              val loc = s (SELECTleft, tablesright)
 
@@ -1460,7 +1465,9 @@
                                                            end
 
                                              val e = (EVar (["Basis"], "sql_query1", Infer), loc)
-                                             val re = (ERecord [((CName "From", loc),
+                                             val re = (ERecord [((CName "Distinct", loc),
+                                                                 dopt),
+                                                                ((CName "From", loc),
                                                                  #2 tables),
                                                                 ((CName "Where", loc),
                                                                  wopt),
--- a/src/urweb.lex	Tue Oct 06 13:11:03 2009 -0400
+++ b/src/urweb.lex	Tue Oct 06 15:39:27 2009 -0400
@@ -408,6 +408,7 @@
 <INITIAL> "Unit"      => (Tokens.KUNIT (pos yypos, pos yypos + size yytext));
 
 <INITIAL> "SELECT"    => (Tokens.SELECT (pos yypos, pos yypos + size yytext));
+<INITIAL> "DISTINCT"  => (Tokens.DISTINCT (pos yypos, pos yypos + size yytext));
 <INITIAL> "FROM"      => (Tokens.FROM (pos yypos, pos yypos + size yytext));
 <INITIAL> "AS"        => (Tokens.AS (pos yypos, pos yypos + size yytext));
 <INITIAL> "WHERE"     => (Tokens.CWHERE (pos yypos, pos yypos + size yytext));