From 9638ff0c1f7b2254186a089a0e883a6b9f04f2bd Mon Sep 17 00:00:00 2001
From: Adam Chlipala
Date: Sun, 3 May 2009 12:01:55 -0400
Subject: constraints demo
---
demo/constraints.ur | 38 ++++++++++++++++++++++++++++++
demo/constraints.urp | 4 ++++
demo/constraints.urs | 1 +
demo/prose | 65 ++++++++++++++++++++++++++++++----------------------
src/demo.sml | 2 +-
5 files changed, 82 insertions(+), 28 deletions(-)
create mode 100644 demo/constraints.ur
create mode 100644 demo/constraints.urp
create mode 100644 demo/constraints.urs
diff --git a/demo/constraints.ur b/demo/constraints.ur
new file mode 100644
index 00000000..bcd88bbd
--- /dev/null
+++ b/demo/constraints.ur
@@ -0,0 +1,38 @@
+table t : { Id : int, Nam : string, Parent : option int }
+ PRIMARY KEY Id,
+ CONSTRAINT Nam UNIQUE Nam,
+ CONSTRAINT Id CHECK Id >= 0,
+ CONSTRAINT Parent FOREIGN KEY Parent REFERENCES t(Id)
+
+fun main () =
+ list <- queryX (SELECT * FROM t)
+ (fn r =>
+ {[r.T.Id]} |
+ {[r.T.Nam]} |
+ {case r.T.Parent of
+ None => NULL
+ | Some id => {[id]}} |
+
);
+ return
+
+ Id | Name | Parent |
+ {list}
+
+
+
+
+
+and add r =
+ dml (INSERT INTO t (Id, Nam, Parent)
+ VALUES ({[readError r.Id]}, {[r.Nam]},
+ {[case r.Parent of
+ "" => None
+ | s => Some (readError s)]}));
+ main ()
diff --git a/demo/constraints.urp b/demo/constraints.urp
new file mode 100644
index 00000000..d5b991b0
--- /dev/null
+++ b/demo/constraints.urp
@@ -0,0 +1,4 @@
+database dbname=test
+sql constraints.sql
+
+constraints
diff --git a/demo/constraints.urs b/demo/constraints.urs
new file mode 100644
index 00000000..6ac44e0b
--- /dev/null
+++ b/demo/constraints.urs
@@ -0,0 +1 @@
+val main : unit -> transaction page
diff --git a/demo/prose b/demo/prose
index 1639301e..4dd15565 100644
--- a/demo/prose
+++ b/demo/prose
@@ -100,12 +100,23 @@ tree.urp
The signature of TreeFun.Make tells us that, to instantiate the functor, we must provide
- - A primary key type key
- - SQL field names id (for primary keys) and parent (for parent links)
- - A type-level record cols of field names besides id and parent
- - "Proofs" that id is distinct from parent and that neither of id and parent appears in cols
- - A witness that key belongs to the type class sql_injectable_prim, which indicates that both key and option key are fair game to use with SQL
- - An SQL table tab, containing a field id of type key, a field parent of type option key, and every field of cols
+ - A primary key type key
+ - SQL field names id (for primary keys) and parent (for parent links)
+ - A type-level record cols of field names besides id and parent
+ - "Proofs" that id is distinct from parent and that neither of id and parent appears in cols
+ - A witness that key belongs to the type class sql_injectable_prim, which indicates that both key and option key are fair game to use with SQL
+ - An SQL table tab, containing a field id of type key, a field parent of type option key, and every field of cols
+
+
+constraints.urp
+
+Ur/Web supports attaching SQL table constraints to table definitions. We've sprinkled a few such constraints throughout our examples so far, without mentioning them. This example shows a table with all four of the supported kinds of constraints. An application would generally try to avoid inserting data that violates constraints, but, in this example, we let you insert arbitrary data, so that you can see each of the constraints failing.
+
+
+ - The PRIMARY KEY constraint establishes the field of the table that we expect to use as a key in looking up specific rows. It is an error for two rows to share the same primary key.
+ - The UNIQUE constraint is like PRIMARY KEY, with the difference being that a table may have many UNIQUE constraints but no more than one primary key.
+ - The CHECK constraint declares a boolean assertion that must hold for every row of the table.
+ - The FOREIGN KEY constraint declares that a row of the table references a particular column of another table, or of the same table, as we see in this example. It's a static type error to reference a foreign key column that has no PRIMARY KEY or UNIQUE constraint.
sum.urp
@@ -121,13 +132,13 @@ sum.urp
Another library function foldUR is defined at the level of expressions, while mapU is a type-level function. foldUR takes 7 arguments, some of them types and some values. Type arguments are distinguished by being written within brackets. The arguments to foldUR respectively tell us:
-- The type we will assign to each record field
-- The type of the final and all intermediate results of the fold, expressed as a function over the portion of the {Unit} that has been traversed so far
-- A function that updates the accumulator based on the current record field name, the rest of the input record type, the current record field value, and the current accumulator
-- The initial accumulator value
-- The input record type
-- A folder for that type
-- The input record value
+- The type we will assign to each record field
+- The type of the final and all intermediate results of the fold, expressed as a function over the portion of the {Unit} that has been traversed so far
+- A function that updates the accumulator based on the current record field name, the rest of the input record type, the current record field value, and the current accumulator
+- The initial accumulator value
+- The input record type
+- A folder for that type
+- The input record value
An unusual part of the third argument is the syntax [t1 ~ t2] within a multi-argument fn. This syntax denotes a proof that row types t1 and t2 have no field names in common. The proof is not named, because it is applied automatically as needed. Indeed, the proof appears unused in this case, though it is actually needed to ensure the validity of some inferred types, as well as to unify with the type of foldUR.
@@ -158,12 +169,12 @@ crud1.urp
The signature of Crud.Make is based around a type function colMeta, which describes which supporting values we need for each column. This function is declared with the keyword con, which stands for "constructor," the general class of "compile-time things" that includes types. An argument to colMeta has kind (Type * Type), which means that it must be a type-level tuple. The first type is how the column is represented in SQL, and the second is how we represent it in HTML forms. In order, the components of the resulting record give:
-- A display name
-- A way of pretty-printing values of the column
-- A way of generating an HTML form widget to input this column
-- A way of generating an HTML form widget with an initial value specified
-- A way of parsing values of the column from strings
-- A type class witness, showing that the SQL representation can really be included in SQL
+- A display name
+- A way of pretty-printing values of the column
+- A way of generating an HTML form widget to input this column
+- A way of generating an HTML form widget with an initial value specified
+- A way of parsing values of the column from strings
+- A type class witness, showing that the SQL representation can really be included in SQL
The function colsMeta lifts colMeta over type-level records of type pairs. The Crud module also defines reasonable default colMeta values for some primitive types.
@@ -171,11 +182,11 @@ crud1.urp
The functor signature tells us (in order) that an input must contain:
-- A type pair record cols
-- A proof that cols does not contain a field named Id
-- A SQL table tab with an Id field of type int and other fields whose names and types are read off of cols
-- A display title for the admin interface
-- A record of meta-data for the columns
+- A type pair record cols
+- A proof that cols does not contain a field named Id
+- A SQL table tab with an Id field of type int and other fields whose names and types are read off of cols
+- A display title for the admin interface
+- A record of meta-data for the columns
Looking at crud1.ur, we see that a use of the functor is almost trivial. Only the value components of the argument structure must be provided. The column row type is inferred, and the disjointness constraint is proved automatically.
@@ -226,9 +237,9 @@ batchG.urp
The first three fields of a colMeta record are the same as for Crud. The rest of the fields are:
- - NewState, which allocates some new widget local state
- - Widget, which produces a reactive widget from some state
- - ReadState, which reads the current value of some state to determine which SQL value it encodes
+ - NewState, which allocates some new widget local state
+ - Widget, which produces a reactive widget from some state
+ - ReadState, which reads the current value of some state to determine which SQL value it encodes
BatchFun.Make handles the plumbing of allocating the local state, using it to create widgets, and reading the state values when the user clicks "Batch it."
diff --git a/src/demo.sml b/src/demo.sml
index dc4715d7..55ff5bb8 100644
--- a/src/demo.sml
+++ b/src/demo.sml
@@ -44,7 +44,7 @@ fun make {prefix, dirname, guided} =
file = "index.html"}
val out = TextIO.openOut fname
- val () = (TextIO.output (out, "