packages feed

ampersand-3.0.3: src/Database/Design/Ampersand/FSpec/Crud.hs

module Database.Design.Ampersand.FSpec.Crud (CrudInfo(..), showCrudInfo, getCrudObjectsForInterface, mkCrudInfo) where

import Data.Function
import Data.List
import Data.Map (Map) 
import qualified Data.Map as Map 
import Database.Design.Ampersand.Basics
import Database.Design.Ampersand.Classes.ConceptStructure
import Database.Design.Ampersand.Classes.Relational
import Database.Design.Ampersand.Core.AbstractSyntaxTree

fatal :: Int -> String -> a
fatal = fatalMsg "Crud"

-- For a description of the algorithms in this module, see https://github.com/AmpersandTarski/ampersand/issues/45 

-- NOTE: The definitions of the various CRUD aspects are still a bit quirky and will most-likely need refinement. 
--      (see notes/todo's here and in ampersand-models/Tests/NoSentinel/Crud.adl)

data CrudInfo = CrudInfo { allCrudObjects :: [(A_Concept,[A_Concept])] -- crud concept together with its target concept in the surjective/total transitive closure of declarations 
                         , crudObjsPerInterface :: [ (Interface, [(A_Concept,Bool,Bool,Bool,Bool)]) ]
                         , crudObjsPerConcept :: [(A_Concept, ([Interface], [Interface], [Interface], [Interface]))]
                         -- TODO: think about representation of these matrices
                         } deriving Show

showCrudInfo :: CrudInfo -> String
showCrudInfo (CrudInfo crudObjs ifcCrudObjs _) =
  "CRUD info\nObjects:\n" ++ unlines [ name crudCncpt ++" : " ++ show (map name crudDecls) | (crudCncpt, crudDecls) <- crudObjs] ++
  "\nMatrices\n" ++ concat
    [ "Interface " ++ name ifc ++
      "\nC R U D Object\n" ++
      (unlines $ map showCrud cObjs)
    | (ifc, cObjs) <- ifcCrudObjs
    ] ++ "\n"
  where showCrud (cncpt, isC, isR, isU, isD) = concat [ showX isX ++ " " | isX <- [isC, isR, isU, isD] ] ++ show (name cncpt)
        showX isX = if isX then "X" else " "

getCrudObjectsForInterface :: CrudInfo -> Interface -> [(A_Concept,Bool,Bool,Bool,Bool)]
getCrudObjectsForInterface crudInfo ifc = 
  case lookup ifc $ crudObjsPerInterface crudInfo of
    Nothing       -> fatal 33 $ "NO CRUD objects for interface " ++ show (name ifc)
    Just crudObjs -> crudObjs
  
mkCrudInfo :: [A_Concept] -> [Declaration] -> [Interface] -> CrudInfo
mkCrudInfo  allConceptsPrim allDecls allIfcs =
  CrudInfo crudObjs crudObjsPerIfc (getCrudObjsPerConcept crudObjsPerIfc)
  where allConcepts = [ c | c <- allConceptsPrim, not $ c == ONE || name c == "SESSION" ]
        nonCrudConcpts = [ source d | d <- allDecls, isUni d && isSur d ] ++
                         [ target d | d <- allDecls, isInj d && isTot d ]
        crudCncpts = allConcepts \\ nonCrudConcpts
        
        transSurjClosureMap :: Map A_Concept [A_Concept]
        transSurjClosureMap = transClosureMap . Map.fromListWith union $
          [ (target d, [source d]) | d <- allDecls, isSur d ] ++ -- TODO: no isUni?
          [ (source d, [target d]) | d <- allDecls, isTot d ]    -- TODO: no isInj?
        
        
        -- crud concept together with its target concept in the surjective/total transitive closure of declarations
        crudObjs :: [(A_Concept, [A_Concept])]
        crudObjs = [ (crudCncpt, Map.findWithDefault [] crudCncpt transSurjClosureMap) -- TODO: should [] be a fatal? 
                   | crudCncpt <- crudCncpts ]
        
        getCrudUpdateConcpts :: Declaration -> [A_Concept]
        getCrudUpdateConcpts decl = 
          if  isSur decl || isTot decl  -- TODO: no isUni?  -- TODO: no isInj?
          then [ cObj | (cObj, cCncpts) <- crudObjs, source decl `elem` cCncpts && target decl `elem` cCncpts ]    
          else []
          
        crudObjsPerIfc = [ (ifc, getCrudObjsPerIfc ifc) | ifc <- allIfcs ]
        
        -- Not the most efficient implementation, but it is easy to read, and the total number of concepts will not be enormous.
        getCrudObjsPerIfc :: Interface -> [(A_Concept,Bool,Bool,Bool,Bool)]
        getCrudObjsPerIfc ifc = [ (cObj, isC, isR, isU, isD)
                                | cObj <- crudCncpts
                                , let isC = cObj `elem` crudCreateCncpts
                                , let isR = cObj `elem` crudReadCncpts
                                , let isU = cObj `elem` crudUpdateCncpts
                                , let isD = cObj `elem` crudDeleteCncpts
                                , or [isC, isR, isU, isD]
                                ]                            
          where crudCreateCncpts = editableTgts
                crudReadCncpts   = concs (relsUsedIn ifc) -- NOTE: this includes interface params, even if they do not appear in any of the field expressions
                crudDeleteCncpts = crudCreateCncpts -- We can't currently distinguish between these two.
                crudUpdateCncpts = concatMap getCrudUpdateConcpts editableDecls
                (editableDecls, editableTgts) = unzip $ getEditableDeclsAndTargets allIfcs ifc
                                             
-- NOTE: editable target is not necessarily the target of decl, as it may have been flipped (in which case it's the source)
getEditableDeclsAndTargets :: [Interface] -> Interface -> [(Declaration, A_Concept)]
getEditableDeclsAndTargets allIfcs ifc = concatMap editableTarget $ getAllInterfaceExprs allIfcs ifc
  where editableTarget expr = 
          case getExpressionRelation expr of
            Nothing                                                              -> []
            Just (declSrc, decl, declTgt, isFlipped) | decl `elem` ifcParams ifc -> [(decl, if isFlipped then declSrc else declTgt)]
                                                     | otherwise                 -> []

getAllInterfaceExprs :: [Interface] -> Interface -> [Expression]
getAllInterfaceExprs allIfcs ifc = getExprs $ ifcObj ifc
  where getExprs Obj{objctx=expr, objmsub=subObj} = 
          expr : case subObj of Nothing                -> []
                                Just (InterfaceRef nm) ->
                                  case filter (\rIfc -> name rIfc == nm) $ allIfcs of -- Follow interface ref
                                    []      -> fatal 65 $ "Referenced interface " ++ nm ++ " missing"
                                    (_:_:_) -> fatal 66 $ "Multiple declarations of referenced interface " ++ nm
                                    [i]     -> getAllInterfaceExprs allIfcs i
                                Just (Box _ _ objs)    -> concatMap getExprs objs

getCrudObjsPerConcept :: [(Interface, [(A_Concept,Bool,Bool,Bool,Bool)])] ->
                         [(A_Concept, ([Interface], [Interface], [Interface], [Interface]))]
getCrudObjsPerConcept crudsPerIfc = sortBy (compare `on` fst)  conceptsAndInterfaces
  where conceptsAndInterfaces :: [(A_Concept, ([Interface], [Interface], [Interface], [Interface]))]
        conceptsAndInterfaces = concatMap toIfcPerConcept crudsPerIfc
        
        toIfcPerConcept :: (Interface, [(A_Concept,Bool,Bool,Bool,Bool)]) -> 
                           [(A_Concept, ([Interface], [Interface], [Interface], [Interface]))]
        toIfcPerConcept (ifc, ifcCrudObjs) = [ (cncpt, ( if isC then [ifc] else []
                                                       , if isR then [ifc] else []
                                                       , if isU then [ifc] else []
                                                       , if isD then [ifc] else []
                                                       )
                                               )
                                             | (cncpt, isC, isR, isU, isD) <- ifcCrudObjs
                                             ]