CurryInfo: ertools-3.0.0 / Database.ERD.Generic

classes:

              
documentation:
------------------------------------------------------------------------------
--- Generic operations and integrity tests
--- to support the database code generated from ERDs
------------------------------------------------------------------------------
name:
Database.ERD.Generic
operations:
defaultString deleteEntryR duplicateKeyTest duplicatePTest existsEntryWithDBKey getAllEntities getEntry maxPTest maxTest maxTestC maxTestInsert maxTestUpdate minTestC minTestDelete newEntry newEntryR readDatabaseKey requiredForeignDBKey restoreDBRelTerms restoreDBTerms saveDBTerms showDatabaseKey unique unique2 unique2C uniqueC uniqueUpdate
sourcecode:
module Database.ERD.Generic where

import Data.Char ( isDigit )
import Data.List

import Database.KeyDatabaseSQLite

------------------------------------------------------------------------------
-- Handling of database keys

--- The general type of database keys.
type Key = Int

--- Shows a database key for an entity name as a string.
--- Useful if a textual representation of a database key is necessary,
--- e.g., as URL parameters in web pages. This textual representation
--- should not be used to store database keys in attributes!
showDatabaseKey :: String -> (enkey -> Key) -> enkey -> String
showDatabaseKey en fromenkey enkey = en ++ show (fromenkey enkey)

--- Transforms a string into a key for an entity name.
--- Nothing is returned if the string does not represent a reasonable key.
readDatabaseKey :: String -> (Key -> enkey) -> String -> Maybe enkey
readDatabaseKey en toenkey s =
  let (ens,ks) = splitAt (length en) s
   in if ens==en && all isDigit ks then Just (toenkey (read ks))
                                   else Nothing


------------------------------------------------------------------------------
-- Generic operations to modify the database

--- Insert a new entity and assign a new key for it.
newEntry :: Show t =>
            (Key -> t -> Dynamic) -> (Key -> t -> en) -> t -> Transaction en
newEntry pred info2entry info =
  newDBEntry pred info |>>= \k -> returnT (info2entry k info)

-- Insert new relationship represented as an entity.
newEntryR :: (Show a, Show b) =>
             (Key -> (a,b) -> Dynamic) -> a -> b -> Transaction ()
newEntryR entrypred key1 key2 = newDBEntry entrypred (key1,key2) |>> doneT

getEntry :: (Read t, Show t) =>
            (Key -> t -> Dynamic) -> (Key -> t -> en) -> Key -> Transaction en
getEntry pred info2entry key = seq pred $ seq key $
  getDB (getDBInfo pred key) |>>=
  maybe (errorT (TError KeyNotExistsError
                        ("database contains no entry for key: "++show key)))
        (\info -> returnT (info2entry key info))

-- Delete a relationship represented as an entity.
-- If the relationship does not exist, a NoRelationshipError is raised.
deleteEntryR :: (Eq a, Read a, Show a, Eq b, Read b, Show b) =>
                (Key -> (a,b) -> Dynamic) -> a -> b -> Transaction ()
deleteEntryR entrypred key1 key2 =
  getDB (transformQ (map fst . filter (\ (_,i) -> i==(key1,key2)))
                    (allDBKeyInfos entrypred)) |>>= \kis ->
  if null kis
   then errorT (TError NoRelationshipError 
                       ("relationship for deletion not found for keys: "
                        ++show key1++" "++show key2))
   else deleteDBEntries entrypred kis


------------------------------------------------------------------------------
-- Generic integrity tests for keys.

-- If there is no entry with a given key, raise a transaction error.
existsEntryWithDBKey :: (Read t, Show t) =>
                        String -> (Key -> t -> Dynamic) -> Key -> Transaction ()
existsEntryWithDBKey ename entrypred key =
  getDB (getDBInfo entrypred key) |>>=
  maybe (errorT (TError KeyNotExistsError
                        ("database contains no entry for key: "++show key 
                         ++" in table: "++ename))  )
        (const doneT)

-- If a given key occurs in a (foreign key) attribute of an entity,
-- raise a transaction error.
requiredForeignDBKey :: (Read t, Show t, Eq k, Show k) =>
                        String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
                     -> (en -> k) -> k -> Transaction ()
requiredForeignDBKey ename entrypred info2entry keyf key =
  getDB (getAllEntities entrypred info2entry) |>>= \ens ->
  if null (filter (\e -> keyf e == key) ens)
   then doneT
   else errorT (TError KeyRequiredError
                       ("key: "++show key ++ " required in table: " ++ ename))

