Changed to use new smlsql interface
authoradamch <adamch>
Fri, 15 Apr 2005 17:14:46 +0000 (17:14 +0000)
committeradamch <adamch>
Fri, 15 Apr 2005 17:14:46 +0000 (17:14 +0000)
balance.sml
contact.mlt
contact.sig
contact.sml
exn.mlt
group.sml
init.sig
init.sml
money.sml
poll.sml
tables.sql

index b2e905c..7b25933 100644 (file)
@@ -10,7 +10,7 @@ type balance = {id :int, name : string, amount : real}
 
 fun mkBalanceRow [id, name, amount] =
     {id = C.intFromSql id, name = C.stringFromSql name, amount = C.realFromSql amount}
-  | mkBalanceRow row = raise Fail ("Bad balance row : " ^ makeSet id row)
+  | mkBalanceRow row = Init.rowError ("balance", row)
 
 fun addBalance name =
     let
@@ -18,8 +18,8 @@ fun addBalance name =
        val id = nextSeq (db, "BalanceSeq")
     in
        C.dml db ($`INSERT INTO Balance (id, name, amount)
-                    VALUES (^id, ^(C.stringToSql name), 0.0)`);
-       C.intFromSql id
+                    VALUES (^(C.intToSql id), ^(C.stringToSql name), 0.0)`);
+       id
     end
 
 fun lookupBalance id =
dissimilarity index 95%
index 0dc61d4..75b6978 100644 (file)
@@ -1,5 +1,43 @@
-<% @header[("title", ["Contact information"])] %>
-
-
-
-<% @footer[] %>
\ No newline at end of file
+<% @header[("title", ["Contact information"])];
+
+ref showNormal = true;
+
+if $"cmd" = "add" then
+       val id = Contact.addContact (Init.getUserId(), Web.stoi ($"knd"), $"v", Contact.privFromInt (Web.stoi ($"priv"))) %>
+       <h3><b>Contact added</b></h3>
+<% end;
+
+if showNormal then %>
+
+<h3><b>Add new contact information</b></h3>
+
+<form action="contact">
+<input type="hidden" name="cmd" value="add">
+<table>
+<tr> <td align="right"><b>Kind</b>:</td> <td><select name="knd">
+<% foreach kind in Contact.listKinds () do %>
+       <option value="<% #id kind %>"><% Web.html (#name kind) %></option>
+<% end %>
+</select></td> </tr>
+<tr> <td align="right"><b>Value</b>:</td> <td><input name="v"></td> </tr>
+<tr> <td align="right"><b>Readable by</b>:</td> <td><select name="priv">
+       <option value="0">Anyone</option>
+       <option value="1" selected>Members only</option>
+       <option value="2">Admins only</option>
+</select></td> </tr>
+<tr> <td><input type="submit" value="Add"></td> </tr>
+</table>
+</form>
+
+<h3><b>Your contact information</b></h3>
+
+<table>
+<% foreach (kind, cont) in Contact.listUserContacts (Init.getUserId(), Contact.ADMINS) do %>
+       <tr> <td align="right" valign="top"><b><% Web.html (#name kind) %></b>:</td>
+       <td><% Contact.format (kind, cont) %></td> </tr>
+<% end %>
+</table>
+
+<% end %>
+
+<% @footer[] %>
\ No newline at end of file
index 8d7a2eb..1fb0454 100644 (file)
@@ -7,4 +7,21 @@ sig
     val modKind : kind -> unit
     val deleteKind : int -> unit
     val listKinds : unit -> kind list
+
+    datatype priv =
+            PUBLIC
+          | MEMBERS
+          | ADMINS
+    val privToInt : priv -> int
+    val privFromInt : int -> priv
+
+    type contact = {id :int, usr : int, knd : int, v : string, priv : priv}
+
+    val addContact : int * int * string * priv -> int
+    val lookupContact : int -> contact
+    val modContact : contact -> unit
+    val deleteContact : int -> unit
+    val listUserContacts : int * priv -> (kind * contact) list
+
+    val format : kind * contact -> string
 end
\ No newline at end of file
index 2214ead..345ab51 100644 (file)
@@ -4,7 +4,7 @@ struct
 open Util Sql Init
 
 
-(* Managing transactions *)
+(* Managing kinds *)
 
 type kind = {id :int, name : string, makeUrl : (string * string) option}
 
@@ -20,7 +20,7 @@ fun mkKindRow [id, name, url, urlPrefix, urlPostfix] =
                    SOME (C.stringFromSql urlPrefix, C.stringFromSql urlPostfix)
                else
                    NONE)}
-  | mkKindRow row = raise Fail ("Bad kind row : " ^ makeSet id row)
+  | mkKindRow row = Init.rowError ("kind", row)
 
 fun addKind (name, makeUrl) =
     let
@@ -28,8 +28,8 @@ fun addKind (name, makeUrl) =
        val id = nextSeq (db, "ContactKindSeq")
     in
        C.dml db ($`INSERT INTO ContactKind (id, name, url, urlPrefix, urlPostfix)
