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 Control.Monad.State hiding (join)
18import Control.Exception
21import Data.Char (isUpper)
22import qualified Data.Text as T
23import qualified Data.Vector as V
24import qualified ProjectM36.TypeConstructorDef as TCD
25import Control.Monad.Trans.Except
26import Control.Monad.Trans.Reader
31data DatabaseContextExprDetails = CountUpdatedTuples
33databaseContextExprDetailsFunc :: DatabaseContextExprDetails -> ResultAccumFunc
34databaseContextExprDetailsFunc CountUpdatedTuples _ relIn = Relation attrs newTups
36 attrs = A.attributesFromList [Attribute "count" IntAtomType]
37 existingTuple = fromMaybe (error "impossible counting error in singletonTuple") (singletonTuple relIn)
38 existingCount = case V.head (tupleAtoms existingTuple) of
40 _ -> error "impossible counting error in tupleAtoms"
41 newTups = case mkTupleSetFromList attrs [[IntAtom (existingCount + 1)]] of
42 Left err -> error ("impossible counting error in " ++ show err)
45-- | Used to start a fresh database state for a new database context expression.
46freshDatabaseState :: DatabaseContext -> DatabaseStateElems
47freshDatabaseState ctx = (ctx, M.empty, False) --future work: propagate return accumulator
49-- we need to pass around a higher level RelationTuple and Attributes in order to solve #52
50data RelationalExprStateElems = RelationalExprStateTupleElems DatabaseContext RelationTuple | -- used when fully evaluating a relexpr
51 RelationalExprStateAttrsElems DatabaseContext Attributes | --used when evaluating the type of a relexpr
52 RelationalExprStateElems DatabaseContext --used by default at the top level of evaluation
54instance Show RelationalExprStateElems where
55 show (RelationalExprStateTupleElems _ tup) = "RelationalExprStateTupleElems " ++ show tup
56 show (RelationalExprStateAttrsElems _ attrs) = "RelationalExprStateAttrsElems" ++ show attrs
57 show (RelationalExprStateElems _) = "RelationalExprStateElems"
59mkRelationalExprState :: DatabaseContext -> RelationalExprStateElems
60mkRelationalExprState = RelationalExprStateElems
62mergeTuplesIntoRelationalExprState :: RelationTuple -> RelationalExprStateElems -> RelationalExprStateElems
63mergeTuplesIntoRelationalExprState tupIn (RelationalExprStateElems ctx) = RelationalExprStateTupleElems ctx tupIn
64mergeTuplesIntoRelationalExprState _ st@(RelationalExprStateAttrsElems _ _) = st
65mergeTuplesIntoRelationalExprState tupIn (RelationalExprStateTupleElems ctx existingTuple) = let mergedTupMap = M.union (tupleToMap tupIn) (tupleToMap existingTuple) in
66 RelationalExprStateTupleElems ctx (mkRelationTupleFromMap mergedTupMap)
68mergeAttributesIntoRelationalExprState :: Attributes -> RelationalExprStateElems -> RelationalExprStateElems
69mergeAttributesIntoRelationalExprState attrs (RelationalExprStateElems ctx) = RelationalExprStateAttrsElems ctx attrs
70mergeAttributesIntoRelationalExprState _ st@(RelationalExprStateTupleElems _ _) = st
71mergeAttributesIntoRelationalExprState attrsIn (RelationalExprStateAttrsElems ctx attrs) = RelationalExprStateAttrsElems ctx (A.union attrsIn attrs)
73type ResultAccumName = StringType
75type ResultAccumFunc = (RelationTuple -> Relation -> Relation) -> Relation -> Relation
77data ResultAccum = ResultAccum { resultAccumFunc :: ResultAccumFunc,
78 resultAccumResult :: Relation
81type DatabaseStateElems = (DatabaseContext, M.Map ResultAccumName ResultAccum, DirtyFlag)
83type DatabaseState a = State DatabaseStateElems a
85getStateContext :: DatabaseState DatabaseContext
90putStateContext :: DatabaseContext -> DatabaseState ()
91putStateContext ctx = do
93 put (ctx, accum, True)
95type RelationalExprState a = Reader RelationalExprStateElems a
97stateElemsContext :: RelationalExprStateElems -> DatabaseContext
98stateElemsContext (RelationalExprStateTupleElems ctx _) = ctx
99stateElemsContext (RelationalExprStateElems ctx) = ctx
100stateElemsContext (RelationalExprStateAttrsElems ctx _) = ctx
102setStateElemsContext :: RelationalExprStateElems -> DatabaseContext -> RelationalExprStateElems
103setStateElemsContext (RelationalExprStateTupleElems _ tup) ctx = RelationalExprStateTupleElems ctx tup
104setStateElemsContext (RelationalExprStateElems _) ctx = RelationalExprStateElems ctx
105setStateElemsContext (RelationalExprStateAttrsElems _ attrs) ctx = RelationalExprStateAttrsElems ctx attrs
107--relvar state is needed in evaluation of relational expression but only as read-only in order to extract current relvar values
108evalRelationalExpr :: RelationalExpr -> RelationalExprState (Either RelationalError Relation)
109evalRelationalExpr (RelationVariable name _) = do
110 relvarTable <- fmap (relationVariables . stateElemsContext) ask
111 return $ case M.lookup name relvarTable of
112 Just res -> Right res
113 Nothing -> Left $ RelVarNotDefinedError name
115evalRelationalExpr (Project attrNames expr) = do
116 rel <- evalRelationalExpr expr
118 Right rel2 -> return $ project attrNames rel2
119 Left err -> return $ Left err
121evalRelationalExpr (Union exprA exprB) = do
122 relA <- evalRelationalExpr exprA
123 relB <- evalRelationalExpr exprB
125 Left err -> return $ Left err
126 Right relA2 -> case relB of
127 Left err -> return $ Left err
128 Right relB2 -> return $ union relA2 relB2
130evalRelationalExpr (Join exprA exprB) = do
131 relA <- evalRelationalExpr exprA
132 relB <- evalRelationalExpr exprB
134 Left err -> return $ Left err
135 Right relA2 -> case relB of
136 Left err -> return $ Left err
137 Right relB2 -> return $ join relA2 relB2
139evalRelationalExpr (Difference exprA exprB) = do
140 relA <- evalRelationalExpr exprA
141 relB <- evalRelationalExpr exprB
143 Left err -> return $ Left err
144 Right relA2 -> case relB of
145 Left err -> return $ Left err
146 Right relB2 -> return $ difference relA2 relB2
148evalRelationalExpr (MakeStaticRelation attributeSet tupleSet) =
149 case mkRelation attributeSet tupleSet of
150 Right rel -> return $ Right rel
151 Left err -> return $ Left err
153evalRelationalExpr (MakeRelationFromExprs mAttrExprs tupleExprs) = do
154 currentContext <- fmap stateElemsContext ask
155 let tConss = typeConstructorMapping currentContext
156 -- 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
158 mAttrs <- case mAttrExprs of
160 attrs <- mapM (either throwE pure . evalAttrExpr tConss) (fromMaybe [] mAttrExprs)
161 pure (Just (A.attributesFromList attrs))
162 Nothing -> pure Nothing
163 tuples <- mapM (liftE . evalTupleExpr mAttrs) tupleExprs
164 let attrs = fromMaybe firstTupleAttrs mAttrs
165 firstTupleAttrs = if null tuples then A.emptyAttributes else tupleAttributes (head tuples)
166 either throwE pure (mkRelation attrs (RelationTupleSet tuples))
168evalRelationalExpr (ExistingRelation rel) = pure (Right rel)
170evalRelationalExpr (Rename oldAttrName newAttrName relExpr) = do
171 evald <- evalRelationalExpr relExpr
173 Right rel -> return $ rename oldAttrName newAttrName rel
174 Left err -> return $ Left err
176evalRelationalExpr (Group oldAttrNameSet newAttrName relExpr) = do
177 evald <- evalRelationalExpr relExpr
179 Right rel -> return $ group oldAttrNameSet newAttrName rel
180 Left err -> return $ Left err
182evalRelationalExpr (Ungroup attrName relExpr) = do
183 evald <- evalRelationalExpr relExpr
185 Right rel -> return $ ungroup attrName rel
186 Left err -> return $ Left err
188evalRelationalExpr (Restrict predicateExpr relExpr) = do
189 evald <- evalRelationalExpr relExpr
191 Left err -> return $ Left err
193 eFilterFunc <- predicateRestrictionFilter (attributes rel) predicateExpr
195 Left err -> return $ Left err
197 pure (restrict filterfunc rel)
199evalRelationalExpr (Equals relExprA relExprB) = do
200 evaldA <- evalRelationalExpr relExprA
201 evaldB <- evalRelationalExpr relExprB
203 Left err -> return $ Left err
204 Right relA -> case evaldB of
205 Left err -> return $ Left err
206 Right relB -> return $ Right $ if relA == relB then relationTrue else relationFalse
208--warning: copy-pasta from above- refactor
209evalRelationalExpr (NotEquals relExprA relExprB) = do
210 evaldA <- evalRelationalExpr relExprA
211 evaldB <- evalRelationalExpr relExprB
213 Left err -> return $ Left err
214 Right relA -> case evaldB of
215 Left err -> return $ Left err
216 Right relB -> return $ Right $ if relA /= relB then relationTrue else relationFalse
218-- extending a relation adds a single attribute with the results of the per-tuple expression evaluated
219evalRelationalExpr (Extend tupleExpression relExpr) = do
221 let evald = runReader (evalRelationalExpr relExpr) rstate
223 Left err -> pure (Left err)
225 tupProc <- extendTupleExpressionProcessor rel tupleExpression
227 Left err -> pure (Left err)
228 Right (newAttrs, tupProc') -> pure $ relMogrify tupProc' newAttrs rel
230--helper function to process relation variable creation/assignment
231setRelVar :: RelVarName -> Relation -> DatabaseState (Maybe RelationalError)
232setRelVar relVarName rel = do
233 currentContext <- getStateContext
234 let newRelVars = M.insert relVarName rel $ relationVariables currentContext
235 potentialContext = currentContext { relationVariables = newRelVars }
237 case checkConstraints potentialContext of
238 Just err -> return $ Just err
240 putStateContext potentialContext
243-- 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
244deleteRelVar :: RelVarName -> DatabaseState (Maybe RelationalError)
245deleteRelVar relVarName = do
246 currContext <- getStateContext
247 let relVars = relationVariables currContext
248 if M.notMember relVarName relVars then
251 let newRelVars = M.delete relVarName relVars
252 newContext = currContext { relationVariables = newRelVars }
253 putStateContext newContext
256evalDatabaseContextExpr :: DatabaseContextExpr -> DatabaseState (Maybe RelationalError)
257evalDatabaseContextExpr NoOperation = pure Nothing
259evalDatabaseContextExpr (Define relVarName attrExprs) = do
260 relvars <- fmap relationVariables getStateContext
261 tConss <- fmap typeConstructorMapping getStateContext
262 let eAttrs = map (evalAttrExpr tConss) attrExprs
264 err:_ -> pure (Just err)
265 [] -> case M.member relVarName relvars of
266 True -> return (Just (RelVarAlreadyDefinedError relVarName))
267 False -> setRelVar relVarName emptyRelation >> pure Nothing
269 attrs = A.attributesFromList (rights eAttrs)
270 emptyRelation = Relation attrs emptyTupleSet
272evalDatabaseContextExpr (Undefine relVarName) = deleteRelVar relVarName
274evalDatabaseContextExpr (Assign relVarName expr) = do
275 -- in the future, it would be nice to get types from the RelationalExpr instead of needing to evaluate it
276 context <- getStateContext
277 let existingRelVar = M.lookup relVarName relVarTable
278 relVarTable = relationVariables context
279 value = runReader (evalRelationalExpr expr) (RelationalExprStateElems context)
281 Left err -> return $ Just err
282 Right rel -> case existingRelVar of
283 Nothing -> setRelVar relVarName rel
284 Just existingRel -> let expectedAttributes = attributes existingRel
285 foundAttributes = attributes rel in
286 if A.attributesEqual expectedAttributes foundAttributes then
287 setRelVar relVarName rel
289 return $ Just (RelVarAssignmentTypeMismatchError expectedAttributes foundAttributes)
291evalDatabaseContextExpr (Insert relVarName relExpr) = do
292 context <- getStateContext
293 let unionexp = Union relExpr rv
294 rv = RelationVariable relVarName ()
295 unioned = runReader (evalRelationalExpr unionexp) (RelationalExprStateElems context)
296 origRel = runReader (evalRelationalExpr rv) (RelationalExprStateElems context)
298 Left err -> pure (Just err)
299 Right unioned' -> case origRel of
300 Left err -> pure (Just err)
301 Right origRel' -> if cardinality unioned' == cardinality origRel' then --no tuples actually inserted
304 evalDatabaseContextExpr $ Assign relVarName (ExistingRelation unioned')
306evalDatabaseContextExpr (Delete relVarName predicate) = do
307 context <- getStateContext
308 let rv = RelationVariable relVarName ()
309 let updatedRel = runReader (evalRelationalExpr (Restrict (NotPredicate predicate) rv)) (RelationalExprStateElems context)
310 origRel = runReader (evalRelationalExpr rv) (RelationalExprStateElems context)
312 Left err -> pure (Just err)
313 Right updatedRel' -> case origRel of
314 Left err -> pure (Just err)
315 Right origRel' -> if cardinality origRel' == cardinality updatedRel' then
318 setRelVar relVarName updatedRel'
320--union of restricted+updated portion and the unrestricted+unupdated portion
321evalDatabaseContextExpr (Update relVarName atomExprMap restrictionPredicateExpr) = do
322 context <- getStateContext
323 let relVarTable = relationVariables context
324 case M.lookup relVarName relVarTable of
325 Nothing -> return $ Just (RelVarNotDefinedError relVarName)
327 case runReader (predicateRestrictionFilter (attributes rel) restrictionPredicateExpr) (RelationalExprStateElems context) of
328 Left err -> return $ Just err
329 Right predicateFunc -> do
331 restrictedPortion <- restrict predicateFunc rel
332 if cardinality restrictedPortion == Finite 0 then
335 unrestrictedPortion <- restrict (predicateFunc >=> (pure . not)) rel
336 updatedPortion <- relMap (updateTupleWithAtomExprs atomExprMap context) restrictedPortion
337 updatedRel <- updatedPortion `union` unrestrictedPortion
338 pure (Just updatedRel)
340 Left err -> pure (Just err)
341 Right Nothing -> pure Nothing
342 Right (Just updatedRel) -> setRelVar relVarName updatedRel
344evalDatabaseContextExpr (AddInclusionDependency newDepName newDep) = do
345 currContext <- getStateContext
346 let currDeps = inclusionDependencies currContext
347 newDeps = M.insert newDepName newDep currDeps
348 if M.member newDepName currDeps then
349 return $ Just (InclusionDependencyNameInUseError newDepName)
351 let potentialContext = currContext { inclusionDependencies = newDeps }
352 case checkConstraints potentialContext of
353 Just err -> return $ Just err
355 putStateContext potentialContext
358evalDatabaseContextExpr (RemoveInclusionDependency depName) = do
359 currContext <- getStateContext
360 let currDeps = inclusionDependencies currContext
361 newDeps = M.delete depName currDeps
362 if M.notMember depName currDeps then
363 return $ Just (InclusionDependencyNameNotInUseError depName)
365 putStateContext $ currContext {inclusionDependencies = newDeps }
368-- | Add a notification which will send the resultExpr when triggerExpr changes between commits.
369evalDatabaseContextExpr (AddNotification notName triggerExpr resultExpr) = do
370 currentContext <- getStateContext
371 let nots = notifications currentContext
372 if M.member notName nots then
373 return $ Just (NotificationNameInUseError notName)
375 let newNotifications = M.insert notName newNotification nots
376 newNotification = Notification { changeExpr = triggerExpr,
377 reportExpr = resultExpr }
378 putStateContext $ currentContext { notifications = newNotifications }
381evalDatabaseContextExpr (RemoveNotification notName) = do
382 currentContext <- getStateContext
383 let nots = notifications currentContext
384 if M.notMember notName nots then
385 return $ Just (NotificationNameNotInUseError notName)
387 let newNotifications = M.delete notName nots
388 putStateContext $ currentContext { notifications = newNotifications }
391-- | Adds type and data constructors to the database context.
392-- validate that the type *and* constructor names are unique! not yet implemented!
393evalDatabaseContextExpr (AddTypeConstructor tConsDef dConsDefList) = do
394 currentContext <- getStateContext
395 let oldTypes = typeConstructorMapping currentContext
396 tConsName = TCD.name tConsDef
397 -- validate that the constructor's types exist
398 case validateTypeConstructorDef tConsDef dConsDefList of
399 errs@(_:_) -> pure $ Just (someErrors errs)
400 [] | T.null tConsName || not (isUpper (T.head tConsName)) -> pure $ Just (InvalidAtomTypeName tConsName)
401 | isJust (findTypeConstructor tConsName oldTypes) -> pure $ Just (AtomTypeNameInUseError tConsName)
403 let newTypes = oldTypes ++ [(tConsDef, dConsDefList)]
404 putStateContext $ currentContext { typeConstructorMapping = newTypes }
407-- | 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.
408evalDatabaseContextExpr (RemoveTypeConstructor tConsName) = do
409 currentContext <- getStateContext
410 let oldTypes = typeConstructorMapping currentContext
411 if isNothing (findTypeConstructor tConsName oldTypes) then
412 pure $ Just (AtomTypeNameNotInUseError tConsName)
414 let newTypes = filter (\(tCons, _) -> TCD.name tCons /= tConsName) oldTypes
415 putStateContext $ currentContext { typeConstructorMapping = newTypes }
418evalDatabaseContextExpr (MultipleExpr exprs) = do
419 --the multiple expressions must pass the same context around- not the old unmodified context
420 evald <- forM exprs evalDatabaseContextExpr
421 --some lifting magic needed here
422 case catMaybes evald of
424 err:_ -> pure (Just err)
426evalDatabaseContextExpr (RemoveAtomFunction funcName) = do
427 currentContext <- getStateContext
428 let atomFuncs = atomFunctions currentContext
429 case atomFunctionForName funcName atomFuncs of
430 Left err -> pure (Just err)
431 Right realFunc -> if isScriptedAtomFunction realFunc then do
432 let updatedFuncs = HS.delete realFunc atomFuncs
433 putStateContext (currentContext {atomFunctions = updatedFuncs })
436 pure (Just (PrecompiledFunctionRemoveError funcName))
439evalDatabaseContextExpr (RemoveDatabaseContextFunction funcName) = do
440 context <- getStateContext
441 let dbcFuncs = dbcFunctions context
442 case databaseContextFunctionForName funcName dbcFuncs of
443 Left err -> pure (Just err)
444 Right realFunc -> if isScriptedDatabaseContextFunction realFunc then do
445 let updatedFuncs = HS.delete realFunc dbcFuncs
446 putStateContext (context { dbcFunctions = updatedFuncs })
449 pure (Just (PrecompiledFunctionRemoveError funcName))
451evalDatabaseContextExpr (ExecuteDatabaseContextFunction funcName atomArgExprs) = do
452 context <- getStateContext
453 --resolve atom arguments
454 let relExprState = mkRelationalExprState context
455 eAtomTypes = map (\atomExpr -> runReader (typeFromAtomExpr emptyAttributes atomExpr) relExprState) atomArgExprs
456 eFunc = databaseContextFunctionForName funcName (dbcFunctions context)
458 Left err -> pure (Just err)
460 let expectedArgCount = length (dbcFuncType func)
461 actualArgCount = length atomArgExprs
462 if expectedArgCount /= actualArgCount then
463 pure (Just (FunctionArgumentCountMismatch expectedArgCount actualArgCount))
465 --check that the atom types are valid
466 case lefts eAtomTypes of
467 _:_ -> pure (Just (someErrors (lefts eAtomTypes)))
469 let atomTypes = rights eAtomTypes
470 let mValidTypes = map (\(expType, actType) -> case atomTypeVerify expType actType of
472 Right _ -> Nothing) (zip (dbcFuncType func) atomTypes)
473 typeErrors = catMaybes mValidTypes
474 eAtomArgs = map (\arg -> runReader (evalAtomExpr emptyTuple arg) relExprState) atomArgExprs
475 if length (lefts eAtomArgs) > 1 then
476 pure (Just (someErrors (lefts eAtomArgs)))
477 else if not (null typeErrors) then
478 pure (Just (someErrors typeErrors))
480 case evalDatabaseContextFunction func (rights eAtomArgs) context of
481 Left err -> pure (Just err)
482 Right newContext -> putStateContext newContext >> pure Nothing
484evalDatabaseContextIOExpr :: Maybe ScriptSession -> DatabaseContext -> DatabaseContextIOExpr -> IO (Either RelationalError DatabaseContext)
485evalDatabaseContextIOExpr mScriptSession currentContext (AddAtomFunction funcName funcType script) =
486 case mScriptSession of
487 Nothing -> pure (Left (ScriptError ScriptCompilationDisabledError))
488 Just scriptSession -> do
489 res <- try $ runGhc (Just libdir) $ do
490 setSession (hscEnv scriptSession)
491 let atomFuncs = atomFunctions currentContext
492 case extractAtomFunctionType funcType of
493 Left err -> pure (Left err)
494 Right adjustedAtomTypeCons -> do
495 --compile the function
496 eCompiledFunc <- compileScript (atomFunctionBodyType scriptSession) script
497 pure $ case eCompiledFunc of
498 Left err -> Left (ScriptError err)
499 Right compiledFunc -> do
500 funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext)) adjustedAtomTypeCons
501 let updatedFuncs = HS.insert newAtomFunc atomFuncs
502 newContext = currentContext { atomFunctions = updatedFuncs }
503 newAtomFunc = AtomFunction { atomFuncName = funcName,
504 atomFuncType = funcAtomType,
505 atomFuncBody = AtomFunctionBody (Just script) compiledFunc }
506 -- check if the name is already in use
507 if HS.member funcName (HS.map atomFuncName atomFuncs) then
508 Left (FunctionNameInUseError funcName)
512 Left (exc :: SomeException) -> pure $ Left (ScriptError (OtherScriptCompilationError (show exc)))
513 Right eContext -> case eContext of
514 Left err -> pure (Left err)
515 Right context' -> pure (Right context')
517evalDatabaseContextIOExpr mScriptSession currentContext (AddDatabaseContextFunction funcName funcType script) =
518 case mScriptSession of
519 Nothing -> pure (Left (ScriptError ScriptCompilationDisabledError))
520 Just scriptSession -> do
521 --validate that the function signature is of the form x -> y -> ... -> DatabaseContext -> DatabaseContext
522 let last2Args = reverse (take 2 (reverse funcType))
523 atomArgs = take (length funcType - 2) funcType
524 dbContextTypeCons = ADTypeConstructor "Either" [ADTypeConstructor "DatabaseContextFunctionError" [], ADTypeConstructor "DatabaseContext" []]
525 expectedType = "DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext"
526 actualType = show funcType
527 if last2Args /= [ADTypeConstructor "DatabaseContext" [], dbContextTypeCons] then
528 pure (Left (ScriptError (TypeCheckCompilationError expectedType actualType)))
530 res <- try $ runGhc (Just libdir) $ do
531 setSession (hscEnv scriptSession)
532 eCompiledFunc <- compileScript (dbcFunctionBodyType scriptSession) script
533 pure $ case eCompiledFunc of
534 Left err -> Left (ScriptError err)
535 Right compiledFunc -> do
536 --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
537 funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext)) atomArgs
538 let updatedDBCFuncs = HS.insert newDBCFunc (dbcFunctions currentContext)
539 newContext = currentContext { dbcFunctions = updatedDBCFuncs }
540 dbcFuncs = dbcFunctions currentContext
541 newDBCFunc = DatabaseContextFunction {
542 dbcFuncName = funcName,
543 dbcFuncType = funcAtomType,
544 dbcFuncBody = DatabaseContextFunctionBody (Just script) compiledFunc
546 -- check if the name is already in use
547 if HS.member funcName (HS.map dbcFuncName dbcFuncs) then
548 Left (FunctionNameInUseError funcName)
552 Left (exc :: SomeException) -> pure $ Left (ScriptError (OtherScriptCompilationError (show exc)))
553 Right eContext -> case eContext of
554 Left err -> pure (Left err)
555 Right context' -> pure (Right context')
558updateTupleWithAtomExprs :: M.Map AttributeName AtomExpr -> DatabaseContext -> RelationTuple -> Either RelationalError RelationTuple
559updateTupleWithAtomExprs exprMap context tupIn = do
560 --resolve all atom exprs
561 atomsAssoc <- mapM (\(attrName, atomExpr) -> do
562 atom <- runReader (evalAtomExpr tupIn atomExpr) (RelationalExprStateElems context)
563 pure (attrName, atom)
565 pure (updateTupleWithAtoms (M.fromList atomsAssoc) tupIn)
567--run verification on all constraints
568checkConstraints :: DatabaseContext -> Maybe RelationalError
569checkConstraints context = case failures of
573 failures = M.elems $ M.mapMaybeWithKey checkIncDep deps
574 deps = inclusionDependencies context
575 eval expr = runReader (evalRelationalExpr expr) (RelationalExprStateElems context)
576 checkIncDep depName (InclusionDependency subsetExpr supersetExpr) = do
577 let checkExpr = Equals supersetExpr (Union subsetExpr supersetExpr)
578 case eval checkExpr of
580 Right resultRel -> if resultRel == relationTrue then
583 Just $ InclusionDependencyCheckError depName
585-- 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
586-- furthermore, the type of a relational expression is the resultant header of the evaluated empty-tupled relation
588typeForRelationalExpr :: RelationalExpr -> RelationalExprState (Either RelationalError Relation)
589typeForRelationalExpr expr = do
591 let context = stateElemsContext rstate
592 --replace the relationVariables context element with a cloned set of relation devoid of tuples
593 let context' = contextWithEmptyTupleSets context
594 rstate' = setStateElemsContext rstate context'
595 pure (runReader (evalRelationalExpr expr) rstate')
597--returns a database context with all tuples removed
598--this is useful for type checking and optimization
599contextWithEmptyTupleSets :: DatabaseContext -> DatabaseContext
600contextWithEmptyTupleSets contextIn = contextIn { relationVariables = relVars }
602 relVars = M.map (\rel -> Relation (attributes rel) emptyTupleSet) (relationVariables contextIn)
604liftE :: (Monad m) => m (Either a b) -> ExceptT a m b
608 Left err -> throwE err
609 Right val -> pure val
611{- used for restrictions- take the restrictionpredicate and return the corresponding filter function -}
612predicateRestrictionFilter :: Attributes -> RestrictionPredicateExpr -> RelationalExprState (Either RelationalError RestrictionFilter)
613predicateRestrictionFilter attrs (AndPredicate expr1 expr2) =
615 expr1v <- liftE (predicateRestrictionFilter attrs expr1)
616 expr2v <- liftE (predicateRestrictionFilter attrs expr2)
622predicateRestrictionFilter attrs (OrPredicate expr1 expr2) =
624 expr1v <- liftE (predicateRestrictionFilter attrs expr1)
625 expr2v <- liftE (predicateRestrictionFilter attrs expr2)
631predicateRestrictionFilter _ TruePredicate = pure (Right (\_ -> pure True))
633predicateRestrictionFilter attrs (NotPredicate expr) =
635 exprv <- liftE (predicateRestrictionFilter attrs expr)
640--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
641predicateRestrictionFilter _ (RelationalExprPredicate relExpr) = do
643 pure (Right (\tup -> case runReader (evalRelationalExpr relExpr) (mergeTuplesIntoRelationalExprState tup rstate) of
645 Right rel -> if arity rel /= 0 then
646 Left (PredicateExpressionError "Relational restriction filter must evaluate to 'true' or 'false'")
648 pure (rel == relationTrue)))
650predicateRestrictionFilter attrs (AttributeEqualityPredicate attrName atomExpr) = do
652 let (attrs', ctxtup') = case rstate of
653 RelationalExprStateElems _ -> (attrs, emptyTuple)
654 RelationalExprStateAttrsElems _ _ -> (attrs, emptyTuple)
655 RelationalExprStateTupleElems _ ctxtup -> (A.union attrs (tupleAttributes ctxtup), ctxtup)
657 atomExprType <- liftE (typeFromAtomExpr attrs' atomExpr)
658 attr <- either throwE pure $ case A.attributeForName attrName attrs of
659 Right attr -> Right attr
660 Left (NoSuchAttributeNamesError _) -> case A.attributeForName attrName (tupleAttributes ctxtup') of
661 Right ctxattr -> Right ctxattr
662 Left err2@(NoSuchAttributeNamesError _) -> Left err2
665 if atomExprType /= A.atomType attr then
666 throwE (TupleAttributeTypeMismatchError (A.attributesFromList [attr]))
668 pure $ \tupleIn -> let evalAndCmp atomIn = case atomEvald of
669 Right atomCmp -> atomCmp == atomIn
671 atomEvald = runReader (evalAtomExpr tupleIn atomExpr) rstate
673 pure $ case atomForAttributeName attrName tupleIn of
674 Left (NoSuchAttributeNamesError _) -> case atomForAttributeName attrName ctxtup' of
676 Right ctxatom -> evalAndCmp ctxatom
678 Right atomIn -> evalAndCmp atomIn
679-- in the future, it would be useful to do typechecking on the attribute and atom expr filters in advance
680predicateRestrictionFilter attrs (AtomExprPredicate atomExpr) = do
681 --merge attrs into the state attributes
684 aType <- liftE (typeFromAtomExpr attrs atomExpr)
685 if aType /= BoolAtomType then
686 throwE (AtomTypeMismatchError aType BoolAtomType)
689 pure $ case runReader (evalAtomExpr tupleIn atomExpr) rstate of
691 Right boolAtomValue -> boolAtomValue == BoolAtom True)
693tupleExprCheckNewAttrName :: AttributeName -> Relation -> Either RelationalError Relation
694tupleExprCheckNewAttrName attrName rel = if isRight (attributeForName attrName rel) then
695 Left (error "SPAMMIT" $ AttributeNameInUseError attrName)
699extendTupleExpressionProcessor :: Relation -> ExtendTupleExpr -> RelationalExprState (Either RelationalError (Attributes, RelationTuple -> Either RelationalError RelationTuple))
700extendTupleExpressionProcessor relIn (AttributeExtendTupleExpr newAttrName atomExpr) = do
702 -- check that the attribute name is not in use
703 case tupleExprCheckNewAttrName newAttrName relIn of
704 Left err -> pure (Left err)
705 Right _ -> runExceptT $ do
706 atomExprType <- liftE (typeFromAtomExpr (attributes relIn) atomExpr)
707 atomExprType' <- liftE (verifyAtomExprTypes relIn atomExpr atomExprType)
708 let newAttrs = A.attributesFromList [Attribute newAttrName atomExprType']
709 newAndOldAttrs = A.addAttributes (attributes relIn) newAttrs
710 pure (newAndOldAttrs, \tup -> let substate = mergeTuplesIntoRelationalExprState tup rstate in case runReader (evalAtomExpr tup atomExpr) substate of
712 Right atom -> Right (tupleAtomExtend newAttrName atom tup)
715evalAtomExpr :: RelationTuple -> AtomExpr -> RelationalExprState (Either RelationalError Atom)
716evalAtomExpr tupIn (AttributeAtomExpr attrName) = case atomForAttributeName attrName tupIn of
717 Right atom -> pure (Right atom)
718 err@(Left (NoSuchAttributeNamesError _)) -> do
721 RelationalExprStateElems _ -> pure err
722 RelationalExprStateAttrsElems _ _ -> pure err
723 RelationalExprStateTupleElems _ ctxtup -> pure (atomForAttributeName attrName ctxtup)
724 Left err -> pure (Left err)
725evalAtomExpr _ (NakedAtomExpr atom) = pure (Right atom)
726evalAtomExpr tupIn (FunctionAtomExpr funcName arguments ()) = do
727 argTypes <- mapM (typeFromAtomExpr (tupleAttributes tupIn)) arguments
728 context <- fmap stateElemsContext ask
730 let functions = atomFunctions context
731 func <- either throwE pure (atomFunctionForName funcName functions)
732 let expectedArgCount = length (atomFuncType func) - 1
733 actualArgCount = length argTypes
735 safeInit [] = [] -- different behavior from normal init
736 safeInit (_:xs) = safeInit xs
737 if expectedArgCount /= actualArgCount then
738 throwE (FunctionArgumentCountMismatch expectedArgCount actualArgCount)
740 _ <- mapM (\(expType, actType) -> either throwE pure (atomTypeVerify expType actType)) (safeInit (zip (atomFuncType func) argTypes))
741 evaldArgs <- mapM (liftE . evalAtomExpr tupIn) arguments
742 case evalAtomFunction func evaldArgs of
743 Left err -> throwE (AtomFunctionUserError err)
745 --validate that the result matches the expected type
746 _ <- either throwE pure (atomTypeVerify (last (atomFuncType func)) (atomTypeForAtom result))
748evalAtomExpr tupIn (RelationAtomExpr relExpr) = do
749 --merge existing state tuple context into new state tuple context to support an arbitrary number of levels, but new attributes trounce old attributes
752 let newState = mergeTuplesIntoRelationalExprState tupIn rstate
753 relAtom <- either throwE pure (runReader (evalRelationalExpr relExpr) newState)
754 pure (RelationAtom relAtom)
755evalAtomExpr tupIn cons@(ConstructedAtomExpr dConsName dConsArgs ()) = runExceptT $ do
757 let newState = mergeTuplesIntoRelationalExprState tupIn rstate
758 aType <- either throwE pure (runReader (typeFromAtomExpr (tupleAttributes tupIn) cons) newState)
759 argAtoms <- mapM (\arg -> either throwE pure (runReader (evalAtomExpr tupIn arg) newState)) dConsArgs
760 pure (ConstructedAtom dConsName aType argAtoms)
762typeFromAtomExpr :: Attributes -> AtomExpr -> RelationalExprState (Either RelationalError AtomType)
763typeFromAtomExpr attrs (AttributeAtomExpr attrName) = do
765 case A.atomTypeForAttributeName attrName attrs of
766 Right aType -> pure (Right aType)
767 Left err@(NoSuchAttributeNamesError _) -> case rstate of
768 RelationalExprStateAttrsElems _ attrs' -> case A.attributeForName attrName attrs' of
769 Left err' -> pure (error "SPAMMO2" $ Left err')
770 Right attr -> pure (Right (A.atomType attr))
771 RelationalExprStateElems _ -> pure (error (show attrs) $ Left err)
772 RelationalExprStateTupleElems _ tup -> case atomForAttributeName attrName tup of
773 Left err' -> pure (error "STAMP" $ Left err')
774 Right atom -> pure (Right (atomTypeForAtom atom))
775 Left err -> pure (error "GONK" $ Left err)
776typeFromAtomExpr _ (NakedAtomExpr atom) = pure (Right (atomTypeForAtom atom))
777typeFromAtomExpr attrs (FunctionAtomExpr funcName atomArgs _) = do
778 context <- fmap stateElemsContext ask
779 let funcs = atomFunctions context
780 case atomFunctionForName funcName funcs of
781 Left err -> pure (Left err)
783 let funcRetType = last (atomFuncType func)
784 funcArgTypes = init (atomFuncType func)
785 eArgTypes <- mapM (typeFromAtomExpr attrs) atomArgs
786 case lefts eArgTypes of
787 errs@(_:_) -> pure (Left (someErrors errs))
789 let eTvMap = resolveTypeVariables funcArgTypes argTypes
790 argTypes = rights eArgTypes
792 Left err -> pure (Left err)
793 Right tvMap -> pure (resolveFunctionReturnValue funcName tvMap funcRetType)
794typeFromAtomExpr attrs (RelationAtomExpr relExpr) = runExceptT $ do
796 relType <- either throwE pure (runReader (typeForRelationalExpr relExpr) (mergeAttributesIntoRelationalExprState attrs rstate))
797 pure (RelationAtomType (attributes relType))
798-- grab the type of the data constructor, then validate that the args match the expected types
799typeFromAtomExpr attrs (ConstructedAtomExpr dConsName dConsArgs _) =
801 argsTypes <- mapM (liftE . typeFromAtomExpr attrs) dConsArgs
802 context <- fmap stateElemsContext (lift ask)
803 aType <- either throwE pure (atomTypeForDataConstructor (typeConstructorMapping context) dConsName argsTypes)
806-- | Validate that the type of the AtomExpr matches the expected type.
807verifyAtomExprTypes :: Relation -> AtomExpr -> AtomType -> RelationalExprState (Either RelationalError AtomType)
808verifyAtomExprTypes relIn (AttributeAtomExpr attrName) expectedType = runExceptT $ do
810 case A.atomTypeForAttributeName attrName (attributes relIn) of
811 Right aType -> pure aType
812 (Left err@(NoSuchAttributeNamesError _)) -> case rstate of
813 RelationalExprStateTupleElems _ _ -> throwE err
814 RelationalExprStateElems _ -> throwE err
815 RelationalExprStateAttrsElems _ attrs -> case A.attributeForName attrName attrs of
816 Left err' -> throwE (error "GONK" err')
817 Right attrType -> either throwE pure (atomTypeVerify expectedType (A.atomType attrType))
818 Left err -> throwE err
819verifyAtomExprTypes _ (NakedAtomExpr atom) expectedType = pure (atomTypeVerify expectedType (atomTypeForAtom atom))
820verifyAtomExprTypes relIn (FunctionAtomExpr funcName funcArgExprs _) expectedType = do
822 let functions = atomFunctions context
823 context = stateElemsContext rstate
825 func <- either throwE pure (atomFunctionForName funcName functions)
826 let expectedArgTypes = atomFuncType func
827 funcArgTypes <- mapM (\(atomExpr,expectedType2,argCount) -> case runReader (verifyAtomExprTypes relIn atomExpr expectedType2) rstate of
828 Left (AtomTypeMismatchError expSubType actSubType) -> throwE (AtomFunctionTypeError funcName argCount expSubType actSubType)
829 Left err -> throwE err
831 ) $ zip3 funcArgExprs expectedArgTypes [1..]
832 if length funcArgTypes /= length expectedArgTypes - 1 then
833 throwE (AtomTypeCountError funcArgTypes expectedArgTypes)
835 either throwE pure (atomTypeVerify expectedType (last expectedArgTypes))
836verifyAtomExprTypes relIn (RelationAtomExpr relationExpr) expectedType = runExceptT $ do
838 relType <- either throwE pure (runReader (typeForRelationalExpr relationExpr) (mergeAttributesIntoRelationalExprState (attributes relIn) rstate))
839 either throwE pure (atomTypeVerify expectedType (RelationAtomType (attributes relType)))
840verifyAtomExprTypes rel cons@ConstructedAtomExpr{} expectedType = runExceptT $ do
841 cType <- liftE (typeFromAtomExpr (attributes rel) cons)
842 either throwE pure (atomTypeVerify expectedType cType)
844-- | Look up the type's name and create a new attribute.
845evalAttrExpr :: TypeConstructorMapping -> AttributeExpr -> Either RelationalError Attribute
846evalAttrExpr aTypes (AttributeAndTypeNameExpr attrName tCons ()) = do
847 aType <- atomTypeForTypeConstructor tCons aTypes
848 Right (Attribute attrName aType)
850evalAttrExpr _ (NakedAttributeExpr attr) = Right attr
852evalTupleExpr :: Maybe Attributes -> TupleExpr -> RelationalExprState (Either RelationalError RelationTuple)
853evalTupleExpr attrs (TupleExpr tupMap) = do
854 context <- fmap stateElemsContext ask
856 -- 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
857 -- 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
858 attrAtoms <- mapM (\(attrName, aExpr) -> do
859 newAtomType <- liftE (typeFromAtomExpr A.emptyAttributes aExpr)
860 newAtom <- liftE (evalAtomExpr emptyTuple aExpr)
861 pure (attrName, newAtom, newAtomType)
863 let tupAttrs = A.attributesFromList $ map (\(attrName, _, aType) -> Attribute attrName aType) attrAtoms
864 atoms = V.fromList $ map (\(_, atom, _) -> atom) attrAtoms
865 tup = mkRelationTuple tupAttrs atoms
866 tConss = typeConstructorMapping context
867 finalAttrs = fromMaybe tupAttrs attrs
868 --verify that the attributes match
869 when (A.attributeNameSet finalAttrs /= A.attributeNameSet tupAttrs) $ throwE (TupleAttributeTypeMismatchError tupAttrs)
870 tup' <- either throwE pure (resolveTypesInTuple finalAttrs (reorderTuple finalAttrs tup))
871 _ <- either throwE pure (validateTuple tup' tConss)
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