1{-# LANGUAGE ScopedTypeVariables #-}
2module ProjectM36.RelationalExpression where
3import ProjectM36.Relation
5import ProjectM36.TupleSet
8import ProjectM36.AtomType
9import ProjectM36.Attribute (emptyAttributes)
10import ProjectM36.ScriptSession
11import ProjectM36.DataTypes.Primitive
12import ProjectM36.AtomFunction
13import ProjectM36.DatabaseContextFunction
14import qualified ProjectM36.Attribute as A
15import qualified Data.Map as M
16import qualified Data.HashSet as HS
17import qualified Data.Set as S
18import Control.Monad.State hiding (join)
19import Control.Exception
22import Data.Char (isUpper)
23import qualified Data.Text as T
24import qualified Data.Vector as V
25import qualified ProjectM36.TypeConstructorDef as TCD
26import Control.Monad.Trans.Except
27import Control.Monad.Trans.Reader
32data DatabaseContextExprDetails = CountUpdatedTuples
34databaseContextExprDetailsFunc :: DatabaseContextExprDetails -> ResultAccumFunc
35databaseContextExprDetailsFunc CountUpdatedTuples _ relIn = Relation attrs newTups
37 attrs = A.attributesFromList [Attribute "count" IntAtomType]
38 existingTuple = fromMaybe (error "impossible counting error in singletonTuple") (singletonTuple relIn)
39 existingCount = case V.head (tupleAtoms existingTuple) of
41 _ -> error "impossible counting error in tupleAtoms"
42 newTups = case mkTupleSetFromList attrs [[IntAtom (existingCount + 1)]] of
43 Left err -> error ("impossible counting error in " ++ show err)
46-- | Used to start a fresh database state for a new database context expression.
47freshDatabaseState :: DatabaseContext -> DatabaseStateElems
48freshDatabaseState ctx = (ctx, M.empty, False) --future work: propagate return accumulator
50-- we need to pass around a higher level RelationTuple and Attributes in order to solve #52
51data RelationalExprStateElems = RelationalExprStateTupleElems DatabaseContext RelationTuple | -- used when fully evaluating a relexpr
52 RelationalExprStateAttrsElems DatabaseContext Attributes | --used when evaluating the type of a relexpr
53 RelationalExprStateElems DatabaseContext --used by default at the top level of evaluation
55instance Show RelationalExprStateElems where
56 show (RelationalExprStateTupleElems _ tup) = "RelationalExprStateTupleElems " ++ show tup
57 show (RelationalExprStateAttrsElems _ attrs) = "RelationalExprStateAttrsElems" ++ show attrs
58 show (RelationalExprStateElems _) = "RelationalExprStateElems"
60mkRelationalExprState :: DatabaseContext -> RelationalExprStateElems
61mkRelationalExprState = RelationalExprStateElems
63mergeTuplesIntoRelationalExprState :: RelationTuple -> RelationalExprStateElems -> RelationalExprStateElems
64mergeTuplesIntoRelationalExprState tupIn (RelationalExprStateElems ctx) = RelationalExprStateTupleElems ctx tupIn
65mergeTuplesIntoRelationalExprState _ st@(RelationalExprStateAttrsElems _ _) = st
66mergeTuplesIntoRelationalExprState tupIn (RelationalExprStateTupleElems ctx existingTuple) = let mergedTupMap = M.union (tupleToMap tupIn) (tupleToMap existingTuple) in
67 RelationalExprStateTupleElems ctx (mkRelationTupleFromMap mergedTupMap)
69mergeAttributesIntoRelationalExprState :: Attributes -> RelationalExprStateElems -> RelationalExprStateElems
70mergeAttributesIntoRelationalExprState attrs (RelationalExprStateElems ctx) = RelationalExprStateAttrsElems ctx attrs
71mergeAttributesIntoRelationalExprState _ st@(RelationalExprStateTupleElems _ _) = st
72mergeAttributesIntoRelationalExprState attrsIn (RelationalExprStateAttrsElems ctx attrs) = RelationalExprStateAttrsElems ctx (A.union attrsIn attrs)
74type ResultAccumName = StringType
76type ResultAccumFunc = (RelationTuple -> Relation -> Relation) -> Relation -> Relation
78data ResultAccum = ResultAccum { resultAccumFunc :: ResultAccumFunc,
79 resultAccumResult :: Relation
82type DatabaseStateElems = (DatabaseContext, M.Map ResultAccumName ResultAccum, DirtyFlag)
84type DatabaseState a = State DatabaseStateElems a
86getStateContext :: DatabaseState DatabaseContext
91putStateContext :: DatabaseContext -> DatabaseState ()
92putStateContext ctx = do
94 put (ctx, accum, True)
96type RelationalExprState a = Reader RelationalExprStateElems a
98stateElemsContext :: RelationalExprStateElems -> DatabaseContext
99stateElemsContext (RelationalExprStateTupleElems ctx _) = ctx
100stateElemsContext (RelationalExprStateElems ctx) = ctx
101stateElemsContext (RelationalExprStateAttrsElems ctx _) = ctx
103setStateElemsContext :: RelationalExprStateElems -> DatabaseContext -> RelationalExprStateElems
104setStateElemsContext (RelationalExprStateTupleElems _ tup) ctx = RelationalExprStateTupleElems ctx tup
105setStateElemsContext (RelationalExprStateElems _) ctx = RelationalExprStateElems ctx
106setStateElemsContext (RelationalExprStateAttrsElems _ attrs) ctx = RelationalExprStateAttrsElems ctx attrs
108--relvar state is needed in evaluation of relational expression but only as read-only in order to extract current relvar values
109evalRelationalExpr :: RelationalExpr -> RelationalExprState (Either RelationalError Relation)
110evalRelationalExpr (RelationVariable name _) = do
111 relvarTable <- fmap (relationVariables . stateElemsContext) ask
112 return $ case M.lookup name relvarTable of
113 Just res -> Right res
114 Nothing -> Left $ RelVarNotDefinedError name
116evalRelationalExpr (Project attrNames expr) = do
117 eAttrNameSet <- evalAttributeNames attrNames expr
119 Left err -> pure (Left err)
120 Right attrNameSet -> do
121 rel <- evalRelationalExpr expr
123 Right rel2 -> pure $ project attrNameSet rel2
124 Left err -> pure $ Left err
126evalRelationalExpr (Union exprA exprB) = do
127 relA <- evalRelationalExpr exprA
128 relB <- evalRelationalExpr exprB
130 Left err -> return $ Left err
131 Right relA2 -> case relB of
132 Left err -> return $ Left err
133 Right relB2 -> return $ union relA2 relB2
135evalRelationalExpr (Join exprA exprB) = do
136 relA <- evalRelationalExpr exprA
137 relB <- evalRelationalExpr exprB
139 Left err -> return $ Left err
140 Right relA2 -> case relB of
141 Left err -> return $ Left err
142 Right relB2 -> return $ join relA2 relB2
144evalRelationalExpr (Difference exprA exprB) = do
145 relA <- evalRelationalExpr exprA
146 relB <- evalRelationalExpr exprB
148 Left err -> return $ Left err
149 Right relA2 -> case relB of
150 Left err -> return $ Left err
151 Right relB2 -> return $ difference relA2 relB2
153evalRelationalExpr (MakeStaticRelation attributeSet tupleSet) =
154 case mkRelation attributeSet tupleSet of
155 Right rel -> return $ Right rel
156 Left err -> return $ Left err
158evalRelationalExpr (MakeRelationFromExprs mAttrExprs tupleExprs) = do
159 currentContext <- fmap stateElemsContext ask
160 let tConss = typeConstructorMapping currentContext
161 -- if the mAttrExprs is Nothing, then we should attempt to infer the tuple attributes from the first tuple itself- note that this is not always possible
163 mAttrs <- case mAttrExprs of
165 Just . A.attributesFromList <$> mapM (either throwE pure . evalAttrExpr tConss) (fromMaybe [] mAttrExprs)
166 Nothing -> pure Nothing
167 tuples <- mapM (liftE . evalTupleExpr mAttrs) tupleExprs
168 let attrs = fromMaybe firstTupleAttrs mAttrs
169 firstTupleAttrs = if null tuples then A.emptyAttributes else tupleAttributes (head tuples)
170 either throwE pure (mkRelation attrs (RelationTupleSet tuples))
172evalRelationalExpr (ExistingRelation rel) = pure (Right rel)
174evalRelationalExpr (Rename oldAttrName newAttrName relExpr) = do
175 evald <- evalRelationalExpr relExpr
177 Right rel -> return $ rename oldAttrName newAttrName rel
178 Left err -> return $ Left err
180evalRelationalExpr (Group oldAttrNames newAttrName relExpr) = do
181 eOldAttrNameSet <- evalAttributeNames oldAttrNames relExpr
182 case eOldAttrNameSet of
183 Left err -> pure (Left err)
184 Right oldAttrNameSet -> do
185 evald <- evalRelationalExpr relExpr
187 Right rel -> return $ group oldAttrNameSet newAttrName rel
188 Left err -> return $ Left err
190evalRelationalExpr (Ungroup attrName relExpr) = do
191 evald <- evalRelationalExpr relExpr
193 Right rel -> return $ ungroup attrName rel
194 Left err -> return $ Left err
196evalRelationalExpr (Restrict predicateExpr relExpr) = do
197 evald <- evalRelationalExpr relExpr
199 Left err -> return $ Left err
201 eFilterFunc <- predicateRestrictionFilter (attributes rel) predicateExpr
203 Left err -> return $ Left err
205 pure (restrict filterfunc rel)
207evalRelationalExpr (Equals relExprA relExprB) = do
208 evaldA <- evalRelationalExpr relExprA
209 evaldB <- evalRelationalExpr relExprB
211 Left err -> return $ Left err
212 Right relA -> case evaldB of
213 Left err -> return $ Left err
214 Right relB -> return $ Right $ if relA == relB then relationTrue else relationFalse
216--warning: copy-pasta from above- refactor
217evalRelationalExpr (NotEquals relExprA relExprB) = do
218 evaldA <- evalRelationalExpr relExprA
219 evaldB <- evalRelationalExpr relExprB
221 Left err -> return $ Left err
222 Right relA -> case evaldB of
223 Left err -> return $ Left err
224 Right relB -> return $ Right $ if relA /= relB then relationTrue else relationFalse
226-- extending a relation adds a single attribute with the results of the per-tuple expression evaluated
227evalRelationalExpr (Extend tupleExpression relExpr) = do
229 let evald = runReader (evalRelationalExpr relExpr) rstate
231 Left err -> pure (Left err)
233 tupProc <- extendTupleExpressionProcessor rel tupleExpression
235 Left err -> pure (Left err)
236 Right (newAttrs, tupProc') -> pure $ relMogrify tupProc' newAttrs rel
238--helper function to process relation variable creation/assignment
239setRelVar :: RelVarName -> Relation -> DatabaseState (Either RelationalError ())
240setRelVar relVarName rel = do
241 currentContext <- getStateContext
242 let newRelVars = M.insert relVarName rel $ relationVariables currentContext
243 potentialContext = currentContext { relationVariables = newRelVars }
245 case checkConstraints potentialContext of
246 Left err -> pure (Left err)
248 putStateContext potentialContext
251-- it is not an error to delete a relvar which does not exist, just like it is not an error to insert a pre-existing tuple into a relation
252deleteRelVar :: RelVarName -> DatabaseState (Either RelationalError ())
253deleteRelVar relVarName = do
254 currContext <- getStateContext
255 let relVars = relationVariables currContext
256 if M.notMember relVarName relVars then
259 let newRelVars = M.delete relVarName relVars
260 newContext = currContext { relationVariables = newRelVars }
261 putStateContext newContext
264evalDatabaseContextExpr :: DatabaseContextExpr -> DatabaseState (Either RelationalError ())
265evalDatabaseContextExpr NoOperation = pure (Right ())
267evalDatabaseContextExpr (Define relVarName attrExprs) = do
268 relvars <- fmap relationVariables getStateContext
269 tConss <- fmap typeConstructorMapping getStateContext
270 let eAttrs = map (evalAttrExpr tConss) attrExprs
272 err:_ -> pure (Left err)
273 [] -> case M.member relVarName relvars of
274 True -> pure (Left (RelVarAlreadyDefinedError relVarName))
275 False -> setRelVar relVarName emptyRelation >> pure (Right ())
277 attrs = A.attributesFromList (rights eAttrs)
278 emptyRelation = Relation attrs emptyTupleSet
280evalDatabaseContextExpr (Undefine relVarName) = deleteRelVar relVarName
282evalDatabaseContextExpr (Assign relVarName expr) = do
283 -- in the future, it would be nice to get types from the RelationalExpr instead of needing to evaluate it
284 context <- getStateContext
285 let existingRelVar = M.lookup relVarName relVarTable
286 relVarTable = relationVariables context
287 value = runReader (evalRelationalExpr expr) (RelationalExprStateElems context)
289 Left err -> pure (Left err)
290 Right rel -> case existingRelVar of
291 Nothing -> setRelVar relVarName rel
292 Just existingRel -> let expectedAttributes = attributes existingRel
293 foundAttributes = attributes rel in
294 if A.attributesEqual expectedAttributes foundAttributes then
295 setRelVar relVarName rel
297 pure (Left (RelationTypeMismatchError expectedAttributes foundAttributes))
299evalDatabaseContextExpr (Insert relVarName relExpr) = do
300 context <- getStateContext
301 let unionexp = Union relExpr rv
302 rv = RelationVariable relVarName ()
303 unioned = runReader (evalRelationalExpr unionexp) (RelationalExprStateElems context)
304 origRel = runReader (evalRelationalExpr rv) (RelationalExprStateElems context)
306 Left err -> pure (Left err)
307 Right unioned' -> case origRel of
308 Left err -> pure (Left err)
309 Right origRel' -> if cardinality unioned' == cardinality origRel' then --no tuples actually inserted
312 evalDatabaseContextExpr $ Assign relVarName (ExistingRelation unioned')
314evalDatabaseContextExpr (Delete relVarName predicate) = do
315 context <- getStateContext
316 let rv = RelationVariable relVarName ()
317 let updatedRel = runReader (evalRelationalExpr (Restrict (NotPredicate predicate) rv)) (RelationalExprStateElems context)
318 origRel = runReader (evalRelationalExpr rv) (RelationalExprStateElems context)
320 Left err -> pure (Left err)
321 Right updatedRel' -> case origRel of
322 Left err -> pure (Left err)
323 Right origRel' -> if cardinality origRel' == cardinality updatedRel' then
326 setRelVar relVarName updatedRel'
328--union of restricted+updated portion and the unrestricted+unupdated portion
329evalDatabaseContextExpr (Update relVarName atomExprMap restrictionPredicateExpr) = do
330 context <- getStateContext
331 let relVarTable = relationVariables context
332 case M.lookup relVarName relVarTable of
333 Nothing -> pure (Left (RelVarNotDefinedError relVarName))
335 case runReader (predicateRestrictionFilter (attributes rel) restrictionPredicateExpr) (RelationalExprStateElems context) of
336 Left err -> pure (Left err)
337 Right predicateFunc -> do
339 restrictedPortion <- restrict predicateFunc rel
340 if cardinality restrictedPortion == Finite 0 then
343 unrestrictedPortion <- restrict (predicateFunc >=> (pure . not)) rel
344 updatedPortion <- relMap (updateTupleWithAtomExprs atomExprMap context) restrictedPortion
345 updatedRel <- updatedPortion `union` unrestrictedPortion
346 pure (Just updatedRel)
348 Left err -> pure (Left err)
349 Right Nothing -> pure (Right ())
350 Right (Just updatedRel) -> setRelVar relVarName updatedRel
352evalDatabaseContextExpr (AddInclusionDependency newDepName newDep) = do
353 currContext <- getStateContext
354 let currDeps = inclusionDependencies currContext
355 newDeps = M.insert newDepName newDep currDeps
356 if M.member newDepName currDeps then
357 pure (Left (InclusionDependencyNameInUseError newDepName))
359 let potentialContext = currContext { inclusionDependencies = newDeps }
360 case checkConstraints potentialContext of
361 Left err -> pure (Left err)
363 putStateContext potentialContext
366evalDatabaseContextExpr (RemoveInclusionDependency depName) = do
367 currContext <- getStateContext
368 let currDeps = inclusionDependencies currContext
369 newDeps = M.delete depName currDeps
370 if M.notMember depName currDeps then
371 pure (Left (InclusionDependencyNameNotInUseError depName))
373 putStateContext $ currContext {inclusionDependencies = newDeps }
376-- | Add a notification which will send the resultExpr when triggerExpr changes between commits.
377evalDatabaseContextExpr (AddNotification notName triggerExpr resultOldExpr resultNewExpr) = do
378 currentContext <- getStateContext
379 let nots = notifications currentContext
380 if M.member notName nots then
381 pure (Left (NotificationNameInUseError notName))
383 let newNotifications = M.insert notName newNotification nots
384 newNotification = Notification { changeExpr = triggerExpr,
385 reportOldExpr = resultOldExpr,
386 reportNewExpr = resultNewExpr}
387 putStateContext $ currentContext { notifications = newNotifications }
390evalDatabaseContextExpr (RemoveNotification notName) = do
391 currentContext <- getStateContext
392 let nots = notifications currentContext
393 if M.notMember notName nots then
394 pure (Left (NotificationNameNotInUseError notName))
396 let newNotifications = M.delete notName nots
397 putStateContext $ currentContext { notifications = newNotifications }
400-- | Adds type and data constructors to the database context.
401-- validate that the type *and* constructor names are unique! not yet implemented!
402evalDatabaseContextExpr (AddTypeConstructor tConsDef dConsDefList) = do
403 currentContext <- getStateContext
404 let oldTypes = typeConstructorMapping currentContext
405 tConsName = TCD.name tConsDef
406 -- validate that the constructor's types exist
407 case validateTypeConstructorDef tConsDef dConsDefList of
408 errs@(_:_) -> pure (Left (someErrors errs))
409 [] | T.null tConsName || not (isUpper (T.head tConsName)) -> pure (Left (InvalidAtomTypeName tConsName))
410 | isJust (findTypeConstructor tConsName oldTypes) -> pure (Left (AtomTypeNameInUseError tConsName))
412 let newTypes = oldTypes ++ [(tConsDef, dConsDefList)]
413 putStateContext $ currentContext { typeConstructorMapping = newTypes }
416-- | Removing the atom constructor prevents new atoms of the type from being created. Existing atoms of the type remain. Thus, the atomTypes list in the DatabaseContext need not be all-inclusive.
417evalDatabaseContextExpr (RemoveTypeConstructor tConsName) = do
418 currentContext <- getStateContext
419 let oldTypes = typeConstructorMapping currentContext
420 if isNothing (findTypeConstructor tConsName oldTypes) then
421 pure (Left (AtomTypeNameNotInUseError tConsName))
423 let newTypes = filter (\(tCons, _) -> TCD.name tCons /= tConsName) oldTypes
424 putStateContext $ currentContext { typeConstructorMapping = newTypes }
427evalDatabaseContextExpr (MultipleExpr exprs) = do
428 --the multiple expressions must pass the same context around- not the old unmodified context
429 evald <- forM exprs evalDatabaseContextExpr
430 --some lifting magic needed here
432 [] -> pure (Right ())
433 errs -> pure (Left (someErrors errs))
435evalDatabaseContextExpr (RemoveAtomFunction funcName) = do
436 currentContext <- getStateContext
437 let atomFuncs = atomFunctions currentContext
438 case atomFunctionForName funcName atomFuncs of
439 Left err -> pure (Left err)
440 Right realFunc -> if isScriptedAtomFunction realFunc then do
441 let updatedFuncs = HS.delete realFunc atomFuncs
442 putStateContext (currentContext {atomFunctions = updatedFuncs })
445 pure (Left (PrecompiledFunctionRemoveError funcName))
447evalDatabaseContextExpr (RemoveDatabaseContextFunction funcName) = do
448 context <- getStateContext
449 let dbcFuncs = dbcFunctions context
450 case databaseContextFunctionForName funcName dbcFuncs of
451 Left err -> pure (Left err)
452 Right realFunc -> if isScriptedDatabaseContextFunction realFunc then do
453 let updatedFuncs = HS.delete realFunc dbcFuncs
454 putStateContext (context { dbcFunctions = updatedFuncs })
457 pure (Left (PrecompiledFunctionRemoveError funcName))
459evalDatabaseContextExpr (ExecuteDatabaseContextFunction funcName atomArgExprs) = do
460 context <- getStateContext
461 --resolve atom arguments
462 let relExprState = mkRelationalExprState context
463 eAtomTypes = map (\atomExpr -> runReader (typeFromAtomExpr emptyAttributes atomExpr) relExprState) atomArgExprs
464 eFunc = databaseContextFunctionForName funcName (dbcFunctions context)
466 Left err -> pure (Left err)
468 let expectedArgCount = length (dbcFuncType func)
469 actualArgCount = length atomArgExprs
470 if expectedArgCount /= actualArgCount then
471 pure (Left (FunctionArgumentCountMismatchError expectedArgCount actualArgCount))
473 --check that the atom types are valid
474 case lefts eAtomTypes of
475 _:_ -> pure (Left (someErrors (lefts eAtomTypes)))
477 let atomTypes = rights eAtomTypes
478 let mValidTypes = map (\(expType, actType) -> case atomTypeVerify expType actType of
480 Right _ -> Nothing) (zip (dbcFuncType func) atomTypes)
481 typeErrors = catMaybes mValidTypes
482 eAtomArgs = map (\arg -> runReader (evalAtomExpr emptyTuple arg) relExprState) atomArgExprs
483 if length (lefts eAtomArgs) > 1 then
484 pure (Left (someErrors (lefts eAtomArgs)))
485 else if not (null typeErrors) then
486 pure (Left (someErrors typeErrors))
488 case evalDatabaseContextFunction func (rights eAtomArgs) context of
489 Left err -> pure (Left err)
490 Right newContext -> putStateContext newContext >> pure (Right ())
492evalDatabaseContextIOExpr :: Maybe ScriptSession -> DatabaseContext -> DatabaseContextIOExpr -> IO (Either RelationalError DatabaseContext)
493evalDatabaseContextIOExpr mScriptSession currentContext (AddAtomFunction funcName funcType script) =
494 case mScriptSession of
495 Nothing -> pure (Left (ScriptError ScriptCompilationDisabledError))
496 Just scriptSession -> do
497 res <- try $ runGhc (Just libdir) $ do
498 setSession (hscEnv scriptSession)
499 let atomFuncs = atomFunctions currentContext
500 case extractAtomFunctionType funcType of
501 Left err -> pure (Left err)
502 Right adjustedAtomTypeCons -> do
503 --compile the function
504 eCompiledFunc <- compileScript (atomFunctionBodyType scriptSession) script
505 pure $ case eCompiledFunc of
506 Left err -> Left (ScriptError err)
507 Right compiledFunc -> do
508 funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext) M.empty) adjustedAtomTypeCons
509 let updatedFuncs = HS.insert newAtomFunc atomFuncs
510 newContext = currentContext { atomFunctions = updatedFuncs }
511 newAtomFunc = AtomFunction { atomFuncName = funcName,
512 atomFuncType = funcAtomType,
513 atomFuncBody = AtomFunctionBody (Just script) compiledFunc }
514 -- check if the name is already in use
515 if HS.member funcName (HS.map atomFuncName atomFuncs) then
516 Left (FunctionNameInUseError funcName)
520 Left (exc :: SomeException) -> pure $ Left (ScriptError (OtherScriptCompilationError (show exc)))
521 Right eContext -> case eContext of
522 Left err -> pure (Left err)
523 Right context' -> pure (Right context')
525evalDatabaseContextIOExpr mScriptSession currentContext (AddDatabaseContextFunction funcName funcType script) =
526 case mScriptSession of
527 Nothing -> pure (Left (ScriptError ScriptCompilationDisabledError))
528 Just scriptSession -> do
529 --validate that the function signature is of the form x -> y -> ... -> DatabaseContext -> DatabaseContext
530 let last2Args = reverse (take 2 (reverse funcType))
531 atomArgs = take (length funcType - 2) funcType
532 dbContextTypeCons = ADTypeConstructor "Either" [ADTypeConstructor "DatabaseContextFunctionError" [], ADTypeConstructor "DatabaseContext" []]
533 expectedType = "DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext"
534 actualType = show funcType
535 if last2Args /= [ADTypeConstructor "DatabaseContext" [], dbContextTypeCons] then
536 pure (Left (ScriptError (TypeCheckCompilationError expectedType actualType)))
538 res <- try $ runGhc (Just libdir) $ do
539 setSession (hscEnv scriptSession)
540 eCompiledFunc <- compileScript (dbcFunctionBodyType scriptSession) script
541 pure $ case eCompiledFunc of
542 Left err -> Left (ScriptError err)
543 Right compiledFunc -> do
544 --if we are here, we have validated that the written function type is X -> DatabaseContext -> DatabaseContext, so we need to munge the first elements into an array
545 funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext) M.empty) atomArgs
546 let updatedDBCFuncs = HS.insert newDBCFunc (dbcFunctions currentContext)
547 newContext = currentContext { dbcFunctions = updatedDBCFuncs }
548 dbcFuncs = dbcFunctions currentContext
549 newDBCFunc = DatabaseContextFunction {
550 dbcFuncName = funcName,
551 dbcFuncType = funcAtomType,
552 dbcFuncBody = DatabaseContextFunctionBody (Just script) compiledFunc
554 -- check if the name is already in use
555 if HS.member funcName (HS.map dbcFuncName dbcFuncs) then
556 Left (FunctionNameInUseError funcName)
560 Left (exc :: SomeException) -> pure $ Left (ScriptError (OtherScriptCompilationError (show exc)))
561 Right eContext -> case eContext of
562 Left err -> pure (Left err)
563 Right context' -> pure (Right context')
564evalDatabaseContextIOExpr _ currentContext (LoadAtomFunctions modName funcName modPath) = do
565 eLoadFunc <- loadAtomFunctions (T.unpack modName) (T.unpack funcName) modPath
567 Left LoadSymbolError -> pure (Left LoadFunctionError)
568 Right atomFunctionListFunc -> let newContext = currentContext { atomFunctions = mergedFuncs }
569 mergedFuncs = HS.union (atomFunctions currentContext) (HS.fromList atomFunctionListFunc)
570 in pure (Right newContext)
571evalDatabaseContextIOExpr _ currentContext (LoadDatabaseContextFunctions modName funcName modPath) = do
572 eLoadFunc <- loadDatabaseContextFunctions (T.unpack modName) (T.unpack funcName) modPath
574 Left LoadSymbolError -> pure (Left LoadFunctionError)
575 Right dbcListFunc -> let newContext = currentContext { dbcFunctions = mergedFuncs }
576 mergedFuncs = HS.union (dbcFunctions currentContext) (HS.fromList dbcListFunc)
577 in pure (Right newContext)
579updateTupleWithAtomExprs :: M.Map AttributeName AtomExpr -> DatabaseContext -> RelationTuple -> Either RelationalError RelationTuple
580updateTupleWithAtomExprs exprMap context tupIn = do
581 --resolve all atom exprs
582 atomsAssoc <- mapM (\(attrName, atomExpr) -> do
583 atom <- runReader (evalAtomExpr tupIn atomExpr) (RelationalExprStateElems context)
584 pure (attrName, atom)
586 pure (updateTupleWithAtoms (M.fromList atomsAssoc) tupIn)
588--run verification on all constraints
589checkConstraints :: DatabaseContext -> Either RelationalError ()
590checkConstraints context = do
591 mapM_ (uncurry checkIncDep) (M.toList deps)
594 deps = inclusionDependencies context
595 eval expr = evalReader (evalRelationalExpr expr)
596 evalReader f = runReader f (RelationalExprStateElems context)
597 checkIncDep depName (InclusionDependency subsetExpr supersetExpr) = do
598 --if both expressions are of a single-attribute (such as with a simple foreign key), the names of the attributes are irrelevant (they need not match) because the expression is unambiguous, but special-casing this to rename the attribute automatically would not be orthogonal behavior and probably cause confusion. Instead, special case the error to make it clear.
599 typeSub <- evalReader (typeForRelationalExpr subsetExpr)
600 typeSuper <- evalReader (typeForRelationalExpr supersetExpr)
601 when (typeSub /= typeSuper) (Left (RelationTypeMismatchError (attributes typeSub) (attributes typeSuper)))
602 let checkExpr = Equals supersetExpr (Union subsetExpr supersetExpr)
603 case eval checkExpr of
605 Right resultRel -> if resultRel == relationTrue then
608 Left (InclusionDependencyCheckError depName)
610-- the type of a relational expression is equal to the relation attribute set returned from executing the relational expression; therefore, the type can be cheaply derived by evaluating a relational expression and ignoring and tuple processing
611-- furthermore, the type of a relational expression is the resultant header of the evaluated empty-tupled relation
613typeForRelationalExpr :: RelationalExpr -> RelationalExprState (Either RelationalError Relation)
614typeForRelationalExpr expr = do
616 let context = stateElemsContext rstate
617 --replace the relationVariables context element with a cloned set of relation devoid of tuples
618 let context' = contextWithEmptyTupleSets context
619 rstate' = setStateElemsContext rstate context'
620 pure (runReader (evalRelationalExpr expr) rstate')
622--returns a database context with all tuples removed
623--this is useful for type checking and optimization
624contextWithEmptyTupleSets :: DatabaseContext -> DatabaseContext
625contextWithEmptyTupleSets contextIn = contextIn { relationVariables = relVars }
627 relVars = M.map (\rel -> Relation (attributes rel) emptyTupleSet) (relationVariables contextIn)
629liftE :: (Monad m) => m (Either a b) -> ExceptT a m b
633 Left err -> throwE err
634 Right val -> pure val
636{- used for restrictions- take the restrictionpredicate and return the corresponding filter function -}
637predicateRestrictionFilter :: Attributes -> RestrictionPredicateExpr -> RelationalExprState (Either RelationalError RestrictionFilter)
638predicateRestrictionFilter attrs (AndPredicate expr1 expr2) =
640 expr1v <- liftE (predicateRestrictionFilter attrs expr1)
641 expr2v <- liftE (predicateRestrictionFilter attrs expr2)
647predicateRestrictionFilter attrs (OrPredicate expr1 expr2) =
649 expr1v <- liftE (predicateRestrictionFilter attrs expr1)
650 expr2v <- liftE (predicateRestrictionFilter attrs expr2)
656predicateRestrictionFilter _ TruePredicate = pure (Right (\_ -> pure True))
658predicateRestrictionFilter attrs (NotPredicate expr) =
660 exprv <- liftE (predicateRestrictionFilter attrs expr)
661 pure (fmap not . exprv)
663--optimization opportunity: if the subexpression does not reference attributes in the top-level expression, then it need only be evaluated once, statically, outside the tuple filter- see historical implementation here
664predicateRestrictionFilter _ (RelationalExprPredicate relExpr) = do
666 pure (Right (\tup -> case runReader (evalRelationalExpr relExpr) (mergeTuplesIntoRelationalExprState tup rstate) of
668 Right rel -> if arity rel /= 0 then
669 Left (PredicateExpressionError "Relational restriction filter must evaluate to 'true' or 'false'")
671 pure (rel == relationTrue)))
673predicateRestrictionFilter attrs (AttributeEqualityPredicate attrName atomExpr) = do
675 let (attrs', ctxtup') = case rstate of
676 RelationalExprStateElems _ -> (attrs, emptyTuple)
677 RelationalExprStateAttrsElems _ _ -> (attrs, emptyTuple)
678 RelationalExprStateTupleElems _ ctxtup -> (A.union attrs (tupleAttributes ctxtup), ctxtup)
680 atomExprType <- liftE (typeFromAtomExpr attrs' atomExpr)
681 attr <- either throwE pure $ case A.attributeForName attrName attrs of
682 Right attr -> Right attr
683 Left (NoSuchAttributeNamesError _) -> case A.attributeForName attrName (tupleAttributes ctxtup') of
684 Right ctxattr -> Right ctxattr
685 Left err2@(NoSuchAttributeNamesError _) -> Left err2
688 if atomExprType /= A.atomType attr then
689 throwE (TupleAttributeTypeMismatchError (A.attributesFromList [attr]))
691 pure $ \tupleIn -> let evalAndCmp atomIn = case atomEvald of
692 Right atomCmp -> atomCmp == atomIn
694 atomEvald = runReader (evalAtomExpr tupleIn atomExpr) rstate
696 pure $ case atomForAttributeName attrName tupleIn of
697 Left (NoSuchAttributeNamesError _) -> case atomForAttributeName attrName ctxtup' of
699 Right ctxatom -> evalAndCmp ctxatom
701 Right atomIn -> evalAndCmp atomIn
702-- in the future, it would be useful to do typechecking on the attribute and atom expr filters in advance
703predicateRestrictionFilter attrs (AtomExprPredicate atomExpr) = do
704 --merge attrs into the state attributes
707 aType <- liftE (typeFromAtomExpr attrs atomExpr)
708 if aType /= BoolAtomType then
709 throwE (AtomTypeMismatchError aType BoolAtomType)
712 pure $ case runReader (evalAtomExpr tupleIn atomExpr) rstate of
714 Right boolAtomValue -> boolAtomValue == BoolAtom True)
716tupleExprCheckNewAttrName :: AttributeName -> Relation -> Either RelationalError Relation
717tupleExprCheckNewAttrName attrName rel = if isRight (attributeForName attrName rel) then
718 Left (error "SPAMMIT" $ AttributeNameInUseError attrName)
722extendTupleExpressionProcessor :: Relation -> ExtendTupleExpr -> RelationalExprState (Either RelationalError (Attributes, RelationTuple -> Either RelationalError RelationTuple))
723extendTupleExpressionProcessor relIn (AttributeExtendTupleExpr newAttrName atomExpr) = do
725 -- check that the attribute name is not in use
726 case tupleExprCheckNewAttrName newAttrName relIn of
727 Left err -> pure (Left err)
728 Right _ -> runExceptT $ do
729 atomExprType <- liftE (typeFromAtomExpr (attributes relIn) atomExpr)
730 atomExprType' <- liftE (verifyAtomExprTypes relIn atomExpr atomExprType)
731 let newAttrs = A.attributesFromList [Attribute newAttrName atomExprType']
732 newAndOldAttrs = A.addAttributes (attributes relIn) newAttrs
733 pure (newAndOldAttrs, \tup -> let substate = mergeTuplesIntoRelationalExprState tup rstate in case runReader (evalAtomExpr tup atomExpr) substate of
735 Right atom -> Right (tupleAtomExtend newAttrName atom tup)
738evalAtomExpr :: RelationTuple -> AtomExpr -> RelationalExprState (Either RelationalError Atom)
739evalAtomExpr tupIn (AttributeAtomExpr attrName) = case atomForAttributeName attrName tupIn of
740 Right atom -> pure (Right atom)
741 err@(Left (NoSuchAttributeNamesError _)) -> do
744 RelationalExprStateElems _ -> pure err
745 RelationalExprStateAttrsElems _ _ -> pure err
746 RelationalExprStateTupleElems _ ctxtup -> pure (atomForAttributeName attrName ctxtup)
747 Left err -> pure (Left err)
748evalAtomExpr _ (NakedAtomExpr atom) = pure (Right atom)
749evalAtomExpr tupIn (FunctionAtomExpr funcName arguments ()) = do
750 argTypes <- mapM (typeFromAtomExpr (tupleAttributes tupIn)) arguments
751 context <- fmap stateElemsContext ask
753 let functions = atomFunctions context
754 func <- either throwE pure (atomFunctionForName funcName functions)
755 let expectedArgCount = length (atomFuncType func) - 1
756 actualArgCount = length argTypes
758 safeInit [] = [] -- different behavior from normal init
759 safeInit (_:xs) = safeInit xs
760 if expectedArgCount /= actualArgCount then
761 throwE (FunctionArgumentCountMismatchError expectedArgCount actualArgCount)
763 mapM_ (\(expType, actType) -> either throwE pure (atomTypeVerify expType actType)) (safeInit (zip (atomFuncType func) argTypes))
764 evaldArgs <- mapM (liftE . evalAtomExpr tupIn) arguments
765 case evalAtomFunction func evaldArgs of
766 Left err -> throwE (AtomFunctionUserError err)
768 --validate that the result matches the expected type
769 _ <- either throwE pure (atomTypeVerify (last (atomFuncType func)) (atomTypeForAtom result))
771evalAtomExpr tupIn (RelationAtomExpr relExpr) = do
772 --merge existing state tuple context into new state tuple context to support an arbitrary number of levels, but new attributes trounce old attributes
775 let newState = mergeTuplesIntoRelationalExprState tupIn rstate
776 relAtom <- either throwE pure (runReader (evalRelationalExpr relExpr) newState)
777 pure (RelationAtom relAtom)
778evalAtomExpr tupIn cons@(ConstructedAtomExpr dConsName dConsArgs ()) = runExceptT $ do
780 let newState = mergeTuplesIntoRelationalExprState tupIn rstate
781 aType <- either throwE pure (runReader (typeFromAtomExpr (tupleAttributes tupIn) cons) newState)
782 argAtoms <- mapM (\arg -> either throwE pure (runReader (evalAtomExpr tupIn arg) newState)) dConsArgs
783 pure (ConstructedAtom dConsName aType argAtoms)
785typeFromAtomExpr :: Attributes -> AtomExpr -> RelationalExprState (Either RelationalError AtomType)
786typeFromAtomExpr attrs (AttributeAtomExpr attrName) = do
788 case A.atomTypeForAttributeName attrName attrs of
789 Right aType -> pure (Right aType)
790 Left err@(NoSuchAttributeNamesError _) -> case rstate of
791 RelationalExprStateAttrsElems _ attrs' -> case A.attributeForName attrName attrs' of
792 Left err' -> pure (Left err')
793 Right attr -> pure (Right (A.atomType attr))
794 RelationalExprStateElems _ -> pure (Left err)
795 RelationalExprStateTupleElems _ tup -> case atomForAttributeName attrName tup of
796 Left err' -> pure (Left err')
797 Right atom -> pure (Right (atomTypeForAtom atom))
798 Left err -> pure (Left err)
799typeFromAtomExpr _ (NakedAtomExpr atom) = pure (Right (atomTypeForAtom atom))
800typeFromAtomExpr attrs (FunctionAtomExpr funcName atomArgs _) = do
801 context <- fmap stateElemsContext ask
802 let funcs = atomFunctions context
803 case atomFunctionForName funcName funcs of
804 Left err -> pure (Left err)
806 let funcRetType = last (atomFuncType func)
807 funcArgTypes = init (atomFuncType func)
808 eArgTypes <- mapM (typeFromAtomExpr attrs) atomArgs
809 case lefts eArgTypes of
810 errs@(_:_) -> pure (Left (someErrors errs))
812 let eTvMap = resolveTypeVariables funcArgTypes argTypes
813 argTypes = rights eArgTypes
815 Left err -> pure (Left err)
816 Right tvMap -> pure (resolveFunctionReturnValue funcName tvMap funcRetType)
817typeFromAtomExpr attrs (RelationAtomExpr relExpr) = runExceptT $ do
819 relType <- either throwE pure (runReader (typeForRelationalExpr relExpr) (mergeAttributesIntoRelationalExprState attrs rstate))
820 pure (RelationAtomType (attributes relType))
821-- grab the type of the data constructor, then validate that the args match the expected types
822typeFromAtomExpr attrs (ConstructedAtomExpr dConsName dConsArgs _) =
824 argsTypes <- mapM (liftE . typeFromAtomExpr attrs) dConsArgs
825 context <- fmap stateElemsContext (lift ask)
826 either throwE pure (atomTypeForDataConstructor (typeConstructorMapping context) dConsName argsTypes)
828-- | Validate that the type of the AtomExpr matches the expected type.
829verifyAtomExprTypes :: Relation -> AtomExpr -> AtomType -> RelationalExprState (Either RelationalError AtomType)
830verifyAtomExprTypes relIn (AttributeAtomExpr attrName) expectedType = runExceptT $ do
832 case A.atomTypeForAttributeName attrName (attributes relIn) of
833 Right aType -> either throwE pure (atomTypeVerify expectedType aType)
834 (Left err@(NoSuchAttributeNamesError _)) -> case rstate of
835 RelationalExprStateTupleElems _ _ -> throwE err
836 RelationalExprStateElems _ -> throwE err
837 RelationalExprStateAttrsElems _ attrs -> case A.attributeForName attrName attrs of
838 Left err' -> throwE err'
839 Right attrType -> either throwE pure (atomTypeVerify expectedType (A.atomType attrType))
840 Left err -> throwE err
841verifyAtomExprTypes _ (NakedAtomExpr atom) expectedType = pure (atomTypeVerify expectedType (atomTypeForAtom atom))
842verifyAtomExprTypes relIn (FunctionAtomExpr funcName funcArgExprs _) expectedType = do
844 let functions = atomFunctions context
845 context = stateElemsContext rstate
847 func <- either throwE pure (atomFunctionForName funcName functions)
848 let expectedArgTypes = atomFuncType func
849 funcArgTypes <- mapM (\(atomExpr,expectedType2,argCount) -> case runReader (verifyAtomExprTypes relIn atomExpr expectedType2) rstate of
850 Left (AtomTypeMismatchError expSubType actSubType) -> throwE (AtomFunctionTypeError funcName argCount expSubType actSubType)
851 Left err -> throwE err
853 ) $ zip3 funcArgExprs expectedArgTypes [1..]
854 if length funcArgTypes /= length expectedArgTypes - 1 then
855 throwE (AtomTypeCountError funcArgTypes expectedArgTypes)
857 either throwE pure (atomTypeVerify expectedType (last expectedArgTypes))
858verifyAtomExprTypes relIn (RelationAtomExpr relationExpr) expectedType = runExceptT $ do
860 relType <- either throwE pure (runReader (typeForRelationalExpr relationExpr) (mergeAttributesIntoRelationalExprState (attributes relIn) rstate))
861 either throwE pure (atomTypeVerify expectedType (RelationAtomType (attributes relType)))
862verifyAtomExprTypes rel cons@ConstructedAtomExpr{} expectedType = runExceptT $ do
863 cType <- liftE (typeFromAtomExpr (attributes rel) cons)
864 either throwE pure (atomTypeVerify expectedType cType)
866-- | Look up the type's name and create a new attribute.
867evalAttrExpr :: TypeConstructorMapping -> AttributeExpr -> Either RelationalError Attribute
868evalAttrExpr aTypes (AttributeAndTypeNameExpr attrName tCons ()) = do
869 aType <- atomTypeForTypeConstructor tCons aTypes M.empty
870 Right (Attribute attrName aType)
872evalAttrExpr _ (NakedAttributeExpr attr) = Right attr
874evalTupleExpr :: Maybe Attributes -> TupleExpr -> RelationalExprState (Either RelationalError RelationTuple)
875evalTupleExpr attrs (TupleExpr tupMap) = do
876 context <- fmap stateElemsContext ask
878 -- it's not possible for AtomExprs in tuple constructors to reference other Attributes' atoms due to the necessary order-of-operations (need a tuple to pass to evalAtomExpr)- it may be possible with some refactoring of type usage or delayed evaluation- needs more thought, but not a priority
879 -- I could adjust this logic so that when the attributes are not specified (Nothing), then I can attempt to extract the attributes from the tuple- the type resolution will blow up if an ambiguous data constructor is used (Left 4) and this should allow simple cases to "relation{tuple{a 4}}" to be processed
880 attrAtoms <- mapM (\(attrName, aExpr) -> do
881 newAtomType <- liftE (typeFromAtomExpr A.emptyAttributes aExpr)
882 newAtom <- liftE (evalAtomExpr emptyTuple aExpr)
883 pure (attrName, newAtom, newAtomType)
885 let tupAttrs = A.attributesFromList $ map (\(attrName, _, aType) -> Attribute attrName aType) attrAtoms
886 atoms = V.fromList $ map (\(_, atom, _) -> atom) attrAtoms
887 tup = mkRelationTuple tupAttrs atoms
888 tConss = typeConstructorMapping context
889 finalAttrs = fromMaybe tupAttrs attrs
890 --verify that the attributes match
891 when (A.attributeNameSet finalAttrs /= A.attributeNameSet tupAttrs) $ throwE (TupleAttributeTypeMismatchError tupAttrs)
892 tup' <- either throwE pure (resolveTypesInTuple finalAttrs (reorderTuple finalAttrs tup))
893 _ <- either throwE pure (validateTuple tup' tConss)
896evalAttributeNames :: AttributeNames -> RelationalExpr -> RelationalExprState (Either RelationalError (S.Set AttributeName))
897evalAttributeNames attrNames expr = do
898 eExprType <- typeForRelationalExpr expr
900 Left err -> pure (Left err)
902 let typeNameSet = S.fromList (V.toList (A.attributeNames (attributes exprTyp)))
904 AttributeNames names ->
905 case A.projectionAttributesForNames names (attributes exprTyp) of
906 Left err -> pure (Left err)
907 Right attrs -> pure (Right (S.fromList (V.toList (A.attributeNames attrs))))
909 InvertedAttributeNames names -> do
910 let nonExistentAttributeNames = A.attributeNamesNotContained names typeNameSet
911 if not (S.null nonExistentAttributeNames) then
912 pure (Left (AttributeNamesMismatchError nonExistentAttributeNames))
914 pure (Right (A.nonMatchingAttributeNameSet names typeNameSet))
916 UnionAttributeNames namesA namesB -> do
917 eNameSetA <- evalAttributeNames namesA expr
919 Left err -> pure (Left err)
921 eNameSetB <- evalAttributeNames namesB expr
923 Left err -> pure (Left err)
925 pure (Right (S.union nameSetA nameSetB))
927 IntersectAttributeNames namesA namesB -> do
928 eNameSetA <- evalAttributeNames namesA expr
930 Left err -> pure (Left err)
932 eNameSetB <- evalAttributeNames namesB expr
934 Left err -> pure (Left err)
936 pure (Right (S.intersection nameSetA nameSetB))
938 RelationalExprAttributeNames attrExpr -> do
939 eAttrExprType <- typeForRelationalExpr attrExpr
940 case eAttrExprType of
941 Left err -> pure (Left err)
942 Right attrExprType -> pure (Right (A.attributeNameSet (attributes attrExprType)))
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