getAllEntities :: (Read t, Show t) =>
                  (Key -> t -> Dynamic) -> (Key -> t -> en) -> Query [en]
getAllEntities entrypred info2entry =
  transformQ (map (uncurry info2entry)) (allDBKeyInfos entrypred)

duplicateKeyTest :: (Key -> t -> Dynamic) -> Transaction ()
duplicateKeyTest pred =
  getDB (allDBKeys pred) |>>= \keys ->
  if length (nub keys) == length keys
     then doneT
     else errorT (TError DuplicateKeyError
                         ("database contains duplicate key for table: " 
                          {- ++show pred-})) 
     
duplicatePTest :: Eq a => [a] -> Transaction ()
duplicatePTest xs =
  if length (nub xs) == length xs
  then doneT
  else errorT (TError DuplicateKeyError "duplicate parameters in new-function")

-------------------------------------------------------------------------
-- Uniqueness tests.
 
-- Test whether an attribute value does not yet exist
unique :: (Read t, Show t, Eq a, Show a) =>
          String -> (Key -> t -> Dynamic) -> (Key -> t -> en) -> (en -> a) -> a
       -> Transaction ()
unique ename entrypred info2entry selector attrval =
  getDB (allDBKeyInfos entrypred) |>>= \kis ->
  if null (filter (\e -> selector e == attrval)
                  (map (\(k,i) -> info2entry k i) kis))
   then doneT
   else errorT (TError UniqueError
                       (ename++" entry for unique attribute "
                        ++show attrval++" already exists"))

uniqueUpdate :: (Read t, Show t, Eq a, Show a) =>
                String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
             -> (en -> Key) -> (en -> a) -> en -> Transaction ()
uniqueUpdate ename entrypred info2entry keyf selector obj =
  let oldkey = keyf obj
  in
  getDB (getDBInfo entrypred oldkey) |>>=
  maybe (errorT (TError KeyNotExistsError
                    ("database contains no entry for key: "++show oldkey)))
        (\oldt -> getDB (allDBKeyInfos entrypred) |>>= \kis ->
           let oldentry = info2entry oldkey oldt
               entries = filter (\e -> selector obj == selector e)
                                (map (uncurry info2entry) kis)
            in if null entries ||
                  (length entries == 1 && selector oldentry == selector obj)
               then doneT
               else errorT (TError UniqueError
                                   (ename++" entry for unique attribute "
                                    ++show (selector obj)++" already exists")))

uniqueC :: (Read t, Show t, Eq a, Show a) =>
           String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
        -> (en -> a) -> en -> Transaction ()
uniqueC ename entrypred info2entry selector obj =
  getDB (allDBKeyInfos entrypred) |>>= \kis ->
  let entries = filter (\e -> selector obj == selector e)
                       (map (uncurry info2entry) kis)
   in if length entries <= 1
      then doneT
      else errorT (TError UniqueError
                     (ename++" unique attribute "
                      ++show (selector obj)++" is not unique"))

-- Uniqueness of a combination of two attributes.
-- Check whether this combination already exists.
-- If it exists, a transaction error is generated, otherwise everything is ok.
unique2 :: (Eq a, Eq b, Read a, Read b, Show a, Show b) =>
           (Key -> (a,b) -> Dynamic) -> a -> b -> Transaction ()
unique2 entrypred k1 k2 =
  getDB (allDBInfos entrypred) |>>= \is ->
  if null (filter (== (k1,k2)) is)
   then doneT
   else errorT (TError UniqueError "relationship already exists")

unique2C :: (Eq a, Eq b, Read a, Read b, Show a, Show b) =>
            (Key -> (a,b) -> Dynamic) ->  a -> b -> Transaction ()
unique2C entrypred k1 k2 =
  getDB (allDBInfos entrypred) |>>= \is ->
  if length (filter (== (k1,k2)) is) > 1
   then errorT (TError UniqueError "relationship not unique")
   else doneT

-------------------------------------------------------------------------
-- Maximum and minimum tests.

maxPTest :: Int -> [a] -> Transaction ()
maxPTest max xs = 
  if length xs > max
  then errorT (TError MaxError "max reached in parameter list in new function")
  else doneT


maxTest :: (Read t, Show t, Eq a, Show a) =>
           String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
        -> (en -> a) -> Int -> a -> Transaction ()
maxTest ename entrypred info2entry selector max attr =
  getDB (getAllEntities entrypred info2entry) |>>= \es ->
  let entries = filter (\e -> attr == selector e) es in 
  if length entries < max
   then doneT
   else errorT (TError MaxError ("max reached for attribute " 
                                 ++show attr++" in entity "++ename))

