1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
------------------------------------------------------------------------------

--- This module provides a transformation on ERD terms that eliminates

--- the original relationships by introducing new entities for complex

--- relationships and inserting foreign keys for simple relationships.

------------------------------------------------------------------------------


{-# OPTIONS_CYMAKE -Wno-incomplete-patterns #-}

module Database.ERD.Transformation ( transform )
 where

import Database.ERD
import Database.ERD.Goodies

---------------------------------------------------

-- The main transformation which adds

-- - artificial keys of type Int for all entities (not null)

--   user defined keys -> unique

-- - foreign keys for 1:1 and 1:n relationships

-- - new entities and relationships for n:m relationships (delete old relationship) 


transform :: ERD -> ERD
transform (ERD name entities relationships) =
  let (es,rs) = transformRel (map addKey entities,[]) relationships
  in ERD name es rs

-- Adds a new artificial primary key to an entity

addKey :: Entity -> Entity
addKey (Entity en attrs) =
  Entity en ((Attribute ("Key") (IntDom Nothing) PKey False)
             : (map deleteKey attrs))
 where
  -- set user-defined primary keys to "Unique"

  deleteKey :: Attribute -> Attribute
  deleteKey a@(Attribute an d k null)
    | k == PKey = Attribute an d Unique null
    | otherwise = a


transformRel :: ([Entity], [Relationship]) -> [Relationship]
             -> ([Entity], [Relationship])
transformRel (ens,rels) [] = (ens,rels)
transformRel (ens,rels) (r@(Relationship _ [(REnd e1 _ c1), (REnd e2 _ c2)]) : rs) = case c1 of
  (Exactly i1)         -> case c2 of
    (Exactly _)           -> error "2 minima" --error in XML2ERD

    (Between _ Infinite)  -> transformRel (eRN i1 e1 e2 (ens, rels) r) rs
    (Between i2 (Max i3)) -> transformRel (eRJ i1 i2 i3 e1 e2 (ens, rels) r) rs

  (Between i1 Infinite)   -> case c2 of
    (Exactly i2)          -> transformRel (eRN i2 e2 e1 (ens, rels) r) rs
    (Between _ Infinite)  -> transformRel (rNRN ens rels r) rs
    (Between i2 (Max i3)) -> transformRel (rNRJ i1 i2 i3 e1 e2 ens rels r) rs

  (Between i1 (Max i2)) -> case c2 of
    (Exactly i3)          -> transformRel (eRJ i3 i1 i2 e2 e1 (ens, rels) r) rs
    (Between i3 Infinite) -> transformRel (rNRJ i3 i1 i2 e1 e2 ens rels r) rs
    (Between i3 (Max i4)) -> transformRel (rJRJ i1 i2 i3 i4 e1 e2 ens rels r) rs


eRN :: Int -> String -> String -> ([Entity],[Relationship]) -> Relationship
    -> ([Entity],[Relationship])
eRN i1 e1 e2 (ens,rels) r@(Relationship rname _)
  | i1==1 = (addFKey e1 e2 rname False False ens ens, (r:rels)) --(1,1):(0,n)

  | otherwise = let (r1,e,r2) = addExtraEntity r ens            --(i,i):(0,n)

                in
                (e:ens, r1:r2:rels)

eRJ :: Int -> Int -> Int -> String -> String -> ([Entity],[Relationship]) -> Relationship
    -> ([Entity],[Relationship])
eRJ i1 _ i3 e1 e2 (ens, rels) r@(Relationship rname _)
  | i1==1 = (addFKey e1 e2 rname False (i3==1) ens ens, (r:rels))       --(1,1):(0,j)

  | otherwise = if i3==1
                then (addFKey e2 e1 rname True False ens ens, (r:rels)) --(i,i):(0,1) 

                else let (r1,e,r2) = addExtraEntity r ens               --(i,i):(0,j)

                     in
                     (e:ens, r1:r2:rels)

rNRN :: [Entity] -> [Relationship] -> Relationship -> ([Entity],[Relationship])
rNRN ens rels r =
  let (r1,e,r2) = addExtraEntity r ens
  in
  (e:ens, r1:r2:rels)

rNRJ :: a -> Int -> Int -> String -> String -> [Entity]
     -> [Relationship] -> Relationship -> ([Entity], [Relationship])
rNRJ _ i2 i3 e1 e2 ens rels r@(Relationship rname _)
  | i2==0 && i3==1 = (addFKey e1 e2 rname True False ens ens, (r:rels)) --(_,n):(0,1)

  | otherwise = let (r1,e,r2) = addExtraEntity r ens                    --(_,n):(_,i)

                in
                (e:ens, r1:r2:rels)

rJRJ :: Int -> Int -> a -> Int -> String -> String -> [Entity]
     -> [Relationship] -> Relationship -> ([Entity], [Relationship])
rJRJ i1 i2 _ i4 e1 e2 ens rels r@(Relationship rname _)
  | i1==0 && i2==1 = (addFKey e1 e2 rname True (i4==1) ens ens, (r:rels)) --(0,1):(0,1)/(0,1):(_,j)

  | otherwise = let (r1,e,r2) = addExtraEntity r ens                      --(_,i):(_,j)

                in
                (e:ens, r1:r2:rels)


addFKey :: String -> String -> String -> Bool -> Bool -> [Entity] -> [Entity]
        -> [Entity]
addFKey _ _ _ _ _ [] ens = ens
addFKey e1 e2 rname null unique (e@(Entity n (a:attrs)) : ens) ens'
  | e2 == n =
     let aname = attributeName (getKeyAttribute e1 ens')
     in
     (Entity n
             (a:attrs++[Attribute (fKeyName e1 rname aname) (KeyDom e1)
                                  (if unique then Unique else NoKey) null]))
      : ens
  | otherwise = e : addFKey e1 e2 rname null unique ens ens'


--foreign key for extra entity

addFKey' :: String -> String -> Bool -> Entity -> [Entity] -> Entity
addFKey' ename rname null (Entity n attrs) es = ensureUniqueAttributeNames $
 Entity n
        ((Attribute (fKeyName ename rname
                              (attributeName (getKeyAttribute ename es)))
                    (KeyDom ename) PKey null) : attrs)

getKeyAttribute :: String -> [Entity] -> Attribute
getKeyAttribute ename ((Entity n attrs) : ens)
  | ename == n = getKey attrs
  | otherwise = getKeyAttribute ename ens
getKey :: [Attribute] -> Attribute
getKey (a@(Attribute _ _ k _):attrs)
  | PKey == k = a
  | otherwise = getKey attrs

-- e1   -   e2

-- e1 - R - e2

addExtraEntity :: Relationship -> [Entity]
                  -> (Relationship, Entity, Relationship)
addExtraEntity (Relationship r [(REnd e1 r1 c1), (REnd e2 r2 c2)]) es =
  (Relationship "" [(REnd e1 "" (Exactly 1)), (REnd r r2 c2)],
   addFKey' e1 r False (addFKey' e2 r False (Entity r []) es) es,
   Relationship "" [(REnd e2 "" (Exactly 1)), (REnd r r1 c1)])

-- create a name for a foreign key for a given entity, relationship,

-- and key attribute name.

fKeyName :: String -> String -> String -> String
fKeyName ename rname kname = combineIds [ename,rname,kname]

-- add an index to the name of an attribute if it occurs twice

-- (possible in case of generated entities)

ensureUniqueAttributeNames :: Entity -> Entity
ensureUniqueAttributeNames (Entity ename attrs) =
  Entity ename (uniqueNames [] attrs)

uniqueNames :: [Attribute] -> [Attribute] -> [Attribute]
uniqueNames oldattrs [] = reverse oldattrs
uniqueNames oldattrs (attr@(Attribute aname dom key nll) : attrs) =
  if aname `elem` names
  then uniqueNames (Attribute (makeUnique 1 aname) dom key nll : oldattrs) attrs
  else uniqueNames (attr:oldattrs) attrs
 where
  names = map attributeName (oldattrs++attrs)

  makeUnique i name = let newname = name++show i in
    if newname `elem` names then makeUnique (i+1) name else newname