@@ -2,6 +2,7 @@
22module ProjectM36.IsomorphicSchema where
33import ProjectM36.Base
44import ProjectM36.Error
5+import ProjectM36.MiscUtils
56import ProjectM36.RelationalExpression
67import Control.Monad
78import Control.Monad.State
@@ -9,6 +10,7 @@ import GHC.Generics
910import Data.Binary
1011import qualified Data.Map as M
1112import qualified Data.Set as S
13+import qualified Data.List as L
1214--import Debug.Trace
1315-- isomorphic schemas offer bi-directional functors between two schemas
1416
@@ -20,22 +22,41 @@ import qualified Data.Set as S
2022
2123-- the isomorphic building blocks should not be arbitrarily combined; for example, combing restrict and union on the same target relvar does not make sense as that would create effects at a distance in the secondary schema
2224
23−data SchemaExpr = AddSubschema SchemaName |
24− AddSubschemaIsomorph SchemaName SchemaIsomorph |
25+data SchemaExpr = AddSubschema SchemaName SchemaIsomorphs|
2526 RemoveSubschema SchemaName
2627 deriving (Generic, Binary)
2728
29+
2830isomorphs :: Schema -> SchemaIsomorphs
2931isomorphs (Schema i) = i
3032
31−{- -- useful for transforming a concrete context into a virtual schema and vice versa
33+-- | Return an error if the schema is not isomorphic to the base database context.
34+-- A schema is fully isomorphic iff all relvars in the base context are in the "out" relvars, but only once.
35+--TODO: add relvar must appear exactly once constraint
36+validateSchema :: Schema -> DatabaseContext -> Maybe SchemaError
37+validateSchema potentialSchema baseContext = if not (S.null rvDiff) then
38+ Just (RelVarReferencesMissing rvDiff)
39+ else if not (null duplicateNames) then
40+ Just (RelVarReferencedMoreThanOnce (head duplicateNames))
41+ else
42+ Nothing
43+ where
44+ duplicateNames = dupes (L.sort outRvNamesList)
45+ outRvNamesList = concat (map isomorphOutRelVarNames (isomorphs potentialSchema))
46+ expectedRelVars = M.keysSet (relationVariables baseContext)
47+ schemaRelVars = isomorphsOutRelVarNames (isomorphs potentialSchema)
48+ rvDiff = S.difference expectedRelVars schemaRelVars
49+
50+-- useful for transforming a concrete context into a virtual schema and vice versa
3251invert :: SchemaIsomorph -> SchemaIsomorph
33−invert (IsoRelVarName nameA nameB) = IsoRelVarName nameB nameA
34−-}
52+invert (IsoRename rvIn rvOut) = IsoRename rvOut rvIn
53+invert (IsoRestrict rvIn predi (rvAOut, rvBOut)) = IsoUnion (rvAOut, rvBOut) predi rvIn
54+invert (IsoUnion (rvAIn, rvBIn) predi rvOut) = IsoRestrict rvOut predi (rvAIn, rvBIn)
3555
3656isomorphInRelVarNames :: SchemaIsomorph -> [RelVarName]
3757isomorphInRelVarNames (IsoRestrict rv _ _) = [rv]
3858isomorphInRelVarNames (IsoUnion (rvA, rvB) _ _) = [rvA, rvB]
59+isomorphInRelVarNames (IsoRename rv _) = [rv]
3960
4061-- | Relation variables names represented in the virtual schema space. Useful for determining if a relvar name is valid in the schema.
4162isomorphsInRelVarNames :: SchemaIsomorphs -> S.Set RelVarName
@@ -45,7 +66,8 @@ isomorphsInRelVarNames morphs = S.fromList (foldr rvnames [] morphs)
4566
4667isomorphOutRelVarNames :: SchemaIsomorph -> [RelVarName]
4768isomorphOutRelVarNames (IsoRestrict _ _ (rvA, rvB)) = [rvA, rvB]
48−isomorphOutRelVarNames (IsoUnion _ _ rvA) = [rvA]
69+isomorphOutRelVarNames (IsoUnion _ _ rv) = [rv]
70+isomorphOutRelVarNames (IsoRename _ rv) = [rv]
4971
5072isomorphsOutRelVarNames :: SchemaIsomorphs -> S.Set RelVarName
5173isomorphsOutRelVarNames morphs = S.fromList (foldr rvnames [] morphs)
@@ -54,12 +76,14 @@ isomorphsOutRelVarNames morphs = S.fromList (foldr rvnames [] morphs)
5476
5577processRelationalExprInSchema :: Schema -> RelationalExpr -> Either RelationalError RelationalExpr
5678processRelationalExprInSchema (Schema morphs) relExprIn = do
57− let validRelVarNames = isomorphsInRelVarNames morphs
5879 --validate that all rvs are present in the virtual schema- this prevents relation variables being referenced in the underlying schema (falling through the transformation)
59− let processRelExpr rexpr morph = relExprMogrify (\expr -> case expr of
60− RelationVariable rv () | S.notMember rv validRelVarNames -> Left (RelVarNotDefinedError rv)
61− ex -> relExprMorph morph ex) rexpr
62−
80+ let processRelExpr rexpr morph = relExprMogrify (relExprMorph morph) rexpr
81+ -- validate all in relvar names so that the actual relvar names don't leak through as errors
82+ validRelVarNames = isomorphsInRelVarNames morphs
83+ _ <- relExprMogrify (\expr -> case expr of
84+ RelationVariable rv () | S.notMember rv validRelVarNames -> Left (RelVarNotDefinedError rv)
85+ ex -> Right ex) relExprIn
86+
6387 foldM processRelExpr relExprIn morphs
6488
6589processDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr
@@ -98,6 +122,9 @@ relExprMorph (IsoUnion (relInT, relInF) predi relTarget) = \expr -> case expr of
98122
99123 RelationVariable rv () | rv == relInF -> Right (Restrict (NotPredicate predi) (RelationVariable relTarget ()))
100124 orig -> Right orig
125+relExprMorph (IsoRename relIn relOut) = \expr -> case expr of
126+ RelationVariable rv () | rv == relIn -> Right (RelationVariable relOut ())
127+ orig -> Right orig
101128
102129relExprMogrify :: (RelationalExpr -> Either RelationalError RelationalExpr) -> RelationalExpr -> Either RelationalError RelationalExpr
103130relExprMogrify func (Project attrs expr) = func expr >>= \ex -> func (Project attrs ex)
@@ -168,18 +195,40 @@ databaseContextExprMorph (IsoUnion (rvTrue, rvFalse) filt rvOut) relExprFunc exp
168195 Update rv attrMap predi | rv == rvTrue -> pure $ Update rvOut attrMap (AndPredicate predi filt)
169196 Update rv attrMap predi | rv == rvFalse -> pure $ Update rvOut attrMap (AndPredicate (NotPredicate filt) predi)
170197 orig -> pure orig
198+databaseContextExprMorph (IsoRename relIn relOut) relExprFunc expr = case expr of
199+ Assign rv relExpr | rv == relIn -> relExprFunc relExpr >>= \ex -> pure (Assign relOut ex)
200+ Insert rv relExpr | rv == relIn -> relExprFunc relExpr >>= \ex -> pure $ Insert relOut ex
201+ Delete rv delPred | rv == relIn -> pure $ Delete relOut delPred
202+ Update rv attrMap predi | rv == relIn -> pure $ Update relOut attrMap predi
203+ orig -> pure orig
171204
172205-- | Apply the isomorphism transformations to the relational expression to convert the relational expression from operating on one schema to a disparate, isomorphic schema.
173206applyRelationalExprSchemaIsomorphs :: SchemaIsomorphs -> RelationalExpr -> Either RelationalError RelationalExpr
174207applyRelationalExprSchemaIsomorphs morphs expr = foldM (\expr' morph -> relExprMogrify (relExprMorph morph) expr') expr morphs
175208
176−inclusionDependenciesMorph :: SchemaIsomorph -> (InclusionDependency -> Either RelationalError InclusionDependency)
177−inclusionDependenciesMorph iso = \(InclusionDependency subExpr expr) -> InclusionDependency <$> relExprMorph iso subExpr <*> relExprMorph iso expr
178−
179−applyInclusionDependenciesSchemaIsoMorphs :: SchemaIsomorphs -> InclusionDependencies -> Either RelationalError InclusionDependencies
180−--add inc deps for restriction with some automatic name
181−applyInclusionDependenciesSchemaIsoMorphs _ _ = undefined
209+-- the morph must be applied in the opposite direction
210+--algorithm: create a relexpr for each relvar in the schema, then replace those rel exprs wherever they appear in the inc dep relexprs
211+-- x = x1 union x2
212+inclusionDependencyInSchema :: Schema -> InclusionDependency -> Either RelationalError InclusionDependency
213+inclusionDependencyInSchema schema (InclusionDependency rexprA rexprB) = do
214+ --collect all relvars which appear in the schema
215+ let schemaRelVars = isomorphsInRelVarNames (isomorphs schema)
216+ rvAssoc <- mapM (\rvIn -> do
217+ rvOut <- processRelationalExprInSchema schema (RelationVariable rvIn ())
218+ pure (rvOut, RelationVariable rvIn ())
219+ )
220+ (S.toList schemaRelVars)
221+ let replacer exprOrig = foldM (\expr (find, replace) -> if expr == find then
222+ pure replace
223+ else
224+ pure expr) exprOrig rvAssoc
225+ rexprA' <- relExprMogrify replacer rexprA
226+ rexprB' <- relExprMogrify replacer rexprB
227+ pure (InclusionDependency rexprA' rexprB')
182228
229+inclusionDependenciesInSchema :: Schema -> InclusionDependencies -> Either RelationalError InclusionDependencies
230+inclusionDependenciesInSchema schema incDeps = mapM (\(depName, dep) -> inclusionDependencyInSchema schema dep >>= \newDep -> pure (depName, newDep)) (M.toList incDeps) >>= pure . M.fromList
231+
183232relationVariablesInSchema :: Schema -> DatabaseContext -> Either RelationalError RelationVariables
184233relationVariablesInSchema schema@(Schema morphs) context = foldM transform M.empty morphs
185234 where
@@ -206,15 +255,16 @@ applyRelationVariablesSchemaIsomorphs = undefined
206255
207256applySchemaIsomorphsToDatabaseContext :: SchemaIsomorphs -> DatabaseContext -> Either RelationalError DatabaseContext
208257applySchemaIsomorphsToDatabaseContext morphs context = do
209− incdeps <- applyInclusionDependenciesSchemaIsoMorphs morphs (inclusionDependencies context)
258+-- incdeps <- inclusionDependen morphs (inclusionDependencies context)
210259 relvars <- applyRelationVariablesSchemaIsomorphs morphs (relationVariables context)
211− pure (context { inclusionDependencies = incdeps,
260+ pure (context { --inclusionDependencies = incdeps,
212261 relationVariables = relvars
213262 --atomFunctions = atomfuncs,
214263 --notifications = notifs,
215264 --typeConstructorMapping = tconsmapping
216265 })
217266
267+{-
218268validate :: SchemaIsomorph -> S.Set RelVarName -> Either RelationalError SchemaIsomorph
219269validate morph underlyingRvNames = if S.size invalidRvNames > 0 then
220270 Left (MultipleErrors (map RelVarNotDefinedError (S.toList invalidRvNames)))
@@ -223,18 +273,18 @@ validate morph underlyingRvNames = if S.size invalidRvNames > 0 then
223273 where
224274 morphRvNames = S.fromList (isomorphOutRelVarNames morph)
225275 invalidRvNames = S.difference morphRvNames underlyingRvNames
276+-}
226277
227278-- the second argument really should be a database context in the future so we can morph other context items
228−evalSchemaExpr :: SchemaExpr -> S.Set RelVarName -> Subschemas -> Either RelationalError Subschemas
229−evalSchemaExpr (AddSubschema sname) _ sschemas = if M.member sname sschemas then
279+evalSchemaExpr :: SchemaExpr -> DatabaseContext -> Subschemas -> Either RelationalError Subschemas
280+evalSchemaExpr (AddSubschema sname morphs) context sschemas = if M.member sname sschemas then
230281 Left (SubschemaNameInUseError sname)
231− else
232− pure (M.insert sname (Schema []) sschemas)
233−evalSchemaExpr (AddSubschemaIsomorph sname morph) underlyingRvNames sschemas = case M.lookup sname sschemas of
234− Nothing -> Left (SubschemaNameNotInUseError sname)
235− Just (Schema oldMorphs) -> do
236− morph' <- validate morph underlyingRvNames
237− pure (M.insert sname (Schema (oldMorphs ++ [morph'])) sschemas)
282+ else case valid of
283+ Just err -> Left (SchemaCreationError err)
284+ Nothing -> pure (M.insert sname newSchema sschemas)
285+ where
286+ newSchema = Schema morphs
287+ valid = validateSchema newSchema context
238288evalSchemaExpr (RemoveSubschema sname) _ sschemas = if M.member sname sschemas then
239289 pure (M.delete sname sschemas)
240290 else