var db = database "winestore"; var page_size = 12; var usersTable = table "users" with (cust_id : Int, user_name : String, password : String) from db; var orderTable = table "orders" with (cust_id : Int, order_id : Int, date : String, instructions : String, creditcard : String, expirydate : String) from db; var shortOrderTable = table "orders" with (cust_id : Int, order_id : Int) from db; var cartItemsTable = table "items" with (item_id : Int, cust_id : Int, order_id : Int, price : Float, qty : Int, wine_id : Int) from db; var shortCartItemsTable = table "items" with (cust_id : Int, order_id : Int) from db; var wineTable = table "wine" with (wine_id : Int, wine_name : String, wine_type : Int, year : Int, winery_id : Int) from db; var wineTypeTable = table "wine_type" with (wine_type_id : Int, wine_type : String) from db; var regionTable = table "region" with (region_id : Int, region_name : String) from db; var inventoryTable = table "inventory" with (wine_id : Int, cost : Float) from db; var wineryTable = table "winery" with (winery_id : Int, winery_name : String, region_id : Int) from db; fun snd(pair) { var (a, b) = pair; b } fun fst(pair) { var (a, b) = pair; a } fun map_comprehension(f, l) { for (x <- l) [f(x)] } fun range(n) { if (n < 0) error("range: argument less than zero") else if (n==0) [0] else range(n-1) ++ [n] } fun sum_float(l) { switch (l) { case [] -> 0.0 case hd::tl -> hd +. sum_float(tl) } } fun sum_int(l) { switch (l) { case [] -> 0 case hd::tl -> hd + sum_int(tl) } } fun maximum(result, l) { switch (l) { case [] -> result case hd::tl -> if (result >= hd) maximum(result, tl) else maximum(hd, tl) } } fun minimum(result, l) { switch (l) { case [] -> result case hd::tl -> if (result <= hd) maximum(result, tl) else maximum(hd, tl) } } fun assocd(x, l, d) { switch (l) { case [] -> d case (k,v)::tl -> if (x == k) v else assocd(x, tl, d) } } # This function demonstrates that we can now support abstraction # over queries. sig firstField : (TableHandle (r, w, n), (r) {}-> Bool, (r) {}-> a) ~e~> (a::Base) fun firstField(t, p, body) { var matches = for (r <-- t) where (p(r)) [(v=body(r))]; hd(matches).v } fun wineTypeName(wine_type_id) { stringToXml (firstField (wineTypeTable, fun (wineType) {wineType.wine_type_id == wine_type_id}, (.wine_type))) } fun wine_name(wine_id) { var matches = for (wine <-- wineTable) where (wine.wine_id == wine_id) [(name=wine.wine_name)]; hd(matches).name } fun get_region_name(region_id) { var matches = for (region <-- regionTable) where (region_id == region.region_id) [(region=region.region_name)]; hd(matches).region } fun get_wine_price(wine_id) { firstField (inventoryTable, fun (costRec) {wine_id == costRec.wine_id}, (.cost)) } fun cust_id_next() { # WARNING: race condition here var ids = map((.1), for (u <-- usersTable) [(1=u.cust_id)]); maximum(0, ids) + 1 } fun claimCart(order_id, to_cust_id) { # FIXME: This normally fails to do an update, because Links is # trying to select NULL-valued rows by checking against ''. update (x <-- shortOrderTable) where (x.order_id == order_id && x.cust_id == -1) set (cust_id = to_cust_id, order_id = x.order_id); update (x <-- shortCartItemsTable) where (x.order_id == order_id && x.cust_id == -1) set (cust_id = to_cust_id, order_id = x.order_id); } fun htmlHead() {
} sig sign_up : (Int, String, String) ~> Page fun sign_up(order_id, username, password) { var new_cust_id = cust_id_next(); insert (usersTable) values [( cust_id = new_cust_id, user_name = username, password = password )]; if (order_id <> -1) claimCart(order_id, new_cust_id) else (); freshResource(); page{stringToXml(msg)}
} fun customerName(cust_id) { stringToXml (firstField (usersTable, fun (cust) {cust.cust_id == cust_id}, (.user_name))) } sig header : (Int, Int) ~> Xml fun header(cust_id, order_id) { if (cust_id == -1) elseThanks, {if (cust_id <> -1) customerName(cust_id) else []}, your order has been dispatched. Your order reference number is {intToXml(cust_id)} - {intToXml(order_id)}. Please quote this number in any correspondence.
If it existed, the order would have been shipped to:
(TBD: Insert shipping details)
We have billed your fictional credit card.
| Quantity | Wine | Unit Price | Total |
|---|---|---|---|
| {intToXml(item.qty)} | {stringToXml(item.name)} | ${floatToXml(item.price)} | ${floatToXml(item.price *. intToFloat(item.qty))} |
| Total of this order: | ${floatToXml(total)} | ||
An email confirmation has NOT been sent to you. Thank you for shopping with Linkswine.
} sig order_total : (Int, Int) ~> Float fun order_total(cust_id, order_id) { sum_float( map ((.1), for (item <-- cartItemsTable) where (item.cust_id == cust_id && item.order_id == order_id) [(1=item.price *. intToFloat(item.qty))])) } fun getOrder(cust_id, order_id) { var the_orders = for (x <-- orderTable ) where (cust_id == x.cust_id && order_id == x.order_id) [x]; switch (the_orders) { case [] -> None case (order::_) -> Some(order) } } fun checkout(cust_id, order_id, card_no, expiry, instr) { if (cust_id == -1) errorPage("You must have an account to make a purchase!") else { var total = order_total(cust_id, order_id); if (valid_cc_details(card_no, expiry)) { var the_order = getOrder(cust_id, order_id); switch (the_order) { case None -> errorPage("Internal error: You have no shopping cart. (**This may be due to a bug with claiming your cart; try signing in before adding items to your cart**) (" ^^ intToString(cust_id) ^^ ", " ^^ intToString(order_id) ^^ ")") case Some(order) -> { update (x <-- orderTable) where (x.order_id == order_id && cust_id == x.cust_id) set (cust_id = order.cust_id, order_id = order.order_id, date = order.date, instructions = instr, creditcard = card_no, expirydate = expiry); debug("successfully updated the order with purchase details."); freshResource(); purchase_confirmation(cust_id, order_id, total) } } } else errorPage("Bogus credit card details!") } } fun begin_checkout(cust_id, order_id) { page {htmlHead()}Please enter your SurchargeCard details (Try: 8000000000001001 ) and delivery instructions. Fields shown in red are mandatory.
} fun withButton(label, frm) { debug("withButton"); formlet <#> {frm -> result} {submit(label)} #> yields result } sig update_cart : (Int, Int, [(Int,Int)]) ~> () fun update_cart(cust_id, order_id, qtys) { # Gosh, here's an ugly idiom: ignore(for ((item_id, newQty) <- qtys) { update (item <-- cartItemsTable) where (item.cust_id == cust_id && item.order_id == order_id && item.item_id == item_id) set (qty = newQty, item_id = item.item_id, cust_id = item.cust_id, order_id = item.order_id, price = item.price, wine_id = item.wine_id); [] }); freshResource(); } fun cart_itemlist(cust_id, order_id) { debug("starting cart_itemlist"); var cart_items = for (cart_item <-- cartItemsTable) where (cart_item.order_id == order_id && cart_item.cust_id == cust_id) { for (wine <-- wineTable) where (wine.wine_id == cart_item.wine_id) { for (cost_rec <-- inventoryTable) where (cost_rec.wine_id == wine.wine_id) [(id=cart_item.item_id, qty=cart_item.qty, name=wine.wine_name, cost=cost_rec.cost)] } }; debug("got results in cart_items"); if (length(cart_items) == 0) pageYour cart is empty.
else { var total_cost = floatToString(sum_float(map(fun (i){ i.cost *. intToFloat(i.qty)}, cart_items))); debug("got total cost"); var total_items = intToString(length(cart_items)); debug("got total items"); var cartForm = page <#>{ withButton("Update Cart", formlet| Quantity | Wine | Unit Price | Total |
|---|---|---|---|
| {inputIntValue(cartItem.qty) -> qty} | {stringToXml(cartItem.name)} | ${floatToXml(cartItem.cost)} | |
| ${stringToXml(total_cost)} |
Total in cart: ${floatToXml(total)}
({intToXml(count)} items)
View cart.
| Region: | {choiceDefault(for (region <-- regionTable) [(region.region_id, region.region_name)], region_id) -> search_region_id} |
| Wine type: | {choiceDefault(for (type <-- wineTypeTable) [(type.wine_type_id, type.wine_type)], wine_type) -> search_wine_type} |
| { submit("Show wines") } |