-                    VALUES (^id, ^(C.stringToSql name), ^(makerToSql makeUrl))`);
-       C.intFromSql id
+                    VALUES (^(C.intToSql id), ^(C.stringToSql name), ^(makerToSql makeUrl))`);
+       id
     end
 
 fun lookupKind id =
@@ -57,4 +57,86 @@ fun listKinds () =
     C.map (getDb ()) mkKindRow ($`SELECT id, name, url, urlPrefix, urlPostfix FROM ContactKind
                                  ORDER BY name`)
 
+(* Managing contact entries *)
+
+datatype priv =
+        PUBLIC
+       | MEMBERS
+       | ADMINS
+
+val privFromInt =
+    fn 0 => PUBLIC
+     | 1 => MEMBERS
+     | 2 => ADMINS
+     | _ => raise C.Sql "Bad contact private information"
+
+fun privFromSql v = privFromInt (C.intFromSql v)
+
+val privToSql =
+    fn PUBLIC => "0"
+     | MEMBERS => "1"
+     | ADMINS => "2"
+
+val privToInt =
+    fn PUBLIC => 0
+     | MEMBERS => 1
+     | ADMINS => 2
+
+type contact = {id :int, usr : int, knd : int, v : string, priv : priv}
+
+fun mkContactRow [id, usr, knd, v, priv] =
+    {id = C.intFromSql id, usr = C.intFromSql usr, knd = C.intFromSql knd,
+     v = C.stringFromSql v, priv = privFromSql priv}
+  | mkContactRow row = Init.rowError ("contact", row)
+
+fun addContact (usr, knd, v, priv) =
+    let
+       val db = getDb ()
+       val id = nextSeq (db, "ContactSeq")
+    in
+       C.dml db ($`INSERT INTO Contact (id, usr, knd, v, priv)
+                    VALUES (^(C.intToSql id), ^(C.intToSql usr), ^(C.intToSql knd), ^(C.stringToSql v), ^(privToSql priv))`);
+       id
+    end
+
+fun lookupContact id =
+    let
+       val c = getDb ()
+    in
+       (case C.oneOrNoRows c ($`SELECT id, usr, knd, v, priv Contact WHERE id = ^(C.intToSql id)`) of
+            NONE => raise Fail "Contact not found"
+          | SOME r => mkContactRow r)
+    end
+
+fun modContact (ct : contact) =
+    let
+       val db = getDb ()
+    in
+       ignore (C.dml db ($`UPDATE Contact
+                           SET usr = ^(C.intToSql (#usr ct)), knd = ^(C.intToSql (#knd ct)),
+                              v = ^(C.stringToSql (#v ct)), priv = ^(privToSql (#priv ct))
+                           WHERE id = ^(C.intToSql (#id ct))`))
+    end
+
+fun deleteContact id =
+    ignore (C.dml (getDb ()) ($`DELETE FROM Contact WHERE id = ^(C.intToSql id)`))
+
+fun mkUserContactRow r =
+    if length r >= 5 then
+       (mkKindRow (List.take (r, 5)), mkContactRow (List.drop (r, 5)))
+    else
+       Init.rowError ("kind/contact", r)
+
+fun listUserContacts (usr, priv) =
+    C.map (getDb ()) mkUserContactRow ($`SELECT ContactKind.id, name, url, urlPrefix, urlPostfix, Contact.id, usr, knd, v, priv
+                                        FROM Contact JOIN ContactKind ON knd = ContactKind.id
+                                        WHERE usr = ^(C.intToSql usr)
+                                           AND priv <= ^(privToSql priv)
+                                        ORDER BY name, v`)
+
+fun format (kind : kind, cont : contact) =
+    case #makeUrl kind of
+       SOME (pre, post) => String.concat ["<a href=\"", pre, Web.html (#v cont), post, "\">", Web.html (#v cont), "</a>"]
+      | NONE => Web.html (#v cont)
+
 end
\ No newline at end of file
diff --git a/exn.mlt b/exn.mlt
index e645be1..efb7dfb 100644 (file)
--- a/exn.mlt
+++ b/exn.mlt
@@ -1,4 +1,8 @@
-<% @header [("title", ["Exception"])] %>
+<html><head>
+<title>Hcoop Portal: Exception</title>
+</head><body>
+
+<h1><b>Exception</b></h1>
 
 <% switch Web.getExn () of
      Fail msg => %>
@@ -16,4 +20,4 @@
 <% end
 end %>
 
-<% @footer [] %>
+</body></html>
index 8bad77f..7c24bbc 100644 (file)
--- a/group.sml
+++ b/group.sml
@@ -10,7 +10,7 @@ type group = {id :int, name : string}
 
 fun mkGroupRow [id, name] =
     {id = C.intFromSql id, name = C.stringFromSql name}
-  | mkGroupRow row = raise Fail ("Bad group row : " ^ makeSet id row)
+  | mkGroupRow row = Init.rowError ("group", row)
 
 fun addGroup name =
     let
@@ -18,8 +18,8 @@ fun addGroup name =
        val id = nextSeq (db, "WebGroupSeq")
     in
        C.dml db ($`INSERT INTO WebGroup (id, name)
-                    VALUES (^id, ^(C.stringToSql name))`);
-       C.intFromSql id
+                    VALUES (^(C.intToSql id), ^(C.stringToSql name))`);
+       id
     end
 
 fun lookupGroup id =
@@ -57,7 +57,7 @@ fun userInGroupNum (usr, grp) =
                                    FROM Membership
                                    WHERE grp = ^(C.intToSql grp)
                                       AND usr = ^(C.intToSql usr)`) of
-            SOME[x] => x <> "0"
+            SOME[x] => not (C.isNull x) andalso C.intFromSql x <> 0
           | _ => false)
     end
 
@@ -70,7 +70,7 @@ fun userInGroupName (usr, grp) =
                                    WHERE name = ^(C.stringToSql grp)
                                       AND usr = ^(C.intToSql usr)
                                        AND grp = id`) of
-            SOME[x] => x <> "0"
+            SOME[x] => not (C.isNull x) andalso C.intFromSql x <> 0
           | _ => false)
     end
 
@@ -106,7 +106,7 @@ fun removeFromGroup (mem : membership) =
 
 fun mkMembershipRow [grp, usr] =
     {grp = C.intFromSql grp, usr = C.intFromSql usr}
-  | mkMembershipRow row = raise Fail ("Bad membership row : " ^ makeSet id row)
+  | mkMembershipRow row = Init.rowError ("membership", row)
 
 fun groupMembers grp =
     C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined FROM Membership, WebUser
index 4746bf2..08c29dd 100644 (file)
--- a/init.sig
+++ b/init.sig
@@ -5,13 +5,15 @@ signature INIT = sig
 
     type user = {id : int, name : string, rname : string, bal : int, joined : C.timestamp}
 
-    val mkUserRow : string list -> user
+    val rowError : string * C.value list -> 'a
+
+    val mkUserRow : C.value list -> user
 
     (* Open or close a session, wrapped in a transaction *)
     val init : unit -> unit
     val done : unit -> unit
 
-    val nextSeq : C.conn * string -> string
+    val nextSeq : C.conn * string -> int
 
     (* Fun with users *)
 
index 390b9a8..e0a9bb3 100644 (file)
--- a/init.sml
+++ b/init.sml
@@ -14,12 +14,20 @@ type user = {id : int, name : string, rname : string, bal : int, joined : C.time
 val db = ref (NONE : C.conn option)
 val user = ref (NONE : user option)
 
+fun fromSql v =
+    if C.isNull v then
+       "NULL"
+    else
+       C.stringFromSql v
+
+fun rowError (tab, vs) = raise Fail ("Bad " ^ tab ^ "row: " ^ makeSet fromSql vs)
+
 fun getDb () = valOf (!db)
 
 fun mkUserRow [id, name, rname, bal, joined] =
     {id = C.intFromSql id, name = C.stringFromSql name, rname = C.stringFromSql rname,
      bal = C.intFromSql bal, joined = C.timestampFromSql joined}
-  | mkUserRow row = raise Fail ("Bad user row : " ^ makeSet id row)
+  | mkUserRow row = rowError ("user", row)
 
 fun init () =
     let
@@ -61,7 +69,7 @@ fun listUsers () =
 
 fun nextSeq (db, seq) =
     case C.oneRow db ($`SELECT nextval('^(seq)')`) of
-       [id] => id
+       [id] => C.intFromSql id
       | _ => raise Fail "Bad next sequence val"
 
 fun addUser (name, rname, bal) =
@@ -70,8 +78,8 @@ fun addUser (name, rname, bal) =
        val id = nextSeq (db, "WebUserSeq")
     in
        C.dml db ($`INSERT INTO WebUser (id, name, rname, bal, joined)
-                   VALUES (^id, ^(C.stringToSql name), ^(C.stringToSql rname), ^(C.intToSql bal), CURRENT_TIMESTAMP)`);
-       C.intFromSql id
+                   VALUES (^(C.intToSql id), ^(C.stringToSql name), ^(C.stringToSql rname), ^(C.intToSql bal), CURRENT_TIMESTAMP)`);
+       id
     end
 
 fun modUser (user : user) =
