1{-# LANGUAGE DeriveGeneric, DeriveAnyClass, LambdaCase #-}
2module ProjectM36.IsomorphicSchema where
5import ProjectM36.MiscUtils
6import ProjectM36.RelationalExpression
7import ProjectM36.Relation
8import qualified ProjectM36.AttributeNames as AN
10import Control.Monad.State
11import Control.Monad.Trans.Reader
14import qualified Data.Map as M
15import qualified Data.Set as S
16import qualified Data.List as L
17import qualified Data.Text as T
20-- isomorphic schemas offer bi-directional functors between two schemas
22--TODO: note that renaming a relvar should alter any stored isomorphisms as well
23--TODO: rel attrs rename or transform (needs bidirectional atom functions)
24-- TODO: IsoRestrict should include requirement that union'd relations should retain the same tuple count (no tuples are lost or ambiguous between the two relations)
25--TODO: allow morphs to stack (morph a schema to a new schema)
26 -- this could be accomplished by morphing the morphs or by chain linking schemas so that they need not directly reference the underlying concrete schema
28-- 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
30data SchemaExpr = AddSubschema SchemaName SchemaIsomorphs |
31 RemoveSubschema SchemaName
32 deriving (Generic, Binary, Show)
35isomorphs :: Schema -> SchemaIsomorphs
36isomorphs (Schema i) = i
38-- | Return an error if the schema is not isomorphic to the base database context.
39-- A schema is fully isomorphic iff all relvars in the base context are in the "out" relvars, but only once.
40--TODO: add relvar must appear exactly once constraint
41validateSchema :: Schema -> DatabaseContext -> Maybe SchemaError
42validateSchema potentialSchema baseContext | not (S.null rvDiff) = Just (RelVarReferencesMissing rvDiff)
43 | not (null outDupes) = Just (RelVarOutReferencedMoreThanOnce (head outDupes))
44 | not (null inDupes) = Just (RelVarInReferencedMoreThanOnce (head inDupes))
47 --check that the predicate for IsoUnion and IsoRestrict holds right now
48 outDupes = duplicateNames (namesList isomorphOutRelVarNames)
49 inDupes = duplicateNames (namesList isomorphInRelVarNames)
50 duplicateNames = dupes . L.sort
51 namesList isoFunc = concatMap isoFunc (isomorphs potentialSchema)
52 expectedRelVars = M.keysSet (relationVariables baseContext)
53 schemaRelVars = isomorphsOutRelVarNames (isomorphs potentialSchema)
54 rvDiff = S.difference expectedRelVars schemaRelVars
56-- useful for transforming a concrete context into a virtual schema and vice versa
57invert :: SchemaIsomorph -> SchemaIsomorph
58invert (IsoRename rvIn rvOut) = IsoRename rvOut rvIn
59invert (IsoRestrict rvIn predi (rvAOut, rvBOut)) = IsoUnion (rvAOut, rvBOut) predi rvIn
60invert (IsoUnion (rvAIn, rvBIn) predi rvOut) = IsoRestrict rvOut predi (rvAIn, rvBIn)
62isomorphInRelVarNames :: SchemaIsomorph -> [RelVarName]
63isomorphInRelVarNames (IsoRestrict rv _ _) = [rv]
64isomorphInRelVarNames (IsoUnion (rvA, rvB) _ _) = [rvA, rvB]
65isomorphInRelVarNames (IsoRename rv _) = [rv]
67-- | Relation variables names represented in the virtual schema space. Useful for determining if a relvar name is valid in the schema.
68isomorphsInRelVarNames :: SchemaIsomorphs -> S.Set RelVarName
69isomorphsInRelVarNames morphs = S.fromList (foldr rvnames [] morphs)
71 rvnames morph acc = acc ++ isomorphInRelVarNames morph
73isomorphOutRelVarNames :: SchemaIsomorph -> [RelVarName]
74isomorphOutRelVarNames (IsoRestrict _ _ (rvA, rvB)) = [rvA, rvB]
75isomorphOutRelVarNames (IsoUnion _ _ rv) = [rv]
76isomorphOutRelVarNames (IsoRename _ rv) = [rv]
78isomorphsOutRelVarNames :: SchemaIsomorphs -> S.Set RelVarName
79isomorphsOutRelVarNames morphs = S.fromList (foldr rvnames [] morphs)
81 rvnames morph acc = acc ++ isomorphOutRelVarNames morph
83-- | Check that all mentioned relvars are actually present in the current schema.
84validateRelationalExprInSchema :: Schema -> RelationalExpr -> Either RelationalError ()
85validateRelationalExprInSchema schema relExprIn = relExprMogrify (\case
86 RelationVariable rv () | S.notMember rv validRelVarNames -> Left (RelVarNotDefinedError rv)
87 ex -> Right ex) relExprIn >> pure ()
89 validRelVarNames = isomorphsInRelVarNames (isomorphs schema)
91processRelationalExprInSchema :: Schema -> RelationalExpr -> Either RelationalError RelationalExpr
92processRelationalExprInSchema schema relExprIn = do
93 --validate that all rvs are present in the virtual schema- this prevents relation variables being referenced in the underlying schema (falling through the transformation)
94 let processRelExpr rexpr morph = relExprMogrify (relExprMorph morph) rexpr
95 validateRelationalExprInSchema schema relExprIn
96 foldM processRelExpr relExprIn (isomorphs schema)
98validateDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError ()
99validateDatabaseContextExprInSchema schema dbExpr = mapM_ (\morph -> databaseContextExprMorph morph (\e -> validateRelationalExprInSchema schema e >> pure e) dbExpr) (isomorphs schema) >> pure ()
101processDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr
102processDatabaseContextExprInSchema schema@(Schema morphs) dbExpr = do
103 let relExprMogrifier = processRelationalExprInSchema schema
104 --validate that all mentioned relvars are in the valid set
105 _ <- validateDatabaseContextExprInSchema schema dbExpr
107 foldM (\ex morph -> databaseContextExprMorph morph relExprMogrifier ex) dbExpr morphs
109-- | If the database context expression adds or removes a relvar, we need to update the isomorphs to create a passthrough Isomorph.
110processDatabaseContextExprSchemaUpdate :: Schema -> DatabaseContextExpr -> Schema
111processDatabaseContextExprSchemaUpdate schema@(Schema morphs) expr = case expr of
112 Define rv _ | S.notMember rv validSchemaName -> passthru rv
113 Assign rv _ | S.notMember rv validSchemaName -> passthru rv
114 Undefine rv | S.member rv validSchemaName -> Schema (filter (elem rv . isomorphInRelVarNames) morphs)
115 MultipleExpr exprs -> foldr (flip processDatabaseContextExprSchemaUpdate) schema exprs
118 validSchemaName = isomorphsInRelVarNames morphs
119 passthru rvname = Schema (morphs ++ [IsoRename rvname rvname])
121processDatabaseContextExprSchemasUpdate :: Subschemas -> DatabaseContextExpr -> Subschemas
122processDatabaseContextExprSchemasUpdate subschemas expr = M.map (`processDatabaseContextExprSchemaUpdate` expr) subschemas
124-- re-evaluate- it's not possible to display an incdep that may be for a foreign key to a relvar which is not available in the subschema!
125-- weird compromise: allow inclusion dependencies failures not in the subschema to be propagated- in the worst case, only the inclusion dependency's name is leaked.
127-- | Convert inclusion dependencies for display in a specific schema.
128applySchemaToInclusionDependencies :: Schema -> InclusionDependencies -> Either RelationalError InclusionDependencies
129applySchemaToInclusionDependencies (Schema morphs) incDeps =
130 let incDepMorph incDep = --check that the mentioned relvars are in fact in the current schema
131 M.update incDepMorph incDeps
134-- | Morph a relational expression in one schema to another isomorphic schema.
135relExprMorph :: SchemaIsomorph -> (RelationalExpr -> Either RelationalError RelationalExpr)
136relExprMorph (IsoRestrict relIn _ (relOutTrue, relOutFalse)) = \case
137 RelationVariable rv () | rv == relIn -> Right (Union (RelationVariable relOutTrue ()) (RelationVariable relOutFalse ()))
139relExprMorph (IsoUnion (relInT, relInF) predi relTarget) = \case
140 --only the true predicate portion appears in the virtual schema
141 RelationVariable rv () | rv == relInT -> Right (Restrict predi (RelationVariable relTarget ()))
143 RelationVariable rv () | rv == relInF -> Right (Restrict (NotPredicate predi) (RelationVariable relTarget ()))
145relExprMorph (IsoRename relIn relOut) = \case
146 RelationVariable rv () | rv == relIn -> Right (RelationVariable relOut ())
149relExprMogrify :: (RelationalExpr -> Either RelationalError RelationalExpr) -> RelationalExpr -> Either RelationalError RelationalExpr
150relExprMogrify func (Project attrs expr) = func expr >>= \ex -> func (Project attrs ex)
151relExprMogrify func (Union exprA exprB) = do
155relExprMogrify func (Join exprA exprB) = do
159relExprMogrify func (Rename n1 n2 expr) = func expr >>= \ex -> func (Rename n1 n2 ex)
160relExprMogrify func (Difference exprA exprB) = do
163 func (Difference exA exB)
164relExprMogrify func (Group ns n expr) = func expr >>= \ex -> func (Group ns n ex)
165relExprMogrify func (Ungroup n expr) = func expr >>= \ex -> func (Ungroup n ex)
166relExprMogrify func (Restrict predi expr) = func expr >>= \ex -> func (Restrict predi ex)
167relExprMogrify func (Equals exprA exprB) = do
170 func (Equals exA exB)
171relExprMogrify func (NotEquals exprA exprB) = do
174 func (NotEquals exA exB)
175relExprMogrify func (Extend ext expr) = func expr >>= \ex -> func (Extend ext ex)
176relExprMogrify func other = func other
179spam :: Either RelationalError RelationalExpr
180spam = relExprMogrify (relExprMorph (IsoRestrict "emp" TruePredicate (Just "nonboss", Just "boss"))) (RelationVariable "emp" ())
182spam2 :: Either RelationalError RelationalExpr
183spam2 = relExprMogrify (relExprMorph (IsoUnion ("boss", Just "nonboss") TruePredicate "emp")) (RelationVariable "boss" ())
186databaseContextExprMorph :: SchemaIsomorph -> (RelationalExpr -> Either RelationalError RelationalExpr) -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr
187databaseContextExprMorph iso@(IsoRestrict rvIn filt (rvTrue, rvFalse)) relExprFunc expr = case expr of
188 Assign rv relExpr | rv == rvIn -> do
189 ex <- relExprFunc relExpr
190 let trueExpr n = Assign n (Restrict filt ex)
191 falseExpr n = Assign n (Restrict (NotPredicate filt) ex)
192 pure $ MultipleExpr [trueExpr rvTrue, falseExpr rvFalse]
193 Insert rv relExpr | rv == rvIn -> do
194 ex <- relExprFunc relExpr
195 let trueExpr n = Insert n (Restrict filt ex)
196 falseExpr n = Insert n (Restrict (NotPredicate filt) ex)
197 pure $ MultipleExpr [trueExpr rvTrue, falseExpr rvFalse]
198 Update rv attrMap predi | rv == rvIn -> do
199 -- if the update would "shift" a tuple from the true->false relvar or vice versa, that would be a constraint violation in the virtual schema
200 let trueExpr n = Update n attrMap (AndPredicate predi filt)
201 falseExpr n = Update n attrMap (AndPredicate predi (NotPredicate filt))
202 pure (MultipleExpr [trueExpr rvTrue, falseExpr rvFalse])
203 MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso relExprFunc) exprs
205databaseContextExprMorph iso@(IsoUnion (rvTrue, rvFalse) filt rvOut) relExprFunc expr = case expr of
206 --assign: replace all instances in the portion of the target relvar with the new tuples from the relExpr
207 --problem: between the delete->insert, constraints could be violated which would not otherwise be violated in the "in" schema. This implies that there should be a combo operator which can insert/update/delete in a single pass based on relexpr queries, or perhaps MultipleExpr should be the infamous "comma" operator from TutorialD?
208 -- if any tuples are filtered out of the insert/assign, we need to simulate a constraint violation
209 Assign rv relExpr | rv == rvTrue -> relExprFunc relExpr >>= \ex -> pure $ MultipleExpr [Delete rvOut filt,
210 Insert rvOut (Restrict filt ex)]
211 Assign rv relExpr | rv == rvFalse -> relExprFunc relExpr >>= \ex -> pure $ MultipleExpr [Delete rvOut (NotPredicate filt),
212 Insert rvOut (Restrict (NotPredicate filt) ex)]
213 Insert rv relExpr | rv == rvTrue || rv == rvFalse -> relExprFunc relExpr >>= \ex -> pure $ Insert rvOut ex
214 Delete rv delPred | rv == rvTrue -> pure $ Delete rvOut (AndPredicate delPred filt)
215 Delete rv delPred | rv == rvFalse -> pure $ Delete rvOut (AndPredicate delPred (NotPredicate filt))
216 Update rv attrMap predi | rv == rvTrue -> pure $ Update rvOut attrMap (AndPredicate predi filt)
217 Update rv attrMap predi | rv == rvFalse -> pure $ Update rvOut attrMap (AndPredicate (NotPredicate filt) predi)
218 MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso relExprFunc) exprs
220databaseContextExprMorph iso@(IsoRename relIn relOut) relExprFunc expr = case expr of
221 Assign rv relExpr | rv == relIn -> relExprFunc relExpr >>= \ex -> pure (Assign relOut ex)
222 Insert rv relExpr | rv == relIn -> relExprFunc relExpr >>= \ex -> pure $ Insert relOut ex
223 Delete rv delPred | rv == relIn -> pure $ Delete relOut delPred
224 Update rv attrMap predi | rv == relIn -> pure $ Update relOut attrMap predi
225 MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso relExprFunc) exprs
228-- | Apply the isomorphism transformations to the relational expression to convert the relational expression from operating on one schema to a disparate, isomorphic schema.
229applyRelationalExprSchemaIsomorphs :: SchemaIsomorphs -> RelationalExpr -> Either RelationalError RelationalExpr
230applyRelationalExprSchemaIsomorphs morphs expr = foldM (\expr' morph -> relExprMogrify (relExprMorph morph) expr') expr morphs
232-- the morph must be applied in the opposite direction
233--algorithm: create a relexpr for each relvar in the schema, then replace those rel exprs wherever they appear in the inc dep relexprs
235inclusionDependencyInSchema :: Schema -> InclusionDependency -> Either RelationalError InclusionDependency
236inclusionDependencyInSchema schema (InclusionDependency rexprA rexprB) = do
237 --collect all relvars which appear in the schema
238 let schemaRelVars = isomorphsInRelVarNames (isomorphs schema)
239 rvAssoc <- mapM (\rvIn -> do
240 rvOut <- processRelationalExprInSchema schema (RelationVariable rvIn ())
241 pure (rvOut, RelationVariable rvIn ())
243 (S.toList schemaRelVars)
244 let replacer exprOrig = foldM (\expr (find, replace) -> if expr == find then
247 pure expr) exprOrig rvAssoc
248 rexprA' <- relExprMogrify replacer rexprA
249 rexprB' <- relExprMogrify replacer rexprB
250 pure (InclusionDependency rexprA' rexprB')
252-- #55 add two virtual constraints for IsoUnion and enforce them before the tuples disappear
254-- also, it's inverse to IsoRestrict which adds two constraints at the base level
255-- for IsoRestrict, consider hiding the two, generated constraints since they can never be thrown in the isomorphic schema
256inclusionDependenciesInSchema :: Schema -> InclusionDependencies -> Either RelationalError InclusionDependencies
257inclusionDependenciesInSchema schema incDeps = M.fromList <$> mapM (\(depName, dep) -> inclusionDependencyInSchema schema dep >>= \newDep -> pure (depName, newDep)) (M.toList incDeps)
259relationVariablesInSchema :: Schema -> DatabaseContext -> Either RelationalError RelationVariables
260relationVariablesInSchema schema@(Schema morphs) context = foldM transform M.empty morphs
262 transform newRvMap morph = do
263 let rvNames = isomorphInRelVarNames morph
264 rvAssocs <- mapM (\rv -> do
265 expr' <- processRelationalExprInSchema schema (RelationVariable rv ())
266 rel <- runReader (evalRelationalExpr expr') (RelationalExprStateElems context)
267 pure (rv, rel)) rvNames
268 pure (M.union newRvMap (M.fromList rvAssocs))
274data DatabaseContext =
279applyRelationVariablesSchemaIsomorphs :: SchemaIsomorphs -> RelationVariables -> Either RelationalError RelationVariables
280applyRelationVariablesSchemaIsomorphs = undefined
283applySchemaIsomorphsToDatabaseContext :: SchemaIsomorphs -> DatabaseContext -> Either RelationalError DatabaseContext
284applySchemaIsomorphsToDatabaseContext morphs context = do
285-- incdeps <- inclusionDependen morphs (inclusionDependencies context)
286 relvars <- applyRelationVariablesSchemaIsomorphs morphs (relationVariables context)
287 pure (context { --inclusionDependencies = incdeps,
288 relationVariables = relvars
289 --atomFunctions = atomfuncs,
290 --notifications = notifs,
291 --typeConstructorMapping = tconsmapping
295validate :: SchemaIsomorph -> S.Set RelVarName -> Either RelationalError SchemaIsomorph
296validate morph underlyingRvNames = if S.size invalidRvNames > 0 then
297 Left (MultipleErrors (map RelVarNotDefinedError (S.toList invalidRvNames)))
301 morphRvNames = S.fromList (isomorphOutRelVarNames morph)
302 invalidRvNames = S.difference morphRvNames underlyingRvNames
305-- | Create inclusion dependencies mainly for IsoRestrict because the predicate should hold in the base schema.
306createIncDepsForIsomorph :: SchemaName -> SchemaIsomorph -> InclusionDependencies
307createIncDepsForIsomorph sname (IsoRestrict _ predi (rvTrue, rvFalse)) = let
308 newIncDep predicate rv = InclusionDependency (Project AN.empty (Restrict predicate (RelationVariable rv ()))) (ExistingRelation relationTrue)
309 incDepName b = "schema" <> "_" <> sname <> "_" <> T.pack (show b) in
310 M.fromList [(incDepName True, newIncDep predi rvTrue),
311 (incDepName False, newIncDep (NotPredicate predi) rvFalse)]
312createIncDepsForIsomorph _ _ = M.empty
314-- in the case of IsoRestrict, the database context should be updated with the restriction so that if the restriction does not hold, then the schema cannot be created
315evalSchemaExpr :: SchemaExpr -> DatabaseContext -> Subschemas -> Either RelationalError (Subschemas, DatabaseContext)
316evalSchemaExpr (AddSubschema sname morphs) context sschemas =
317 if M.member sname sschemas then
318 Left (SubschemaNameInUseError sname)
320 Just err -> Left (SchemaCreationError err)
322 let newSchemas = M.insert sname newSchema sschemas
323 moreIncDeps = foldr (\morph acc -> M.union acc (createIncDepsForIsomorph sname morph)) M.empty morphs
324 incDepExprs = MultipleExpr (map (uncurry AddInclusionDependency) (M.toList moreIncDeps))
326 case runState (evalDatabaseContextExpr incDepExprs) (context, M.empty, False) of
327 (Left err, _) -> Left err
328 (Right (), (newContext,_,_)) -> pure (newSchemas, newContext) --need to propagate dirty flag here
330 newSchema = Schema morphs
331 valid = validateSchema newSchema context
332evalSchemaExpr (RemoveSubschema sname) context sschemas = if M.member sname sschemas then
333 pure (M.delete sname sschemas, context)
335 Left (SubschemaNameNotInUseError sname)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list