maxTestUpdate :: (Read t, Show t, Eq a, Show a) =>
                 String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
              -> (en -> Key) -> (en -> a) -> Int -> en -> Transaction ()
maxTestUpdate ename entrypred info2entry keyf selector max obj =
  getDB (getAllEntities entrypred info2entry) |>>= \es ->
  let entries = filter (\e -> selector obj == selector e) es in 
  getEntry entrypred info2entry (keyf obj) |>>= \old ->
  if (length entries < max
        || (length entries == max && selector old == selector obj))
   then doneT
   else errorT (TError MaxError ("max reached for attribute "
                                 ++show (selector obj)++" in entity "++ename))

maxTestC :: (Read t, Show t, Eq a, Show a) =>
            String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
         -> (en -> a) -> Int -> a -> Transaction ()
maxTestC ename entrypred info2entry selector max attr =
  getDB (getAllEntities entrypred info2entry) |>>= \es ->
  if length (filter (\e -> selector e == attr) es) <= max
   then doneT
   else errorT (TError MaxError ("maximum exceeded for attribute " 
                                 ++show attr++" in entity "++ename))

minTestC :: (Read t, Show t, Eq a, Show a) =>
            String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
         -> (en -> a) -> Int -> a -> Transaction ()
minTestC ename entrypred info2entry selector min attr =
  getDB (getAllEntities entrypred info2entry) |>>= \es ->
  if length (filter (\e -> selector e == attr) es) >= min
     then doneT
     else errorT (TError MinError ("below min for attribute " 
                                   ++show attr++" in entity "++ename))

-- Maximum test before inserting a relationship with a given key:
maxTestInsert :: (Read t, Show t, Eq a, Show a) =>
                 String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
              -> (en -> a) -> Int -> a -> Transaction ()
maxTestInsert ename entrypred info2entry selector maxrange attr =
  getDB (getAllEntities entrypred info2entry) |>>= \es ->
  if length (filter (\e -> selector e == attr) es) < maxrange
   then doneT
   else errorT (TError MaxError ("maximum reached for attribute " 
                                 ++show attr++" in entity "++ename))

-- Minimum test before deleting a relationship
minTestDelete :: (Read t, Show t, Eq a, Show a) =>
                 String -> (Key -> t -> Dynamic) -> (Key -> t -> en)
              -> (en -> a) -> Int -> a -> Transaction ()
minTestDelete ename entrypred info2entry selector min attr =
  getDB (getAllEntities entrypred info2entry) |>>= \es ->
  if length (filter (\e -> selector e == attr) es) > min
     then doneT
     else errorT (TError MinError ("below min for attribute " 
                                   ++show attr++" in entity "++ename))


-------------------------------------------------------------------------
-- Saving and restoring dynamic predicates.

saveDBTerms :: (Read a, Show a, Show en) => String -> String
            -> (Key -> a -> Dynamic) -> (Key -> a -> en) -> IO ()
saveDBTerms path ename dynpred toentity = do
  keyinfos <- runQ (allDBKeyInfos dynpred)
  let savefile  = path++"/"++ename++".terms"
      terms     = map (uncurry toentity) keyinfos
      showterms = unlines (map show terms)
  if null path
    then putStrLn showterms -- show only
    else do putStrLn $ "Saving into " ++ savefile
            writeFile savefile showterms

restoreDBTerms :: (Read en, Show a) => String -> String -> (Key -> a -> Dynamic)
               -> (en -> Key) -> (en -> a)  -> IO ()
restoreDBTerms path ename dynpred enkey eninfo = do
  let savefile = path++"/"++ename++".terms"
  putStrLn $ "Restoring from "++savefile
  terms <- readFile savefile >>= return . map read . lines 
  runJustT (mapT_ (\t -> newDBKeyEntry dynpred (enkey t) (eninfo t)) terms)

restoreDBRelTerms :: (Read en, Show a) => String -> String
                  -> (Key -> a -> Dynamic) -> (en -> a)  -> IO ()
restoreDBRelTerms path ename dynpred eninfo = do
  let savefile = path++"/"++ename++".terms"
  putStrLn $ "Restoring from "++savefile
  terms <- readFile savefile >>= return . map read . lines 
  runJustT (mapT_ (\t -> newDBEntry dynpred (eninfo t)) terms)

-------------------------------------------------------------------------
-- If the second argument is a null string, return the first argument
-- (the default string), otherwise return the second argument.
defaultString :: String -> String -> String
defaultString def s = if null s then def else s

-------------------------------------------------------------------------
types:
Key
unsafe:
unsafe due to modules Data.Global System.IO.Unsafe