index b5b20f1..056b051 100644 (file)
--- a/money.sml
+++ b/money.sml
@@ -11,7 +11,7 @@ type transaction = {id :int, descr : string, amount : real, d : string, stamp :
 fun mkTransactionRow [id, descr, amount, d, stamp] =
     {id = C.intFromSql id, descr = C.stringFromSql descr, amount = C.realFromSql amount,
      d = C.stringFromSql d, stamp = C.timestampFromSql stamp}
-  | mkTransactionRow row = raise Fail ("Bad transaction row : " ^ makeSet id row)
+  | mkTransactionRow row = Init.rowError ("transaction", row)
 
 fun addTransaction (descr, amount, d) =
     let
@@ -19,8 +19,8 @@ fun addTransaction (descr, amount, d) =
        val id = nextSeq (db, "TransactionSeq")
     in
        C.dml db ($`INSERT INTO Transaction (id, descr, amount, d, stamp)
-                    VALUES (^id, ^(C.stringToSql descr), ^(C.realToSql amount), ^(C.stringToSql d), CURRENT_TIMESTAMP)`);
-       C.intFromSql id
+                    VALUES (^(C.intToSql id), ^(C.stringToSql descr), ^(C.realToSql amount), ^(C.stringToSql d), CURRENT_TIMESTAMP)`);
+       id
     end
 
 fun lookupTransaction id =
@@ -83,7 +83,7 @@ val mkUserRow' =
     fn (trn :: rest) =>
        (if C.isNull trn then false else true,
        mkUserRow rest)
-     | row => raise Fail ("Bad listUsers row: " ^ makeSet id row)
+     | row => Init.rowError ("listUsers", row)
 
 fun listUsers trn =
     C.map (getDb ()) mkUserRow' ($`SELECT trn, id, name, rname, bal, joined
@@ -97,7 +97,7 @@ type charge = {trn : int, usr : int, amount : real}
 
 fun mkChargeRow [trn, usr, amount] =
     {trn = C.intFromSql trn, usr = C.intFromSql usr, amount = C.realFromSql amount}
-  | mkChargeRow row = raise Fail ("Bad charge row : " ^ makeSet id row)
+  | mkChargeRow row = Init.rowError ("charge", row)
 
 fun addCharge {trn, usr, amount} =
     ignore (C.dml (getDb ()) ($`INSERT INTO Charge (trn, usr, amount)
@@ -109,7 +109,7 @@ fun listCharges trn =
 
 val mkChargeRow' =
     fn (name :: rest) => (C.stringFromSql name, mkChargeRow rest)
-     | row => raise Fail ("Bad name+charge row: " ^ makeSet id row)
+     | row => Init.rowError ("name+charge", row)
 
 fun listChargesWithNames trn =
     C.map (getDb ()) mkChargeRow' ($`SELECT name, trn, usr, amount FROM Charge, WebUser
@@ -126,9 +126,9 @@ fun clearCharges trn =
 
        fun clearCharge [bal, amount] =
            ignore (C.dml db ($`UPDATE Balance
-                               SET amount = amount - ^amount
-                               WHERE id = ^bal`))
-         | clearCharge row = raise Fail ("Bad clearCharge row : " ^ makeSet id row)
+                               SET amount = amount - ^(C.stringFromSql amount)
+                               WHERE id = ^(C.stringFromSql bal)`))
+         | clearCharge row = Init.rowError ("clearCharge", row)
     in
        C.app db clearCharge ($`SELECT bal, SUM(amount) FROM Charge, WebUser
                                WHERE trn = ^(C.intToSql trn)
@@ -143,9 +143,9 @@ fun applyCharges trn =
 
        fun applyCharge [bal, amount] =
            ignore (C.dml db ($`UPDATE Balance
-                               SET amount = amount + ^amount
-                               WHERE id = ^bal`))
-         | applyCharge row = raise Fail ("Bad applyCharge row : " ^ makeSet id row)
+                               SET amount = amount + ^(C.stringFromSql amount)
+                               WHERE id = ^(C.stringFromSql bal)`))
+         | applyCharge row = Init.rowError ("applyCharge", row)
     in
        C.app db applyCharge ($`SELECT bal, SUM(amount) FROM Charge, WebUser
                                WHERE trn = ^(C.intToSql trn)
index 2800d98..987c5e2 100644 (file)
--- a/poll.sml
+++ b/poll.sml
@@ -9,7 +9,7 @@ fun mkPollRow [id, usr, title, descr, starts, ends, votes] =
     {id = C.intFromSql id, usr = C.intFromSql usr, title = C.stringFromSql title,
      descr = C.stringFromSql descr, starts = C.stringFromSql starts,
      ends = C.stringFromSql ends, votes = C.intFromSql votes}
-  | mkPollRow row = raise Fail ("Bad poll row : " ^ makeSet id row)
+  | mkPollRow row = Init.rowError ("poll", row)
 
 fun lookupPoll id =
     case C.oneOrNoRows (getDb ()) ($`SELECT id, usr, title, descr, starts, ends, votes
@@ -42,9 +42,9 @@ fun addPoll (usr, title, descr, starts, ends, votes) =
        val id = nextSeq (db, "PollSeq")
     in
        C.dml db ($`INSERT INTO Poll (id, usr, title, descr, starts, ends, votes)
-                   VALUES (^id, ^(C.intToSql usr), ^(C.stringToSql title), ^(C.stringToSql descr),
+                   VALUES (^(C.intToSql id), ^(C.intToSql usr), ^(C.stringToSql title), ^(C.stringToSql descr),
                            ^(C.stringToSql starts), ^(C.stringToSql ends), ^(C.intToSql votes))`);
-       C.intFromSql id
+       id
     end
 
 fun modPoll (poll : poll) =
@@ -70,7 +70,7 @@ type choice = {id : int, pol : int, seq : real, descr : string}
 fun mkChoiceRow [id, pol, seq, descr] =
     {id = C.intFromSql id, pol = C.intFromSql pol,
      seq = C.realFromSql seq, descr = C.stringFromSql descr}
-  | mkChoiceRow row = raise Fail ("Bad choice row : " ^ makeSet id row)
+  | mkChoiceRow row = Init.rowError ("choice", row)
 
 fun lookupChoice id =
     case C.oneOrNoRows (getDb ()) ($`SELECT id, pol, seq, descr
@@ -86,8 +86,9 @@ fun listChoices pol =
                                    ORDER BY seq`)
 
 val mkChoiceRow' =
-    fn (yours :: total :: rest) => (yours <> "0", if C.isNull total then 0 else C.intFromSql total, mkChoiceRow rest)
-     | row => raise Fail ("Bad choice' row: " ^ makeSet id row)
+    fn (yours :: total :: rest) => (not (C.isNull yours) andalso C.intFromSql yours <> 0,
+                                   if C.isNull total then 0 else C.intFromSql total, mkChoiceRow rest)
+     | row => Init.rowError ("choice'", row)
 
 fun listChoicesWithVotes pol =
     C.map (getDb ()) mkChoiceRow' ($`SELECT (SELECT COUNT( * ) FROM Vote V WHERE V.cho = id AND V.usr = ^(C.intToSql (Init.getUserId ()))),
@@ -97,8 +98,9 @@ fun listChoicesWithVotes pol =
                                     ORDER BY total DESC, seq`)
 
 val mkChoiceRow'' =
-    fn (yours :: rest) => (yours <> "0", mkChoiceRow rest)
-     | row => raise Fail ("Bad choice'' row: " ^ makeSet id row)
+    fn (yours :: rest) => (not (C.isNull yours) andalso C.intFromSql yours <> 0,
+                          mkChoiceRow rest)
+     | row => Init.rowError ("choice''", row)
 
 fun listChoicesWithMyVotes pol =
     C.map (getDb ()) mkChoiceRow'' ($`SELECT (SELECT COUNT( * ) FROM Vote V WHERE V.cho = id AND V.usr = ^(C.intToSql (Init.getUserId ()))),
@@ -113,8 +115,8 @@ fun addChoice (pol, seq, descr) =
        val id = nextSeq (db, "PollChoiceSeq")
     in
        C.dml db ($`INSERT INTO PollChoice (id, pol, seq, descr)
-                   VALUES (^id, ^(C.intToSql pol), ^(C.realToSql seq), ^(C.stringToSql descr))`);
-       C.intFromSql id
+                   VALUES (^(C.intToSql id), ^(C.intToSql pol), ^(C.realToSql seq), ^(C.stringToSql descr))`);
+       id
     end
 
 fun modChoice (choice : choice) =
@@ -150,22 +152,22 @@ fun vote (usr, pol, chos) =
 fun dateLe (d1, d2) =
     case C.oneRow (getDb ()) ($`SELECT (EXTRACT(EPOCH FROM DATE ^(C.stringToSql d1)) <= EXTRACT(EPOCH FROM DATE ^(C.stringToSql d2)))`) of
        [res] => C.boolFromSql res
-      | row => raise Fail ("Bad dateLe row: " ^ makeSet id row)
+      | row => Init.rowError ("dateLe", row)
 
 fun dateGeNow d =
     case C.oneRow (getDb ()) ($`SELECT (EXTRACT(EPOCH FROM CURRENT_DATE) <= EXTRACT(EPOCH FROM DATE ^(C.stringToSql d)))`) of
        [res] => C.boolFromSql res
-      | row => raise Fail ("Bad dateGeNow row: " ^ makeSet id row)
+      | row => Init.rowError ("dateGeNow", row)
 
 fun dateLeNow d =
     case C.oneRow (getDb ()) ($`SELECT (EXTRACT(EPOCH FROM CURRENT_DATE) >= EXTRACT(EPOCH FROM DATE ^(C.stringToSql d)))`) of
        [res] => C.boolFromSql res
-      | row => raise Fail ("Bad dateLeNow row: " ^ makeSet id row)
+      | row => Init.rowError ("dateLeNow", row)
 
 fun dateLtNow d =
     case C.oneRow (getDb ()) ($`SELECT (EXTRACT(EPOCH FROM CURRENT_DATE) > EXTRACT(EPOCH FROM DATE ^(C.stringToSql d)))`) of
        [res] => C.boolFromSql res
-      | row => raise Fail ("Bad dateLtNow row: " ^ makeSet id row)
+      | row => Init.rowError ("dateLtNow", row)
 
 fun canModify (poll : poll) =
     Group.inGroupName "poll"
@@ -183,7 +185,7 @@ fun nextSeq pol =
                                FROM PollChoice
                                WHERE pol = ^(C.intToSql pol)`) of
        [max] => if C.isNull max then 1.0 else C.realFromSql max
-      | row => raise Fail ("Bad nextSeq row: " ^ makeSet id row)
+      | row => Init.rowError ("nextSeq", row)
 
 fun takingVotes (poll : poll) =
     dateLeNow (#starts poll) andalso dateGeNow (#ends poll)
index 5481e29..df2d5be 100644 (file)
@@ -111,11 +111,12 @@ CREATE TABLE ContactKind(
 CREATE SEQUENCE ContactKindSeq START 1;
 
 CREATE TABLE Contact(
+       id INTEGER PRIMARY KEY,
        usr INTEGER NOT NULL,
        knd INTEGER NOT NULL,
        v TEXT NOT NULL,
        priv INTEGER NOT NULL,
-       PRIMARY KEY (usr, knd),
        FOREIGN KEY (usr) REFERENCES WebUser(id) ON DELETE CASCADE,
        FOREIGN KEY (knd) REFERENCES ContactKind(id) ON DELETE CASCADE);
-       
\ No newline at end of file
+
+CREATE SEQUENCE ContactSeq START 1;