packages feed

project-m36 1.1.1 → 1.2.0

raw patch · 144 files changed

+7699/−1781 lines, 144 filesdep +byteunitsdep +cryptondep +ghc-bignumdep ~aesondep ~basedep ~base16-bytestringnew-component:exe:Example-Hospitalnew-component:exe:Example-ZooPVP ok

version bump matches the API change (PVP)

Dependencies added: byteunits, crypton, ghc-bignum, integer-gmp, linux-xattr, network-byte-order, process, sqlite-simple, streamly-core, system-linux-proc

Dependency ranges changed: aeson, base, base16-bytestring, cassava, curryer-rpc, data-default, ghc, megaparsec, stm-containers, streamly, uuid, winery

API changes (from Hackage documentation)

- ProjectM36.Base: DatabaseContext :: InclusionDependencies -> RelationVariables -> AtomFunctions -> DatabaseContextFunctions -> Notifications -> TypeConstructorMapping -> RegisteredQueries -> DatabaseContext
- ProjectM36.Base: DisconnectedTransaction :: TransactionId -> Schemas -> DirtyFlag -> DisconnectedTransaction
- ProjectM36.Base: IsoRename :: RelVarName -> RelVarName -> SchemaIsomorph
- ProjectM36.Base: IsoRestrict :: RelVarName -> RestrictionPredicateExpr -> (RelVarName, RelVarName) -> SchemaIsomorph
- ProjectM36.Base: IsoUnion :: (RelVarName, RelVarName) -> RestrictionPredicateExpr -> RelVarName -> SchemaIsomorph
- ProjectM36.Base: Schema :: SchemaIsomorphs -> Schema
- ProjectM36.Base: Schemas :: DatabaseContext -> Subschemas -> Schemas
- ProjectM36.Base: Transaction :: TransactionId -> TransactionInfo -> Schemas -> Transaction
- ProjectM36.Base: TransactionGraph :: TransactionHeads -> Set Transaction -> TransactionGraph
- ProjectM36.Base: TransactionInfo :: TransactionParents -> UTCTime -> MerkleHash -> TransactionInfo
- ProjectM36.Base: [asList] :: RelationTupleSet -> [RelationTuple]
- ProjectM36.Base: [atomFunctions] :: DatabaseContext -> AtomFunctions
- ProjectM36.Base: [attributesVec] :: Attributes -> Vector Attribute
- ProjectM36.Base: [changeExpr] :: Notification -> RelationalExpr
- ProjectM36.Base: [dbcFunctions] :: DatabaseContext -> DatabaseContextFunctions
- ProjectM36.Base: [funcBody] :: Function a -> FunctionBody a
- ProjectM36.Base: [funcName] :: Function a -> FunctionName
- ProjectM36.Base: [funcType] :: Function a -> [AtomType]
- ProjectM36.Base: [inclusionDependencies] :: DatabaseContext -> InclusionDependencies
- ProjectM36.Base: [merkleHash] :: TransactionInfo -> MerkleHash
- ProjectM36.Base: [notifications] :: DatabaseContext -> Notifications
- ProjectM36.Base: [parents] :: TransactionInfo -> TransactionParents
- ProjectM36.Base: [registeredQueries] :: DatabaseContext -> RegisteredQueries
- ProjectM36.Base: [relationVariables] :: DatabaseContext -> RelationVariables
- ProjectM36.Base: [reportNewExpr] :: Notification -> RelationalExpr
- ProjectM36.Base: [reportOldExpr] :: Notification -> RelationalExpr
- ProjectM36.Base: [stamp] :: TransactionInfo -> UTCTime
- ProjectM36.Base: [typeConstructorMapping] :: DatabaseContext -> TypeConstructorMapping
- ProjectM36.Base: data DatabaseContext
- ProjectM36.Base: data DisconnectedTransaction
- ProjectM36.Base: data SchemaIsomorph
- ProjectM36.Base: data Schemas
- ProjectM36.Base: data Transaction
- ProjectM36.Base: data TransactionGraph
- ProjectM36.Base: data TransactionInfo
- ProjectM36.Base: instance Control.DeepSeq.NFData ProjectM36.Base.DatabaseContext
- ProjectM36.Base: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (ProjectM36.Base.DatabaseContextExprBase a)
- ProjectM36.Base: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (ProjectM36.Base.Function a)
- ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.Function a)
- ProjectM36.Base: instance GHC.Classes.Eq (ProjectM36.Base.Function a)
- ProjectM36.Base: instance GHC.Classes.Eq ProjectM36.Base.Transaction
- ProjectM36.Base: instance GHC.Classes.Eq a => GHC.Classes.Eq (ProjectM36.Base.DatabaseContextExprBase a)
- ProjectM36.Base: instance GHC.Classes.Ord ProjectM36.Base.Transaction
- ProjectM36.Base: instance GHC.Generics.Generic (ProjectM36.Base.DatabaseContextExprBase a)
- ProjectM36.Base: instance GHC.Generics.Generic (ProjectM36.Base.Function a)
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.DatabaseContext
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.Schema
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.SchemaIsomorph
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.Schemas
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.Transaction
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.TransactionGraph
- ProjectM36.Base: instance GHC.Generics.Generic ProjectM36.Base.TransactionInfo
- ProjectM36.Base: instance GHC.Read.Read a => GHC.Read.Read (ProjectM36.Base.DatabaseContextExprBase a)
- ProjectM36.Base: instance GHC.Show.Show ProjectM36.Base.SchemaIsomorph
- ProjectM36.Base: instance GHC.Show.Show ProjectM36.Base.TransactionInfo
- ProjectM36.Base: instance GHC.Show.Show a => GHC.Show.Show (ProjectM36.Base.DatabaseContextExprBase a)
- ProjectM36.Base: newtype Schema
- ProjectM36.Base: transactionHeadsForGraph :: TransactionGraph -> TransactionHeads
- ProjectM36.Base: transactionId :: Transaction -> TransactionId
- ProjectM36.Base: transactionIdsForGraph :: TransactionGraph -> Set TransactionId
- ProjectM36.Base: transactionInfo :: Transaction -> TransactionInfo
- ProjectM36.Base: transactionsForGraph :: TransactionGraph -> Set Transaction
- ProjectM36.Base: type DatabaseContextFunction = Function DatabaseContextFunctionBodyType
- ProjectM36.Base: type DatabaseContextFunctionBody = FunctionBody DatabaseContextFunctionBodyType
- ProjectM36.Base: type DatabaseContextFunctionBodyType = [Atom] -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext
- ProjectM36.Base: type DatabaseContextFunctions = HashSet DatabaseContextFunction
- ProjectM36.Base: type SchemaIsomorphs = [SchemaIsomorph]
- ProjectM36.Base: type SchemaName = StringType
- ProjectM36.Base: type Subschemas = Map SchemaName Schema
- ProjectM36.Base: type TransactionHeads = Map HeadName Transaction
- ProjectM36.Base: type TransactionParents = NonEmpty TransactionId
- ProjectM36.Client: createScriptedAtomFunction :: FunctionName -> [TypeConstructor] -> TypeConstructor -> FunctionBodyScript -> DatabaseContextIOExpr
- ProjectM36.Client: createSessionAtCommit :: Connection -> TransactionId -> IO (Either RelationalError SessionId)
- ProjectM36.Client: data TransactionGraphOperator
- ProjectM36.Client: executeGraphExpr :: SessionId -> Connection -> TransactionGraphOperator -> IO (Either RelationalError ())
- ProjectM36.Client: headName :: SessionId -> Connection -> IO (Either RelationalError HeadName)
- ProjectM36.DatabaseContext: basicDatabaseContext :: DatabaseContext
- ProjectM36.DatabaseContext: databaseContextAsDatabaseContextExpr :: DatabaseContext -> DatabaseContextExpr
- ProjectM36.DatabaseContextFunction: basicDatabaseContextFunctions :: DatabaseContextFunctions
- ProjectM36.DatabaseContextFunction: createScriptedDatabaseContextFunction :: FunctionName -> [TypeConstructor] -> TypeConstructor -> FunctionBodyScript -> DatabaseContextIOExpr
- ProjectM36.DatabaseContextFunction: precompiledDatabaseContextFunctions :: DatabaseContextFunctions
- ProjectM36.DatabaseContextFunctionError: DatabaseContextFunctionUserError :: String -> DatabaseContextFunctionError
- ProjectM36.DatabaseContextFunctionError: data DatabaseContextFunctionError
- ProjectM36.DatabaseContextFunctionError: instance Control.DeepSeq.NFData ProjectM36.DatabaseContextFunctionError.DatabaseContextFunctionError
- ProjectM36.DatabaseContextFunctionError: instance GHC.Classes.Eq ProjectM36.DatabaseContextFunctionError.DatabaseContextFunctionError
- ProjectM36.DatabaseContextFunctionError: instance GHC.Generics.Generic ProjectM36.DatabaseContextFunctionError.DatabaseContextFunctionError
- ProjectM36.DatabaseContextFunctionError: instance GHC.Show.Show ProjectM36.DatabaseContextFunctionError.DatabaseContextFunctionError
- ProjectM36.DisconnectedTransaction: isDirty :: DisconnectedTransaction -> Bool
- ProjectM36.DisconnectedTransaction: loadGraphRefRelVarsOnly :: TransactionId -> Schemas -> Schemas
- ProjectM36.DisconnectedTransaction: schemas :: DisconnectedTransaction -> Schemas
- ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.Base.DatabaseContext
- ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.Base.DatabaseContextFunction
- ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.Base.DatabaseContextFunctions
- ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.Base.Schema
- ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.Base.SchemaIsomorph
- ProjectM36.ReferencedTransactionIds: instance ProjectM36.ReferencedTransactionIds.ReferencedTransactionIds ProjectM36.Base.DatabaseContext
- ProjectM36.RelationalExpression: [dbc_dirty] :: DatabaseContextEvalState -> DirtyFlag
- ProjectM36.RelationalExpression: liftE :: Monad m => m (Either a b) -> ExceptT a m b
- ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.Base.Schema
- ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.Base.SchemaIsomorph
- ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.Base.TransactionInfo
- ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise a => Codec.Winery.Class.Serialise (ProjectM36.Base.DatabaseContextExprBase a)
- ProjectM36.Serialise.DatabaseContextFunctionError: instance Codec.Winery.Class.Serialise ProjectM36.DatabaseContextFunctionError.DatabaseContextFunctionError
- ProjectM36.Server.EntryPoints: handleCreateSessionAtCommit :: Maybe Timeout -> Connection -> TransactionId -> IO (Either RelationalError SessionId)
- ProjectM36.Server.EntryPoints: handleExecuteGraphExpr :: Maybe Timeout -> SessionId -> Connection -> TransactionGraphOperator -> IO (Either RelationalError ())
- ProjectM36.Server.EntryPoints: handleExecuteHeadName :: Maybe Timeout -> SessionId -> Connection -> IO (Either RelationalError HeadName)
- ProjectM36.Server.ParseArgs: parseTlsCertificatePath :: Parser (Maybe String)
- ProjectM36.Server.ParseArgs: parseTlsKeyPath :: Parser (Maybe String)
- ProjectM36.Server.RemoteCallTypes: CreateSessionAtCommit :: TransactionId -> CreateSessionAtCommit
- ProjectM36.Server.RemoteCallTypes: data CreateSessionAtCommit
- ProjectM36.Server.RemoteCallTypes: instance Codec.Winery.Class.Serialise ProjectM36.Server.RemoteCallTypes.CreateSessionAtCommit
- ProjectM36.Server.RemoteCallTypes: instance GHC.Generics.Generic ProjectM36.Server.RemoteCallTypes.CreateSessionAtCommit
- ProjectM36.Session: disconnectedTransaction :: Session -> DisconnectedTransaction
- ProjectM36.Session: isDirty :: Session -> DirtyFlag
- ProjectM36.Session: schemaName :: Session -> SchemaName
- ProjectM36.Shortcuts: instance GHC.Exts.IsList (ProjectM36.Base.AttributeNamesBase ())
- ProjectM36.Shortcuts: instance GHC.Exts.IsList (ProjectM36.Base.TupleExprsBase ())
- ProjectM36.Shortcuts: instance GHC.Exts.IsList ProjectM36.Base.TupleExpr
- ProjectM36.StaticOptimizer: isEmptyRelationExpr :: RelationalExprBase a -> Bool
- ProjectM36.Transaction: concreteDatabaseContext :: Transaction -> DatabaseContext
- ProjectM36.Transaction: parentIds :: Transaction -> Set TransactionId
- ProjectM36.Transaction: rootParent :: TransactionParents
- ProjectM36.Transaction: schemas :: Transaction -> Schemas
- ProjectM36.Transaction: singleParent :: TransactionId -> TransactionParents
- ProjectM36.Transaction: subschemas :: Transaction -> Subschemas
- ProjectM36.Transaction.Persist: readIncDep :: FilePath -> IncDepName -> IO (IncDepName, InclusionDependency)
- ProjectM36.Transaction.Persist: relvarsPath :: FilePath -> FilePath
- ProjectM36.Transaction.Persist: writeIncDep :: DiskSync -> FilePath -> (IncDepName, InclusionDependency) -> IO ()
- ProjectM36.TransactionGraph: addBranch :: UTCTime -> TransactionId -> HeadName -> TransactionId -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
- ProjectM36.TransactionGraph: data TransactionGraphOperator
- ProjectM36.TransactionGraph: evalGraphOp :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
- ProjectM36.TransactionGraph: headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName
- ProjectM36.TransactionGraph: instance Codec.Winery.Class.Serialise ProjectM36.TransactionGraph.TransactionGraphOperator
- ProjectM36.TransactionGraph: instance GHC.Classes.Eq ProjectM36.TransactionGraph.TransactionGraphOperator
- ProjectM36.TransactionGraph: instance GHC.Generics.Generic ProjectM36.TransactionGraph.TransactionGraphOperator
- ProjectM36.TransactionGraph: instance GHC.Show.Show ProjectM36.TransactionGraph.TransactionGraphOperator
- ProjectM36.TransactionGraph.Persist: transactionsPersist :: DiskSync -> [TransactionId] -> FilePath -> TransactionGraph -> IO ()
- ProjectM36.TupleSet: emptyTupleSet :: RelationTupleSet
- ProjectM36.TupleSet: singletonTupleSet :: RelationTupleSet
+ ProjectM36.AccessControl: applyACLAlterTransGraphExpr :: [RoleId] -> AlterTransGraphAccessControlList -> AlterTransactionGraphExpr -> Either RelationalError ()
+ ProjectM36.AccessControl: applyACLDatabaseContextExpr :: [RoleId] -> DatabaseContextExpr' -> DatabaseContextEvalMonad ()
+ ProjectM36.AccessControl: applyACLDatabaseContextIOExpr :: [RoleId] -> DatabaseContextIOExpr -> DatabaseContextIOEvalMonad ()
+ ProjectM36.AccessControl: applyACLGraphRefRelationalExpr :: [RoleId] -> [RelVarAccessControlList] -> Either RelationalError ()
+ ProjectM36.AccessControl: applyACLRelationalExpr :: Show a => [RoleId] -> RelVarAccessControlList -> RelationalExprBase a -> Either RelationalError ()
+ ProjectM36.AccessControl: applyACLSchemaExpr :: [RoleId] -> SchemaAccessControlList -> SchemaExpr -> Either RelationalError ()
+ ProjectM36.AccessControlList: AccessControlList :: Map role' (RoleAccess permission) -> AccessControlList role' permission
+ ProjectM36.AccessControlList: AccessRelVarsPermission :: RelVarPermission
+ ProjectM36.AccessControlList: AlterACLPermission :: ACLPermission
+ ProjectM36.AccessControlList: AlterDBCFunctionPermission :: DBCFunctionPermission
+ ProjectM36.AccessControlList: AlterFunctionPermission :: FunctionPermission
+ ProjectM36.AccessControlList: AlterSchemaPermission :: AlterSchemaPermission
+ ProjectM36.AccessControlList: CommitTransactionPermission :: AlterTransGraphPermission
+ ProjectM36.AccessControlList: DatabaseContextACL :: RelVarAccessControlList -> FunctionAccessControlList -> AlterTransGraphAccessControlList -> SchemaAccessControlList -> ACLAccessControlList -> DatabaseContextACL
+ ProjectM36.AccessControlList: ExecuteDBCFunctionPermission :: DBCFunctionPermission
+ ProjectM36.AccessControlList: ExecuteFunctionPermission :: FunctionPermission
+ ProjectM36.AccessControlList: SomeACLPermission :: ACLPermission -> SomePermission
+ ProjectM36.AccessControlList: SomeAlterSchemaPermission :: AlterSchemaPermission -> SomePermission
+ ProjectM36.AccessControlList: SomeAlterTransGraphPermission :: AlterTransGraphPermission -> SomePermission
+ ProjectM36.AccessControlList: SomeDBCFunctionPermission :: DBCFunctionPermission -> SomePermission
+ ProjectM36.AccessControlList: SomeFunctionPermission :: FunctionPermission -> SomePermission
+ ProjectM36.AccessControlList: SomeRelVarPermission :: RelVarPermission -> SomePermission
+ ProjectM36.AccessControlList: ViewACLPermission :: ACLPermission
+ ProjectM36.AccessControlList: ViewDBCFunctionPermission :: DBCFunctionPermission
+ ProjectM36.AccessControlList: ViewFunctionPermission :: FunctionPermission
+ ProjectM36.AccessControlList: [aclACL] :: DatabaseContextACL -> ACLAccessControlList
+ ProjectM36.AccessControlList: [dbcFunctionsACL] :: DatabaseContextACL -> FunctionAccessControlList
+ ProjectM36.AccessControlList: [relvarsACL] :: DatabaseContextACL -> RelVarAccessControlList
+ ProjectM36.AccessControlList: [schemaACL] :: DatabaseContextACL -> SchemaAccessControlList
+ ProjectM36.AccessControlList: [transGraphACL] :: DatabaseContextACL -> AlterTransGraphAccessControlList
+ ProjectM36.AccessControlList: addAccess :: (Eq role', Ord role', Eq perm, Ord perm) => role' -> perm -> MayGrant -> AccessControlList role' perm -> AccessControlList role' perm
+ ProjectM36.AccessControlList: adminRoleId :: RoleId
+ ProjectM36.AccessControlList: allPermissions :: AllPermissions a => Set a
+ ProjectM36.AccessControlList: allPermissionsForRoleId :: (AllPermissions p, Ord p) => r -> AccessControlList r p
+ ProjectM36.AccessControlList: basic :: DatabaseContextACL
+ ProjectM36.AccessControlList: class AllPermissions a
+ ProjectM36.AccessControlList: data ACLPermission
+ ProjectM36.AccessControlList: data AlterSchemaPermission
+ ProjectM36.AccessControlList: data AlterTransGraphPermission
+ ProjectM36.AccessControlList: data DBCFunctionPermission
+ ProjectM36.AccessControlList: data DatabaseContextACL
+ ProjectM36.AccessControlList: data FunctionPermission
+ ProjectM36.AccessControlList: data RelVarPermission
+ ProjectM36.AccessControlList: data SomePermission
+ ProjectM36.AccessControlList: empty :: Ord r => AccessControlList r p
+ ProjectM36.AccessControlList: grantable :: perm -> Map perm MayGrant
+ ProjectM36.AccessControlList: hasAccess :: (Ord perm, Ord role', Eq role', Eq perm) => [role'] -> perm -> AccessControlList role' perm -> Bool
+ ProjectM36.AccessControlList: instance (Control.DeepSeq.NFData role', Control.DeepSeq.NFData permission) => Control.DeepSeq.NFData (ProjectM36.AccessControlList.AccessControlList role' permission)
+ ProjectM36.AccessControlList: instance (GHC.Classes.Eq role', GHC.Classes.Eq permission) => GHC.Classes.Eq (ProjectM36.AccessControlList.AccessControlList role' permission)
+ ProjectM36.AccessControlList: instance (GHC.Classes.Ord p, GHC.Classes.Eq r, GHC.Classes.Ord r) => GHC.Base.Monoid (ProjectM36.AccessControlList.AccessControlList r p)
+ ProjectM36.AccessControlList: instance (GHC.Classes.Ord p, GHC.Classes.Eq r, GHC.Classes.Ord r) => GHC.Base.Semigroup (ProjectM36.AccessControlList.AccessControlList r p)
+ ProjectM36.AccessControlList: instance (GHC.Classes.Ord role', GHC.Classes.Ord permission, GHC.Read.Read role', GHC.Read.Read permission) => GHC.Read.Read (ProjectM36.AccessControlList.AccessControlList role' permission)
+ ProjectM36.AccessControlList: instance (GHC.Show.Show role', GHC.Show.Show permission) => GHC.Show.Show (ProjectM36.AccessControlList.AccessControlList role' permission)
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.DatabaseContextACL
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance Control.DeepSeq.NFData ProjectM36.AccessControlList.SomePermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance Data.Hashable.Class.Hashable ProjectM36.AccessControlList.SomePermission
+ ProjectM36.AccessControlList: instance GHC.Base.Monoid ProjectM36.AccessControlList.DatabaseContextACL
+ ProjectM36.AccessControlList: instance GHC.Base.Semigroup ProjectM36.AccessControlList.DatabaseContextACL
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Eq ProjectM36.AccessControlList.SomePermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance GHC.Classes.Ord r => Data.Default.Internal.Default (ProjectM36.AccessControlList.AccessControlList r p)
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic (ProjectM36.AccessControlList.AccessControlList role' permission)
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.DatabaseContextACL
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance GHC.Generics.Generic ProjectM36.AccessControlList.SomePermission
+ ProjectM36.AccessControlList: instance GHC.Read.Read ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.DatabaseContextACL
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: instance GHC.Show.Show ProjectM36.AccessControlList.SomePermission
+ ProjectM36.AccessControlList: instance ProjectM36.AccessControlList.AllPermissions ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.AccessControlList: instance ProjectM36.AccessControlList.AllPermissions ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.AccessControlList: instance ProjectM36.AccessControlList.AllPermissions ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.AccessControlList: instance ProjectM36.AccessControlList.AllPermissions ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.AccessControlList: instance ProjectM36.AccessControlList.AllPermissions ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.AccessControlList: instance ProjectM36.AccessControlList.AllPermissions ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.AccessControlList: merge :: (Ord p, Eq r, Ord r) => AccessControlList r p -> AccessControlList r p -> AccessControlList r p
+ ProjectM36.AccessControlList: newtype AccessControlList role' permission
+ ProjectM36.AccessControlList: normalize :: AccessControlList r p -> AccessControlList r p
+ ProjectM36.AccessControlList: notGrantable :: perm -> Map perm MayGrant
+ ProjectM36.AccessControlList: removeAccess :: (Eq role', Ord role', Eq perm, Ord perm) => role' -> perm -> AccessControlList role' perm -> AccessControlList role' perm
+ ProjectM36.AccessControlList: type ACLAccessControlList = AccessControlList RoleId ACLPermission
+ ProjectM36.AccessControlList: type AlterTransGraphAccessControlList = AccessControlList RoleId AlterTransGraphPermission
+ ProjectM36.AccessControlList: type DBCFunctionAccessControlList = AccessControlList RoleId DBCFunctionPermission
+ ProjectM36.AccessControlList: type FunctionAccessControlList = AccessControlList RoleId FunctionPermission
+ ProjectM36.AccessControlList: type MayGrant = Bool
+ ProjectM36.AccessControlList: type RelVarAccessControlList = AccessControlList RoleId RelVarPermission
+ ProjectM36.AccessControlList: type RoleAccess permission = Map permission MayGrant
+ ProjectM36.AccessControlList: type RoleId = UUID
+ ProjectM36.AccessControlList: type SchemaAccessControlList = AccessControlList RoleId AlterSchemaPermission
+ ProjectM36.AtomFunctions.SelfTest: selfTestAtomFunctions :: AtomFunctions
+ ProjectM36.Attribute: reorderAttributes :: Attributes -> Attributes -> Either RelationalError Attributes
+ ProjectM36.AttributeExpr: typeConstructor :: AttributeExprBase a -> Maybe TypeConstructor
+ ProjectM36.BTree: BTree :: Branches -> Int -> Vector Int -> BTree
+ ProjectM36.BTree: [branches] :: BTree -> Branches
+ ProjectM36.BTree: [elemCount] :: BTree -> Int
+ ProjectM36.BTree: [vec] :: BTree -> Vector Int
+ ProjectM36.BTree: build :: Branches -> Vector Int -> BTree
+ ProjectM36.BTree: data BTree
+ ProjectM36.BTree: instance Control.DeepSeq.NFData ProjectM36.BTree.BTree
+ ProjectM36.BTree: instance GHC.Generics.Generic ProjectM36.BTree.BTree
+ ProjectM36.BTree: instance GHC.Show.Show ProjectM36.BTree.BTree
+ ProjectM36.BTree: member :: Int -> BTree -> Bool
+ ProjectM36.BTree: size :: BTree -> Int
+ ProjectM36.BTree: totalBytes :: BTree -> Int
+ ProjectM36.BTree: type Branches = Int
+ ProjectM36.BTree: type Index = Int
+ ProjectM36.BTree: type Level = Int
+ ProjectM36.Base: AddInclusionDependencyF :: Text -> InclusionDependency -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: AddNotificationF :: Text -> RelationalExprBase () -> RelationalExprBase () -> RelationalExprBase () -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: AddRegisteredQueryF :: Text -> RelationalExprBase () -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: AddTypeConstructorF :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: AlterACL :: AlterDBCACLExprBase r -> DatabaseContextExprBase a r
+ ProjectM36.Base: AlterACLF :: AlterDBCACLExprBase r_aF1t -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: AssignF :: Text -> RelationalExprBase a_aF1s -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: DefineF :: Text -> [AttributeExprBase a_aF1s] -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: DeleteF :: Text -> RestrictionPredicateExprBase a_aF1s -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: ExecuteDatabaseContextFunctionF :: Text -> [AtomExprBase a_aF1s] -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: GrantAccessExpr :: r -> SomePermission -> MayGrant -> AlterDBCACLExprBase r
+ ProjectM36.Base: GrantAccessExprF :: r_aEXM -> SomePermission -> Bool -> AlterDBCACLExprBaseF (r_aEXM :: Type) r_aRVC
+ ProjectM36.Base: GrantDBCFunctionAccessExpr :: r -> FunctionName -> DBCFunctionPermission -> MayGrant -> AlterDBCACLExprBase r
+ ProjectM36.Base: GrantDBCFunctionAccessExprF :: r_aEXM -> Text -> DBCFunctionPermission -> Bool -> AlterDBCACLExprBaseF (r_aEXM :: Type) r_aRVC
+ ProjectM36.Base: InsertF :: Text -> RelationalExprBase a_aF1s -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: MultipleExprF :: [r_aRgI] -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: NoOperationF :: DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RemoveAtomFunctionF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RemoveDatabaseContextFunctionF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RemoveInclusionDependencyF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RemoveNotificationF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RemoveRegisteredQueryF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RemoveTypeConstructorF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: RevokeAccessExpr :: r -> SomePermission -> AlterDBCACLExprBase r
+ ProjectM36.Base: RevokeAccessExprF :: r_aEXM -> SomePermission -> AlterDBCACLExprBaseF (r_aEXM :: Type) r_aRVC
+ ProjectM36.Base: RevokeDBCFunctionAccessExpr :: r -> FunctionName -> DBCFunctionPermission -> AlterDBCACLExprBase r
+ ProjectM36.Base: RevokeDBCFunctionAccessExprF :: r_aEXM -> Text -> DBCFunctionPermission -> AlterDBCACLExprBaseF (r_aEXM :: Type) r_aRVC
+ ProjectM36.Base: UndefineF :: Text -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: UpdateF :: Text -> Map Text (AtomExprBase ()) -> RestrictionPredicateExprBase a_aF1s -> DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: [$sel:asList:RelationTupleSet] :: RelationTupleSet -> [RelationTuple]
+ ProjectM36.Base: [$sel:attributesVec:Attributes] :: Attributes -> Vector Attribute
+ ProjectM36.Base: [$sel:changeExpr:Notification] :: Notification -> RelationalExpr
+ ProjectM36.Base: [$sel:funcACL:Function] :: Function a acl -> acl
+ ProjectM36.Base: [$sel:funcBody:Function] :: Function a acl -> FunctionBody a
+ ProjectM36.Base: [$sel:funcName:Function] :: Function a acl -> FunctionName
+ ProjectM36.Base: [$sel:funcType:Function] :: Function a acl -> [AtomType]
+ ProjectM36.Base: [$sel:reportNewExpr:Notification] :: Notification -> RelationalExpr
+ ProjectM36.Base: [$sel:reportOldExpr:Notification] :: Notification -> RelationalExpr
+ ProjectM36.Base: data AlterDBCACLExprBase r
+ ProjectM36.Base: data AlterDBCACLExprBaseF (r_aEXM :: Type) r_aRVC
+ ProjectM36.Base: data DatabaseContextExprBaseF (a_aF1s :: Type) (r_aF1t :: Type) r_aRgI
+ ProjectM36.Base: instance (Control.DeepSeq.NFData a, Control.DeepSeq.NFData acl) => Control.DeepSeq.NFData (ProjectM36.Base.Function a acl)
+ ProjectM36.Base: instance (Control.DeepSeq.NFData a, Control.DeepSeq.NFData r) => Control.DeepSeq.NFData (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Base: instance (GHC.Classes.Eq a, GHC.Classes.Eq r) => GHC.Classes.Eq (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Base: instance (GHC.Show.Show a, GHC.Show.Show r) => GHC.Show.Show (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Base: instance Control.DeepSeq.NFData r => Control.DeepSeq.NFData (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: instance Data.Foldable.Foldable (ProjectM36.Base.AlterDBCACLExprBaseF r)
+ ProjectM36.Base: instance Data.Foldable.Foldable (ProjectM36.Base.DatabaseContextExprBaseF a r)
+ ProjectM36.Base: instance Data.Functor.Foldable.Corecursive (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: instance Data.Functor.Foldable.Corecursive (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Base: instance Data.Functor.Foldable.Recursive (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: instance Data.Functor.Foldable.Recursive (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.AtomExprBase ProjectM36.Base.TransactionId)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.AttributeNamesBase ProjectM36.Base.TransactionId)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.ExtendTupleExprBase ProjectM36.Base.TransactionId)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.Function a acl)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.RestrictionPredicateExprBase ProjectM36.Base.TransactionId)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.TupleExprBase ProjectM36.Base.TransactionId)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable (ProjectM36.Base.TupleExprsBase ProjectM36.Base.TransactionId)
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable ProjectM36.Base.PinnedRelationalExpr
+ ProjectM36.Base: instance Data.Hashable.Class.Hashable r => Data.Hashable.Class.Hashable (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: instance Data.Traversable.Traversable (ProjectM36.Base.AlterDBCACLExprBaseF r)
+ ProjectM36.Base: instance Data.Traversable.Traversable (ProjectM36.Base.DatabaseContextExprBaseF a r)
+ ProjectM36.Base: instance GHC.Base.Functor (ProjectM36.Base.AlterDBCACLExprBaseF r)
+ ProjectM36.Base: instance GHC.Base.Functor (ProjectM36.Base.DatabaseContextExprBaseF a r)
+ ProjectM36.Base: instance GHC.Classes.Eq (ProjectM36.Base.Function a acl)
+ ProjectM36.Base: instance GHC.Classes.Eq r => GHC.Classes.Eq (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: instance GHC.Generics.Generic (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: instance GHC.Generics.Generic (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Base: instance GHC.Generics.Generic (ProjectM36.Base.Function a acl)
+ ProjectM36.Base: instance GHC.Show.Show r => GHC.Show.Show (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Base: type AlterDBCACLExpr = AlterDBCACLExprBase RoleName
+ ProjectM36.Base: type AlterDBCACLRoleIdExpr = AlterDBCACLExprBase RoleId
+ ProjectM36.Base: type DatabaseContextExpr' = DatabaseContextExprBase () RoleId
+ ProjectM36.Base: type GraphRefDatabaseContextExpr' = DatabaseContextExprBase GraphRefTransactionMarker RoleId
+ ProjectM36.Base: type Permission = Text
+ ProjectM36.Base: type PinnedRelationalExpr = RelationalExprBase TransactionId
+ ProjectM36.Base: type RenameAssocs = Set (AttributeName, AttributeName)
+ ProjectM36.Base: type RoleName = Text
+ ProjectM36.Cache.RelationalExprCache: AscSortOrder :: SortOrder
+ ProjectM36.Cache.RelationalExprCache: DescSortOrder :: SortOrder
+ ProjectM36.Cache.RelationalExprCache: PinnedExpressionRep :: PinnedRelationalExpr -> RelationRepresentation
+ ProjectM36.Cache.RelationalExprCache: RelExprCache :: TVar ByteCount -> TVar ByteCount -> Map PinnedRelationalExpr RelExprCacheInfo -> RelExprCache
+ ProjectM36.Cache.RelationalExprCache: RelExprCacheInfo :: !NominalDiffTime -> RelationRepresentation -> !UTCTime -> !Maybe UTCTime -> !ByteCount -> RelExprCacheInfo
+ ProjectM36.Cache.RelationalExprCache: SortedTuplesRep :: [RelationTuple] -> NonEmpty (AttributeName, SortOrder) -> RelationRepresentation
+ ProjectM36.Cache.RelationalExprCache: UnsortedTupleSetRep :: Attributes -> RelationTupleSet -> RelationRepresentation
+ ProjectM36.Cache.RelationalExprCache: [cacheMap] :: RelExprCache -> Map PinnedRelationalExpr RelExprCacheInfo
+ ProjectM36.Cache.RelationalExprCache: [calculatedInTime] :: RelExprCacheInfo -> !NominalDiffTime
+ ProjectM36.Cache.RelationalExprCache: [createTime] :: RelExprCacheInfo -> !UTCTime
+ ProjectM36.Cache.RelationalExprCache: [currentSize] :: RelExprCache -> TVar ByteCount
+ ProjectM36.Cache.RelationalExprCache: [lastRequestTime] :: RelExprCacheInfo -> !Maybe UTCTime
+ ProjectM36.Cache.RelationalExprCache: [result] :: RelExprCacheInfo -> RelationRepresentation
+ ProjectM36.Cache.RelationalExprCache: [size] :: RelExprCacheInfo -> !ByteCount
+ ProjectM36.Cache.RelationalExprCache: [upperBound] :: RelExprCache -> TVar ByteCount
+ ProjectM36.Cache.RelationalExprCache: add :: RandomGen g => g -> PinnedRelationalExpr -> RelationRepresentation -> NominalDiffTime -> IsRegisteredQuery -> MemoryStats -> RelExprCache -> STM g
+ ProjectM36.Cache.RelationalExprCache: data RelExprCache
+ ProjectM36.Cache.RelationalExprCache: data RelExprCacheInfo
+ ProjectM36.Cache.RelationalExprCache: data RelationRepresentation
+ ProjectM36.Cache.RelationalExprCache: data SortOrder
+ ProjectM36.Cache.RelationalExprCache: defaultUpperBound :: IO ByteCount
+ ProjectM36.Cache.RelationalExprCache: empty :: ByteCount -> IO RelExprCache
+ ProjectM36.Cache.RelationalExprCache: euler :: Double
+ ProjectM36.Cache.RelationalExprCache: executeLRUStrategy :: ByteCount -> NominalDiffTime -> RelExprCache -> FreeMemBytes -> STM (Probability, [PinnedRelationalExpr])
+ ProjectM36.Cache.RelationalExprCache: instance ProjectM36.RelExprSize.Size ProjectM36.Cache.RelationalExprCache.RelationRepresentation
+ ProjectM36.Cache.RelationalExprCache: leastRecentlyUsedEntries :: ByteCount -> RelExprCache -> STM [PinnedRelationalExpr]
+ ProjectM36.Cache.RelationalExprCache: logistic :: Double -> Double -> Double -> Double
+ ProjectM36.Cache.RelationalExprCache: logisticProb :: Double -> Double -> Double
+ ProjectM36.Cache.RelationalExprCache: lookup :: PinnedRelationalExpr -> RelExprCache -> STM (Maybe RelExprCacheInfo)
+ ProjectM36.Cache.RelationalExprCache: normalizedLogProb :: Double -> Double -> Double -> Double
+ ProjectM36.Cache.RelationalExprCache: purgeToSize :: RelExprCache -> ByteCount -> STM ()
+ ProjectM36.Cache.RelationalExprCache: stdlogistic :: Double -> Double
+ ProjectM36.Cache.RelationalExprCache: type HitCount = Int64
+ ProjectM36.Cache.RelationalExprCache: type IsRegisteredQuery = Bool
+ ProjectM36.Cache.RelationalExprCache: type MemoryPressure = Double
+ ProjectM36.Cache.RelationalExprCache: type Probability = Double
+ ProjectM36.Cache.Tuple: InvalidFileMagicException :: ByteString -> InvalidFileMagicException
+ ProjectM36.Cache.Tuple: SerialisationSchemaMismatch :: SerialisationSchemaMismatch
+ ProjectM36.Cache.Tuple: TruncatedFileException :: ByteCount -> TruncatedFileException
+ ProjectM36.Cache.Tuple: TupleCacheInfo :: Vector (ByteOffset, ByteCount) -> PinnedRelationalExpr -> UTCTime -> Schema -> TupleCacheInfo
+ ProjectM36.Cache.Tuple: [blockSizes] :: TupleCacheInfo -> Vector (ByteOffset, ByteCount)
+ ProjectM36.Cache.Tuple: [created] :: TupleCacheInfo -> UTCTime
+ ProjectM36.Cache.Tuple: [representing] :: TupleCacheInfo -> PinnedRelationalExpr
+ ProjectM36.Cache.Tuple: [tupleSchema] :: TupleCacheInfo -> Schema
+ ProjectM36.Cache.Tuple: blockCount :: TupleCacheInfo -> Int
+ ProjectM36.Cache.Tuple: data SerialisationSchemaMismatch
+ ProjectM36.Cache.Tuple: data TupleCacheInfo
+ ProjectM36.Cache.Tuple: deserialiseOnly' :: forall s. Serialise s => ByteString -> Either WineryException s
+ ProjectM36.Cache.Tuple: fileMagic :: FileMagic
+ ProjectM36.Cache.Tuple: instance Codec.Winery.Class.Serialise ProjectM36.Cache.Tuple.TupleCacheInfo
+ ProjectM36.Cache.Tuple: instance GHC.Exception.Type.Exception ProjectM36.Cache.Tuple.InvalidFileMagicException
+ ProjectM36.Cache.Tuple: instance GHC.Exception.Type.Exception ProjectM36.Cache.Tuple.SerialisationSchemaMismatch
+ ProjectM36.Cache.Tuple: instance GHC.Exception.Type.Exception ProjectM36.Cache.Tuple.TruncatedFileException
+ ProjectM36.Cache.Tuple: instance GHC.Generics.Generic ProjectM36.Cache.Tuple.TupleCacheInfo
+ ProjectM36.Cache.Tuple: instance GHC.Show.Show ProjectM36.Cache.Tuple.InvalidFileMagicException
+ ProjectM36.Cache.Tuple: instance GHC.Show.Show ProjectM36.Cache.Tuple.SerialisationSchemaMismatch
+ ProjectM36.Cache.Tuple: instance GHC.Show.Show ProjectM36.Cache.Tuple.TruncatedFileException
+ ProjectM36.Cache.Tuple: instance GHC.Show.Show ProjectM36.Cache.Tuple.TupleCacheInfo
+ ProjectM36.Cache.Tuple: newtype InvalidFileMagicException
+ ProjectM36.Cache.Tuple: newtype TruncatedFileException
+ ProjectM36.Cache.Tuple: readBlock :: Serialise a => Handle -> IO a
+ ProjectM36.Cache.Tuple: readByteCount :: Handle -> IO Int
+ ProjectM36.Cache.Tuple: readCacheInfo :: Handle -> IO TupleCacheInfo
+ ProjectM36.Cache.Tuple: readTupleStream :: MonadIO m => Handle -> Stream m RelationTuple
+ ProjectM36.Cache.Tuple: type ByteOffset = ByteCount
+ ProjectM36.Cache.Tuple: type FileMagic = ByteString
+ ProjectM36.Cache.Tuple: validateQuickly :: Handle -> IO ()
+ ProjectM36.Cache.Tuple: writeTupleStream :: Handle -> PinnedRelationalExpr -> ByteCount -> [RelationTuple] -> IO ()
+ ProjectM36.Client: AcceptAnonymousClient :: ClientAuth
+ ProjectM36.Client: AccessDeniedError :: SomePermission -> RelationalError
+ ProjectM36.Client: AccessRelVarsPermission :: RelVarPermission
+ ProjectM36.Client: AddLoginRoleExpr :: RoleName -> MayLogin -> AlterLoginRolesExpr
+ ProjectM36.Client: AddPermissionToRoleExpr :: RoleName -> Permission -> MayGrant -> AlterLoginRolesExpr
+ ProjectM36.Client: AddRoleToRoleExpr :: RoleName -> RoleName -> MayGrant -> AlterLoginRolesExpr
+ ProjectM36.Client: AlterACL :: AlterDBCACLExprBase r -> DatabaseContextExprBase a r
+ ProjectM36.Client: AlterDBCFunctionPermission :: DBCFunctionPermission
+ ProjectM36.Client: AlterFunctionPermission :: FunctionPermission
+ ProjectM36.Client: AlterLoginRoleExpr :: RoleName -> Maybe RoleName -> Maybe MayLogin -> AlterLoginRolesExpr
+ ProjectM36.Client: AttributeExtendTupleExpr :: AttributeName -> AtomExprBase a -> ExtendTupleExprBase a
+ ProjectM36.Client: ClientAuthRequired :: ClientAuth
+ ProjectM36.Client: CurrentHeadBranch :: HeadName -> CurrentHead
+ ProjectM36.Client: CurrentHeadTransactionId :: TransactionId -> CurrentHead
+ ProjectM36.Client: ExecuteDBCFunctionPermission :: DBCFunctionPermission
+ ProjectM36.Client: ExecuteFunctionPermission :: FunctionPermission
+ ProjectM36.Client: GrantAccessExpr :: r -> SomePermission -> MayGrant -> AlterDBCACLExprBase r
+ ProjectM36.Client: GrantDBCFunctionAccessExpr :: r -> FunctionName -> DBCFunctionPermission -> MayGrant -> AlterDBCACLExprBase r
+ ProjectM36.Client: HeadNameAlreadyInUseError :: HeadName -> RelationalError
+ ProjectM36.Client: InProcessClientInfo :: MVar EvaluatedNotifications -> ClientInfo
+ ProjectM36.Client: InProcessConnectionConf :: PersistenceStrategy -> ClientNodes -> Sessions -> TVar TransactionGraph -> Maybe ScriptSession -> Maybe (LockFile, MVar LockFileHash) -> Async () -> RelExprCache -> LoginRolesDB -> RoleName -> StdGen -> InProcessConnectionConf
+ ProjectM36.Client: InfoResult :: Text -> SuccessResult
+ ProjectM36.Client: NoSuchRoleIdError :: RelationalError
+ ProjectM36.Client: NoSuchRoleNameError :: RoleName -> RelationalError
+ ProjectM36.Client: QuietSuccessResult :: SuccessResult
+ ProjectM36.Client: RelVarNameShadowingForbiddenError :: RelVarName -> RelationalError
+ ProjectM36.Client: RemoteClientInfo :: SocketContext -> RoleName -> ClientInfo
+ ProjectM36.Client: RemoveLoginRoleExpr :: RoleName -> AlterLoginRolesExpr
+ ProjectM36.Client: RemovePermissionFromRoleExpr :: RoleName -> Permission -> AlterLoginRolesExpr
+ ProjectM36.Client: RemoveRoleFromRoleExpr :: RoleName -> RoleName -> AlterLoginRolesExpr
+ ProjectM36.Client: RevokeAccessExpr :: r -> SomePermission -> AlterDBCACLExprBase r
+ ProjectM36.Client: RevokeDBCFunctionAccessExpr :: r -> FunctionName -> DBCFunctionPermission -> AlterDBCACLExprBase r
+ ProjectM36.Client: ShowAllRolesExpr :: AlterLoginRolesExpr
+ ProjectM36.Client: ShowRolesForRoleExpr :: RoleName -> AlterLoginRolesExpr
+ ProjectM36.Client: SomeACLPermission :: ACLPermission -> SomePermission
+ ProjectM36.Client: SomeAlterSchemaPermission :: AlterSchemaPermission -> SomePermission
+ ProjectM36.Client: SomeAlterTransGraphPermission :: AlterTransGraphPermission -> SomePermission
+ ProjectM36.Client: SomeDBCFunctionPermission :: DBCFunctionPermission -> SomePermission
+ ProjectM36.Client: SomeFunctionPermission :: FunctionPermission -> SomePermission
+ ProjectM36.Client: SomeRelVarPermission :: RelVarPermission -> SomePermission
+ ProjectM36.Client: SystemError :: String -> RelationalError
+ ProjectM36.Client: TlsConfig :: String -> String -> Maybe (FilePath, FilePath) -> Maybe FilePath -> TlsConfig
+ ProjectM36.Client: ViewDBCFunctionPermission :: DBCFunctionPermission
+ ProjectM36.Client: ViewFunctionPermission :: FunctionPermission
+ ProjectM36.Client: [ipCallbackAsync] :: InProcessConnectionConf -> Async ()
+ ProjectM36.Client: [ipClientNodes] :: InProcessConnectionConf -> ClientNodes
+ ProjectM36.Client: [ipLocks] :: InProcessConnectionConf -> Maybe (LockFile, MVar LockFileHash)
+ ProjectM36.Client: [ipLoginRoles] :: InProcessConnectionConf -> LoginRolesDB
+ ProjectM36.Client: [ipPersistenceStrategy] :: InProcessConnectionConf -> PersistenceStrategy
+ ProjectM36.Client: [ipRandomGen] :: InProcessConnectionConf -> StdGen
+ ProjectM36.Client: [ipRelExprCache] :: InProcessConnectionConf -> RelExprCache
+ ProjectM36.Client: [ipRoleName] :: InProcessConnectionConf -> RoleName
+ ProjectM36.Client: [ipScriptSession] :: InProcessConnectionConf -> Maybe ScriptSession
+ ProjectM36.Client: [ipSessions] :: InProcessConnectionConf -> Sessions
+ ProjectM36.Client: [ipTransactionGraph] :: InProcessConnectionConf -> TVar TransactionGraph
+ ProjectM36.Client: [serverTlsHostName] :: TlsConfig -> String
+ ProjectM36.Client: [serverTlsServiceName] :: TlsConfig -> String
+ ProjectM36.Client: [serverX509CertificatePath] :: TlsConfig -> Maybe FilePath
+ ProjectM36.Client: [serverX509PublicPrivateKeyPaths] :: TlsConfig -> Maybe (FilePath, FilePath)
+ ProjectM36.Client: adminRoleName :: RoleName
+ ProjectM36.Client: createSessionAtTransactionId :: Connection -> TransactionId -> IO (Either RelationalError SessionId)
+ ProjectM36.Client: currentHead :: SessionId -> Connection -> IO (Either RelationalError CurrentHead)
+ ProjectM36.Client: data AlterDBCACLExprBase r
+ ProjectM36.Client: data AlterLoginRolesExpr
+ ProjectM36.Client: data AlterTransactionGraphExpr
+ ProjectM36.Client: data () => ClientAuth
+ ProjectM36.Client: data ClientInfo
+ ProjectM36.Client: data CurrentHead
+ ProjectM36.Client: data DBCFunctionPermission
+ ProjectM36.Client: data ExtendTupleExprBase a
+ ProjectM36.Client: data FunctionPermission
+ ProjectM36.Client: data InProcessConnectionConf
+ ProjectM36.Client: data RelVarPermission
+ ProjectM36.Client: data SomePermission
+ ProjectM36.Client: data SuccessResult
+ ProjectM36.Client: data TlsConfig
+ ProjectM36.Client: data TransactionGraphExpr
+ ProjectM36.Client: executeAlterLoginRolesExpr :: SessionId -> Connection -> AlterLoginRolesExpr -> IO (Either LoginRoleError SuccessResult)
+ ProjectM36.Client: executeAlterTransactionGraphExpr :: SessionId -> Connection -> AlterTransactionGraphExpr -> IO (Either RelationalError ())
+ ProjectM36.Client: executeTransactionGraphExpr :: SessionId -> Connection -> TransactionGraphExpr -> IO (Either RelationalError ())
+ ProjectM36.Client: instance GHC.Show.Show ProjectM36.Client.TlsConfig
+ ProjectM36.Client: planForRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Text)
+ ProjectM36.Client: setRoleName :: RoleName -> Connection -> Connection
+ ProjectM36.Client: type RoleName = Text
+ ProjectM36.Client.Simple: AccessDeniedError :: SomePermission -> RelationalError
+ ProjectM36.Client.Simple: AlterACL :: AlterDBCACLExprBase r -> DatabaseContextExprBase a r
+ ProjectM36.Client.Simple: HeadNameAlreadyInUseError :: HeadName -> RelationalError
+ ProjectM36.Client.Simple: NoSuchRoleIdError :: RelationalError
+ ProjectM36.Client.Simple: NoSuchRoleNameError :: RoleName -> RelationalError
+ ProjectM36.Client.Simple: RelVarNameShadowingForbiddenError :: RelVarName -> RelationalError
+ ProjectM36.Client.Simple: SystemError :: String -> RelationalError
+ ProjectM36.DatabaseContext: freshDatabaseContext :: TransactionId -> DatabaseContext -> DatabaseContext
+ ProjectM36.DatabaseContext: isUpdated :: DatabaseContext -> Bool
+ ProjectM36.DatabaseContext: toDatabaseContext :: ResolvedDatabaseContext -> DatabaseContext
+ ProjectM36.DatabaseContext.Basic: basicDatabaseContext :: ResolvedDatabaseContext
+ ProjectM36.DatabaseContext.Fields: AtomFunctionsField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: DbcFunctionsField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: InclusionDependenciesField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: NotificationsField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: RegisteredQueriesField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: RelationVariablesField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: TypeConstructorMappingField :: DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: data DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: instance Control.DeepSeq.NFData ProjectM36.DatabaseContext.Fields.DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: instance GHC.Classes.Eq ProjectM36.DatabaseContext.Fields.DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: instance GHC.Generics.Generic ProjectM36.DatabaseContext.Fields.DatabaseContextField
+ ProjectM36.DatabaseContext.Fields: instance GHC.Show.Show ProjectM36.DatabaseContext.Fields.DatabaseContextField
+ ProjectM36.DatabaseContext.SelfTest: selfTestDatabaseContext :: ResolvedDatabaseContext
+ ProjectM36.DatabaseContext.Types: DatabaseContext :: a InclusionDependencies -> a RelationVariables -> a AtomFunctions -> a DatabaseContextFunctions -> a Notifications -> a TypeConstructorMapping -> a RegisteredQueries -> a DatabaseContextACL -> DatabaseContextBase a
+ ProjectM36.DatabaseContext.Types: DatabaseContextFunctionUtils :: (DatabaseContext -> DatabaseContextExpr -> Either RelationalError DatabaseContext) -> (DatabaseContext -> RelationalExpr -> Either RelationalError Relation) -> DatabaseContextFunctionUtils
+ ProjectM36.DatabaseContext.Types: TransactionIdMarker :: TransactionId -> a -> TransactionIdMarker a
+ ProjectM36.DatabaseContext.Types: [acl] :: DatabaseContextBase a -> a DatabaseContextACL
+ ProjectM36.DatabaseContext.Types: [atomFunctions] :: DatabaseContextBase a -> a AtomFunctions
+ ProjectM36.DatabaseContext.Types: [dbcFunctions] :: DatabaseContextBase a -> a DatabaseContextFunctions
+ ProjectM36.DatabaseContext.Types: [executeDatabaseContextExpr] :: DatabaseContextFunctionUtils -> DatabaseContext -> DatabaseContextExpr -> Either RelationalError DatabaseContext
+ ProjectM36.DatabaseContext.Types: [executeRelationalExpr] :: DatabaseContextFunctionUtils -> DatabaseContext -> RelationalExpr -> Either RelationalError Relation
+ ProjectM36.DatabaseContext.Types: [inclusionDependencies] :: DatabaseContextBase a -> a InclusionDependencies
+ ProjectM36.DatabaseContext.Types: [notifications] :: DatabaseContextBase a -> a Notifications
+ ProjectM36.DatabaseContext.Types: [registeredQueries] :: DatabaseContextBase a -> a RegisteredQueries
+ ProjectM36.DatabaseContext.Types: [relationVariables] :: DatabaseContextBase a -> a RelationVariables
+ ProjectM36.DatabaseContext.Types: [typeConstructorMapping] :: DatabaseContextBase a -> a TypeConstructorMapping
+ ProjectM36.DatabaseContext.Types: data DatabaseContextBase a
+ ProjectM36.DatabaseContext.Types: data DatabaseContextFunctionUtils
+ ProjectM36.DatabaseContext.Types: data TransactionIdMarker a
+ ProjectM36.DatabaseContext.Types: instance Control.DeepSeq.NFData ProjectM36.DatabaseContext.Types.DatabaseContext
+ ProjectM36.DatabaseContext.Types: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (ProjectM36.DatabaseContext.Types.TransactionIdMarker a)
+ ProjectM36.DatabaseContext.Types: instance GHC.Generics.Generic (ProjectM36.DatabaseContext.Types.TransactionIdMarker a)
+ ProjectM36.DatabaseContext.Types: instance GHC.Generics.Generic ProjectM36.DatabaseContext.Types.DatabaseContext
+ ProjectM36.DatabaseContext.Types: type DatabaseContext = DatabaseContextBase ValueMarker
+ ProjectM36.DatabaseContext.Types: type DatabaseContextFunction = Function DatabaseContextFunctionBodyType DBCFunctionAccessControlList
+ ProjectM36.DatabaseContext.Types: type DatabaseContextFunctionBody = FunctionBody DatabaseContextFunctionBodyType
+ ProjectM36.DatabaseContext.Types: type DatabaseContextFunctionBodyType = DatabaseContextFunctionUtils -> [Atom] -> DatabaseContext -> Either RelationalError DatabaseContext
+ ProjectM36.DatabaseContext.Types: type DatabaseContextFunctions = HashSet DatabaseContextFunction
+ ProjectM36.DatabaseContext.Types: type ResolvedDatabaseContext = DatabaseContextBase Identity
+ ProjectM36.DatabaseContextExpr: databaseContextAsDatabaseContextExpr :: DatabaseContext -> TransactionGraph -> Either RelationalError DatabaseContextExpr
+ ProjectM36.DatabaseContextExpr: recurseRoles :: (a -> Either RelationalError b) -> AlterDBCACLExprBase a -> Either RelationalError (AlterDBCACLExprBase b)
+ ProjectM36.DatabaseContextExpr: resolveRoleIds :: (RoleName -> Maybe RoleId) -> DatabaseContextExprBase a RoleName -> Either RelationalError (DatabaseContextExprBase a RoleId)
+ ProjectM36.DatabaseContextExpr: resolveRoleIdsDBCACLExpr :: (RoleName -> Maybe RoleId) -> AlterDBCACLExprBase RoleName -> Either RelationalError (AlterDBCACLExprBase RoleId)
+ ProjectM36.DatabaseContextExpr: resolveRoleNames :: (RoleId -> Maybe RoleName) -> DatabaseContextExprBase a RoleId -> Either RelationalError (DatabaseContextExprBase a RoleName)
+ ProjectM36.DatabaseContextExpr: resolveRoleNamesDBCACLExpr :: (RoleId -> Maybe RoleName) -> AlterDBCACLExprBase RoleId -> Either RelationalError (AlterDBCACLExprBase RoleName)
+ ProjectM36.DatabaseContextExpr: resolvedDatabaseContextAsDatabaseContextExpr :: ResolvedDatabaseContext -> DatabaseContextExpr
+ ProjectM36.DatabaseContextFunctionUtils: dud :: DatabaseContextFunctionUtils
+ ProjectM36.DatabaseContextFunctions.Basic: basicDatabaseContextFunctions :: DatabaseContextFunctions
+ ProjectM36.DatabaseContextFunctions.Basic: defaultACL :: DBCFunctionAccessControlList
+ ProjectM36.DatabaseContextFunctions.Basic: precompiledDatabaseContextFunctions :: DatabaseContextFunctions
+ ProjectM36.DateExamples: dateExamplesDatabaseContextExpr :: Either RelationalError DatabaseContextExpr
+ ProjectM36.DisconnectedTransaction: CurrentHeadBranch :: HeadName -> CurrentHead
+ ProjectM36.DisconnectedTransaction: CurrentHeadTransactionId :: TransactionId -> CurrentHead
+ ProjectM36.DisconnectedTransaction: DisconnectedTransaction :: TransactionId -> Schemas DatabaseContext -> CurrentHead -> DisconnectedTransaction
+ ProjectM36.DisconnectedTransaction: [disconCurrentHead] :: DisconnectedTransaction -> CurrentHead
+ ProjectM36.DisconnectedTransaction: [disconSchemas] :: DisconnectedTransaction -> Schemas DatabaseContext
+ ProjectM36.DisconnectedTransaction: [disconTransactionId] :: DisconnectedTransaction -> TransactionId
+ ProjectM36.DisconnectedTransaction: data CurrentHead
+ ProjectM36.DisconnectedTransaction: data DisconnectedTransaction
+ ProjectM36.DisconnectedTransaction: freshTransaction' :: CurrentHead -> TransactionId -> Schemas DatabaseContext -> DisconnectedTransaction
+ ProjectM36.DisconnectedTransaction: instance GHC.Classes.Eq ProjectM36.DisconnectedTransaction.CurrentHead
+ ProjectM36.DisconnectedTransaction: instance GHC.Generics.Generic ProjectM36.DisconnectedTransaction.CurrentHead
+ ProjectM36.DisconnectedTransaction: instance GHC.Show.Show ProjectM36.DisconnectedTransaction.CurrentHead
+ ProjectM36.DisconnectedTransaction: isUpdated :: DisconnectedTransaction -> Bool
+ ProjectM36.DisconnectedTransaction: type TransactionRefSchemas = Schemas DatabaseContext
+ ProjectM36.Error: AccessDeniedError :: SomePermission -> RelationalError
+ ProjectM36.Error: HeadNameAlreadyInUseError :: HeadName -> RelationalError
+ ProjectM36.Error: NoSuchRoleIdError :: RelationalError
+ ProjectM36.Error: NoSuchRoleNameError :: RoleName -> RelationalError
+ ProjectM36.Error: RelVarNameShadowingForbiddenError :: RelVarName -> RelationalError
+ ProjectM36.Error: SystemError :: String -> RelationalError
+ ProjectM36.Error: instance GHC.Exception.Type.Exception ProjectM36.Error.RelationalError
+ ProjectM36.GraphRefRelationalExpr: decomposeGraphRefTransactionMarkers :: GraphRefRelationalExpr -> Set (GraphRefTransactionMarker, RelVarName)
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.DatabaseContext.Types.DatabaseContext
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.DatabaseContext.Types.DatabaseContextFunction
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.DatabaseContext.Types.DatabaseContextFunctions
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.IsomorphicSchema.Types.Schema
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.IsomorphicSchema.Types.SchemaIsomorph
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes ProjectM36.IsomorphicSchema.Types.Subschemas
+ ProjectM36.HashSecurely: instance ProjectM36.HashSecurely.HashBytes a => ProjectM36.HashSecurely.HashBytes (ProjectM36.ValueMarker.ValueMarker a)
+ ProjectM36.IsomorphicSchema.Types: IsoRename :: RelVarName -> RelVarName -> SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: IsoRestrict :: RelVarName -> RestrictionPredicateExpr -> (RelVarName, RelVarName) -> SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: IsoUnion :: (RelVarName, RelVarName) -> RestrictionPredicateExpr -> RelVarName -> SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: Schema :: SchemaIsomorphs -> Schema
+ ProjectM36.IsomorphicSchema.Types: Schemas :: ctx -> ValueMarker Subschemas -> Schemas ctx
+ ProjectM36.IsomorphicSchema.Types: [concreteDatabaseContext] :: Schemas ctx -> ctx
+ ProjectM36.IsomorphicSchema.Types: [subschemas] :: Schemas ctx -> ValueMarker Subschemas
+ ProjectM36.IsomorphicSchema.Types: data SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: data Schemas ctx
+ ProjectM36.IsomorphicSchema.Types: instance GHC.Classes.Eq ProjectM36.IsomorphicSchema.Types.Schema
+ ProjectM36.IsomorphicSchema.Types: instance GHC.Classes.Eq ProjectM36.IsomorphicSchema.Types.SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: instance GHC.Generics.Generic (ProjectM36.IsomorphicSchema.Types.Schemas ctx)
+ ProjectM36.IsomorphicSchema.Types: instance GHC.Generics.Generic ProjectM36.IsomorphicSchema.Types.Schema
+ ProjectM36.IsomorphicSchema.Types: instance GHC.Generics.Generic ProjectM36.IsomorphicSchema.Types.SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: instance GHC.Show.Show ProjectM36.IsomorphicSchema.Types.SchemaIsomorph
+ ProjectM36.IsomorphicSchema.Types: newtype Schema
+ ProjectM36.IsomorphicSchema.Types: type SchemaIsomorphs = [SchemaIsomorph]
+ ProjectM36.IsomorphicSchema.Types: type SchemaName = StringType
+ ProjectM36.IsomorphicSchema.Types: type Subschemas = Map SchemaName Schema
+ ProjectM36.LoginRoles: AddLoginRoleExpr :: RoleName -> MayLogin -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: AddPermissionToRoleExpr :: RoleName -> Permission -> MayGrant -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: AddRoleToRoleExpr :: RoleName -> RoleName -> MayGrant -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: AlterLoginRoleExpr :: RoleName -> Maybe RoleName -> Maybe MayLogin -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: InfoResult :: Text -> SuccessResult
+ ProjectM36.LoginRoles: InvalidRoleIdError :: Text -> LoginRoleError
+ ProjectM36.LoginRoles: NoSuchPermissionError :: Permission -> LoginRoleError
+ ProjectM36.LoginRoles: NoSuchRoleNameError :: RoleName -> LoginRoleError
+ ProjectM36.LoginRoles: PermissionDeniedError :: LoginRoleError
+ ProjectM36.LoginRoles: QuietSuccessResult :: SuccessResult
+ ProjectM36.LoginRoles: RemoveLoginRoleExpr :: RoleName -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: RemovePermissionFromRoleExpr :: RoleName -> Permission -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: RemoveRoleFromRoleExpr :: RoleName -> RoleName -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: RoleNameAlreadyExistsError :: RoleName -> LoginRoleError
+ ProjectM36.LoginRoles: RoleNameConflictError :: RoleName -> LoginRoleError
+ ProjectM36.LoginRoles: ShowAllRolesExpr :: AlterLoginRolesExpr
+ ProjectM36.LoginRoles: ShowRolesForRoleExpr :: RoleName -> AlterLoginRolesExpr
+ ProjectM36.LoginRoles: addPermissionToRoleName :: RoleName -> Permission -> Bool -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: addRoleName :: RoleName -> RoleId -> MayLogin -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: addRoleToRoleName :: RoleName -> RoleName -> MayGrant -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: adminRoleName :: RoleName
+ ProjectM36.LoginRoles: allRoles :: LoginRolesDB -> IO [(RoleName, (RoleId, MayLogin))]
+ ProjectM36.LoginRoles: alterLoginRolesPerm :: Permission
+ ProjectM36.LoginRoles: alterRoleName :: RoleName -> Maybe RoleName -> Maybe MayLogin -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: close :: LoginRolesDB -> IO ()
+ ProjectM36.LoginRoles: data AlterLoginRolesExpr
+ ProjectM36.LoginRoles: data LoginRoleError
+ ProjectM36.LoginRoles: data SuccessResult
+ ProjectM36.LoginRoles: executeAlterLoginRolesExpr :: RoleName -> LoginRolesDB -> AlterLoginRolesExpr -> IO (Either LoginRoleError SuccessResult)
+ ProjectM36.LoginRoles: instance GHC.Classes.Eq ProjectM36.LoginRoles.AlterLoginRolesExpr
+ ProjectM36.LoginRoles: instance GHC.Classes.Eq ProjectM36.LoginRoles.LoginRoleError
+ ProjectM36.LoginRoles: instance GHC.Classes.Eq ProjectM36.LoginRoles.SuccessResult
+ ProjectM36.LoginRoles: instance GHC.Generics.Generic ProjectM36.LoginRoles.AlterLoginRolesExpr
+ ProjectM36.LoginRoles: instance GHC.Generics.Generic ProjectM36.LoginRoles.LoginRoleError
+ ProjectM36.LoginRoles: instance GHC.Generics.Generic ProjectM36.LoginRoles.SuccessResult
+ ProjectM36.LoginRoles: instance GHC.Show.Show ProjectM36.LoginRoles.AlterLoginRolesExpr
+ ProjectM36.LoginRoles: instance GHC.Show.Show ProjectM36.LoginRoles.LoginRoleError
+ ProjectM36.LoginRoles: instance GHC.Show.Show ProjectM36.LoginRoles.SuccessResult
+ ProjectM36.LoginRoles: open :: String -> IO LoginRolesDB
+ ProjectM36.LoginRoles: openNoPersistence :: IO LoginRolesDB
+ ProjectM36.LoginRoles: permissionsForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError [Permission])
+ ProjectM36.LoginRoles: removePermissionFromRoleName :: RoleName -> Permission -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: removeRole :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: removeRoleFromRoleName :: RoleName -> RoleName -> LoginRolesDB -> IO (Either LoginRoleError ())
+ ProjectM36.LoginRoles: roleHasPermission :: RoleName -> Permission -> LoginRolesDB -> IO (Either LoginRoleError Bool)
+ ProjectM36.LoginRoles: roleIdForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError RoleId)
+ ProjectM36.LoginRoles: roleIdsForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError [RoleId])
+ ProjectM36.LoginRoles: roleMayGrantRole :: RoleName -> RoleName -> LoginRolesDB -> IO (Either LoginRoleError Bool)
+ ProjectM36.LoginRoles: roleNameMayLogin :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError Bool)
+ ProjectM36.LoginRoles: roleNamesForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError [(RoleName, MayGrant)])
+ ProjectM36.LoginRoles: setupDatabaseIfNecessary :: LoginRolesDB -> IO ()
+ ProjectM36.LoginRoles: type LoginRolesDB = Connection
+ ProjectM36.LoginRoles: type MayLogin = Bool
+ ProjectM36.LoginRoles: viewLoginRolesPerm :: Permission
+ ProjectM36.LoginRoles: withTransaction :: LoginRolesDB -> IO a -> IO a
+ ProjectM36.Persist: readDeserialise :: Serialise a => FilePath -> IO a
+ ProjectM36.PinnedRelationalExpr: toGraphRefRelationalExpr :: PinnedRelationalExpr -> GraphRefRelationalExpr
+ ProjectM36.PinnedRelationalExpr: toPinnedRelationalExpr :: GraphRefRelationalExpr -> Maybe PinnedRelationalExpr
+ ProjectM36.PrettyBytes: prettyBytes :: Int64 -> String
+ ProjectM36.PrettyBytes: prettyBytes' :: [String] -> Int64 -> String
+ ProjectM36.PrettyBytes: units :: [String]
+ ProjectM36.ReferencedTransactionIds: instance ProjectM36.ReferencedTransactionIds.ReferencedTransactionIds ProjectM36.DatabaseContext.Types.DatabaseContext
+ ProjectM36.ReferencedTransactionIds: instance ProjectM36.ReferencedTransactionIds.ReferencedTransactionIds a => ProjectM36.ReferencedTransactionIds.ReferencedTransactionIds (ProjectM36.ValueMarker.ValueMarker a)
+ ProjectM36.RelExprSize: class Size a
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size (ProjectM36.Base.AtomExprBase a)
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size (ProjectM36.Base.RelationalExprBase a)
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size (ProjectM36.Base.TupleExprBase a)
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size (ProjectM36.Base.TupleExprsBase a)
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size Data.Scientific.Scientific
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size Data.Time.Calendar.Days.Day
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size Data.Time.Clock.Internal.DiffTime.DiffTime
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size GHC.Num.Integer.Integer
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size ProjectM36.Base.Atom
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size ProjectM36.Base.Relation
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size ProjectM36.Base.RelationTuple
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size ProjectM36.Base.RelationTupleSet
+ ProjectM36.RelExprSize: instance ProjectM36.RelExprSize.Size [ProjectM36.Base.RelationTuple]
+ ProjectM36.RelExprSize: ptrSize :: Int -> Int64
+ ProjectM36.RelExprSize: size :: Size a => a -> ByteCount
+ ProjectM36.RelExprSize: type ByteCount = Int64
+ ProjectM36.Relation: ContextTuples :: [RelationTuple] -> ContextTuples
+ ProjectM36.Relation: addContextTuple :: ContextTuples -> RelationTuple -> ContextTuples
+ ProjectM36.Relation: contextTupleAtomForAttributeName :: ContextTuples -> AttributeName -> Either RelationalError Atom
+ ProjectM36.Relation: contextTupleAtomForAttributeName' :: RelationTuple -> ContextTuples -> AttributeName -> Either RelationalError Atom
+ ProjectM36.Relation: instance GHC.Base.Monoid ProjectM36.Relation.ContextTuples
+ ProjectM36.Relation: instance GHC.Base.Semigroup ProjectM36.Relation.ContextTuples
+ ProjectM36.Relation: instance GHC.Classes.Eq ProjectM36.Relation.ContextTuples
+ ProjectM36.Relation: instance GHC.Show.Show ProjectM36.Relation.ContextTuples
+ ProjectM36.Relation: newtype ContextTuples
+ ProjectM36.Relation: singletonContextTuple :: RelationTuple -> ContextTuples
+ ProjectM36.Relation: tupleSet :: Relation -> RelationTupleSet
+ ProjectM36.RelationVariablesMentioned: class RelationVariablesMentioned a
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned (ProjectM36.Base.AtomExprBase a)
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned (ProjectM36.Base.ExtendTupleExprBase a)
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned (ProjectM36.Base.RelationalExprBase a)
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned (ProjectM36.Base.TupleExprBase a)
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned (ProjectM36.Base.TupleExprsBase a)
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned ProjectM36.Base.Atom
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned ProjectM36.Base.RelationTuple
+ ProjectM36.RelationVariablesMentioned: instance ProjectM36.RelationVariablesMentioned.RelationVariablesMentioned ProjectM36.Base.RelationTupleSet
+ ProjectM36.RelationVariablesMentioned: mentionsRelVar :: RelationVariablesMentioned a => a -> Bool
+ ProjectM36.RelationVariablesMentioned: relVarsMentioned :: RelationVariablesMentioned a => a -> Set RelVarName
+ ProjectM36.RelationalExpression: [dbcio_dbcfunctionUtils] :: DatabaseContextIOEvalEnv -> DatabaseContextFunctionUtils
+ ProjectM36.RelationalExpression: [dbcio_roleId] :: DatabaseContextIOEvalEnv -> RoleId
+ ProjectM36.RelationalExpression: [dce_dbcfuncutils] :: DatabaseContextEvalEnv -> DatabaseContextFunctionUtils
+ ProjectM36.RelationalExpression: evalAlterDBCACLRoleIdExpr :: AlterDBCACLRoleIdExpr -> DatabaseContextEvalMonad ()
+ ProjectM36.RelationalExpression: getGraph :: DatabaseContextM m => m TransactionGraph
+ ProjectM36.RelationalExpression: isEmptyRelationExpr :: RelationalExprBase a -> Bool
+ ProjectM36.RelationalExpression: mkEmptyRelVars' :: TransactionGraph -> ValueMarker RelationVariables -> Either RelationalError (ValueMarker RelationVariables)
+ ProjectM36.RelationalExpression: resolveDBC :: (DatabaseContext -> ValueMarker a) -> DatabaseContextEvalMonad a
+ ProjectM36.RelationalExpression: resolveDBC' :: TransactionGraph -> DatabaseContext -> (DatabaseContext -> ValueMarker a) -> Either RelationalError a
+ ProjectM36.RelationalExpression: resolveGR :: (DatabaseContext -> ValueMarker a) -> GraphRefRelationalExprM a
+ ProjectM36.RelationalExpression: resolveIODBC :: (DatabaseContext -> ValueMarker a) -> DatabaseContextIOEvalMonad a
+ ProjectM36.RelationalExpression: resolveValueMarker :: TransactionGraph -> (DatabaseContext -> ValueMarker a) -> ValueMarker a -> Either RelationalError a
+ ProjectM36.RelationalExpression: toResolvedDatabaseContext :: DatabaseContext -> TransactionGraph -> Either RelationalError ResolvedDatabaseContext
+ ProjectM36.RelationalExpression: type ExtendTupleProcessor = (Attributes, RelationTuple -> ContextTuples -> Either RelationalError RelationTuple)
+ ProjectM36.RelationalExpression: typeForGraphRefRestrictionPredicateExpr :: GraphRefRestrictionPredicateExpr -> GraphRefRelationalExprM ()
+ ProjectM36.RelationalExpression: typeForGraphRefTupleExpr :: Maybe Attributes -> GraphRefTupleExpr -> GraphRefRelationalExprM Attributes
+ ProjectM36.RelationalExpression: typeForGraphRefTupleExprs :: Maybe Attributes -> GraphRefTupleExprs -> GraphRefRelationalExprM Attributes
+ ProjectM36.Serialise.Base: GraphRefDatabaseContext :: GraphRefOrUpdate InclusionDependencies -> GraphRefOrUpdate RelationVariables -> GraphRefOrUpdate AtomFunctions -> GraphRefOrUpdate DatabaseContextFunctions -> GraphRefOrUpdate Notifications -> GraphRefOrUpdate TypeConstructorMapping -> GraphRefOrUpdate RegisteredQueries -> GraphRefDatabaseContext
+ ProjectM36.Serialise.Base: GraphRefValue :: GraphRefTransactionMarker -> GraphRefOrUpdate a
+ ProjectM36.Serialise.Base: UpdateValue :: a -> GraphRefOrUpdate a
+ ProjectM36.Serialise.Base: [atomFunctions] :: GraphRefDatabaseContext -> GraphRefOrUpdate AtomFunctions
+ ProjectM36.Serialise.Base: [dbcFunctions] :: GraphRefDatabaseContext -> GraphRefOrUpdate DatabaseContextFunctions
+ ProjectM36.Serialise.Base: [inclusionDependencies] :: GraphRefDatabaseContext -> GraphRefOrUpdate InclusionDependencies
+ ProjectM36.Serialise.Base: [notifications] :: GraphRefDatabaseContext -> GraphRefOrUpdate Notifications
+ ProjectM36.Serialise.Base: [registeredQueries] :: GraphRefDatabaseContext -> GraphRefOrUpdate RegisteredQueries
+ ProjectM36.Serialise.Base: [relationVariables] :: GraphRefDatabaseContext -> GraphRefOrUpdate RelationVariables
+ ProjectM36.Serialise.Base: [typeConstructorMapping] :: GraphRefDatabaseContext -> GraphRefOrUpdate TypeConstructorMapping
+ ProjectM36.Serialise.Base: data GraphRefDatabaseContext
+ ProjectM36.Serialise.Base: data GraphRefOrUpdate a
+ ProjectM36.Serialise.Base: instance (Codec.Winery.Class.Serialise a, Codec.Winery.Class.Serialise r) => Codec.Winery.Class.Serialise (ProjectM36.Base.DatabaseContextExprBase a r)
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.ACLAccessControlList
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.ACLPermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.AlterSchemaPermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.AlterTransGraphAccessControlList
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.AlterTransGraphPermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.DBCFunctionAccessControlList
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.DBCFunctionPermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.DatabaseContextACL
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.FunctionAccessControlList
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.FunctionPermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.RelVarAccessControlList
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.RelVarPermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.SchemaAccessControlList
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.AccessControlList.SomePermission
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.DisconnectedTransaction.CurrentHead
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.IsomorphicSchema.Types.Schema
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.IsomorphicSchema.Types.SchemaIsomorph
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise ProjectM36.Transaction.Types.TransactionInfo
+ ProjectM36.Serialise.Base: instance Codec.Winery.Class.Serialise r => Codec.Winery.Class.Serialise (ProjectM36.Base.AlterDBCACLExprBase r)
+ ProjectM36.Serialise.Base: instance GHC.Generics.Generic ProjectM36.Serialise.Base.GraphRefDatabaseContext
+ ProjectM36.Serialise.Error: instance Codec.Winery.Class.Serialise ProjectM36.DatabaseContext.Fields.DatabaseContextField
+ ProjectM36.Serialise.LoginRoles: instance Codec.Winery.Class.Serialise ProjectM36.LoginRoles.AlterLoginRolesExpr
+ ProjectM36.Serialise.LoginRoles: instance Codec.Winery.Class.Serialise ProjectM36.LoginRoles.LoginRoleError
+ ProjectM36.Serialise.LoginRoles: instance Codec.Winery.Class.Serialise ProjectM36.LoginRoles.SuccessResult
+ ProjectM36.Server.Config: [connConfig] :: ServerConfig -> ServerConnectionConfig
+ ProjectM36.Server.EntryPoints: handleCreateSessionAtTransactionId :: Maybe Timeout -> Connection -> TransactionId -> IO (Either RelationalError SessionId)
+ ProjectM36.Server.EntryPoints: handleExecuteAlterTransactionGraphExpr :: Maybe Timeout -> SessionId -> Connection -> AlterTransactionGraphExpr -> IO (Either RelationalError ())
+ ProjectM36.Server.EntryPoints: handleExecuteCurrentHead :: Maybe Timeout -> SessionId -> Connection -> IO (Either RelationalError CurrentHead)
+ ProjectM36.Server.EntryPoints: handleExecuteTransactionGraphExpr :: Maybe Timeout -> SessionId -> Connection -> TransactionGraphExpr -> IO (Either RelationalError ())
+ ProjectM36.Server.EntryPoints: handleRetrievePlanForRelationalExpr :: Maybe Timeout -> SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError StringType)
+ ProjectM36.Server.ParseArgs: parseClientAuth :: Parser ClientAuth
+ ProjectM36.Server.ParseArgs: parseRoleName :: Parser RoleName
+ ProjectM36.Server.ParseArgs: parseServerConnectionConfig :: Parser ServerConnectionConfig
+ ProjectM36.Server.ParseArgs: parseServerTLSCertInfo :: Parser ServerTLSCertInfo
+ ProjectM36.Server.ParseArgs: parseServerTLSConfig :: Parser ServerTLSConfig
+ ProjectM36.Server.ParseArgs: parseServiceName :: ByteString -> Parser ByteString
+ ProjectM36.Server.ParseArgs: parseWSTlsCertificatePath :: Parser (Maybe String)
+ ProjectM36.Server.ParseArgs: parseWSTlsKeyPath :: Parser (Maybe String)
+ ProjectM36.Server.ParseArgs: type ForServer = Bool
+ ProjectM36.Server.RemoteCallTypes: CreateSessionAtTransactionId :: TransactionId -> CreateSessionAtTransactionId
+ ProjectM36.Server.RemoteCallTypes: ExecuteAlterLoginRolesExpr :: SessionId -> AlterLoginRolesExpr -> ExecuteAlterLoginRolesExpr
+ ProjectM36.Server.RemoteCallTypes: ExecuteAlterTransactionGraphExpr :: SessionId -> AlterTransactionGraphExpr -> ExecuteAlterTransactionGraphExpr
+ ProjectM36.Server.RemoteCallTypes: RetrievePlanForRelationalExpr :: SessionId -> RelationalExpr -> RetrievePlanForRelationalExpr
+ ProjectM36.Server.RemoteCallTypes: data CreateSessionAtTransactionId
+ ProjectM36.Server.RemoteCallTypes: data ExecuteAlterLoginRolesExpr
+ ProjectM36.Server.RemoteCallTypes: data ExecuteAlterTransactionGraphExpr
+ ProjectM36.Server.RemoteCallTypes: data RetrievePlanForRelationalExpr
+ ProjectM36.Server.RemoteCallTypes: instance Codec.Winery.Class.Serialise ProjectM36.Server.RemoteCallTypes.CreateSessionAtTransactionId
+ ProjectM36.Server.RemoteCallTypes: instance Codec.Winery.Class.Serialise ProjectM36.Server.RemoteCallTypes.ExecuteAlterLoginRolesExpr
+ ProjectM36.Server.RemoteCallTypes: instance Codec.Winery.Class.Serialise ProjectM36.Server.RemoteCallTypes.ExecuteAlterTransactionGraphExpr
+ ProjectM36.Server.RemoteCallTypes: instance Codec.Winery.Class.Serialise ProjectM36.Server.RemoteCallTypes.RetrievePlanForRelationalExpr
+ ProjectM36.Server.RemoteCallTypes: instance GHC.Generics.Generic ProjectM36.Server.RemoteCallTypes.CreateSessionAtTransactionId
+ ProjectM36.Server.RemoteCallTypes: instance GHC.Generics.Generic ProjectM36.Server.RemoteCallTypes.ExecuteAlterLoginRolesExpr
+ ProjectM36.Server.RemoteCallTypes: instance GHC.Generics.Generic ProjectM36.Server.RemoteCallTypes.ExecuteAlterTransactionGraphExpr
+ ProjectM36.Server.RemoteCallTypes: instance GHC.Generics.Generic ProjectM36.Server.RemoteCallTypes.RetrievePlanForRelationalExpr
+ ProjectM36.Session: [disconnectedTransaction] :: Session -> DisconnectedTransaction
+ ProjectM36.Session: [schemaName] :: Session -> SchemaName
+ ProjectM36.Session: isUpdated :: Session -> DirtyFlag
+ ProjectM36.Session: resolveSubschemas :: Session -> TransactionGraph -> Either RelationalError Subschemas
+ ProjectM36.Shortcuts: instance GHC.IsList.IsList (ProjectM36.Base.AttributeNamesBase ())
+ ProjectM36.Shortcuts: instance GHC.IsList.IsList (ProjectM36.Base.TupleExprsBase ())
+ ProjectM36.Shortcuts: instance GHC.IsList.IsList ProjectM36.Base.TupleExpr
+ ProjectM36.StaticOptimizer: GraphRefSOptDatabaseContextIOExprEnv :: TransactionGraph -> DatabaseContext -> TransactionId -> GraphRefSOptDatabaseContextIOExprEnv
+ ProjectM36.StaticOptimizer: [odce_dbcfuncutils] :: GraphRefSOptDatabaseContextExprEnv -> DatabaseContextFunctionUtils
+ ProjectM36.StaticOptimizer: [odcioe_context] :: GraphRefSOptDatabaseContextIOExprEnv -> DatabaseContext
+ ProjectM36.StaticOptimizer: [odcioe_graph] :: GraphRefSOptDatabaseContextIOExprEnv -> TransactionGraph
+ ProjectM36.StaticOptimizer: [odcioe_transId] :: GraphRefSOptDatabaseContextIOExprEnv -> TransactionId
+ ProjectM36.StaticOptimizer: class Optimize expr optExpr where {
+ ProjectM36.StaticOptimizer: data GraphRefSOptDatabaseContextIOExprEnv
+ ProjectM36.StaticOptimizer: evalGraphRefRelationalExprWithCache :: RandomGen r => r -> GraphRefRelationalExprEnv -> GraphRefRelationalExpr -> RelExprCache -> IO (Either RelationalError Relation)
+ ProjectM36.StaticOptimizer: instance ProjectM36.StaticOptimizer.Optimize ProjectM36.Base.DatabaseContextExpr' ProjectM36.Base.GraphRefDatabaseContextExpr'
+ ProjectM36.StaticOptimizer: instance ProjectM36.StaticOptimizer.Optimize ProjectM36.Base.RelationalExpr ProjectM36.Base.GraphRefRelationalExpr
+ ProjectM36.StaticOptimizer: optimize :: Optimize expr optExpr => OptimizeEnv expr optExpr -> expr -> Either RelationalError optExpr
+ ProjectM36.StaticOptimizer: optimizeAndEvalRelationalExpr' :: RelationalExprEnv -> RelationalExpr -> RelExprCache -> IO (Either RelationalError Relation)
+ ProjectM36.StaticOptimizer: optimizeAndEvalTransGraphRelationalExprWithCache :: RandomGen r => r -> TransactionGraph -> TransGraphRelationalExpr -> RelExprCache -> IO (Either RelationalError Relation)
+ ProjectM36.StaticOptimizer: runGraphRefSOptDatabaseContextIOExprM :: TransactionId -> DatabaseContext -> TransactionGraph -> GraphRefSOptDatabaseContextIOExprM a -> Either RelationalError a
+ ProjectM36.StaticOptimizer: type GraphRefSOptDatabaseContextIOExprM a = ReaderT GraphRefSOptDatabaseContextIOExprEnv (ExceptT RelationalError Identity) a
+ ProjectM36.StaticOptimizer: type OptimizeEnv expr optExpr;
+ ProjectM36.StaticOptimizer: }
+ ProjectM36.Streaming.RelationalExpression: CacheMissException :: FilePath -> CacheMissException
+ ProjectM36.Streaming.RelationalExpression: DifferenceTupleStreamsPlan :: RelExprExecPlanBase marker execInfo -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: EqualTupleStreamsPlan :: RelExprExecPlanBase marker execInfo -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: ExistingRelationPlan :: Relation -> execInfo -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: ExtendTupleStreamPlan :: ExtendTupleProcessor -> ExtendTupleExprBase marker -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: GroupTupleStreamPlan :: Attributes -> AttributeName -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: MakeRelationFromExprsPlan :: Maybe [AttributeExprBase marker] -> TupleExprsBase marker -> execInfo -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: MakeStaticRelationPlan :: Attributes -> RelationTupleSet -> execInfo -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: NaiveJoinTupleStreamsPlan :: RelExprExecPlanBase marker execInfo -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: NotEqualTupleStreamsPlan :: RelExprExecPlanBase marker execInfo -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: PlanNodeExecutionInfo :: DiffTime -> PlanNodeExecutionInfo
+ ProjectM36.Streaming.RelationalExpression: ProjectTupleStreamPlan :: Attributes -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: RelationValuedAttributeStreamPlan :: Attribute -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: RenameTupleStreamPlan :: Set (AttributeName, AttributeName) -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: RestrictTupleStreamPlan :: RestrictionFilter -> RestrictionPredicateExprBase marker -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: StreamRelation :: Attributes -> Stream m RelationTuple -> StreamRelation m
+ ProjectM36.Streaming.RelationalExpression: UngroupTupleStreamPlan :: AttributeName -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: UnionTupleStreamsPlan :: RelExprExecPlanBase marker execInfo -> RelExprExecPlanBase marker execInfo -> execInfo -> RelationalExprBase marker -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: UniqueifyTupleStreamPlan :: RelExprExecPlanBase marker execInfo -> execInfo -> RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: [duration] :: PlanNodeExecutionInfo -> DiffTime
+ ProjectM36.Streaming.RelationalExpression: [sRelAttributes] :: StreamRelation m -> Attributes
+ ProjectM36.Streaming.RelationalExpression: [sRelTupStream] :: StreamRelation m -> Stream m RelationTuple
+ ProjectM36.Streaming.RelationalExpression: data RelExprExecPlanBase marker execInfo
+ ProjectM36.Streaming.RelationalExpression: data StreamRelation m
+ ProjectM36.Streaming.RelationalExpression: executePlan :: GraphRefRelExprExecPlan -> ContextTuples -> GraphRefRelationalExprEnv -> CacheKeyBlackList -> RelExprCache -> IO (Either RelationalError (StreamRelation IO))
+ ProjectM36.Streaming.RelationalExpression: instance (Prettyprinter.Internal.Pretty t, Prettyprinter.Internal.Pretty a, GHC.Show.Show a) => Prettyprinter.Internal.Pretty (ProjectM36.Streaming.RelationalExpression.RelExprExecPlanBase a t)
+ ProjectM36.Streaming.RelationalExpression: instance GHC.Exception.Type.Exception ProjectM36.Streaming.RelationalExpression.CacheMissException
+ ProjectM36.Streaming.RelationalExpression: instance GHC.Show.Show ProjectM36.Streaming.RelationalExpression.CacheMissException
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.Atom
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.AtomType
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.Attribute
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.Attributes
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.GraphRefTransactionMarker
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.RelationTuple
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.RelationTupleSet
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.RenameAssocs
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty ProjectM36.Base.TypeConstructor
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.AtomExprBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.AttributeExprBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.AttributeNamesBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.ExtendTupleExprBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.RelationalExprBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.RestrictionPredicateExprBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.TupleExprBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.TupleExprsBase a)
+ ProjectM36.Streaming.RelationalExpression: instance Prettyprinter.Internal.Pretty a => Prettyprinter.Internal.Pretty (ProjectM36.Base.WithNameExprBase a)
+ ProjectM36.Streaming.RelationalExpression: newtype CacheMissException
+ ProjectM36.Streaming.RelationalExpression: newtype PlanNodeExecutionInfo
+ ProjectM36.Streaming.RelationalExpression: originalRelExpr :: RelExprExecPlanBase a t -> Maybe (RelationalExprBase a)
+ ProjectM36.Streaming.RelationalExpression: parensList :: [Doc ann] -> Doc ann
+ ProjectM36.Streaming.RelationalExpression: planGraphRefRelationalExpr :: GraphRefRelationalExpr -> GraphRefRelationalExprEnv -> Either RelationalError GraphRefRelExprExecPlan
+ ProjectM36.Streaming.RelationalExpression: relationFalse :: (MonadIO m, MonadAsync m) => StreamRelation m
+ ProjectM36.Streaming.RelationalExpression: relationTrue :: (MonadIO m, MonadAsync m) => StreamRelation m
+ ProjectM36.Streaming.RelationalExpression: renderPretty :: (Show a, Pretty t, Pretty a) => RelExprExecPlanBase a t -> Text
+ ProjectM36.Streaming.RelationalExpression: streamRelationAsRelation :: MonadIO m => StreamRelation m -> m Relation
+ ProjectM36.Streaming.RelationalExpression: streamRelationMap :: MonadIO m => Attributes -> (RelationTuple -> m RelationTuple) -> StreamRelation m -> StreamRelation m
+ ProjectM36.Streaming.RelationalExpression: tuplesHashSet :: MonadIO m => Stream m RelationTuple -> m (HashSet RelationTuple)
+ ProjectM36.Streaming.RelationalExpression: type CacheKeyBlackList = HashSet PinnedRelationalExpr
+ ProjectM36.Streaming.RelationalExpression: type GraphRefRelExprExecPlan = RelExprExecPlanBase GraphRefTransactionMarker ()
+ ProjectM36.Streaming.RelationalExpression: type PostExecutionGraphRefRelExprExecPlan = RelExprExecPlanBase GraphRefTransactionMarker PlanNodeExecutionInfo
+ ProjectM36.Streaming.RelationalExpression: type RelExprExecPlan = RelExprExecPlanBase () ()
+ ProjectM36.Streaming.Tuple: attributesForRelval :: AttributeName -> Attributes -> Either RelationalError Attributes
+ ProjectM36.Streaming.Tuple: cardinality :: MonadAsync m => Stream m RelationTuple -> m RelationCardinality
+ ProjectM36.Streaming.Tuple: difference :: MonadAsync m => [RelationTuple] -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: group' :: MonadAsync m => Attributes -> AttributeName -> Attributes -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: join :: forall m. (MonadAsync m, MonadThrow m, Applicative m) => Attributes -> [RelationTuple] -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: project :: MonadAsync m => Attributes -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: rename :: MonadAsync m => AttributeName -> AttributeName -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: restrict :: MonadAsync m => RestrictionFilter -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: tupleStream :: MonadAsync m => Relation -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: tupleUngroup :: MonadAsync m => AttributeName -> Attributes -> RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: type RestrictionFilter = RelationTuple -> Either RelationalError Bool
+ ProjectM36.Streaming.Tuple: ungroup :: MonadAsync m => AttributeName -> Attributes -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: union :: MonadAsync m => Stream m RelationTuple -> Stream m RelationTuple -> Stream m RelationTuple
+ ProjectM36.Streaming.Tuple: unionMulti :: MonadAsync m => [Stream m RelationTuple] -> Stream m RelationTuple
+ ProjectM36.SystemMemory: getMemoryPressure :: IO (Maybe Double)
+ ProjectM36.SystemMemory: getMemoryStats :: IO (Either String MemoryStats)
+ ProjectM36.SystemMemory: parseMemoryPressureFreeMem :: Parsec Void String MemoryStats
+ ProjectM36.SystemMemory: parseMemoryPressureValue :: Parsec Void String Double
+ ProjectM36.SystemMemory: type FreeMemBytes = Int64
+ ProjectM36.SystemMemory: type MemoryStats = (FreeMemBytes, TotalMemBytes)
+ ProjectM36.SystemMemory: type TotalMemBytes = Int64
+ ProjectM36.Transaction.Persist: NotChangedSinceDatabaseContext :: Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: SingleFileRelationVariables :: RelationVariables -> Map RelVarName FilePath -> SingleFileRelationVariables
+ ProjectM36.Transaction.Persist: UnchangedDatabaseContextValues :: Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> Maybe TransactionId -> UnchangedDatabaseContextValues
+ ProjectM36.Transaction.Persist: [complexRelVarNameMap] :: SingleFileRelationVariables -> Map RelVarName FilePath
+ ProjectM36.Transaction.Persist: [ncsAcl] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsAtomFunctions] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsDbcFunctions] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsInclusionDependencies] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsNotifications] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsRegisteredQueries] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsRelationVariables] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsSchemas] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [ncsTypeConstructorMapping] :: NotChangedSinceDatabaseContext -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [simpleRelVars] :: SingleFileRelationVariables -> RelationVariables
+ ProjectM36.Transaction.Persist: [unchangedAtomFunctions] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [unchangedDBCFunctions] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [unchangedInclusionDependencies] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [unchangedNotifications] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [unchangedRegisteredQueries] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [unchangedRelationVariables] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: [unchangedTypeConstructorMapping] :: UnchangedDatabaseContextValues -> Maybe TransactionId
+ ProjectM36.Transaction.Persist: aclPath :: FilePath -> FilePath
+ ProjectM36.Transaction.Persist: data NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: data SingleFileRelationVariables
+ ProjectM36.Transaction.Persist: data UnchangedDatabaseContextValues
+ ProjectM36.Transaction.Persist: instance Codec.Winery.Class.Serialise ProjectM36.Transaction.Persist.NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: instance Codec.Winery.Class.Serialise ProjectM36.Transaction.Persist.SingleFileRelationVariables
+ ProjectM36.Transaction.Persist: instance Codec.Winery.Class.Serialise ProjectM36.Transaction.Persist.UnchangedDatabaseContextValues
+ ProjectM36.Transaction.Persist: instance GHC.Classes.Eq ProjectM36.Transaction.Persist.SingleFileRelationVariables
+ ProjectM36.Transaction.Persist: instance GHC.Generics.Generic ProjectM36.Transaction.Persist.NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: instance GHC.Generics.Generic ProjectM36.Transaction.Persist.SingleFileRelationVariables
+ ProjectM36.Transaction.Persist: instance GHC.Generics.Generic ProjectM36.Transaction.Persist.UnchangedDatabaseContextValues
+ ProjectM36.Transaction.Persist: instance GHC.Show.Show ProjectM36.Transaction.Persist.NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: instance GHC.Show.Show ProjectM36.Transaction.Persist.SingleFileRelationVariables
+ ProjectM36.Transaction.Persist: instance GHC.Show.Show ProjectM36.Transaction.Persist.UnchangedDatabaseContextValues
+ ProjectM36.Transaction.Persist: mkNotChangedSinceDatabaseContext :: DatabaseContext -> ValueMarker Subschemas -> NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: notChangedSinceDatabaseContextPath :: FilePath -> FilePath
+ ProjectM36.Transaction.Persist: readAcl :: FilePath -> IO DatabaseContextACL
+ ProjectM36.Transaction.Persist: readNotChangedSinceDatabaseContext :: FilePath -> IO NotChangedSinceDatabaseContext
+ ProjectM36.Transaction.Persist: relvarsDir :: FilePath -> FilePath
+ ProjectM36.Transaction.Persist: relvarsSimplePath :: FilePath -> FilePath
+ ProjectM36.Transaction.Persist: unchangedElementsPath :: FilePath -> FilePath
+ ProjectM36.Transaction.Persist: unwrittenTransactionCanUseDatabaseContextValuesFastPath :: UncommittedTransaction -> Bool
+ ProjectM36.Transaction.Persist: writeAcl :: DiskSync -> FilePath -> DatabaseContextACL -> IO ()
+ ProjectM36.Transaction.Persist: writeNotChangedSinceDatabaseContext :: DiskSync -> FilePath -> NotChangedSinceDatabaseContext -> IO ()
+ ProjectM36.Transaction.Persist: xattrName :: String
+ ProjectM36.Transaction.Types: Transaction :: TransactionId -> TransactionInfo -> Schemas ctx -> TransactionBase ctx
+ ProjectM36.Transaction.Types: TransactionInfo :: TransactionParents -> UTCTime -> MerkleHash -> TransactionInfo
+ ProjectM36.Transaction.Types: UncommittedTransaction :: TransactionBase DatabaseContext -> UncommittedTransaction
+ ProjectM36.Transaction.Types: [_uncommittedTransaction] :: UncommittedTransaction -> TransactionBase DatabaseContext
+ ProjectM36.Transaction.Types: [merkleHash] :: TransactionInfo -> MerkleHash
+ ProjectM36.Transaction.Types: [parents] :: TransactionInfo -> TransactionParents
+ ProjectM36.Transaction.Types: [stamp] :: TransactionInfo -> UTCTime
+ ProjectM36.Transaction.Types: concreteDatabaseContext :: TransactionBase a -> a
+ ProjectM36.Transaction.Types: data TransactionBase ctx
+ ProjectM36.Transaction.Types: data TransactionInfo
+ ProjectM36.Transaction.Types: info :: TransactionBase a -> TransactionInfo
+ ProjectM36.Transaction.Types: instance GHC.Classes.Eq (ProjectM36.Transaction.Types.TransactionBase a)
+ ProjectM36.Transaction.Types: instance GHC.Classes.Eq ProjectM36.Transaction.Types.UncommittedTransaction
+ ProjectM36.Transaction.Types: instance GHC.Classes.Ord (ProjectM36.Transaction.Types.TransactionBase a)
+ ProjectM36.Transaction.Types: instance GHC.Classes.Ord ProjectM36.Transaction.Types.UncommittedTransaction
+ ProjectM36.Transaction.Types: instance GHC.Generics.Generic (ProjectM36.Transaction.Types.TransactionBase ctx)
+ ProjectM36.Transaction.Types: instance GHC.Generics.Generic ProjectM36.Transaction.Types.TransactionInfo
+ ProjectM36.Transaction.Types: instance GHC.Show.Show ProjectM36.Transaction.Types.TransactionInfo
+ ProjectM36.Transaction.Types: newtype UncommittedTransaction
+ ProjectM36.Transaction.Types: parentIds :: TransactionBase a -> Set TransactionId
+ ProjectM36.Transaction.Types: rootParent :: TransactionParents
+ ProjectM36.Transaction.Types: schemas :: TransactionBase a -> Schemas a
+ ProjectM36.Transaction.Types: singleParent :: TransactionId -> TransactionParents
+ ProjectM36.Transaction.Types: subschemas :: TransactionBase a -> ValueMarker Subschemas
+ ProjectM36.Transaction.Types: transactionId :: TransactionBase a -> TransactionId
+ ProjectM36.Transaction.Types: transactionInfo :: TransactionBase a -> TransactionInfo
+ ProjectM36.Transaction.Types: type Transaction = TransactionBase DatabaseContext
+ ProjectM36.Transaction.Types: type TransactionParents = NonEmpty TransactionId
+ ProjectM36.TransactionGraph: MakeDisconnectedTransactionFromTransaction :: TransactionId -> ROTransactionGraphCreateDisconnectedTransaction
+ ProjectM36.TransactionGraph: PureDisconnectedTransaction :: DisconnectedTransaction -> ROTransactionGraphCreateDisconnectedTransaction
+ ProjectM36.TransactionGraph: data AlterTransactionGraphExpr
+ ProjectM36.TransactionGraph: data ROTransactionGraphCreateDisconnectedTransaction
+ ProjectM36.TransactionGraph: data TransactionGraphExpr
+ ProjectM36.TransactionGraph: evalAlterTransactionGraphExpr :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> AlterTransactionGraphExpr -> Either RelationalError (DisconnectedTransaction, Maybe UncommittedTransaction, TransactionGraph)
+ ProjectM36.TransactionGraph: evalTransactionGraphExpr :: DisconnectedTransaction -> TransactionGraph -> TransactionGraphExpr -> Either RelationalError DisconnectedTransaction
+ ProjectM36.TransactionGraph: freshTransactionGraph' :: ResolvedDatabaseContext -> IO (TransactionGraph, TransactionId)
+ ProjectM36.TransactionGraph: headNamesForTransaction :: Transaction -> TransactionGraph -> [HeadName]
+ ProjectM36.TransactionGraph: instance Codec.Winery.Class.Serialise ProjectM36.TransactionGraph.AlterTransactionGraphExpr
+ ProjectM36.TransactionGraph: instance Codec.Winery.Class.Serialise ProjectM36.TransactionGraph.TransactionGraphExpr
+ ProjectM36.TransactionGraph: instance GHC.Classes.Eq ProjectM36.TransactionGraph.AlterTransactionGraphExpr
+ ProjectM36.TransactionGraph: instance GHC.Classes.Eq ProjectM36.TransactionGraph.TransactionGraphExpr
+ ProjectM36.TransactionGraph: instance GHC.Generics.Generic ProjectM36.TransactionGraph.AlterTransactionGraphExpr
+ ProjectM36.TransactionGraph: instance GHC.Generics.Generic ProjectM36.TransactionGraph.TransactionGraphExpr
+ ProjectM36.TransactionGraph: instance GHC.Show.Show ProjectM36.TransactionGraph.AlterTransactionGraphExpr
+ ProjectM36.TransactionGraph: instance GHC.Show.Show ProjectM36.TransactionGraph.TransactionGraphExpr
+ ProjectM36.TransactionGraph.Merge: unionMergeACLs :: MergePreference -> TransactionGraph -> ValueMarker DatabaseContextACL -> ValueMarker DatabaseContextACL -> Either RelationalError (ValueMarker DatabaseContextACL)
+ ProjectM36.TransactionGraph.Persist: transactionGraphPersistIncremental :: DiskSync -> FilePath -> TransactionGraphIncrementalWriteInfo -> IO LockFileHash
+ ProjectM36.TransactionGraph.Types: MergeHeadNames :: HeadName -> HeadName -> MergeHeadNames
+ ProjectM36.TransactionGraph.Types: TransactionGraph :: TransactionHeads -> Set Transaction -> TransactionGraph
+ ProjectM36.TransactionGraph.Types: TransactionGraphIncrementalWriteInfo :: Set UncommittedTransaction -> TransactionGraph -> TransactionGraphIncrementalWriteInfo
+ ProjectM36.TransactionGraph.Types: [newGraph] :: TransactionGraphIncrementalWriteInfo -> TransactionGraph
+ ProjectM36.TransactionGraph.Types: [sourceHead] :: MergeHeadNames -> HeadName
+ ProjectM36.TransactionGraph.Types: [targetHead] :: MergeHeadNames -> HeadName
+ ProjectM36.TransactionGraph.Types: [uncommittedTransactions] :: TransactionGraphIncrementalWriteInfo -> Set UncommittedTransaction
+ ProjectM36.TransactionGraph.Types: affectedTransactionIds :: TransactionGraphIncrementalWriteInfo -> Set TransactionId
+ ProjectM36.TransactionGraph.Types: data MergeHeadNames
+ ProjectM36.TransactionGraph.Types: data TransactionGraph
+ ProjectM36.TransactionGraph.Types: data TransactionGraphIncrementalWriteInfo
+ ProjectM36.TransactionGraph.Types: instance GHC.Classes.Eq ProjectM36.TransactionGraph.Types.MergeHeadNames
+ ProjectM36.TransactionGraph.Types: instance GHC.Generics.Generic ProjectM36.TransactionGraph.Types.TransactionGraph
+ ProjectM36.TransactionGraph.Types: instance GHC.Show.Show ProjectM36.TransactionGraph.Types.MergeHeadNames
+ ProjectM36.TransactionGraph.Types: transactionHeads :: TransactionGraph -> TransactionHeads
+ ProjectM36.TransactionGraph.Types: transactionHeadsForGraph :: TransactionGraph -> TransactionHeads
+ ProjectM36.TransactionGraph.Types: transactionIdsForGraph :: TransactionGraph -> Set TransactionId
+ ProjectM36.TransactionGraph.Types: transactions :: TransactionGraph -> Set Transaction
+ ProjectM36.TransactionGraph.Types: transactionsForGraph :: TransactionGraph -> Set Transaction
+ ProjectM36.TransactionGraph.Types: type TransactionHeads = Map HeadName Transaction
+ ProjectM36.TransactionGraph.Types: type TransactionParents = NonEmpty TransactionId
+ ProjectM36.Tuple: indicesForAttributeNames :: Vector AttributeName -> Attributes -> Vector (AttributeName, Maybe Int)
+ ProjectM36.Tuple: tupleRenameAttributes :: Set (AttributeName, AttributeName) -> RelationTuple -> RelationTuple
+ ProjectM36.TupleSet: empty :: RelationTupleSet
+ ProjectM36.TupleSet: emptySingleTuple :: RelationTupleSet
+ ProjectM36.TupleSet: null :: RelationTupleSet -> Bool
+ ProjectM36.ValueMarker: NotChangedSinceMarker :: TransactionId -> ValueMarker a
+ ProjectM36.ValueMarker: ValueMarker :: a -> ValueMarker a
+ ProjectM36.ValueMarker: data ValueMarker a
+ ProjectM36.ValueMarker: emptyValue :: Monoid a => ValueMarker a
+ ProjectM36.ValueMarker: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (ProjectM36.ValueMarker.ValueMarker a)
+ ProjectM36.ValueMarker: instance GHC.Generics.Generic (ProjectM36.ValueMarker.ValueMarker a)
+ ProjectM36.ValueMarker: instance GHC.Show.Show a => GHC.Show.Show (ProjectM36.ValueMarker.ValueMarker a)
+ ProjectM36.ValueMarker: valueIsUpdated :: ValueMarker a -> Bool
+ ProjectM36.WithNameExpr: macroNames :: WithNamesAssocsBase a -> Set RelVarName
- ProjectM36.Atomable: class (Eq a, NFData a, Serialise a, Show a) => Atomable a
+ ProjectM36.Atomable: class Atomable a
- ProjectM36.Base: AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseContextExprBase a
+ ProjectM36.Base: AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseContextExprBase a r
- ProjectM36.Base: AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> RelationalExpr -> DatabaseContextExprBase a
+ ProjectM36.Base: AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> RelationalExpr -> DatabaseContextExprBase a r
- ProjectM36.Base: AddRegisteredQuery :: RegisteredQueryName -> RelationalExpr -> DatabaseContextExprBase a
+ ProjectM36.Base: AddRegisteredQuery :: RegisteredQueryName -> RelationalExpr -> DatabaseContextExprBase a r
- ProjectM36.Base: AddTypeConstructor :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBase a
+ ProjectM36.Base: AddTypeConstructor :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBase a r
- ProjectM36.Base: Assign :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Base: Assign :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Base: Attribute :: AttributeName -> AtomType -> Attribute
+ ProjectM36.Base: Attribute :: AttributeName -> !AtomType -> Attribute
- ProjectM36.Base: Define :: RelVarName -> [AttributeExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Base: Define :: RelVarName -> [AttributeExprBase a] -> DatabaseContextExprBase a r
- ProjectM36.Base: Delete :: RelVarName -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Base: Delete :: RelVarName -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Base: DifferenceF :: r_aFZb -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: DifferenceF :: r_aQAc -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: EqualsF :: r_aFZb -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: EqualsF :: r_aQAc -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: ExecuteDatabaseContextFunction :: FunctionName -> [AtomExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Base: ExecuteDatabaseContextFunction :: FunctionName -> [AtomExprBase a] -> DatabaseContextExprBase a r
- ProjectM36.Base: ExistingRelationF :: Relation -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: ExistingRelationF :: Relation -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: ExtendF :: ExtendTupleExprBase a_atFz -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: ExtendF :: ExtendTupleExprBase a_aF1x -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: Function :: FunctionName -> [AtomType] -> FunctionBody a -> Function a
+ ProjectM36.Base: Function :: FunctionName -> [AtomType] -> FunctionBody a -> acl -> Function a acl
- ProjectM36.Base: GroupF :: AttributeNamesBase a_atFz -> Text -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: GroupF :: AttributeNamesBase a_aF1x -> Text -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: Insert :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Base: Insert :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Base: JoinF :: r_aFZb -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: JoinF :: r_aQAc -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: MakeRelationFromExprsF :: Maybe [AttributeExprBase a_atFz] -> TupleExprsBase a_atFz -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: MakeRelationFromExprsF :: Maybe [AttributeExprBase a_aF1x] -> TupleExprsBase a_aF1x -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: MakeStaticRelationF :: Attributes -> RelationTupleSet -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: MakeStaticRelationF :: Attributes -> RelationTupleSet -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: MultipleExpr :: [DatabaseContextExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Base: MultipleExpr :: [DatabaseContextExprBase a r] -> DatabaseContextExprBase a r
- ProjectM36.Base: NoOperation :: DatabaseContextExprBase a
+ ProjectM36.Base: NoOperation :: DatabaseContextExprBase a r
- ProjectM36.Base: NotEqualsF :: r_aFZb -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: NotEqualsF :: r_aQAc -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: ProjectF :: AttributeNamesBase a_atFz -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: ProjectF :: AttributeNamesBase a_aF1x -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: RelationValuedAttributeF :: Text -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: RelationValuedAttributeF :: Text -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: RelationVariableF :: Text -> a_atFz -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: RelationVariableF :: Text -> a_aF1x -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: RemoveAtomFunction :: FunctionName -> DatabaseContextExprBase a
+ ProjectM36.Base: RemoveAtomFunction :: FunctionName -> DatabaseContextExprBase a r
- ProjectM36.Base: RemoveDatabaseContextFunction :: FunctionName -> DatabaseContextExprBase a
+ ProjectM36.Base: RemoveDatabaseContextFunction :: FunctionName -> DatabaseContextExprBase a r
- ProjectM36.Base: RemoveInclusionDependency :: IncDepName -> DatabaseContextExprBase a
+ ProjectM36.Base: RemoveInclusionDependency :: IncDepName -> DatabaseContextExprBase a r
- ProjectM36.Base: RemoveNotification :: NotificationName -> DatabaseContextExprBase a
+ ProjectM36.Base: RemoveNotification :: NotificationName -> DatabaseContextExprBase a r
- ProjectM36.Base: RemoveRegisteredQuery :: RegisteredQueryName -> DatabaseContextExprBase a
+ ProjectM36.Base: RemoveRegisteredQuery :: RegisteredQueryName -> DatabaseContextExprBase a r
- ProjectM36.Base: RemoveTypeConstructor :: TypeConstructorName -> DatabaseContextExprBase a
+ ProjectM36.Base: RemoveTypeConstructor :: TypeConstructorName -> DatabaseContextExprBase a r
- ProjectM36.Base: Rename :: Set (AttributeName, AttributeName) -> RelationalExprBase a -> RelationalExprBase a
+ ProjectM36.Base: Rename :: RenameAssocs -> RelationalExprBase a -> RelationalExprBase a
- ProjectM36.Base: RenameF :: Set (Text, Text) -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: RenameF :: Set (Text, Text) -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: RestrictF :: RestrictionPredicateExprBase a_atFz -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: RestrictF :: RestrictionPredicateExprBase a_aF1x -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: Undefine :: RelVarName -> DatabaseContextExprBase a
+ ProjectM36.Base: Undefine :: RelVarName -> DatabaseContextExprBase a r
- ProjectM36.Base: UngroupF :: Text -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: UngroupF :: Text -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: UnionF :: r_aFZb -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: UnionF :: r_aQAc -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: Update :: RelVarName -> AttributeNameAtomExprMap -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Base: Update :: RelVarName -> AttributeNameAtomExprMap -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Base: WithF :: [(WithNameExprBase a_atFz, r_aFZb)] -> r_aFZb -> RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: WithF :: [(WithNameExprBase a_aF1x, r_aQAc)] -> r_aQAc -> RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: data DatabaseContextExprBase a
+ ProjectM36.Base: data DatabaseContextExprBase a r
- ProjectM36.Base: data Function a
+ ProjectM36.Base: data Function a acl
- ProjectM36.Base: data RelationalExprBaseF (a_atFz :: Type) r_aFZb
+ ProjectM36.Base: data RelationalExprBaseF (a_aF1x :: Type) r_aQAc
- ProjectM36.Base: type AtomFunction = Function AtomFunctionBodyType
+ ProjectM36.Base: type AtomFunction = Function AtomFunctionBodyType ()
- ProjectM36.Base: type DatabaseContextExpr = DatabaseContextExprBase ()
+ ProjectM36.Base: type DatabaseContextExpr = DatabaseContextExprBase () RoleName
- ProjectM36.Base: type GraphRefDatabaseContextExpr = DatabaseContextExprBase GraphRefTransactionMarker
+ ProjectM36.Base: type GraphRefDatabaseContextExpr = DatabaseContextExprBase GraphRefTransactionMarker RoleName
- ProjectM36.Client: AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseContextExprBase a
+ ProjectM36.Client: AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseContextExprBase a r
- ProjectM36.Client: AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> RelationalExpr -> DatabaseContextExprBase a
+ ProjectM36.Client: AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> RelationalExpr -> DatabaseContextExprBase a r
- ProjectM36.Client: AddRegisteredQuery :: RegisteredQueryName -> RelationalExpr -> DatabaseContextExprBase a
+ ProjectM36.Client: AddRegisteredQuery :: RegisteredQueryName -> RelationalExpr -> DatabaseContextExprBase a r
- ProjectM36.Client: AddTypeConstructor :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBase a
+ ProjectM36.Client: AddTypeConstructor :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBase a r
- ProjectM36.Client: Assign :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client: Assign :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client: Attribute :: AttributeName -> AtomType -> Attribute
+ ProjectM36.Client: Attribute :: AttributeName -> !AtomType -> Attribute
- ProjectM36.Client: Branch :: HeadName -> TransactionGraphOperator
+ ProjectM36.Client: Branch :: HeadName -> AlterTransactionGraphExpr
- ProjectM36.Client: Commit :: TransactionGraphOperator
+ ProjectM36.Client: Commit :: AlterTransactionGraphExpr
- ProjectM36.Client: DatabaseContextFunctionUserError :: DatabaseContextFunctionError -> RelationalError
+ ProjectM36.Client: DatabaseContextFunctionUserError :: String -> RelationalError
- ProjectM36.Client: Define :: RelVarName -> [AttributeExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Client: Define :: RelVarName -> [AttributeExprBase a] -> DatabaseContextExprBase a r
- ProjectM36.Client: Delete :: RelVarName -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client: Delete :: RelVarName -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client: DeleteBranch :: HeadName -> TransactionGraphOperator
+ ProjectM36.Client: DeleteBranch :: HeadName -> AlterTransactionGraphExpr
- ProjectM36.Client: ExecuteDatabaseContextFunction :: FunctionName -> [AtomExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Client: ExecuteDatabaseContextFunction :: FunctionName -> [AtomExprBase a] -> DatabaseContextExprBase a r
- ProjectM36.Client: InProcessConnectionInfo :: PersistenceStrategy -> NotificationCallback -> [GhcPkgPath] -> DatabaseContext -> ConnectionInfo
+ ProjectM36.Client: InProcessConnectionInfo :: PersistenceStrategy -> NotificationCallback -> [GhcPkgPath] -> ResolvedDatabaseContext -> StdGen -> RoleName -> ConnectionInfo
- ProjectM36.Client: Insert :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client: Insert :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client: JumpToHead :: HeadName -> TransactionGraphOperator
+ ProjectM36.Client: JumpToHead :: HeadName -> TransactionGraphExpr
- ProjectM36.Client: JumpToTransaction :: TransactionId -> TransactionGraphOperator
+ ProjectM36.Client: JumpToTransaction :: TransactionId -> TransactionGraphExpr
- ProjectM36.Client: MergeTransactions :: MergeStrategy -> HeadName -> HeadName -> TransactionGraphOperator
+ ProjectM36.Client: MergeTransactions :: MergeStrategy -> HeadName -> HeadName -> AlterTransactionGraphExpr
- ProjectM36.Client: MultipleExpr :: [DatabaseContextExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Client: MultipleExpr :: [DatabaseContextExprBase a r] -> DatabaseContextExprBase a r
- ProjectM36.Client: NoOperation :: DatabaseContextExprBase a
+ ProjectM36.Client: NoOperation :: DatabaseContextExprBase a r
- ProjectM36.Client: RemoteConnectionInfo :: DatabaseName -> RemoteServerAddress -> NotificationCallback -> ConnectionInfo
+ ProjectM36.Client: RemoteConnectionInfo :: DatabaseName -> RemoteServerAddress -> ClientConnectionConfig -> NotificationCallback -> RoleName -> ConnectionInfo
- ProjectM36.Client: RemoveAtomFunction :: FunctionName -> DatabaseContextExprBase a
+ ProjectM36.Client: RemoveAtomFunction :: FunctionName -> DatabaseContextExprBase a r
- ProjectM36.Client: RemoveDatabaseContextFunction :: FunctionName -> DatabaseContextExprBase a
+ ProjectM36.Client: RemoveDatabaseContextFunction :: FunctionName -> DatabaseContextExprBase a r
- ProjectM36.Client: RemoveInclusionDependency :: IncDepName -> DatabaseContextExprBase a
+ ProjectM36.Client: RemoveInclusionDependency :: IncDepName -> DatabaseContextExprBase a r
- ProjectM36.Client: RemoveNotification :: NotificationName -> DatabaseContextExprBase a
+ ProjectM36.Client: RemoveNotification :: NotificationName -> DatabaseContextExprBase a r
- ProjectM36.Client: RemoveRegisteredQuery :: RegisteredQueryName -> DatabaseContextExprBase a
+ ProjectM36.Client: RemoveRegisteredQuery :: RegisteredQueryName -> DatabaseContextExprBase a r
- ProjectM36.Client: RemoveTypeConstructor :: TypeConstructorName -> DatabaseContextExprBase a
+ ProjectM36.Client: RemoveTypeConstructor :: TypeConstructorName -> DatabaseContextExprBase a r
- ProjectM36.Client: Rename :: Set (AttributeName, AttributeName) -> RelationalExprBase a -> RelationalExprBase a
+ ProjectM36.Client: Rename :: RenameAssocs -> RelationalExprBase a -> RelationalExprBase a
- ProjectM36.Client: Rollback :: TransactionGraphOperator
+ ProjectM36.Client: Rollback :: AlterTransactionGraphExpr
- ProjectM36.Client: Undefine :: RelVarName -> DatabaseContextExprBase a
+ ProjectM36.Client: Undefine :: RelVarName -> DatabaseContextExprBase a r
- ProjectM36.Client: Update :: RelVarName -> AttributeNameAtomExprMap -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client: Update :: RelVarName -> AttributeNameAtomExprMap -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client: WalkBackToTime :: UTCTime -> TransactionGraphOperator
+ ProjectM36.Client: WalkBackToTime :: UTCTime -> TransactionGraphExpr
- ProjectM36.Client: addClientNode :: Connection -> Locking Socket -> IO ()
+ ProjectM36.Client: addClientNode :: Connection -> ClientConnectionId -> SocketContext -> RoleName -> IO ()
- ProjectM36.Client: basicDatabaseContext :: DatabaseContext
+ ProjectM36.Client: basicDatabaseContext :: ResolvedDatabaseContext
- ProjectM36.Client: class (Eq a, NFData a, Serialise a, Show a) => Atomable a
+ ProjectM36.Client: class Atomable a
- ProjectM36.Client: data DatabaseContextExprBase a
+ ProjectM36.Client: data DatabaseContextExprBase a r
- ProjectM36.Client: defaultRemoteConnectionInfo :: ConnectionInfo
+ ProjectM36.Client: defaultRemoteConnectionInfo :: RoleName -> ConnectionInfo
- ProjectM36.Client: type DatabaseContextExpr = DatabaseContextExprBase ()
+ ProjectM36.Client: type DatabaseContextExpr = DatabaseContextExprBase () RoleName
- ProjectM36.Client.Simple: AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: AddInclusionDependency :: IncDepName -> InclusionDependency -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> RelationalExpr -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> RelationalExpr -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: AddRegisteredQuery :: RegisteredQueryName -> RelationalExpr -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: AddRegisteredQuery :: RegisteredQueryName -> RelationalExpr -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: AddTypeConstructor :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: AddTypeConstructor :: TypeConstructorDef -> [DataConstructorDef] -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: Assign :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: Assign :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: Attribute :: AttributeName -> AtomType -> Attribute
+ ProjectM36.Client.Simple: Attribute :: AttributeName -> !AtomType -> Attribute
- ProjectM36.Client.Simple: DatabaseContextFunctionUserError :: DatabaseContextFunctionError -> RelationalError
+ ProjectM36.Client.Simple: DatabaseContextFunctionUserError :: String -> RelationalError
- ProjectM36.Client.Simple: Define :: RelVarName -> [AttributeExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: Define :: RelVarName -> [AttributeExprBase a] -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: Delete :: RelVarName -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: Delete :: RelVarName -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: ExecuteDatabaseContextFunction :: FunctionName -> [AtomExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: ExecuteDatabaseContextFunction :: FunctionName -> [AtomExprBase a] -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: InProcessConnectionInfo :: PersistenceStrategy -> NotificationCallback -> [GhcPkgPath] -> DatabaseContext -> ConnectionInfo
+ ProjectM36.Client.Simple: InProcessConnectionInfo :: PersistenceStrategy -> NotificationCallback -> [GhcPkgPath] -> ResolvedDatabaseContext -> StdGen -> RoleName -> ConnectionInfo
- ProjectM36.Client.Simple: Insert :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: Insert :: RelVarName -> RelationalExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: MultipleExpr :: [DatabaseContextExprBase a] -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: MultipleExpr :: [DatabaseContextExprBase a r] -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: NoOperation :: DatabaseContextExprBase a
+ ProjectM36.Client.Simple: NoOperation :: DatabaseContextExprBase a r
- ProjectM36.Client.Simple: RemoteConnectionInfo :: DatabaseName -> RemoteServerAddress -> NotificationCallback -> ConnectionInfo
+ ProjectM36.Client.Simple: RemoteConnectionInfo :: DatabaseName -> RemoteServerAddress -> ClientConnectionConfig -> NotificationCallback -> RoleName -> ConnectionInfo
- ProjectM36.Client.Simple: RemoveAtomFunction :: FunctionName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: RemoveAtomFunction :: FunctionName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: RemoveDatabaseContextFunction :: FunctionName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: RemoveDatabaseContextFunction :: FunctionName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: RemoveInclusionDependency :: IncDepName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: RemoveInclusionDependency :: IncDepName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: RemoveNotification :: NotificationName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: RemoveNotification :: NotificationName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: RemoveRegisteredQuery :: RegisteredQueryName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: RemoveRegisteredQuery :: RegisteredQueryName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: RemoveTypeConstructor :: TypeConstructorName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: RemoveTypeConstructor :: TypeConstructorName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: Rename :: Set (AttributeName, AttributeName) -> RelationalExprBase a -> RelationalExprBase a
+ ProjectM36.Client.Simple: Rename :: RenameAssocs -> RelationalExprBase a -> RelationalExprBase a
- ProjectM36.Client.Simple: Undefine :: RelVarName -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: Undefine :: RelVarName -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: Update :: RelVarName -> AttributeNameAtomExprMap -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a
+ ProjectM36.Client.Simple: Update :: RelVarName -> AttributeNameAtomExprMap -> RestrictionPredicateExprBase a -> DatabaseContextExprBase a r
- ProjectM36.Client.Simple: class (Eq a, NFData a, Serialise a, Show a) => Atomable a
+ ProjectM36.Client.Simple: class Atomable a
- ProjectM36.Client.Simple: data DatabaseContextExprBase a
+ ProjectM36.Client.Simple: data DatabaseContextExprBase a r
- ProjectM36.Client.Simple: type DatabaseContextExpr = DatabaseContextExprBase ()
+ ProjectM36.Client.Simple: type DatabaseContextExpr = DatabaseContextExprBase () RoleName
- ProjectM36.DatabaseContextFunction: evalDatabaseContextFunction :: DatabaseContextFunction -> [Atom] -> DatabaseContext -> Either RelationalError DatabaseContext
+ ProjectM36.DatabaseContextFunction: evalDatabaseContextFunction :: DatabaseContextFunction -> DatabaseContextFunctionUtils -> [Atom] -> DatabaseContext -> Either RelationalError DatabaseContext
- ProjectM36.DatabaseContextFunctionUtils: executeDatabaseContextExpr :: DatabaseContextExpr -> TransactionId -> TransactionGraph -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext
+ ProjectM36.DatabaseContextFunctionUtils: executeDatabaseContextExpr :: DatabaseContextExpr' -> TransactionId -> TransactionGraph -> DatabaseContext -> DatabaseContextFunctionUtils -> Either RelationalError DatabaseContext
- ProjectM36.DateExamples: dateExamples :: DatabaseContext
+ ProjectM36.DateExamples: dateExamples :: ResolvedDatabaseContext
- ProjectM36.DisconnectedTransaction: freshTransaction :: TransactionId -> Schemas -> DisconnectedTransaction
+ ProjectM36.DisconnectedTransaction: freshTransaction :: CurrentHead -> TransactionId -> TransactionRefSchemas -> DisconnectedTransaction
- ProjectM36.Error: DatabaseContextFunctionUserError :: DatabaseContextFunctionError -> RelationalError
+ ProjectM36.Error: DatabaseContextFunctionUserError :: String -> RelationalError
- ProjectM36.Error: StrategyViolatesComponentMergeError :: MergeError
+ ProjectM36.Error: StrategyViolatesComponentMergeError :: DatabaseContextField -> StringType -> MergeError
- ProjectM36.Function: functionForName :: FunctionName -> HashSet (Function a) -> Either RelationalError (Function a)
+ ProjectM36.Function: functionForName :: FunctionName -> HashSet (Function a acl) -> Either RelationalError (Function a acl)
- ProjectM36.Function: functionScript :: Function a -> Maybe FunctionBodyScript
+ ProjectM36.Function: functionScript :: Function a acl -> Maybe FunctionBodyScript
- ProjectM36.Function: loadFunctions :: ModName -> FuncName -> Maybe FilePath -> FilePath -> IO (Either LoadSymbolError [Function a])
+ ProjectM36.Function: loadFunctions :: ModName -> FuncName -> Maybe FilePath -> FilePath -> IO (Either LoadSymbolError [Function a acl])
- ProjectM36.Function: processObjectLoadedFunctions :: Functor f => ObjectModuleName -> ObjectFileEntryFunctionName -> FilePath -> f (Function a) -> f (Function a)
+ ProjectM36.Function: processObjectLoadedFunctions :: Functor f => ObjectModuleName -> ObjectFileEntryFunctionName -> FilePath -> f (Function a acl) -> f (Function a acl)
- ProjectM36.HashSecurely: mkDDLHash :: DatabaseContext -> Map RelVarName Relation -> SecureHash
+ ProjectM36.HashSecurely: mkDDLHash :: ResolvedDatabaseContext -> Map RelVarName Relation -> SecureHash
- ProjectM36.IsomorphicSchema: databaseContextExprMorph :: SchemaIsomorph -> (RelationalExpr -> Either RelationalError RelationalExpr) -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr
+ ProjectM36.IsomorphicSchema: databaseContextExprMorph :: SchemaIsomorph -> (RelationalExpr -> Either RelationalError RelationalExpr) -> DatabaseContextExpr' -> Either RelationalError DatabaseContextExpr'
- ProjectM36.IsomorphicSchema: evalSchemaExpr :: SchemaExpr -> DatabaseContext -> TransactionId -> TransactionGraph -> Subschemas -> Either RelationalError (Subschemas, DatabaseContext)
+ ProjectM36.IsomorphicSchema: evalSchemaExpr :: SchemaExpr -> DatabaseContext -> TransactionId -> TransactionGraph -> DatabaseContextFunctionUtils -> Subschemas -> Either RelationalError (Subschemas, DatabaseContext)
- ProjectM36.IsomorphicSchema: processDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr
+ ProjectM36.IsomorphicSchema: processDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr' -> Either RelationalError DatabaseContextExpr'
- ProjectM36.IsomorphicSchema: validateDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError ()
+ ProjectM36.IsomorphicSchema: validateDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr' -> Either RelationalError ()
- ProjectM36.IsomorphicSchema: validateSchema :: Schema -> DatabaseContext -> Maybe SchemaError
+ ProjectM36.IsomorphicSchema: validateSchema :: Schema -> Set RelVarName -> Maybe SchemaError
- ProjectM36.NormalizeExpr: processDatabaseContextExpr :: DatabaseContextExpr -> ProcessExprM GraphRefDatabaseContextExpr
+ ProjectM36.NormalizeExpr: processDatabaseContextExpr :: DatabaseContextExprBase () r -> ProcessExprM (DatabaseContextExprBase GraphRefTransactionMarker r)
- ProjectM36.Relation: type RestrictionFilter = RelationTuple -> Either RelationalError Bool
+ ProjectM36.Relation: type RestrictionFilter = RelationTuple -> ContextTuples -> Either RelationalError Bool
- ProjectM36.RelationalExpression: DatabaseContextEvalEnv :: TransactionId -> TransactionGraph -> DatabaseContextEvalEnv
+ ProjectM36.RelationalExpression: DatabaseContextEvalEnv :: TransactionId -> TransactionGraph -> DatabaseContextFunctionUtils -> DatabaseContextEvalEnv
- ProjectM36.RelationalExpression: DatabaseContextEvalState :: DatabaseContext -> Map ResultAccumName ResultAccum -> DirtyFlag -> DatabaseContextEvalState
+ ProjectM36.RelationalExpression: DatabaseContextEvalState :: DatabaseContext -> Map ResultAccumName ResultAccum -> DatabaseContextEvalState
- ProjectM36.RelationalExpression: DatabaseContextIOEvalEnv :: TransactionId -> TransactionGraph -> Maybe ScriptSession -> Maybe FilePath -> DatabaseContextIOEvalEnv
+ ProjectM36.RelationalExpression: DatabaseContextIOEvalEnv :: TransactionId -> TransactionGraph -> Maybe ScriptSession -> RoleId -> Maybe FilePath -> DatabaseContextFunctionUtils -> DatabaseContextIOEvalEnv
- ProjectM36.RelationalExpression: evalGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr -> DatabaseContextEvalMonad ()
+ ProjectM36.RelationalExpression: evalGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr' -> DatabaseContextEvalMonad ()
- ProjectM36.RelationalExpression: evalGraphRefDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> DatabaseContextIOEvalMonad (Either RelationalError ())
+ ProjectM36.RelationalExpression: evalGraphRefDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> DatabaseContextIOEvalMonad ()
- ProjectM36.RelationalExpression: extendGraphRefTupleExpressionProcessor :: Relation -> GraphRefExtendTupleExpr -> GraphRefRelationalExprM (Attributes, RelationTuple -> Either RelationalError RelationTuple)
+ ProjectM36.RelationalExpression: extendGraphRefTupleExpressionProcessor :: Attributes -> GraphRefExtendTupleExpr -> GraphRefRelationalExprM ExtendTupleProcessor
- ProjectM36.RelationalExpression: mkDatabaseContextEvalEnv :: TransactionId -> TransactionGraph -> DatabaseContextEvalEnv
+ ProjectM36.RelationalExpression: mkDatabaseContextEvalEnv :: TransactionId -> TransactionGraph -> DatabaseContextFunctionUtils -> DatabaseContextEvalEnv
- ProjectM36.RelationalExpression: putDBCIOContext :: DatabaseContext -> DatabaseContextIOEvalMonad (Either RelationalError ())
+ ProjectM36.RelationalExpression: putDBCIOContext :: DatabaseContext -> DatabaseContextIOEvalMonad ()
- ProjectM36.RelationalExpression: requireScriptSession :: DatabaseContextIOEvalMonad (Either RelationalError ScriptSession)
+ ProjectM36.RelationalExpression: requireScriptSession :: DatabaseContextIOEvalMonad ScriptSession
- ProjectM36.RelationalExpression: runDatabaseContextIOEvalMonad :: DatabaseContextIOEvalEnv -> DatabaseContext -> DatabaseContextIOEvalMonad (Either RelationalError ()) -> IO (Either RelationalError DatabaseContextEvalState)
+ ProjectM36.RelationalExpression: runDatabaseContextIOEvalMonad :: DatabaseContextIOEvalEnv -> DatabaseContext -> DatabaseContextIOEvalMonad () -> IO (Either RelationalError DatabaseContextEvalState)
- ProjectM36.RelationalExpression: tupleExprCheckNewAttrName :: AttributeName -> Relation -> Either RelationalError Relation
+ ProjectM36.RelationalExpression: tupleExprCheckNewAttrName :: AttributeName -> Attributes -> Either RelationalError Attributes
- ProjectM36.RelationalExpression: type DatabaseContextIOEvalMonad a = RWST DatabaseContextIOEvalEnv () DatabaseContextEvalState IO a
+ ProjectM36.RelationalExpression: type DatabaseContextIOEvalMonad a = RWST DatabaseContextIOEvalEnv () DatabaseContextEvalState (ExceptT RelationalError IO) a
- ProjectM36.RelationalExpression: verifyGraphRefAtomExprTypes :: Relation -> GraphRefAtomExpr -> AtomType -> GraphRefRelationalExprM AtomType
+ ProjectM36.RelationalExpression: verifyGraphRefAtomExprTypes :: Attributes -> GraphRefAtomExpr -> AtomType -> GraphRefRelationalExprM AtomType
- ProjectM36.SQL.Select: BetweenOperatorF :: r_a1Kg4 -> r_a1Kg4 -> r_a1Kg4 -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: BetweenOperatorF :: r_a1bp0 -> r_a1bp0 -> r_a1bp0 -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: BinaryOperatorF :: r_a1Kg4 -> OperatorName -> r_a1Kg4 -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: BinaryOperatorF :: r_a1bp0 -> OperatorName -> r_a1bp0 -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: BooleanLiteralF :: Bool -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: BooleanLiteralF :: Bool -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: BooleanOperatorExprF :: r_a1Kg4 -> BoolOp -> r_a1Kg4 -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: BooleanOperatorExprF :: r_a1bp0 -> BoolOp -> r_a1bp0 -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: CaseExprF :: [(r_a1Kg4, r_a1Kg4)] -> Maybe r_a1Kg4 -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: CaseExprF :: [(r_a1bp0, r_a1bp0)] -> Maybe r_a1bp0 -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: DoubleLiteralF :: Double -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: DoubleLiteralF :: Double -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: ExistsExprF :: Select -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: ExistsExprF :: Select -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: FunctionApplicationF :: FuncName -> [r_a1Kg4] -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: FunctionApplicationF :: FuncName -> [r_a1bp0] -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: IdentifierF :: n_a1Aj1 -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: IdentifierF :: n_a1130 -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: InExprF :: InFlag -> r_a1Kg4 -> InPredicateValue -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: InExprF :: InFlag -> r_a1bp0 -> InPredicateValue -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: IntegerLiteralF :: Integer -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: IntegerLiteralF :: Integer -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: NullLiteralF :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: NullLiteralF :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: PostfixOperatorF :: r_a1Kg4 -> OperatorName -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: PostfixOperatorF :: r_a1bp0 -> OperatorName -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: PrefixOperatorF :: OperatorName -> r_a1Kg4 -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: PrefixOperatorF :: OperatorName -> r_a1bp0 -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: QuantifiedComparisonF :: r_a1Kg4 -> ComparisonOperator -> QuantifiedComparisonPredicate -> Select -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: QuantifiedComparisonF :: r_a1bp0 -> ComparisonOperator -> QuantifiedComparisonPredicate -> Select -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: StringLiteralF :: Text -> ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: StringLiteralF :: Text -> ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQL.Select: [caseElseF] :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4 -> Maybe r_a1Kg4
+ ProjectM36.SQL.Select: [caseElseF] :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0 -> Maybe r_a1bp0
- ProjectM36.SQL.Select: [caseWhensF] :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4 -> [(r_a1Kg4, r_a1Kg4)]
+ ProjectM36.SQL.Select: [caseWhensF] :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0 -> [(r_a1bp0, r_a1bp0)]
- ProjectM36.SQL.Select: [qcExprF] :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4 -> r_a1Kg4
+ ProjectM36.SQL.Select: [qcExprF] :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0 -> r_a1bp0
- ProjectM36.SQL.Select: [qcOperatorF] :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4 -> ComparisonOperator
+ ProjectM36.SQL.Select: [qcOperatorF] :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0 -> ComparisonOperator
- ProjectM36.SQL.Select: [qcPredicateF] :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4 -> QuantifiedComparisonPredicate
+ ProjectM36.SQL.Select: [qcPredicateF] :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0 -> QuantifiedComparisonPredicate
- ProjectM36.SQL.Select: [qcQueryF] :: ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4 -> Select
+ ProjectM36.SQL.Select: [qcQueryF] :: ScalarExprBaseF (n_a1130 :: Type) r_a1bp0 -> Select
- ProjectM36.SQL.Select: data ScalarExprBaseF (n_a1Aj1 :: Type) r_a1Kg4
+ ProjectM36.SQL.Select: data ScalarExprBaseF (n_a1130 :: Type) r_a1bp0
- ProjectM36.SQLDatabaseContext: sqlDatabaseContext :: DatabaseContext
+ ProjectM36.SQLDatabaseContext: sqlDatabaseContext :: ResolvedDatabaseContext
- ProjectM36.Server.Config: ServerConfig :: PersistenceStrategy -> Bool -> DatabaseName -> RemoteServerAddress -> [String] -> Int -> Bool -> ServerConfig
+ ProjectM36.Server.Config: ServerConfig :: PersistenceStrategy -> Bool -> DatabaseName -> RemoteServerAddress -> [String] -> Int -> Bool -> ServerConnectionConfig -> ServerConfig
- ProjectM36.Server.EntryPoints: handleLogin :: Connection -> Locking Socket -> IO Bool
+ ProjectM36.Server.EntryPoints: handleLogin :: Connection -> ClientConnectionId -> SocketContext -> RoleName -> IO Bool
- ProjectM36.Server.RemoteCallTypes: ExecuteGraphExpr :: SessionId -> TransactionGraphOperator -> ExecuteGraphExpr
+ ProjectM36.Server.RemoteCallTypes: ExecuteGraphExpr :: SessionId -> TransactionGraphExpr -> ExecuteGraphExpr
- ProjectM36.Server.RemoteCallTypes: Login :: DatabaseName -> Login
+ ProjectM36.Server.RemoteCallTypes: Login :: DatabaseName -> RoleName -> Login
- ProjectM36.Session: schemas :: Session -> Schemas
+ ProjectM36.Session: schemas :: Session -> TransactionRefSchemas
- ProjectM36.Session: setSchemaName :: SchemaName -> Session -> Either RelationalError Session
+ ProjectM36.Session: setSchemaName :: SchemaName -> Session -> TransactionGraph -> Either RelationalError Session
- ProjectM36.Session: subschemas :: Session -> Subschemas
+ ProjectM36.Session: subschemas :: Session -> ValueMarker Subschemas
- ProjectM36.StaticOptimizer: GraphRefSOptDatabaseContextExprEnv :: TransactionGraph -> DatabaseContext -> TransactionId -> GraphRefSOptDatabaseContextExprEnv
+ ProjectM36.StaticOptimizer: GraphRefSOptDatabaseContextExprEnv :: TransactionGraph -> DatabaseContext -> TransactionId -> DatabaseContextFunctionUtils -> GraphRefSOptDatabaseContextExprEnv
- ProjectM36.StaticOptimizer: optimizeAndEvalDatabaseContextExpr :: Bool -> DatabaseContextExpr -> DatabaseContextEvalMonad ()
+ ProjectM36.StaticOptimizer: optimizeAndEvalDatabaseContextExpr :: Bool -> DatabaseContextExpr' -> DatabaseContextEvalMonad ()
- ProjectM36.StaticOptimizer: optimizeAndEvalDatabaseContextIOExpr :: DatabaseContextIOExpr -> DatabaseContextIOEvalMonad (Either RelationalError ())
+ ProjectM36.StaticOptimizer: optimizeAndEvalDatabaseContextIOExpr :: DatabaseContextIOExpr -> DatabaseContextIOEvalMonad ()
- ProjectM36.StaticOptimizer: optimizeDatabaseContextExpr :: DatabaseContextExpr -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr
+ ProjectM36.StaticOptimizer: optimizeDatabaseContextExpr :: DatabaseContextExpr' -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr'
- ProjectM36.StaticOptimizer: optimizeDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextIOExpr
+ ProjectM36.StaticOptimizer: optimizeDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> GraphRefSOptDatabaseContextIOExprM GraphRefDatabaseContextIOExpr
- ProjectM36.StaticOptimizer: optimizeGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr
+ ProjectM36.StaticOptimizer: optimizeGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr' -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr'
- ProjectM36.StaticOptimizer: runGraphRefSOptDatabaseContextExprM :: TransactionId -> DatabaseContext -> TransactionGraph -> GraphRefSOptDatabaseContextExprM a -> Either RelationalError a
+ ProjectM36.StaticOptimizer: runGraphRefSOptDatabaseContextExprM :: TransactionId -> DatabaseContext -> TransactionGraph -> DatabaseContextFunctionUtils -> GraphRefSOptDatabaseContextExprM a -> Either RelationalError a
- ProjectM36.Transaction: fresh :: TransactionId -> UTCTime -> Schemas -> Transaction
+ ProjectM36.Transaction: fresh :: TransactionId -> UTCTime -> Schemas DatabaseContext -> Transaction
- ProjectM36.Transaction.Persist: loadFunc :: FilePath -> HashSet (Function a) -> Maybe ScriptSession -> FunctionName -> [AtomType] -> Maybe FunctionBodyScript -> Maybe ObjectFileInfo -> IO (Function a)
+ ProjectM36.Transaction.Persist: loadFunc :: Default acl => FilePath -> HashSet (Function a acl) -> Maybe ScriptSession -> FunctionName -> [AtomType] -> Maybe FunctionBodyScript -> Maybe ObjectFileInfo -> IO (Function a acl)
- ProjectM36.Transaction.Persist: readFuncs :: FilePath -> FilePath -> HashSet (Function a) -> Maybe ScriptSession -> IO (HashSet (Function a))
+ ProjectM36.Transaction.Persist: readFuncs :: Default acl => FilePath -> FilePath -> HashSet (Function a acl) -> Maybe ScriptSession -> IO (HashSet (Function a acl))
- ProjectM36.Transaction.Persist: writeFuncs :: Traversable t => DiskSync -> FilePath -> t (Function a) -> IO ()
+ ProjectM36.Transaction.Persist: writeFuncs :: Traversable t => DiskSync -> FilePath -> t (Function a acl) -> IO ()
- ProjectM36.Transaction.Persist: writeTransaction :: DiskSync -> FilePath -> Transaction -> IO ()
+ ProjectM36.Transaction.Persist: writeTransaction :: DiskSync -> FilePath -> UncommittedTransaction -> IO ()
- ProjectM36.TransactionGraph: Branch :: HeadName -> TransactionGraphOperator
+ ProjectM36.TransactionGraph: Branch :: HeadName -> AlterTransactionGraphExpr
- ProjectM36.TransactionGraph: Commit :: TransactionGraphOperator
+ ProjectM36.TransactionGraph: Commit :: AlterTransactionGraphExpr
- ProjectM36.TransactionGraph: DeleteBranch :: HeadName -> TransactionGraphOperator
+ ProjectM36.TransactionGraph: DeleteBranch :: HeadName -> AlterTransactionGraphExpr
- ProjectM36.TransactionGraph: JumpToHead :: HeadName -> TransactionGraphOperator
+ ProjectM36.TransactionGraph: JumpToHead :: HeadName -> TransactionGraphExpr
- ProjectM36.TransactionGraph: JumpToTransaction :: TransactionId -> TransactionGraphOperator
+ ProjectM36.TransactionGraph: JumpToTransaction :: TransactionId -> TransactionGraphExpr
- ProjectM36.TransactionGraph: MergeTransactions :: MergeStrategy -> HeadName -> HeadName -> TransactionGraphOperator
+ ProjectM36.TransactionGraph: MergeTransactions :: MergeStrategy -> HeadName -> HeadName -> AlterTransactionGraphExpr
- ProjectM36.TransactionGraph: Rollback :: TransactionGraphOperator
+ ProjectM36.TransactionGraph: Rollback :: AlterTransactionGraphExpr
- ProjectM36.TransactionGraph: WalkBackToTime :: UTCTime -> TransactionGraphOperator
+ ProjectM36.TransactionGraph: WalkBackToTime :: UTCTime -> TransactionGraphExpr
- ProjectM36.TransactionGraph: addDisconnectedTransaction :: UTCTime -> TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
+ ProjectM36.TransactionGraph: addDisconnectedTransaction :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (UncommittedTransaction, TransactionGraph)
- ProjectM36.TransactionGraph: addTransactionToGraph :: HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
+ ProjectM36.TransactionGraph: addTransactionToGraph :: HeadName -> UncommittedTransaction -> TransactionGraph -> Either RelationalError (UncommittedTransaction, TransactionGraph)
- ProjectM36.TransactionGraph: autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)
+ ProjectM36.TransactionGraph: autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraphIncrementalWriteInfo)
- ProjectM36.TransactionGraph: createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction
+ ProjectM36.TransactionGraph: createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM UncommittedTransaction
- ProjectM36.TransactionGraph: createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction
+ ProjectM36.TransactionGraph: createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM UncommittedTransaction
- ProjectM36.TransactionGraph: isCommit :: TransactionGraphOperator -> Bool
+ ProjectM36.TransactionGraph: isCommit :: AlterTransactionGraphExpr -> Bool
- ProjectM36.TransactionGraph: mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> (HeadName, HeadName) -> GraphRefRelationalExprM (DisconnectedTransaction, TransactionGraph)
+ ProjectM36.TransactionGraph: mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> MergeHeadNames -> GraphRefRelationalExprM (UncommittedTransaction, TransactionGraph)
- ProjectM36.TransactionGraph.Merge: unionMergeAtomFunctions :: MergePreference -> AtomFunctions -> AtomFunctions -> Either MergeError AtomFunctions
+ ProjectM36.TransactionGraph.Merge: unionMergeAtomFunctions :: MergePreference -> TransactionGraph -> ValueMarker AtomFunctions -> ValueMarker AtomFunctions -> Either RelationalError (ValueMarker AtomFunctions)
- ProjectM36.TransactionGraph.Merge: unionMergeDatabaseContextFunctions :: MergePreference -> DatabaseContextFunctions -> DatabaseContextFunctions -> Either MergeError DatabaseContextFunctions
+ ProjectM36.TransactionGraph.Merge: unionMergeDatabaseContextFunctions :: MergePreference -> TransactionGraph -> ValueMarker DatabaseContextFunctions -> ValueMarker DatabaseContextFunctions -> Either RelationalError (ValueMarker DatabaseContextFunctions)
- ProjectM36.TransactionGraph.Merge: unionMergeMaps :: (Ord k, Eq a) => MergePreference -> Map k a -> Map k a -> Either MergeError (Map k a)
+ ProjectM36.TransactionGraph.Merge: unionMergeMaps :: Eq a => DatabaseContextField -> MergePreference -> TransactionGraph -> (DatabaseContext -> ValueMarker (Map StringType a)) -> ValueMarker (Map StringType a) -> ValueMarker (Map StringType a) -> Either RelationalError (ValueMarker (Map StringType a))
- ProjectM36.TransactionGraph.Merge: unionMergeRegisteredQueries :: MergePreference -> RegisteredQueries -> RegisteredQueries -> Either MergeError RegisteredQueries
+ ProjectM36.TransactionGraph.Merge: unionMergeRegisteredQueries :: MergePreference -> TransactionGraph -> ValueMarker RegisteredQueries -> ValueMarker RegisteredQueries -> Either RelationalError (ValueMarker RegisteredQueries)
- ProjectM36.TransactionGraph.Merge: unionMergeRelVars :: MergePreference -> RelationVariables -> RelationVariables -> GraphRefRelationalExprM RelationVariables
+ ProjectM36.TransactionGraph.Merge: unionMergeRelVars :: MergePreference -> TransactionGraph -> ValueMarker RelationVariables -> ValueMarker RelationVariables -> GraphRefRelationalExprM (ValueMarker RelationVariables)
- ProjectM36.TransactionGraph.Merge: unionMergeTypeConstructorMapping :: MergePreference -> TypeConstructorMapping -> TypeConstructorMapping -> Either MergeError TypeConstructorMapping
+ ProjectM36.TransactionGraph.Merge: unionMergeTypeConstructorMapping :: MergePreference -> TransactionGraph -> ValueMarker TypeConstructorMapping -> ValueMarker TypeConstructorMapping -> Either RelationalError (ValueMarker TypeConstructorMapping)
- ProjectM36.TransactionGraph.Persist: transactionGraphPersist :: DiskSync -> FilePath -> [TransactionId] -> TransactionGraph -> IO LockFileHash
+ ProjectM36.TransactionGraph.Persist: transactionGraphPersist :: DiskSync -> FilePath -> TransactionGraph -> IO LockFileHash
- ProjectM36.Tupleable.Deriving: class Generic a
+ ProjectM36.Tupleable.Deriving: class () => Generic a

Files

Changelog.markdown view
@@ -1,3 +1,12 @@+# 2026-01-06 (v1.2.0)++* add LRU result caching layer+* add parallel execution engine based on [streamly 0.9.0](https://github.com/composewell/streamly)+* reduce copying of database context fields in persistence layer by enabling references to previous transactions+* require GHC 9.4++* add TLS encryption including mutual TLS support for client authentication+* add role-based authentication for a variety of database permissions+ # 2025-03-30 (v1.1.1)  * upgrade to curryer-rpc 0.4.0 which includes support for IPv6 and Unix domain socket communications
README.markdown view
@@ -4,7 +4,6 @@ [![Public Domain](https://img.shields.io/badge/license-Public%20Domain-brightgreen.svg)](http://unlicense.org) [![Hackage](https://img.shields.io/hackage/v/project-m36.svg)](http://hackage.haskell.org/package/project-m36) [![Hackage dependency status](https://img.shields.io/hackage-deps/v/project-m36.svg)](http://packdeps.haskellers.com/feed?needle=project-m36)-[![Windows Build status](https://ci.appveyor.com/api/projects/status/q7jyddd6dy1ibqdo/branch/master?svg=true)](https://ci.appveyor.com/project/agentm/project-m36) [![Github Workflow status](https://github.com/agentm/project-m36/workflows/CI/badge.svg)](https://github.com/agentm/project-m36/actions?query=workflow%3ACI)  @@ -83,6 +82,8 @@  ### Advanced Features +1. [Role-Based Access Control](docs/role-based_access_control.markdown)+1. [Authentication](docs/authentication.markdown) 1. [Transaction Graph Operators](docs/transaction_graph_operators.markdown) 1. [ProjectM36.Client Library](docs/projectm36_client_library.markdown) 1. [Data Frames for Sorting](docs/dataframes.markdown)
+ examples/Hospital.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE DeriveGeneric, DerivingVia, TypeApplications #-}+import ProjectM36.Client+import Data.Typeable+import ProjectM36.Relation+import GHC.Generics+import ProjectM36.Tuple+import System.Random (initStdGen)++-- define a custom data type to use in both the client and database+data AgeType = PreciseAge Int |+               ForgotToAsk |+               RefusedToDisclose |+               NotApplicable |+               ApproximateAge Int Int+               deriving (Eq, Show, Generic)++-- define an automatically-derived Atomable instance+instance Atomable AgeType++failFast :: Show a => IO (Either a b) -> IO b+failFast m = do+  ret <- m+  case ret of+    Left err -> error (show err)+    Right val -> pure val++ageAtomType :: AtomType+ageAtomType = toAtomType (Proxy :: Proxy AgeType)+               +main :: IO ()+main = do+  let bob_relation_attrs = attributesFromList [Attribute "name" TextAtomType,+                                               Attribute "age" ageAtomType]+      relvar_name = "hospital_patient"+      age_value_in = ApproximateAge 30 40 +      mk_bob_relation = mkRelationFromList +                     bob_relation_attrs +                     [[TextAtom "Bob",+                       toAtom age_value_in]]++  rando <- initStdGen+  -- create the database+  conn <- failFast $ connectProjectM36 (InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando "admin")++  -- create the session at the head of master branch+  sessionId <- failFast $ createSessionAtHead conn "master"++  -- create the data type in the database+  failFast $ executeDatabaseContextExpr sessionId conn (toAddTypeExpr (Proxy :: Proxy AgeType))+  +  -- create the patient data in a relation+  bob_relation <- failFast (pure mk_bob_relation)++  -- save the data to the database+  failFast $ executeDatabaseContextExpr sessionId conn (Assign relvar_name (ExistingRelation bob_relation))++  -- retrieve the data from the database+  result <- failFast $ executeRelationalExpr sessionId conn (RelationVariable relvar_name ())++  -- print the data+  case singletonTuple result of+    Nothing -> error "not a singleton relation!"+    Just tuple -> do+      case atomForAttributeName "age" tuple of+        Left err -> error (show err)+        Right age_value_out -> do+          print age_value_in+          print (fromAtom @AgeType age_value_out)+      +            
examples/Plantfarm.hs view
@@ -22,7 +22,7 @@ import Data.Text as T (Text,pack) import GHC.Generics (Generic) import qualified ProjectM36.Base as Base-import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContext.Basic import ProjectM36.Client        ( AtomExprBase(NakedAtomExpr)        , Atomable(toAddTypeExpr, toAtom)@@ -365,7 +365,8 @@ dbConnection :: IO DBConnection dbConnection = do   --  connect to the database-  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext+  rando <- R.initStdGen+  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando "admin"   --  The code below persists the data in a DB with the name "base". \\ --  let connInfo = InProcessConnectionInfo (CrashSafePersistence "base") emptyNotificationCallback [] \\   --  In addition minimal persistence is available. \\
examples/SimpleClient.hs view
@@ -5,11 +5,12 @@ import ProjectM36.TupleSet import ProjectM36.Relation.Show.Term import Data.Text.IO as TIO+import Network.RPC.Curryer.Client (ClientConnectionConfig(..))  main :: IO () main = do   -- 1. create a ConnectionInfo-  let connInfo = RemoteConnectionInfo "mytestdb" defaultRemoteServerAddress emptyNotificationCallback+  let connInfo = RemoteConnectionInfo "mytestdb" defaultRemoteServerAddress UnencryptedConnectionConfig emptyNotificationCallback "admin"   -- 2. connected to the remote database   eConn <- connectProjectM36 connInfo   case eConn of
examples/blog.hs view
@@ -16,9 +16,6 @@ import Data.Time.Calendar import Control.DeepSeq import Data.Proxy-#if __GLASGOW_HASKELL__ < 804-import Data.Monoid-#endif import Data.List import Control.Monad (when, forM_) import Codec.Winery@@ -30,6 +27,7 @@ --import Control.Monad.IO.Class (liftIO) import Network.HTTP.Types.Status import Data.Time.Format.ISO8601+import System.Random  --define your data types data Blog = Blog {@@ -76,7 +74,8 @@ main :: IO ()                        main = do   --connect to the database-  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext+  rando <- initStdGen+  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando "admin"   conn <- handleIOError $ connectProjectM36 connInfo      sessionId <- handleIOError $ createSessionAtHead conn "master"
examples/hair.hs view
@@ -8,6 +8,7 @@ import qualified Data.Text.IO as TIO import Data.Proxy import Codec.Winery+import System.Random  data Hair = Bald | Brown | Blond | OtherColor Text    deriving (Generic, Show, Eq, NFData, Atomable)@@ -16,7 +17,9 @@ main :: IO () main = do  --connect to the database-  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext+  rando <- initStdGen++  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando "admin"       eCheck v = do         x <- v         case x of 
examples/out_of_the_tarpit.hs view
@@ -5,6 +5,7 @@ import ProjectM36.Tupleable import ProjectM36.Relation import ProjectM36.Error+import ProjectM36.AtomFunction import Data.Either import GHC.Generics import Control.DeepSeq@@ -12,6 +13,7 @@ import Data.Time.Calendar import Data.Proxy import Codec.Winery+import System.Random (initStdGen)  --create various database value (atom) types type Price = Double@@ -39,7 +41,8 @@ main :: IO () main = do   --connect to the database-  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext+  rando <- initStdGen+  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando "admin"       check x = case x of          Left err -> error (show err)         Right x' -> x'
+ examples/zoo.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DeriveGeneric, DerivingVia, DeriveAnyClass, OverloadedStrings #-}+import ProjectM36.Client+import ProjectM36.Relation+import ProjectM36.Tupleable+import ProjectM36.TupleSet+import ProjectM36.Relation.Show.Term++import Data.Typeable+import GHC.Generics+import System.Random (initStdGen)+import Data.Time.Calendar+import qualified Data.Text.IO as T++data Ticket = Ticket+  { ticketId   :: Integer+  , visitorAge :: Integer    -- years+  , basePrice  :: Integer  -- base price before adjustments+  , visitDate  :: Day+}+ deriving (Generic, Tupleable)+++main :: IO ()+main = do+  let ticketAttrs = toAttributes (Proxy :: Proxy Ticket)+      ticket1 = Ticket { ticketId = 1,+                         visitorAge = 8,+                         basePrice = 20,+                         visitDate = fromGregorian 2025 10 01 }+      ticket2 = Ticket { ticketId = 2,+                         visitorAge = 25,+                         basePrice = 20,+                         visitDate = fromGregorian 2025 12 25 }+      Right ticketTupleSet = mkTupleSet ticketAttrs [toTuple ticket1,+                                          toTuple ticket2]+      Right ticketRel = mkRelation ticketAttrs ticketTupleSet+  rando <- initStdGen+  -- connect to the database+  conn <- failFast $ connectProjectM36 (InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando "admin")++  -- create a session on the master branch+  sessionId <- failFast $ createSessionAtHead conn "master"++  -- add the ticket discount function+  let func_type = [integerTypeCons, integerTypeCons, ADTypeConstructor "Either" [ADTypeConstructor "AtomFunctionError" [], integerTypeCons]]+      integerTypeCons = PrimitiveTypeConstructor "Integer" IntegerAtomType+      func_body = "(\\[IntegerAtom age,IntegerAtom price] -> pure (IntegerAtom (if age < 10 then price `div` 2 else price))) :: [Atom] -> Either AtomFunctionError Atom"+  failFast $ executeDatabaseContextIOExpr sessionId conn (AddAtomFunction "apply_discount" func_type func_body)++  -- calculate the proper discount per ticket and add it to the database+  let discountedTicketRel = Extend (AttributeExtendTupleExpr "discounted_price" func_apply_discount) (ExistingRelation ticketRel)+      func_apply_discount = FunctionAtomExpr "apply_discount" [AttributeAtomExpr "visitorAge", AttributeAtomExpr "basePrice"] ()+  failFast $ executeDatabaseContextExpr sessionId conn (Assign "ticket_sales" discountedTicketRel)++  -- print out the resultant relation+  Right ticketSalesRelation <- executeRelationalExpr sessionId conn (RelationVariable "ticket_sales" ())+  T.putStrLn $ showRelation ticketSalesRelation++ +++-- | Apply a 50% discount for kids under 10 years old. Arguments: age, base price+applyDiscount :: Integer -> Integer -> Integer+applyDiscount age base_price =+  if age < 10 then base_price `div` 2 else base_price++failFast :: Show a => IO (Either a b) -> IO b+failFast m = do+  ret <- m+  case ret of+    Left err -> error (show err)+    Right val -> pure val++  
project-m36.cabal view
@@ -1,6 +1,6 @@ Cabal-Version: 2.2 Name: project-m36-Version: 1.1.1+Version: 1.2.0 License: MIT --note that this license specification is erroneous and only labeled MIT to appease hackage which does not recognize public domain packages in cabal >2.2- Project:M36 is dedicated to the public domain Build-Type: Simple@@ -14,7 +14,7 @@ Description: A relational algebra engine which can be used to persist and query Haskell data types. Extra-Source-Files: scripts/DateExamples.tutd scripts/multiline.tutd Extra-Doc-Files: Changelog.markdown README.markdown-tested-with: GHC ==9.2.8 || ==9.4.8 || ==9.6.6 || ==9.8.2 || ==9.10.1+tested-with: GHC ==9.4.8 || ==9.6.6 || ==9.8.2 || ==9.10.1  Source-Repository head     Type: git@@ -46,8 +46,10 @@                   hashable,                   haskeline,                   directory,-                  MonadRandom, +                  MonadRandom,+                  random,                   random-shuffle,+                  prettyprinter,                   uuid >= 1.3.12,                   cassava >= 0.4.5.1 && < 0.6,                   text,@@ -89,7 +91,7 @@                   cryptohash-sha256,                   text-manipulate >= 0.2.0.1 && < 0.4,                   winery >= 1.4,-                  curryer-rpc>=0.4.0,+                  curryer-rpc>=0.5.0,                   network,                   async,                   vector-instances,@@ -97,7 +99,16 @@                   streamly == 0.10.1,                   convertible,                   fast-builder,-                  scientific+                  scientific,+                  crypton,+                  streamly-core,+                  streamly,+                  network-byte-order,+                  sqlite-simple,+                  data-default,+                  process,+                  system-linux-proc,+                  megaparsec      if flag(haskell-scripting)         Build-Depends: ghc >= 9.0         CPP-Options: -DPM36_HASKELL_SCRIPTING@@ -106,6 +117,10 @@         ghc-boot, ghci     if impl(ghc>= 8.6)         Build-Depends: deferred-folds+    if impl(ghc >= 9)+        Build-Depends: ghc-bignum+    if !impl(ghc >= 9)+        Build-Depends: integer-gmp     Exposed-Modules: ProjectM36.Error,                      ProjectM36.Transaction,                      ProjectM36.TransactionGraph,@@ -116,23 +131,28 @@                      ProjectM36.Base,                      ProjectM36.Serialise.Base,                      ProjectM36.Serialise.Error,+                     ProjectM36.Serialise.LoginRoles,                      ProjectM36.DataFrame,                      ProjectM36.Attribute,                      ProjectM36.AttributeNames,                      ProjectM36.Tuple,                      ProjectM36.TupleSet,+                     ProjectM36.RelationVariablesMentioned,                      ProjectM36.Function,                      ProjectM36.Atom,                      ProjectM36.AtomFunction,-                     ProjectM36.AtomFunctionError,+                     ProjectM36.BTree,                      ProjectM36.ScriptSession,                      ProjectM36.Shortcuts,                      ProjectM36.DatabaseContextFunction,-                     ProjectM36.DatabaseContextFunctionError,                      ProjectM36.DatabaseContextFunctionUtils,                      ProjectM36.Key,                      ProjectM36.FunctionalDependency,                      ProjectM36.DatabaseContext,+                     ProjectM36.DatabaseContext.Types,+                     ProjectM36.ValueMarker,+                     ProjectM36.DatabaseContextFunctions.Basic,+                     ProjectM36.TransactionGraph.Types,                      ProjectM36.DateExamples,                      ProjectM36.DisconnectedTransaction,                      ProjectM36.AtomFunctionBody,@@ -154,6 +174,9 @@                      ProjectM36.AttributeExpr,                      ProjectM36.AtomFunctions.Basic,                      ProjectM36.AtomFunctions.Primitive,+                     ProjectM36.AtomFunctionError,+                     ProjectM36.DatabaseContext.SelfTest,+                     ProjectM36.AtomFunctions.SelfTest,                      ProjectM36.Atomable,                      ProjectM36.Tupleable,                      ProjectM36.Tupleable.Deriving,@@ -170,7 +193,10 @@                      ProjectM36.DataTypes.Interval,                      ProjectM36.DataTypes.ByteString,                      ProjectM36.DataTypes.SQL.Null,-                     ProjectM36.SQLDatabaseContext,+                     ProjectM36.SQLDatabaseContext,                     +                     ProjectM36.DatabaseContext.Basic,+                     ProjectM36.DatabaseContext.Fields,                     +                     ProjectM36.DatabaseContextExpr,                                           ProjectM36.MiscUtils,                      ProjectM36.Notifications,                      ProjectM36.Relation,@@ -181,11 +207,12 @@                      ProjectM36.Server.RemoteCallTypes,                      ProjectM36.Serialise.AtomFunctionError,                      ProjectM36.Serialise.DataFrame,-                     ProjectM36.Serialise.DatabaseContextFunctionError,                      ProjectM36.Serialise.IsomorphicSchema,                      ProjectM36.Session,                      ProjectM36.Sessions,                      ProjectM36.Transaction.Persist,+                     ProjectM36.Transaction.Types,+                     ProjectM36.IsomorphicSchema.Types,                      ProjectM36.TypeConstructor,                      ProjectM36.TypeConstructorDef,                      ProjectM36.FileLock,@@ -196,6 +223,11 @@                      ProjectM36.TransactionInfo,                      ProjectM36.WithNameExpr,                       ProjectM36.Trace,+                     ProjectM36.Streaming.Tuple,+                     ProjectM36.Streaming.RelationalExpression,     +                     ProjectM36.Cache.RelationalExprCache,+                     ProjectM36.Cache.Tuple,+                     ProjectM36.RelExprSize,                      ProjectM36.HashSecurely,                      ProjectM36.DDLType,                      ProjectM36.RegisteredQuery,@@ -206,8 +238,14 @@                      ProjectM36.SQL.Delete,                      ProjectM36.SQL.DBUpdate,                      ProjectM36.SQL.CreateTable,-                     ProjectM36.SQL.DropTable-                     ProjectM36.ReferencedTransactionIds+                     ProjectM36.SQL.DropTable,+                     ProjectM36.ReferencedTransactionIds,+                     ProjectM36.SystemMemory,+                     ProjectM36.PinnedRelationalExpr,+                     ProjectM36.AccessControlList,+                     ProjectM36.AccessControl,+                     ProjectM36.LoginRoles,+                     ProjectM36.PrettyBytes     GHC-Options: -Wall -rdynamic     if os(windows)       Build-Depends: Win32 >= 2.12@@ -215,10 +253,10 @@     else       --219-  too many exported symbols under Windows and GHC 8.4       GHC-Options: -rdynamic -fexternal-interpreter-      if impl(ghc <9.4)-        GHC-Options: -eventlog       C-sources: cbits/DirectoryFsync.c, cbits/darwin_statfs.c       Build-Depends: unix+    if os(linux)+      Build-Depends: linux-xattr     CC-Options: -fPIC     Hs-Source-Dirs: ./src/lib     Default-Language: Haskell2010@@ -228,8 +266,8 @@  Executable tutd     if flag(haskell-scripting)-        Build-Depends: ghc >= 9.0-    Build-Depends: base >=4.8,+        Build-Depends: ghc >= 9.4+    Build-Depends: base >=4.17,                    ghc-paths,                    project-m36,                    containers,@@ -240,10 +278,11 @@                    mtl,                    uuid,                    deepseq-generics,-                   MonadRandom, MonadRandom,+                   MonadRandom,                    vector,                    text,                    time,+                   process,                    bytestring,                    stm,                    deepseq,@@ -256,7 +295,7 @@                    temporary,                    megaparsec >= 5.2.0 && < 10,                    haskeline,-                   random, MonadRandom,+                   random,                    base64-bytestring,                    optparse-applicative,                    attoparsec,@@ -272,8 +311,7 @@                    modern-uri,                    http-types,                    recursion-schemes,-                   data-default < 0.8-                   --data-default 0.8.0.0 breaks http-tls-client+                   data-default      Other-Modules: TutorialD.Interpreter,                    TutorialD.Interpreter.Base,                    TutorialD.Interpreter.DatabaseContextExpr,@@ -291,6 +329,7 @@                    TutorialD.Interpreter.Types,                    TutorialD.Interpreter.TransGraphRelationalOperator,                    TutorialD.Interpreter.SchemaOperator,+                   TutorialD.Interpreter.LoginRolesOperator,                    TutorialD.Printer,                    ProjectM36.Cli,                    ProjectM36.Interpreter@@ -299,8 +338,6 @@     GHC-Options: -Wall -threaded -rtsopts     if !os(windows)       GHC-Options: -rdynamic-    if impl(ghc <9.4)-      GHC-Options: -eventlog     if flag(profiler)       GHC-Prof-Options: -fprof-auto -rtsopts -threaded     Hs-Source-Dirs: ./src/bin@@ -309,7 +346,7 @@  Executable sqlegacy     if flag(haskell-scripting)-        Build-Depends: ghc >= 9.0+        Build-Depends: ghc >= 9.4     Build-Depends: base,                    ghc-paths,                    project-m36,@@ -383,7 +420,7 @@  Executable project-m36-server     if flag(haskell-scripting)-        Build-Depends: ghc >= 9.0+        Build-Depends: ghc >= 9.4     Build-Depends: base,                    ghc-paths,                    transformers,@@ -413,9 +450,9 @@                    scientific     Main-Is: ./src/bin/ProjectM36/Server/project-m36-server.hs     if os(windows)-      GHC-Options: -Wall -threaded -rtsopts+      GHC-Options: -Wall -threaded -rtsopts -with-rtsopts=-N     else-      GHC-Options: -Wall -threaded -rtsopts -rdynamic+      GHC-Options: -Wall -threaded -rtsopts -rdynamic -with-rtsopts=-N     if flag(profiler)       GHC-Prof-Options: -fprof-auto -rtsopts -threaded -Wall     Default-Language: Haskell2010@@ -424,14 +461,15 @@ Executable bigrel     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, optparse-applicative, stm-containers, list-t, ghc, ghc-paths, transformers, project-m36, random, MonadRandom, semigroups, parser-combinators+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, optparse-applicative, stm-containers, list-t, ghc, ghc-paths, transformers, project-m36, random, MonadRandom, semigroups, parser-combinators, curryer-rpc     Other-Modules: TutorialD.Interpreter.Base,                    TutorialD.Interpreter.DatabaseContextExpr,                    TutorialD.Interpreter.RelationalExpr,                    TutorialD.Interpreter.Types,-                   ProjectM36.Interpreter+                   ProjectM36.Interpreter,+                   ProjectM36.Cli     main-is: benchmark/bigrel.hs-    GHC-Options: -Wall -threaded -rtsopts+    GHC-Options: -Wall -threaded -rtsopts -with-rtsopts=-N     HS-Source-Dirs: ./src/bin     if flag(profiler)       GHC-Prof-Options: -fprof-auto -rtsopts -threaded -Wall@@ -440,27 +478,39 @@  Common commontest     Default-Language: Haskell2010-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, websockets, optparse-applicative, network, aeson, project-m36, random, MonadRandom, semigroups, parser-combinators, winery, curryer-rpc, prettyprinter, base64-bytestring, modern-uri, http-types, http-conduit, base16-bytestring, cryptohash-sha256, scientific+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, websockets, optparse-applicative, network, aeson, project-m36, random, MonadRandom, semigroups, parser-combinators, winery, curryer-rpc, prettyprinter, base64-bytestring, modern-uri, http-types, http-conduit, base16-bytestring, cryptohash-sha256, scientific, streamly     Default-Extensions: OverloadedStrings-    GHC-Options: -Wall -threaded+    GHC-Options: -Wall -threaded -with-rtsopts=-N     Hs-Source-Dirs: test, src/bin+    Other-Modules: ProjectM36.Interpreter, ProjectM36.Cli  Benchmark basic-benchmark     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, criterion, project-m36, text, vector, transformers, containers, temporary, directory, filepath+    Build-Depends: base, criterion, project-m36, text, vector, containers, temporary, directory, filepath     Main-Is: benchmark/Basic.hs     Type: exitcode-stdio-1.0-    GHC-Options: -Wall -threaded -rtsopts+    GHC-Options: -Wall -threaded -rtsopts -Wunused-packages     HS-Source-Dirs: ./src/bin     if flag(profiler)       GHC-Prof-Options: -fprof-auto -rtsopts -threaded -Wall +Benchmark btree-benchmark+    Default-Language: Haskell2010+    Default-Extensions: OverloadedStrings+    Build-Depends: base, criterion, project-m36, text, vector, transformers, containers, temporary, directory, filepath, deepseq, byteunits+    Main-Is: benchmark/BTree.hs+    Type: exitcode-stdio-1.0+    GHC-Options: -Wall -threaded -rtsopts -Wunused-packages+    HS-Source-Dirs: ./src/bin+    if flag(profiler)+      GHC-Prof-Options: -fprof-auto -rtsopts -threaded -Wall+ --benchmark inserts, updates, deletes for a basic use-case to shake out any space leaks Benchmark update-exprs   Default-Language: Haskell2010   Default-Extensions: OverloadedStrings-  Build-Depends: base, criterion, project-m36, text, winery+  Build-Depends: base, criterion, project-m36, text, winery, random   Main-Is: benchmark/Server.hs   Type: exitcode-stdio-1.0   GHC-Options: -Wall -threaded -rtsopts@@ -474,6 +524,7 @@     main-is: SQL/InterpreterTest.hs     Other-Modules: SQL.Interpreter.Select, SQL.Interpreter.Base, TutorialD.Interpreter.Base, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.Types, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.RODatabaseContextOperator, ProjectM36.Interpreter, SQL.Interpreter.CreateTable                    TutorialD.Printer,+                   ProjectM36.Cli,                    SQL.Interpreter.DBUpdate,                    SQL.Interpreter.Delete,                    SQL.Interpreter.DropTable,@@ -486,19 +537,21 @@     import: commontest     type: exitcode-stdio-1.0     main-is: TutorialD/InterpreterTest.hs-    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter+    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter, ProjectM36.Cli, TutorialD.Interpreter.LoginRolesOperator     Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics, parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, stm-containers, list-t, project-m36, random, MonadRandom, semigroups, parser-combinators, prettyprinter, scientific  Test-Suite test-tutoriald-atomfunctionscript     import: commontest     type: exitcode-stdio-1.0-    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter+    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter, TutorialD.Interpreter.LoginRolesOperator     main-is: TutorialD/Interpreter/AtomFunctionScript.hs  Test-Suite test-tutoriald-databasecontextfunctionscript     import: commontest     type: exitcode-stdio-1.0-    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter+    if flag(haskell-scripting)+        CPP-Options: -DPM36_HASKELL_SCRIPTING+    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter, TutorialD.Interpreter.LoginRolesOperator     main-is: TutorialD/Interpreter/DatabaseContextFunctionScript.hs  Test-Suite test-relation@@ -560,6 +613,26 @@     GHC-Options: -rtsopts -Wall -threaded -finfo-table-map -fdistinct-constructor-tables     if impl(ghc <9.4)       GHC-Options: -eventlog+    Other-Modules: ProjectM36.Cli,+        TutorialD.Interpreter,+        TutorialD.Interpreter.Base,+        TutorialD.Interpreter.DatabaseContextExpr,+        TutorialD.Interpreter.DatabaseContextIOOperator,+        TutorialD.Interpreter.Export.Base,+        TutorialD.Interpreter.Export.CSV,+        TutorialD.Interpreter.Import.Base,+        TutorialD.Interpreter.Import.BasicExamples,+        TutorialD.Interpreter.Import.CSV,+        TutorialD.Interpreter.Import.TutorialD,+        TutorialD.Interpreter.InformationOperator,+        TutorialD.Interpreter.RODatabaseContextOperator,+        TutorialD.Interpreter.RelationalExpr,+        TutorialD.Interpreter.SchemaOperator,+        TutorialD.Interpreter.TransGraphRelationalOperator,+        TutorialD.Interpreter.TransactionGraphOperator,+        TutorialD.Interpreter.Types,+        TutorialD.Interpreter.LoginRolesOperator,+        TutorialD.Printer  Test-Suite test-server     import: commontest@@ -569,28 +642,28 @@ Executable Example-SimpleClient     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, ghc, ghc-paths, project-m36, random, MonadRandom+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, ghc, ghc-paths, project-m36, random, MonadRandom, curryer-rpc     Main-Is: examples/SimpleClient.hs     GHC-Options: -Wall -threaded  Executable Example-OutOfTheTarpit     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, winery+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, winery, random     Main-Is: examples/out_of_the_tarpit.hs     GHC-Options: -Wall -threaded  Executable Example-Blog     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, scotty >= 0.22, blaze-html, http-types, winery+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, scotty >= 0.22, blaze-html, http-types, winery, random     Main-Is: examples/blog.hs     GHC-Options: -Wall -threaded  Executable Example-Hair     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, winery+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, winery, random     Main-Is: examples/hair.hs     GHC-Options: -Wall -threaded @@ -608,27 +681,38 @@     Main-Is: examples/CustomTupleable.hs     GHC-Options: -Wall -threaded +Executable Example-Hospital+    Default-Language: Haskell2010+    Default-Extensions: OverloadedStrings+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, transformers, stm-containers, list-t, aeson, path-pieces, either, conduit, http-api-data, template-haskell, ghc, ghc-paths, project-m36, winery, random+    Main-Is: examples/Hospital.hs+    GHC-Options: -Wall -threaded+               + Executable Example-DerivingCustomTupleable-    if impl(ghc>=8.6)-      Buildable: True-    else-      Buildable: False     Default-Language: Haskell2010     Build-Depends: base, text, deepseq, project-m36, winery     Main-Is: examples/DerivingCustomTupleable.hs     GHC-Options: -Wall -threaded +Executable Example-Zoo+    Default-Language: Haskell2010+    Build-Depends: base, text, deepseq, project-m36, winery, random, time+    Main-Is: examples/zoo.hs+    GHC-Options: -Wall -threaded+         + Test-Suite test-scripts     import: commontest     type: exitcode-stdio-1.0     main-is: scripts.hs-    Other-Modules: TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.Types, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Printer+    Other-Modules: TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.Types, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Printer, TutorialD.Interpreter.LoginRolesOperator  Executable project-m36-websocket-server     Default-Language: Haskell2010-    Build-Depends: base, aeson, path-pieces, either, conduit, http-api-data, template-haskell, websockets, aeson, optparse-applicative, project-m36, containers, bytestring, text, vector, uuid, megaparsec, haskeline, mtl, directory, base64-bytestring, random, MonadRandom, time, semigroups, attoparsec, parser-combinators, prettyprinter, network, modern-uri, http-conduit, base16-bytestring, http-types, cryptohash-sha256, wai, wai-websockets, warp, warp-tls, scientific+    Build-Depends: base, aeson, path-pieces, either, conduit, http-api-data, template-haskell, websockets, optparse-applicative, project-m36, containers, bytestring, text, vector, uuid, megaparsec, haskeline, mtl, directory, base64-bytestring, random, MonadRandom, time, semigroups, attoparsec, parser-combinators, prettyprinter, network, modern-uri, http-conduit, base16-bytestring, http-types, cryptohash-sha256, wai, wai-websockets, warp, warp-tls, scientific, curryer-rpc     Main-Is: ProjectM36/Server/WebSocket/websocket-server.hs-    Other-Modules:  ProjectM36.Client.Json, ProjectM36.Server.RemoteCallTypes.Json, ProjectM36.Server.WebSocket, TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter+    Other-Modules:  ProjectM36.Cli, ProjectM36.Client.Json, ProjectM36.Server.RemoteCallTypes.Json, ProjectM36.Server.WebSocket, TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter, TutorialD.Interpreter.LoginRolesOperator     GHC-Options: -Wall -threaded     Hs-Source-Dirs: ./src/bin     Default-Extensions: OverloadedStrings@@ -637,7 +721,7 @@     import: commontest     type: exitcode-stdio-1.0     main-is: Server/WebSocket.hs-    Other-Modules: TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.TransactionGraphOperator, ProjectM36.Client.Json, ProjectM36.Server.RemoteCallTypes.Json, ProjectM36.Server.WebSocket, TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.Types, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter+    Other-Modules: TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.TransactionGraphOperator, ProjectM36.Client.Json, ProjectM36.Server.RemoteCallTypes.Json, ProjectM36.Server.WebSocket, TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.Types, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.SchemaOperator, TutorialD.Printer, ProjectM36.Interpreter, ProjectM36.Cli, TutorialD.Interpreter.LoginRolesOperator  Test-Suite test-isomorphic-schemas     import: commontest@@ -648,7 +732,7 @@     import: commontest     type: exitcode-stdio-1.0     main-is: Relation/Atomable.hs-    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.SchemaOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Printer+    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.SchemaOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Printer, ProjectM36.Cli, TutorialD.Interpreter.LoginRolesOperator  Test-Suite test-multiprocess-access     import: commontest@@ -659,7 +743,7 @@     import: commontest     type: exitcode-stdio-1.0     main-is: TransactionGraph/Automerge.hs-    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.SchemaOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Printer+    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr, TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.SchemaOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Printer, ProjectM36.Cli, TutorialD.Interpreter.LoginRolesOperator  Test-Suite test-tupleable     import: commontest@@ -675,7 +759,7 @@ Executable handles     Default-Language: Haskell2010     Default-Extensions: OverloadedStrings-    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, optparse-applicative, stm-containers, list-t, ghc, ghc-paths, transformers, project-m36, random, MonadRandom, semigroups, parser-combinators, prettyprinter, modern-uri, http-types, http-conduit, base16-bytestring, cryptohash-sha256+    Build-Depends: base, HUnit, Cabal, containers, hashable, unordered-containers, mtl, vector, time, bytestring, uuid, stm, deepseq, deepseq-generics,parallel, cassava, attoparsec, gnuplot, directory, temporary, haskeline, megaparsec, text, base64-bytestring, data-interval, filepath, optparse-applicative, stm-containers, list-t, ghc, ghc-paths, transformers, project-m36, random, MonadRandom, semigroups, parser-combinators, prettyprinter, modern-uri, http-types, http-conduit, base16-bytestring, cryptohash-sha256, curryer-rpc     main-is: benchmark/Handles.hs     Other-Modules: TutorialD.Interpreter,       TutorialD.Interpreter.Base,@@ -695,7 +779,9 @@       TutorialD.Interpreter.TransactionGraphOperator,       TutorialD.Interpreter.Types,       TutorialD.Printer,-      ProjectM36.Interpreter+      ProjectM36.Interpreter,+      ProjectM36.Cli,+      TutorialD.Interpreter.LoginRolesOperator     GHC-Options: -Wall -threaded -rtsopts     HS-Source-Dirs: ./src/bin     if flag(profiler)@@ -705,7 +791,46 @@     import: commontest     type: exitcode-stdio-1.0     Main-Is: DataFrame.hs-    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr,  TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.SchemaOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Printer, ProjectM36.Interpreter+    Other-Modules: TutorialD.Interpreter, TutorialD.Interpreter.Base, TutorialD.Interpreter.DatabaseContextExpr,  TutorialD.Interpreter.DatabaseContextIOOperator, TutorialD.Interpreter.Export.Base, TutorialD.Interpreter.Export.CSV, TutorialD.Interpreter.Import.Base, TutorialD.Interpreter.Import.BasicExamples, TutorialD.Interpreter.Import.CSV, TutorialD.Interpreter.Import.TutorialD, TutorialD.Interpreter.InformationOperator, TutorialD.Interpreter.RODatabaseContextOperator, TutorialD.Interpreter.RelationalExpr, TutorialD.Interpreter.SchemaOperator, TutorialD.Interpreter.TestBase, TutorialD.Interpreter.TransGraphRelationalOperator, TutorialD.Interpreter.TransactionGraphOperator, TutorialD.Interpreter.Types, TutorialD.Printer, ProjectM36.Interpreter, TutorialD.Interpreter.LoginRolesOperator +Test-Suite test-cache+   import: commontest+   type: exitcode-stdio-1.0+   Main-Is: Cache.hs -  +Test-Suite test-accesscontrollist+    import: commontest+    type: exitcode-stdio-1.0+    Main-Is: AccessControlList.hs++Test-Suite test-loginroles+    import: commontest+    type: exitcode-stdio-1.0    +    Main-Is: LoginRoles.hs++Test-Suite test-accesscontrol+    import: commontest+    type: exitcode-stdio-1.0    +    Main-Is: AccessControl.hs+    Other-Modules:+        TutorialD.Interpreter+        TutorialD.Interpreter.Base+        TutorialD.Interpreter.DatabaseContextExpr+        TutorialD.Interpreter.DatabaseContextIOOperator+        TutorialD.Interpreter.Export.Base+        TutorialD.Interpreter.Export.CSV+        TutorialD.Interpreter.Import.Base+        TutorialD.Interpreter.Import.BasicExamples+        TutorialD.Interpreter.Import.CSV+        TutorialD.Interpreter.Import.TutorialD+        TutorialD.Interpreter.InformationOperator+        TutorialD.Interpreter.LoginRolesOperator+        TutorialD.Interpreter.RODatabaseContextOperator+        TutorialD.Interpreter.RelationalExpr+        TutorialD.Interpreter.SchemaOperator+        TutorialD.Interpreter.TestBase+        TutorialD.Interpreter.TransGraphRelationalOperator+        TutorialD.Interpreter.TransactionGraphOperator+        TutorialD.Interpreter.Types+        TutorialD.Printer+ 
src/bin/ProjectM36/Cli.hs view
@@ -4,6 +4,8 @@ import qualified ProjectM36.Client as C import qualified Data.Text as T import ProjectM36.Base+import ProjectM36.DatabaseContext.Types+import ProjectM36.IsomorphicSchema.Types import ProjectM36.Client (RemoteServerAddress(..)) import System.Console.Haskeline import Control.Exception@@ -20,6 +22,8 @@ import Text.Megaparsec.Error import Data.Void (Void) import ProjectM36.Interpreter hiding (Parser)+import Network.RPC.Curryer.Client+import System.Random (StdGen, initStdGen)  type GhcPkgPath = String type TutorialDExec = String@@ -28,8 +32,8 @@ type DirectExecute = String type ParserError = ParseErrorBundle T.Text Void -data InterpreterConfig = LocalInterpreterConfig PersistenceStrategy HeadName (Maybe DirectExecute) [GhcPkgPath] CheckFS |-                         RemoteInterpreterConfig RemoteServerAddress C.DatabaseName HeadName (Maybe TutorialDExec) CheckFS+data InterpreterConfig = LocalInterpreterConfig PersistenceStrategy HeadName (Maybe DirectExecute) [GhcPkgPath] CheckFS RoleName |+                         RemoteInterpreterConfig RemoteServerAddress ClientConnectionConfig C.DatabaseName HeadName (Maybe TutorialDExec) CheckFS RoleName  outputNotificationCallback :: C.NotificationCallback outputNotificationCallback notName evaldNot = hPutStrLn stderr $ "Notification received " ++ show notName ++ ":\n" ++ "\n" ++ prettyEvaluatedNotification evaldNot@@ -41,15 +45,15 @@   eRelShow (C.reportOldRelation eNotif) <> "\n" <> eRelShow (C.reportNewRelation eNotif)  type ReprLoopEvaluator = C.SessionId -> C.Connection -> Maybe PromptLength -> T.Text -> IO ()-type MakePrompt = Either RelationalError HeadName -> Either RelationalError SchemaName -> StringType+type MakePrompt = Either RelationalError C.CurrentHead -> Either RelationalError SchemaName -> StringType type HistoryFilePath = FilePath    reprLoop :: InterpreterConfig -> HistoryFilePath -> ReprLoopEvaluator -> MakePrompt -> C.SessionId -> C.Connection -> IO () reprLoop config historyFilePath reprLoopEvaluator promptText sessionId conn = do   let settings = defaultSettings {historyFile = Just historyFilePath} -- (homeDirectory ++ "/.tutd_history")}-  eHeadName <- C.headName sessionId conn+  eCurrentHead <- C.currentHead sessionId conn   eSchemaName <- C.currentSchemaName sessionId conn-  let prompt = promptText eHeadName eSchemaName+  let prompt = promptText eCurrentHead eSchemaName       catchInterrupt = handleJust (\case                                       UserInterrupt -> Just Nothing                                       _ -> Nothing) (\_ -> do@@ -63,9 +67,37 @@       reprLoop config historyFilePath reprLoopEvaluator promptText sessionId conn  parseArgs :: Parser InterpreterConfig-parseArgs = LocalInterpreterConfig <$> parsePersistenceStrategy <*> parseHeadName <*> parseDirectExecute <*> many parseGhcPkgPath <*> parseCheckFS <|>-            RemoteInterpreterConfig <$> parseServerAddress <*> parseDatabaseName <*> parseHeadName <*> parseDirectExecute <*> parseCheckFS+parseArgs = LocalInterpreterConfig <$> parsePersistenceStrategy <*> parseHeadName <*> parseDirectExecute <*> many parseGhcPkgPath <*> parseCheckFS <*> parseRoleName <|>+            RemoteInterpreterConfig <$> parseServerAddress <*> parseClientConnectionConfig <*> parseDatabaseName <*> parseHeadName <*> parseDirectExecute <*> parseCheckFS <*> parseRoleName +parseClientConnectionConfig :: Parser ClientConnectionConfig+parseClientConnectionConfig =+  flag' UnencryptedConnectionConfig (long "disable-tls" <>+                                       help "Disable encryption when connecting to the server.")+  <|>+  EncryptedConnectionConfig <$> parseClientTLSConfig++parseClientTLSConfig :: Parser ClientTLSConfig+parseClientTLSConfig =+  ClientTLSConfig <$>+    parseClientTLSCertInfo <*>+    parseHostname "localhost" <*>+    parseServiceName ""++parseClientTLSCertInfo :: Parser ClientTLSCertInfo+parseClientTLSCertInfo =+  ClientTLSCertInfo <$>+  optional ((,) <$>+              strOption (long "public-x509-key" <>+                         metavar "KEY_PATH" <>+                         help "Enables TLS with path to public key.") <*>+              strOption (long "private-x509-key" <>+                         metavar "KEY_PATH" <>+                         help "Enables TLS with path to private key.")) <*>+  optional (strOption (long "certificate-x509-path" <>+                        metavar "CERT_PATH" <>+                        help "Path to certificate for TLS. Elide to use system's certificate store."))+     parseHeadName :: Parser HeadName                parseHeadName = option auto (long "head" <>                              help "Start session at head name." <>@@ -83,11 +115,12 @@ type PrintWelcome = IO () type ExecUserInput = C.SessionId -> C.Connection -> Maybe PromptLength -> T.Text -> IO () -mainLoop :: IO () -> HistoryFilePath -> ReprLoopEvaluator -> MakePrompt -> ExecUserInput -> DatabaseContext -> IO ()+mainLoop :: IO () -> HistoryFilePath -> ReprLoopEvaluator -> MakePrompt -> ExecUserInput -> ResolvedDatabaseContext -> IO () mainLoop printWelcome historyFilePath reprLoopEvaluator promptText execUserInput defaultDBContext = do   setLocaleIfNecessary   interpreterConfig <- execParser opts-  let connInfo = connectionInfoForConfig interpreterConfig defaultDBContext+  rando <- initStdGen+  let connInfo = connectionInfoForConfig interpreterConfig defaultDBContext rando   fscheck <- checkFSType (checkFSForConfig interpreterConfig) (fromMaybe NoPersistence (persistenceStrategyForConfig interpreterConfig))   if not fscheck then     errDie checkFSErrorMsg@@ -119,24 +152,24 @@ opts :: ParserInfo InterpreterConfig             opts = info (parseArgs <**> helpOption) idm -connectionInfoForConfig :: InterpreterConfig -> DatabaseContext -> C.ConnectionInfo-connectionInfoForConfig (LocalInterpreterConfig pStrategy _ _ ghcPkgPaths _) defaultDBContext = C.InProcessConnectionInfo pStrategy outputNotificationCallback ghcPkgPaths defaultDBContext-connectionInfoForConfig (RemoteInterpreterConfig remoteAddress remoteDBName _ _ _) _ = C.RemoteConnectionInfo remoteDBName remoteAddress outputNotificationCallback+connectionInfoForConfig :: InterpreterConfig -> ResolvedDatabaseContext -> StdGen -> C.ConnectionInfo+connectionInfoForConfig (LocalInterpreterConfig pStrategy _ _ ghcPkgPaths _ roleName) defaultDBContext rando = C.InProcessConnectionInfo pStrategy outputNotificationCallback ghcPkgPaths defaultDBContext rando roleName+connectionInfoForConfig (RemoteInterpreterConfig remoteAddress connConfig remoteDBName _ _ _ roleName) _ _ = C.RemoteConnectionInfo remoteDBName remoteAddress connConfig outputNotificationCallback roleName  headNameForConfig :: InterpreterConfig -> HeadName-headNameForConfig (LocalInterpreterConfig _ headn _ _ _) = headn-headNameForConfig (RemoteInterpreterConfig _ _ headn _ _) = headn+headNameForConfig (LocalInterpreterConfig _ headn _ _ _ _) = headn+headNameForConfig (RemoteInterpreterConfig _ _ _ headn _ _ _) = headn  directExecForConfig :: InterpreterConfig -> Maybe String-directExecForConfig (LocalInterpreterConfig _ _ t _ _) = t-directExecForConfig (RemoteInterpreterConfig _ _ _ t _) = t+directExecForConfig (LocalInterpreterConfig _ _ t _ _ _) = t+directExecForConfig (RemoteInterpreterConfig _ _ _ _ t _ _) = t  checkFSForConfig :: InterpreterConfig -> Bool-checkFSForConfig (LocalInterpreterConfig _ _ _ _ c) = c-checkFSForConfig (RemoteInterpreterConfig _ _ _ _ c) = c+checkFSForConfig (LocalInterpreterConfig _ _ _ _ c _) = c+checkFSForConfig (RemoteInterpreterConfig _ _ _ _ _ c _) = c  persistenceStrategyForConfig :: InterpreterConfig -> Maybe PersistenceStrategy-persistenceStrategyForConfig (LocalInterpreterConfig strat _ _ _ _) = Just strat+persistenceStrategyForConfig (LocalInterpreterConfig strat _ _ _ _ _) = Just strat persistenceStrategyForConfig RemoteInterpreterConfig{} = Nothing                           errDie :: String -> IO ()                                                           
src/bin/ProjectM36/Server/RemoteCallTypes/Json.hs view
@@ -5,9 +5,11 @@ import ProjectM36.AtomFunctionError import ProjectM36.Base import ProjectM36.DataFrame-import ProjectM36.DatabaseContextFunctionError+import ProjectM36.IsomorphicSchema.Types import ProjectM36.DataTypes.Primitive+import ProjectM36.DatabaseContext.Fields import ProjectM36.Error+import ProjectM36.AccessControlList import ProjectM36.IsomorphicSchema import ProjectM36.Server.RemoteCallTypes import ProjectM36.MerkleHash@@ -217,12 +219,12 @@ instance ToJSON MergeError instance FromJSON MergeError +instance ToJSON DatabaseContextField+instance FromJSON DatabaseContextField+ instance ToJSON ImportError' instance FromJSON ImportError' -instance ToJSON DatabaseContextFunctionError-instance FromJSON DatabaseContextFunctionError- instance ToJSON MergeStrategy instance FromJSON MergeStrategy @@ -306,3 +308,24 @@  instance ToJSON QueryOperator instance FromJSON QueryOperator++instance ToJSON SomePermission+instance FromJSON SomePermission++instance ToJSON AlterTransGraphPermission+instance FromJSON AlterTransGraphPermission++instance ToJSON RelVarPermission+instance FromJSON RelVarPermission++instance ToJSON AlterSchemaPermission+instance FromJSON AlterSchemaPermission++instance ToJSON FunctionPermission+instance FromJSON FunctionPermission++instance ToJSON DBCFunctionPermission+instance FromJSON DBCFunctionPermission++instance ToJSON ACLPermission+instance FromJSON ACLPermission
src/bin/ProjectM36/Server/WebSocket.hs view
@@ -16,7 +16,7 @@ import Data.Aeson import ProjectM36.Base import TutorialD.Interpreter-import ProjectM36.Interpreter (ConsoleResult(..), SafeEvaluationFlag(..))+import ProjectM36.Interpreter as I (ConsoleResult(..), SafeEvaluationFlag(..))  import ProjectM36.Client import Control.Exception import Text.Megaparsec.Error@@ -25,6 +25,7 @@ import qualified Data.UUID as UUID import GHC.Generics import qualified Data.Text.Encoding as TE+import qualified Network.RPC.Curryer.Client as CRPC  #if MIN_VERSION_megaparsec(7,0,0) import Data.List.NonEmpty as NE@@ -92,7 +93,7 @@  --this creates a new database for each connection- perhaps not what we want (?) createConnection :: WS.Connection -> DatabaseName -> Port -> Hostname -> IO (Either ConnectionError Connection)-createConnection wsconn dbname port host = connectProjectM36 (RemoteConnectionInfo dbname (RemoteServerHostAddress host port) (notificationCallback wsconn))+createConnection wsconn dbname port host = connectProjectM36 (RemoteConnectionInfo dbname (RemoteServerHostAddress host port) CRPC.UnencryptedConnectionConfig (notificationCallback wsconn) "admin")  -- should use TLS auth  handleResponse :: WS.Connection -> Connection -> Response -> IO () handleResponse conn dbconn resp =@@ -131,7 +132,7 @@     DisplayParseErrorResult _ err ->        let err' = ParseError $ T.pack (parseErrorPretty . NE.head . bundleErrors $ err) in         RelationalErrorResponse reqId err'-    QuietSuccessResult ->+    I.QuietSuccessResult ->       SuccessResponse reqId     DisplayRelationResult rel ->       RelationResponse reqId rel presentation@@ -150,9 +151,13 @@ -- get current schema and head name for client promptInfo :: SessionId -> Connection -> IO (HeadName, SchemaName) promptInfo sessionId conn = do-  eHeadName <- headName sessionId conn+  eCurrentHead <- currentHead sessionId conn   eSchemaName <- currentSchemaName sessionId conn-  pure (fromRight "<unknown>" eHeadName, fromRight "<no schema>" eSchemaName)+  let headName = case eCurrentHead of+                   Left _err -> "<unknown>"+                   Right (CurrentHeadTransactionId tid) -> T.pack (show tid)+                   Right (CurrentHeadBranch hname) -> hname+  pure (headName, fromRight "<no schema>" eSchemaName)  sendPromptInfo :: (HeadName, SchemaName) -> WS.Connection -> IO () sendPromptInfo (hName, sName) conn = WS.sendTextData conn (encode (object ["promptInfo" .= object ["headname" .= hName, "schemaname" .= sName]]))
src/bin/SQL/Interpreter.hs view
@@ -3,7 +3,9 @@ import ProjectM36.Interpreter import ProjectM36.SQL.Select import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContextExpr import ProjectM36.DateExamples+import ProjectM36.TransactionGraph (emptyTransactionGraph) import ProjectM36.Error import TutorialD.Printer import SQL.Interpreter.ImportBasicExample@@ -72,8 +74,11 @@             Left err -> pure $ DisplayRelationalErrorResult err             Right df -> pure $ DisplayHintWith ("[Equivalent TutorialD] " <> hint) (DisplayDataFrameResult df)     ImportBasicExampleOp (ImportBasicExampleOperator exampleName) -> do-      if exampleName == "cjdate" then-        evalOneCommand (DatabaseContextExprOp (databaseContextAsDatabaseContextExpr dateExamples))+      if exampleName == "cjdate" then do+        case databaseContextAsDatabaseContextExpr (toDatabaseContext dateExamples) emptyTransactionGraph of+          Left err -> pure $ DisplayRelationalErrorResult err+          Right dbexpr ->+            evalOneCommand (DatabaseContextExprOp dbexpr)         else           pure (DisplayErrorResult ("No such example: " <> exampleName))     DatabaseContextExprOp dbcExpr -> do
src/bin/SQL/Interpreter/sqlegacy.hs view
@@ -1,10 +1,8 @@ -- the sqlegacy SQL interpreter wrap {-# LANGUAGE CPP #-}-import ProjectM36.Base import ProjectM36.Cli import SQL.Interpreter import ProjectM36.SQLDatabaseContext-import ProjectM36.Error import System.Directory import System.FilePath import qualified ProjectM36.Client as C@@ -42,8 +40,13 @@         (\_ -> displayResult (DisplayErrorResult "Request timed out."))        -promptText :: Either RelationalError HeadName -> Either RelationalError SchemaName -> StringType-promptText eHeadName eSchemaName = "SQLegacy (" <> transInfo <> "): "+promptText :: MakePrompt+promptText eCurrentHead eSchemaName = "SQLegacy (" <> transInfo <> "): "   where-    transInfo = fromRight "<unknown>" eHeadName <> "/" <> fromRight "<no schema>" eSchemaName+    headStr = case eCurrentHead of+      Left _ -> "<unknown>"+      Right (C.CurrentHeadTransactionId tid) -> T.pack (show tid)+      Right (C.CurrentHeadBranch branch) -> branch+    transInfo = headStr <> "/" <> fromRight "<no schema>" eSchemaName+   
src/bin/TutorialD/Interpreter.hs view
@@ -9,6 +9,9 @@ import TutorialD.Interpreter.DatabaseContextIOOperator import TutorialD.Interpreter.TransGraphRelationalOperator import TutorialD.Interpreter.SchemaOperator+import TutorialD.Interpreter.RelationalExpr+import TutorialD.Interpreter.LoginRolesOperator+import TutorialD.Interpreter.Types  import TutorialD.Interpreter.Import.CSV import TutorialD.Interpreter.Import.TutorialD@@ -20,7 +23,9 @@  import ProjectM36.Base import ProjectM36.Error+import ProjectM36.Cli (MakePrompt) import ProjectM36.TransactionGraph+import ProjectM36.LoginRoles (AlterLoginRolesExpr) import qualified ProjectM36.Client as C import ProjectM36.Relation (attributes) @@ -41,9 +46,10 @@ -} data ParsedOperation = RODatabaseContextOp RODatabaseContextOperator |                        DatabaseContextExprOp DatabaseContextExpr |-                       DatabaseContextIOExprOp DatabaseContextIOExpr |+                       DatabaseContextIOExprOp DatabaseContextIOOperator |                        InfoOp InformationOperator |-                       GraphOp TransactionGraphOperator |+                       GraphOp TransactionGraphExpr |+                       AlterGraphOp AlterTransactionGraphExpr |                        ConvenienceGraphOp ConvenienceTransactionGraphOperator |                        ROGraphOp ROTransactionGraphOperator |                        ImportRelVarOp RelVarDataImportOperator |@@ -51,7 +57,8 @@                        ImportBasicExampleOp ImportBasicExampleOperator |                        RelVarExportOp RelVarDataExportOperator |                        TransGraphRelationalOp TransGraphRelationalOperator |-                       SchemaOp SchemaOperator+                       SchemaOp SchemaOperator |+                       LoginRolesOp AlterLoginRolesExpr                        deriving (Show)  interpreterParserP :: Parser ParsedOperation@@ -59,24 +66,30 @@                      fmap ImportRelVarOp (importCSVP <* eof) <|>                      fmap ImportDBContextOp (tutdImportP <* eof) <|>                      fmap RelVarExportOp (exportCSVP <* eof) <|>-                     fmap DatabaseContextIOExprOp (dbContextIOExprP <* eof)+                     fmap DatabaseContextIOExprOp (databaseContextIOOperatorP <* eof)  -- the safe interpreter never reads or writes the file system safeInterpreterParserP :: Parser ParsedOperation safeInterpreterParserP = fmap RODatabaseContextOp (roDatabaseContextOperatorP <* eof) <|>                          fmap InfoOp (infoOpP <* eof) <|>                          fmap GraphOp (transactionGraphOpP <* eof) <|>+                         fmap AlterGraphOp (alterTransactionGraphOpP <* eof) <|>                          fmap ConvenienceGraphOp (convenienceTransactionGraphOpP <* eof) <|>                          fmap ROGraphOp (roTransactionGraphOpP <* eof) <|>                          fmap DatabaseContextExprOp (databaseExprOpP <* eof) <|>                          fmap ImportBasicExampleOp (importBasicExampleOperatorP <* eof) <|>                          fmap TransGraphRelationalOp (transGraphRelationalOpP <* eof) <|>-                         fmap SchemaOp (schemaOperatorP <* eof)+                         fmap SchemaOp (schemaOperatorP <* eof) <|> +                         fmap LoginRolesOp (alterLoginRolesExprP <* eof) -promptText :: Either RelationalError HeadName -> Either RelationalError SchemaName -> StringType-promptText eHeadName eSchemaName = "TutorialD (" <> transInfo <> "): "+promptText :: MakePrompt+promptText eCurrentHead eSchemaName = "TutorialD (" <> transInfo <> "): "   where-    transInfo = fromRight "<unknown>" eHeadName <> "/" <> fromRight "<no schema>" eSchemaName+    headStr = case eCurrentHead of+      Left _ -> "<unknown>"+      Right (C.CurrentHeadTransactionId tid) -> T.pack (show tid)+      Right (C.CurrentHeadBranch branch) -> branch+    transInfo = headStr <> "/" <> fromRight "<no schema>" eSchemaName  parseTutorialD :: T.Text -> Either ParserError ParsedOperation parseTutorialD = parse interpreterParserP ""@@ -85,6 +98,9 @@ safeParseTutorialD :: T.Text -> Either ParserError ParsedOperation safeParseTutorialD = parse safeInterpreterParserP "" +-- | Utility method for external apps such as benchmarks to parse a relational expression for execution.+parseRelationalExpr :: RelationalMarkerExpr a => T.Text -> Either ParserError (RelationalExprBase a)+parseRelationalExpr = parse relExprP ""  evalTutorialD :: C.SessionId -> C.Connection -> SafeEvaluationFlag -> ParsedOperation -> IO ConsoleResult evalTutorialD sessionId conn safe = evalTutorialDInteractive sessionId conn safe False@@ -109,13 +125,18 @@   (DatabaseContextIOExprOp execOp) ->     if needsSafe then       unsafeError-      else-      eHandler $ C.executeDatabaseContextIOExpr sessionId conn execOp-+      else do+      eIOExpr <- interpretDatabaseContextIOOperator execOp+      case eIOExpr of+        Left err -> pure (DisplayRelationalErrorResult err)+        Right ioexpr ->+          eHandler $ C.executeDatabaseContextIOExpr sessionId conn ioexpr   (GraphOp execOp) -> do+    eHandler $ C.executeTransactionGraphExpr sessionId conn execOp+  (AlterGraphOp execOp) -> do     -- warn if the graph op could cause uncommited changes to be discarded     eIsDirty <- C.disconnectedTransactionIsDirty sessionId conn-    let runGraphOp = eHandler $ C.executeGraphExpr sessionId conn execOp+    let runGraphOp = eHandler $ C.executeAlterTransactionGraphExpr sessionId conn execOp         settings = Settings {complete = noCompletion,                              historyFile = Nothing,                              autoAddHistory = False}@@ -206,6 +227,8 @@     evalTutorialD sessionId conn safe (DatabaseContextExprOp dbcontextexpr)   (TransGraphRelationalOp execOp) ->     evalTransGraphRelationalOp sessionId conn execOp+  (LoginRolesOp alterLoginRoleExpr) -> do+    evalAlterLoginRolesExpr sessionId conn alterLoginRoleExpr   where     needsSafe = safe == SafeEvaluation     unsafeError = pure $ DisplayErrorResult "File I/O operation prohibited."@@ -218,9 +241,6 @@       case eErr of         Left err -> barf err         Right () -> return QuietSuccessResult---  runTutorialD :: C.SessionId -> C.Connection -> Maybe PromptLength -> T.Text -> IO () runTutorialD sessionId conn mPromptLength tutd =
src/bin/TutorialD/Interpreter/Base.hs view
@@ -72,6 +72,13 @@ identifierP :: Parser Text identifierP = identifier <* spaceConsumer +-- | Roles may include human names, so be generous in what we accept+roleNameP :: Parser Text+roleNameP = normalQuotedString <|> takeWhile1P (Just "non-whitespace character") (not . isSpace) <* spaceConsumer++permissionP :: Parser Text+permissionP = normalQuotedString <|> takeWhile1P (Just "non-whitespace character") (not . isSpace) <* spaceConsumer+   identifierRemainder :: Char -> Parser Text identifierRemainder c = do   rest <- many (alphaNumChar <|> char '_' <|> char '#')@@ -164,6 +171,9 @@  quotedString :: Parser Text quotedString = try tripleQuotedString <|> normalQuotedString++quotedFilePath :: Parser FilePath+quotedFilePath = T.unpack <$> quotedString  quoted :: Parser a -> Parser a quoted = between quote quote
src/bin/TutorialD/Interpreter/DatabaseContextExpr.hs view
@@ -2,7 +2,11 @@ module TutorialD.Interpreter.DatabaseContextExpr where import ProjectM36.Base import ProjectM36.Interpreter+import ProjectM36.DatabaseContext.Types import ProjectM36.DatabaseContext+import ProjectM36.AccessControlList+import ProjectM36.TransactionGraph.Types+import ProjectM36.DatabaseContextExpr import TutorialD.Interpreter.Base import qualified Data.Text as T import TutorialD.Interpreter.RelationalExpr@@ -18,6 +22,7 @@ import Data.Monoid #endif import Data.Functor+import qualified Data.UUID as UUID  --parsers which create "database expressions" which modify the database context (such as relvar assignment) databaseContextExprP :: Parser DatabaseContextExpr@@ -40,6 +45,7 @@                                removeDatabaseContextFunctionP,                                addRegisteredQueryP,                                removeRegisteredQueryP,+                               AlterACL <$> alterACLP,                                nothingP]              nothingP :: Parser DatabaseContextExpr            @@ -193,6 +199,43 @@   reserved "unregisterquery"   RemoveRegisteredQuery <$> quotedString +-- grant perm rolename maygrant+alterACLP :: Parser AlterDBCACLExpr+alterACLP = do+  let grantP = do+        reserved "grant"+        GrantAccessExpr <$> roleNameP <*> somePermissionP  <*> mayGrantP+      mayGrantP = (reserved "maygrant" $> True) <|> (reserved "nogrant" $> False)+      revokeP = do+        reserved "revoke"+        RevokeAccessExpr <$> roleNameP <*> somePermissionP+      grantDBCFuncP = do+        GrantDBCFunctionAccessExpr <$> roleNameP <*> functionNameP <*> dbcFunctionPermP <*> mayGrantP+      revokeDBCFuncP = do+        RevokeDBCFunctionAccessExpr <$> roleNameP <*> functionNameP <*> dbcFunctionPermP+  (try (reserved "grant" >> reserved "dbcfunction") >> grantDBCFuncP) <|>+    (try (reserved "revoke" >> reserved "dbcfunction") >> revokeDBCFuncP) <|>        +    grantP <|> revokeP++dbcFunctionPermP :: Parser DBCFunctionPermission+dbcFunctionPermP = (reserved "viewfunction" $> ViewDBCFunctionPermission) <|>+                   (reserved "executefunction" $> ExecuteDBCFunctionPermission) <|>+                   (reserved "alterfunction" $> AlterDBCFunctionPermission)++somePermissionP :: Parser SomePermission+somePermissionP =+  (reserved "accessrelvars" $> SomeRelVarPermission AccessRelVarsPermission) <|>++  (reserved "executefunctions" $> SomeFunctionPermission ExecuteFunctionPermission) <|>+  (reserved "viewfunctions" $> SomeFunctionPermission ViewFunctionPermission) <|>+  (reserved "alterfunctions" $> SomeFunctionPermission AlterFunctionPermission) <|>++  (reserved "alterschema" $> SomeAlterSchemaPermission AlterSchemaPermission) <|>++  (reserved "alteracl" $> SomeACLPermission AlterACLPermission) <|>+  (reserved "viewacl" $> SomeACLPermission ViewACLPermission) <|>+  (reserved "committransaction" $> SomeAlterTransGraphPermission CommitTransactionPermission)+ executeDatabaseContextFunctionP :: Parser DatabaseContextExpr executeDatabaseContextFunctionP = do   reserved "execute"@@ -212,13 +255,23 @@         (Left err, _) -> Left err -} +-- | Used exclusively by tests, bypasses role-based access control. interpretDatabaseContextExpr :: DatabaseContext -> TransactionId -> TransactionGraph -> T.Text -> Either RelationalError DatabaseContext interpretDatabaseContextExpr context transId graph tutdstring =   case parse databaseExprOpP "" tutdstring of     Left err -> Left $ PM36E.ParseError (T.pack (show err))     Right parsed -> do-      let env = RE.mkDatabaseContextEvalEnv transId graph-      RE.dbc_context <$> RE.runDatabaseContextEvalMonad context env (optimizeAndEvalDatabaseContextExpr True parsed)+      let roleNameResolver _roleName = pure UUID.nil+      case resolveRoleIds roleNameResolver parsed of+        Left err -> Left err+        Right parsed' -> do+          let env = RE.mkDatabaseContextEvalEnv transId graph dbcFuncUtils+              dbcFuncUtils = DatabaseContextFunctionUtils {+                executeDatabaseContextExpr = error "unexpected executeDatabaseContextExpr in test func",+                executeRelationalExpr = error "unexpected executeRelationalExpr in test func"+                }+      +          RE.dbc_context <$> RE.runDatabaseContextEvalMonad context env (optimizeAndEvalDatabaseContextExpr True parsed')  {- --no optimization
src/bin/TutorialD/Interpreter/DatabaseContextIOOperator.hs view
@@ -2,10 +2,19 @@ module TutorialD.Interpreter.DatabaseContextIOOperator where import ProjectM36.Base import ProjectM36.Interpreter+import ProjectM36.Error import TutorialD.Interpreter.Base import TutorialD.Interpreter.Types-import Data.Text+import Data.Text as T+import qualified Data.Text.IO as TIO+import Control.Exception (IOException, handle, displayException) +data DatabaseContextIOOperator =+  DatabaseContextIOOp DatabaseContextIOExpr |+  LoadAtomFunctionFromFileOp FunctionName [TypeConstructor] FilePath |+  LoadDatabaseContextFunctionFromFileOp FunctionName [TypeConstructor] FilePath+  deriving (Show, Eq)+ addAtomFunctionExprP :: Parser DatabaseContextIOExpr addAtomFunctionExprP = dbioexprP "addatomfunction" AddAtomFunction   @@ -32,13 +41,19 @@ atomTypeSignatureP :: Parser [TypeConstructor] atomTypeSignatureP = sepBy typeConstructorP arrow -dbContextIOExprP :: Parser DatabaseContextIOExpr-dbContextIOExprP = addAtomFunctionExprP <|> +databaseContextIOExprP :: Parser DatabaseContextIOExpr+databaseContextIOExprP = addAtomFunctionExprP <|>                     addDatabaseContextFunctionExprP <|>                     loadAtomFunctionsP <|>                    loadDatabaseContextFunctionsP <|>                    createArbitraryRelationP +databaseContextIOOperatorP :: Parser DatabaseContextIOOperator+databaseContextIOOperatorP = +  (DatabaseContextIOOp <$> databaseContextIOExprP) <|>+  (reserved "loadatomfunctionfromfile" >> (LoadAtomFunctionFromFileOp <$> quotedString <*> atomTypeSignatureP <*> quotedFilePath)) <|>+  (reserved "loaddatabasecontextfunctionfromfile" >> (LoadDatabaseContextFunctionFromFileOp <$> quotedString <*> atomTypeSignatureP <*> quotedFilePath))+   loadAtomFunctionsP :: Parser DatabaseContextIOExpr loadAtomFunctionsP = do   reserved "loadatomfunctions"@@ -49,3 +64,23 @@   reserved "loaddatabasecontextfunctions"   LoadDatabaseContextFunctions <$> quotedString <*> quotedString <*> fmap unpack quotedString                                              +interpretDatabaseContextIOOperator :: DatabaseContextIOOperator -> IO (Either RelationalError DatabaseContextIOExpr)+interpretDatabaseContextIOOperator expr = do+  let loadFromFile typ functionName functionArgs functionFilePath = do+        let handler :: IOException -> IO (Either RelationalError Text)+            handler e = pure (Left (ImportError (ImportFileError (T.pack (displayException e)))))+        eFuncBody <- handle handler (Right <$> TIO.readFile functionFilePath)+        case eFuncBody of+          Left err -> pure (Left err)+          Right functionBody ->+            pure (Right (typ functionName functionArgs functionBody))+        +  case expr of+    DatabaseContextIOOp expr' ->+      pure (Right expr')+    LoadAtomFunctionFromFileOp functionName functionArgs functionFilePath ->+      loadFromFile AddAtomFunction functionName functionArgs functionFilePath+    LoadDatabaseContextFunctionFromFileOp functionName functionArgs functionFilePath ->+      loadFromFile AddDatabaseContextFunction functionName functionArgs functionFilePath++
src/bin/TutorialD/Interpreter/Import/BasicExamples.hs view
@@ -4,7 +4,7 @@ import ProjectM36.DateExamples import ProjectM36.Base import ProjectM36.Interpreter-import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContextExpr import TutorialD.Interpreter.Base  #if !MIN_VERSION_megaparsec(6,0,0)@@ -15,7 +15,7 @@                                 deriving (Show)  evalImportBasicExampleOperator :: ImportBasicExampleOperator -> DatabaseContextExpr-evalImportBasicExampleOperator ImportBasicDateExampleOperator = databaseContextAsDatabaseContextExpr dateExamples+evalImportBasicExampleOperator ImportBasicDateExampleOperator = resolvedDatabaseContextAsDatabaseContextExpr dateExamples  importBasicExampleOperatorP :: Parser ImportBasicExampleOperator importBasicExampleOperatorP = do 
src/bin/TutorialD/Interpreter/Import/TutorialD.hs view
@@ -31,7 +31,7 @@             Right tutdBytes' -> importTutorialDBytes tutdBytes'     _ -> pure (Left (ImportError (InvalidFileURIError (URI.render fileURI))))   where-    filePath = drop (length ("file://" :: String)) (URI.renderStr fileURI)+    filePath = drop (length ("file:" :: String)) (URI.renderStr fileURI)           importTutorialDBytes :: BS.ByteString -> IO (Either RelationalError DatabaseContextExpr)
+ src/bin/TutorialD/Interpreter/LoginRolesOperator.hs view
@@ -0,0 +1,64 @@+module TutorialD.Interpreter.LoginRolesOperator where+import ProjectM36.Client as C+import qualified ProjectM36.LoginRoles as LoginRoles+import ProjectM36.Interpreter as I+import qualified Data.Text as T+import Data.Functor++import TutorialD.Interpreter.Base++alterLoginRolesExprP :: Parser AlterLoginRolesExpr+alterLoginRolesExprP =+  showAllRolesExprP <|>+  alterRoleNameExprP <|>+  alterRoleMayLoginExprP <|>+  showRolesForRoleExprP <|>+  addLoginRoleToRoleExprP <|>  +  addLoginRoleExprP <|>+  removeLoginRoleExprP <|>+  removeLoginRoleFromRoleExprP <|>+  addPermissionToRoleExprP <|>+  removePermissionFromRoleExprP+  where+    mayGrantP = do+      (reserved "maygrant" $> True) <|> (reserved "nogrant" $> False)+    mayLoginP = do+      (reserved "maylogin" $> True) <|> (reserved "maynotlogin" $> False)+    showRolesForRoleExprP = do+      colonOp ":showloginrole" +      ShowRolesForRoleExpr <$> roleNameP+    addLoginRoleExprP = do+      colonOp ":addloginrole"+      AddLoginRoleExpr <$> roleNameP <*> mayLoginP+    removeLoginRoleExprP = do+      colonOp ":removeloginrole"+      RemoveLoginRoleExpr <$> roleNameP+    showAllRolesExprP = do+      colonOp ":showloginroles"+      pure ShowAllRolesExpr+    addLoginRoleToRoleExprP = do+      colonOp ":addloginroletorole"+      AddRoleToRoleExpr <$> roleNameP <*> roleNameP <*> mayGrantP+    removeLoginRoleFromRoleExprP = do+      colonOp ":removeloginrolefromrole"+      RemoveRoleFromRoleExpr <$> roleNameP <*> roleNameP+    addPermissionToRoleExprP = do+      colonOp ":addpermissiontologinrole"+      AddPermissionToRoleExpr <$> permissionP <*> roleNameP <*> mayGrantP+    removePermissionFromRoleExprP = do+      colonOp ":removepermissionfromloginrole"+      RemovePermissionFromRoleExpr <$> roleNameP <*> permissionP+    alterRoleNameExprP = do+      colonOp ":alterrolename"+      AlterLoginRoleExpr <$> roleNameP <*> (Just <$> roleNameP) <*> pure Nothing+    alterRoleMayLoginExprP = do+      colonOp ":alterrolemaylogin"+      AlterLoginRoleExpr <$> roleNameP <*> pure Nothing <*> (Just <$> mayLoginP)++evalAlterLoginRolesExpr :: SessionId -> Connection -> AlterLoginRolesExpr -> IO ConsoleResult+evalAlterLoginRolesExpr sessionId conn expr = do+  result <- C.executeAlterLoginRolesExpr sessionId conn expr+  case result of+    Left err -> pure (DisplayErrorResult (T.pack (show err)))+    Right LoginRoles.QuietSuccessResult -> pure I.QuietSuccessResult+    Right (LoginRoles.InfoResult info) -> pure (DisplayResult info)
src/bin/TutorialD/Interpreter/RODatabaseContextOperator.hs view
@@ -21,22 +21,24 @@ import Data.Functor  --operators which only rely on database context reading-data RODatabaseContextOperator where-  ShowRelation :: RelationalExpr -> RODatabaseContextOperator-  PlotRelation :: RelationalExpr -> RODatabaseContextOperator-  ShowRelationType :: RelationalExpr -> RODatabaseContextOperator-  ShowConstraints :: StringType -> RODatabaseContextOperator-  ShowPlan :: DatabaseContextExpr -> RODatabaseContextOperator-  ShowTypes :: RODatabaseContextOperator-  ShowRelationVariables :: RODatabaseContextOperator-  ShowAtomFunctions :: RODatabaseContextOperator-  ShowDatabaseContextFunctions :: RODatabaseContextOperator-  ShowNotifications :: RODatabaseContextOperator-  ShowDataFrame :: DF.DataFrameExpr -> RODatabaseContextOperator-  GetDDLHash :: RODatabaseContextOperator-  ShowDDL :: RODatabaseContextOperator-  ShowRegisteredQueries :: RODatabaseContextOperator-  Quit :: RODatabaseContextOperator++data RODatabaseContextOperator =+  ShowRelation RelationalExpr |+  PlotRelation RelationalExpr |+  ShowRelationType RelationalExpr |+  ShowConstraints StringType |+  ShowDatabaseContextExprPlan DatabaseContextExpr |+  ShowRelationalExprPlan RelationalExpr |+  ShowTypes |+  ShowRelationVariables |+  ShowAtomFunctions |+  ShowDatabaseContextFunctions |+  ShowNotifications |+  ShowDataFrame DF.DataFrameExpr |+  GetDDLHash |+  ShowDDL |+  ShowRegisteredQueries |+  Quit   deriving (Show)  typeP :: Parser RODatabaseContextOperator@@ -49,11 +51,16 @@   colonOp ":showexpr"   ShowRelation <$> relExprP -showPlanP :: Parser RODatabaseContextOperator-showPlanP = do-  colonOp ":showplan"-  ShowPlan <$> databaseContextExprP+showDatabaseContextExprPlanP :: Parser RODatabaseContextOperator+showDatabaseContextExprPlanP = do+  colonOp ":showdbcexprplan"+  ShowDatabaseContextExprPlan <$> databaseContextExprP +showRelationalExprPlanP :: Parser RODatabaseContextOperator+showRelationalExprPlanP = do+  colonOp ":showrelexprplan"+  ShowRelationalExprPlan <$> relExprP+ showTypesP :: Parser RODatabaseContextOperator showTypesP = colonOp ":showtypes" >> pure ShowTypes @@ -90,7 +97,8 @@              <|> showRelationVariables              <|> plotRelExprP              <|> showConstraintsP-             <|> showPlanP+             <|> showDatabaseContextExprPlanP+             <|> showRelationalExprPlanP              <|> showTypesP              <|> showAtomFunctionsP              <|> showDatabaseContextFunctionsP@@ -137,11 +145,17 @@      Left err -> DisplayErrorResult (T.pack (show err))      Right rel -> DisplayRelationResult rel -evalRODatabaseContextOp sessionId conn (ShowPlan dbExpr) = do+evalRODatabaseContextOp sessionId conn (ShowDatabaseContextExprPlan dbExpr) = do   plan <- C.planForDatabaseContextExpr sessionId conn dbExpr   pure $ case plan of      Left err -> DisplayErrorResult (T.pack (show err))     Right optDbExpr -> DisplayResult $ T.pack (show optDbExpr)++evalRODatabaseContextOp sessionId conn (ShowRelationalExprPlan relExpr) = do+  plan <- C.planForRelationalExpr sessionId conn relExpr+  pure $ case plan of+    Left err -> DisplayErrorResult (T.pack (show err))+    Right optRelExpr -> DisplayResult optRelExpr  evalRODatabaseContextOp sessionId conn ShowTypes = do     eRel <- C.atomTypesAsRelation sessionId conn
src/bin/TutorialD/Interpreter/SchemaOperator.hs view
@@ -3,6 +3,7 @@ import ProjectM36.Interpreter import ProjectM36.Base import ProjectM36.IsomorphicSchema+import ProjectM36.IsomorphicSchema.Types import ProjectM36.Session import ProjectM36.Client import TutorialD.Interpreter.RelationalExpr
src/bin/TutorialD/Interpreter/TransactionGraphOperator.hs view
@@ -19,37 +19,37 @@   reserved ":automergetohead"   AutoMergeToHead <$> mergeTransactionStrategyP <*> identifierP -jumpToHeadP :: Parser TransactionGraphOperator+jumpToHeadP :: Parser TransactionGraphExpr jumpToHeadP = do   reservedOp ":jumphead"   JumpToHead <$> identifierP -jumpToTransactionP :: Parser TransactionGraphOperator+jumpToTransactionP :: Parser TransactionGraphExpr jumpToTransactionP = do   reservedOp ":jump"   JumpToTransaction <$> uuidP   -walkBackToTimeP :: Parser TransactionGraphOperator  +walkBackToTimeP :: Parser TransactionGraphExpr  walkBackToTimeP = do   reservedOp ":walkbacktotime"   WalkBackToTime <$> utcTimeP -branchTransactionP :: Parser TransactionGraphOperator+branchTransactionP :: Parser AlterTransactionGraphExpr branchTransactionP = do   reservedOp ":branch"   Branch <$> identifierP -deleteBranchP :: Parser TransactionGraphOperator+deleteBranchP :: Parser AlterTransactionGraphExpr deleteBranchP = do   reserved ":deletebranch"   DeleteBranch <$> identifierP -commitTransactionP :: Parser TransactionGraphOperator+commitTransactionP :: Parser AlterTransactionGraphExpr commitTransactionP = do   reservedOp ":commit"   pure Commit  -rollbackTransactionP :: Parser TransactionGraphOperator+rollbackTransactionP :: Parser AlterTransactionGraphExpr rollbackTransactionP = do   reservedOp ":rollback"   return Rollback@@ -68,7 +68,7 @@                                 reserved "unionpreferbranch"                                 UnionPreferMergeStrategy <$> identifierP)   -mergeTransactionsP :: Parser TransactionGraphOperator+mergeTransactionsP :: Parser AlterTransactionGraphExpr mergeTransactionsP = do   reservedOp ":mergetrans"   MergeTransactions <$> mergeTransactionStrategyP <*> identifierP <*> identifierP@@ -76,12 +76,15 @@ validateMerkleHashesP :: Parser ROTransactionGraphOperator validateMerkleHashesP = reservedOp ":validatemerklehashes" $> ValidateMerkleHashes -transactionGraphOpP :: Parser TransactionGraphOperator+transactionGraphOpP :: Parser TransactionGraphExpr transactionGraphOpP =    jumpToHeadP   <|> jumpToTransactionP   <|> walkBackToTimeP-  <|> branchTransactionP++alterTransactionGraphOpP :: Parser AlterTransactionGraphExpr+alterTransactionGraphOpP =+  branchTransactionP   <|> deleteBranchP   <|> commitTransactionP   <|> rollbackTransactionP
src/bin/TutorialD/Printer.hs view
@@ -7,6 +7,7 @@ import ProjectM36.Base import ProjectM36.Attribute as A hiding (null) import ProjectM36.DataFrame+import ProjectM36.AccessControlList import Prettyprinter import Prettyprinter.Render.Text import qualified Data.Set as S hiding (fromList)@@ -242,8 +243,56 @@         "registerquery" <+> pretty rQName <+> pretty relExpr       RemoveRegisteredQuery rQName ->         "unregisterquery" <+> pretty rQName+      AlterACL expr' -> pretty expr'       MultipleExpr dbcExprs ->         group (encloseSep "" "" "; " (pretty <$> dbcExprs))++prettyMayGrant :: MayGrant -> Doc ann+prettyMayGrant True = "maygrant"+prettyMayGrant False = "nogrant"++instance Pretty SomePermission where+  pretty perm =+    case perm of+      SomeRelVarPermission perm' -> pretty perm'+      SomeFunctionPermission perm' -> pretty perm'+      SomeAlterSchemaPermission perm' -> pretty perm'+      SomeAlterTransGraphPermission perm' -> pretty perm'+      SomeACLPermission perm' -> pretty perm'+      SomeDBCFunctionPermission perm' -> pretty perm'++instance Pretty RelVarPermission where+  pretty AccessRelVarsPermission = "accessrelvars"++instance Pretty FunctionPermission where+  pretty ExecuteFunctionPermission = "executefunctions"+  pretty ViewFunctionPermission = "viewfunctions"+  pretty AlterFunctionPermission = "alterfunctions"++instance Pretty AlterTransGraphPermission where+  pretty CommitTransactionPermission = "committransaction"++instance Pretty AlterSchemaPermission where+  pretty AlterSchemaPermission = "alterschema"++instance Pretty ACLPermission where+  pretty ViewACLPermission = "viewacls"+  pretty AlterACLPermission = "alteracls"++instance Pretty DBCFunctionPermission where+  pretty ViewDBCFunctionPermission = "viewfunction"+  pretty ExecuteDBCFunctionPermission = "executefunction"+  pretty AlterDBCFunctionPermission = "alterfunction"++instance Pretty AlterDBCACLExpr where+  pretty (GrantAccessExpr roleName somePerm mayGrant) =+    "grant" <+> pretty roleName <+> pretty somePerm <+> prettyMayGrant mayGrant+  pretty (RevokeAccessExpr roleName somePerm) =+    "revoke" <+> pretty roleName <+> pretty somePerm+  pretty (GrantDBCFunctionAccessExpr roleName funcName' perm mayGrant) =+    "grant dbcfunction" <+> pretty roleName <+> pretty funcName' <+> pretty perm <+> prettyMayGrant mayGrant+  pretty (RevokeDBCFunctionAccessExpr roleName funcName' perm) =+    "revoke dbcfunction" <+> pretty roleName <+> pretty funcName' <+> pretty perm  instance Pretty AttributeNameAtomExprMap where   pretty m =
src/bin/TutorialD/tutd.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} import TutorialD.Interpreter import ProjectM36.Cli-import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContext.Basic import System.Directory import System.FilePath 
+ src/bin/benchmark/BTree.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE ScopedTypeVariables #-}+import ProjectM36.BTree+import Criterion.Main+import qualified Data.Vector.Unboxed as V+import Control.DeepSeq+import Data.ByteUnits++-- | creates a completely BTree from a uniform distribution of positive integers.++createBTree :: Branches -> Int -> BTree+createBTree branches' itemCount = build branches' (V.fromList [0..itemCount - 1])+  +main :: IO ()+main = do+  let sizes = map (10 ^) [1..7 :: Int]+      prettyBytes x = getShortHand (getAppropriateUnits (ByteValue (fromIntegral x) Bytes))+      btrees16 = force $ map (createBTree 16) sizes+      btrees64 = force $ map (createBTree 64) sizes+      btrees100 = force $ map (createBTree 100) sizes  +      btrees128 = force $ map (createBTree 128) sizes+--  gen <- getStdGen+--  let rands = randomRs gen (0,100)+  +  defaultMain [{-bgroup "create branch-level 4 x " $+               map (\count ->+                       bench (show count) (nf (createBTree 4) count)) sizes,-}+    bgroup "member branch-level 16 x " $+      map (\btree -> bench (show (size btree) <> " " <> prettyBytes (totalBytes btree)) (nf (member 91) btree)) btrees16,++    bgroup "member branch-level 64 x " $+      map (\btree -> bench (show (size btree) <> " " <> prettyBytes (totalBytes btree)) (nf (member 91) btree)) btrees64,++    bgroup "member branch-level 100 x " $+      map (\btree -> bench (show (size btree) <> " " <> prettyBytes (totalBytes btree)) (nf (member 91) btree)) btrees100,+      ++    bgroup "member branch-level 128 x " $+      map (\btree -> bench (show (size btree) <> " " <> prettyBytes (totalBytes btree)) (nf (member 91) btree)) btrees128+      +      +    ]+              
src/bin/benchmark/Basic.hs view
@@ -1,4 +1,5 @@ import ProjectM36.Base+import ProjectM36.DatabaseContext import Criterion.Main import qualified ProjectM36.Attribute as A import ProjectM36.Relation@@ -8,7 +9,7 @@ import ProjectM36.TransactionGraph import qualified Data.Text as T import qualified Data.Vector as V-import qualified ProjectM36.DatabaseContext as DBC+import qualified ProjectM36.DatabaseContext.Basic as DBC import qualified Data.Set as S import System.IO.Temp import System.FilePath@@ -48,7 +49,7 @@   predicateMatch = AtomExprPredicate (FunctionAtomExpr "lte" [AttributeAtomExpr "a0", NakedAtomExpr (IntAtom cutoff)] ())  basicREEnv :: RelationalExprEnv-basicREEnv = mkRelationalExprEnv DBC.basicDatabaseContext emptyTransactionGraph+basicREEnv = mkRelationalExprEnv (toDatabaseContext DBC.basicDatabaseContext) emptyTransactionGraph  projectRelationToAttributes :: AttributeNames -> Relation -> Relation projectRelationToAttributes attrNames rel = validate (runRelationalExprM basicREEnv (evalRelationalExpr projection))
src/bin/benchmark/Handles.hs view
@@ -7,10 +7,8 @@ import ProjectM36.Interpreter hiding (Parser) import TutorialD.Interpreter.Base hiding (option) import qualified Data.Text as T-#if __GLASGOW_HASKELL__ < 804-import Data.Monoid-#endif import Control.Monad+import System.Random  data HandlesArgs = HandlesArgs {   openCloseCount :: Int,@@ -49,7 +47,8 @@  runOpenClose :: T.Text -> T.Text -> Int -> FilePath -> IO () runOpenClose tutdSetup' tutdIterate' tCount dbdir' = do-  let connInfo = InProcessConnectionInfo (MinimalPersistence dbdir') emptyNotificationCallback [] basicDatabaseContext+  rando <- initStdGen+  let connInfo = InProcessConnectionInfo (MinimalPersistence dbdir') emptyNotificationCallback [] basicDatabaseContext rando "admin"   eConn <- connectProjectM36 connInfo    case eConn of     Left err -> error (show err)
src/bin/benchmark/OnDiskClient.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DerivingVia, DeriveGeneric, DeriveAnyClass, TypeApplications #-} -- create a simple, on-disk database of ~20 MB so that we can get a heap profile import ProjectM36.Client+import TutorialD.Interpreter (parseRelationalExpr) import Data.Text (Text) import Codec.Winery import Options.Applicative@@ -10,6 +11,8 @@ import ProjectM36.Tupleable import Data.Time.Calendar import Data.Proxy+import Data.Maybe+import System.Random  data WeatherReading =   WeatherReading@@ -25,20 +28,23 @@  data Opts = Opts { datadir :: FilePath,                    writeData :: Bool, --read or write mode-                   tupleCount :: Int+                   tupleCount :: Int,+                   execTutorialD :: Maybe Text                  }  parseOptions :: Parser Opts parseOptions = Opts <$>   strOption (long "datadir" <> short 'd') <*>   switch (long "write-data" <> short 'w') <*>-  option auto (long "tuple-count" <> short 'c' <> value 10000)+  option auto (long "tuple-count" <> short 'c' <> value 10000) <*>+  optional (strOption (long "exec-tutd" <> short 'e'))  main :: IO () main = do   let parser = info (parseOptions <**> helper) (fullDesc <> progDesc "Read or write data for heap profiling.")   opts <- execParser parser-  let connInfo = InProcessConnectionInfo (MinimalPersistence (datadir opts)) emptyNotificationCallback [] basicDatabaseContext+  rando <- initStdGen+  let connInfo = InProcessConnectionInfo (MinimalPersistence (datadir opts)) emptyNotificationCallback [] basicDatabaseContext rando adminRoleName       eCheck v = do         x <- v         case x of @@ -64,11 +70,22 @@     eCheck $ executeDatabaseContextExpr sessionId conn insertExpr     eCheck $ commit sessionId conn     else do+    {-+    --switch to extend to try to exercise parallel mapM (filter is not parallelized in streamly yet)     putStrLn "reading"     --read one row to see how heap is affected (will load all rows)-    let readOneRow = Restrict (AttributeEqualityPredicate "temperature" (NakedAtomExpr (IntegerAtom 900))) (RelationVariable "x" ())+    let readOneRow = Restrict (AttributeEqualityPredicate "temperature" expensiveMatch) (RelationVariable "x" ())+        expensiveMatch = FunctionAtomExpr "test_bcrypt" [NakedAtomExpr (IntegerAtom 20), NakedAtomExpr (TextAtom "password"), NakedAtomExpr (IntegerAtom 900)] ()+        --simpleMatch = NakedAtomExpr (IntegerAtom 900)     val <- eCheck $ executeRelationalExpr sessionId conn readOneRow     print val-    +    -}+    let tutd = fromMaybe "x where temperature = 5" (execTutorialD opts)+    case parseRelationalExpr tutd of+      Left err -> error (show err)+      Right parsed -> do+        val <- eCheck $ executeRelationalExpr sessionId conn parsed+        print val+            
src/bin/benchmark/Relation.hs view
@@ -18,8 +18,8 @@ main :: IO () main = defaultMain [   bgroup "Big Relation" [ -     bench "100" $ whnf (matrixRelation 10) 100,-     bench "1000" $ whnf (matrixRelation 10) 1000,+--     bench "100" $ whnf (matrixRelation 10) 100,+--     bench "1000" $ whnf (matrixRelation 10) 1000,      bench "10000" $ whnf (matrixRelation 10) 10000      ]   ]
src/bin/benchmark/Server.hs view
@@ -8,7 +8,7 @@ import Data.Proxy import GHC.Generics import Control.Monad-+import System.Random  handleIOError :: Show e => IO (Either e a) -> IO a handleIOError m = do@@ -23,7 +23,8 @@ --test local connection speeds of inserts, updates, and deletes to look for space leaks, etc. main :: IO () main = do-  conn <- handleIOError $ connectProjectM36 (InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext)+  rando <- initStdGen+  conn <- handleIOError $ connectProjectM36 (InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando adminRoleName)   sess <- handleIOError $ createSessionAtHead conn "master"   _ <- handleIOError $ executeDatabaseContextExpr sess conn (toDefineExpr (Proxy :: Proxy User) "user") 
src/bin/benchmark/bigrel.hs view
@@ -13,6 +13,7 @@ import ProjectM36.Relation.Show.HTML import ProjectM36.RelationalExpression import ProjectM36.TransactionGraph+import ProjectM36.DatabaseContext --import qualified Data.HashSet as HS --import qualified Data.ByteString.Lazy.Char8 as BS --import qualified Data.IntMap as IM@@ -25,9 +26,7 @@ import Data.Text hiding (map) import Data.Time.Clock import Data.UUID.V4-#if __GLASGOW_HASKELL__ < 804-import Data.Monoid-#endif+import qualified ProjectM36.DatabaseContextFunctionUtils as Util   {-@@ -69,9 +68,9 @@                    now <- getCurrentTime                    tid <- nextRandom                    let setx = Assign "x" (ExistingRelation (force rel))-                       graph = bootstrapTransactionGraph now tid dateExamples-                       env = mkDatabaseContextEvalEnv tid graph-                       eNewState = runDatabaseContextEvalMonad dateExamples env (optimizeAndEvalDatabaseContextExpr True setx)+                       graph = bootstrapTransactionGraph now tid (toDatabaseContext dateExamples)+                       env = mkDatabaseContextEvalEnv tid graph Util.dud+                       eNewState = runDatabaseContextEvalMonad (toDatabaseContext dateExamples) env (optimizeAndEvalDatabaseContextExpr True setx)                        --plan = interpretRODatabaseContextOp context $ ":showplan " ++ tutd                    --displayOpResult plan                    case eNewState of
+ src/lib/ProjectM36/AccessControl.hs view
@@ -0,0 +1,141 @@+-- | Applies access control lists to the current role to determine whether should be granted+{-# LANGUAGE RankNTypes #-}+module ProjectM36.AccessControl where+import ProjectM36.Base+import ProjectM36.Error+import ProjectM36.AccessControlList+import ProjectM36.TransactionGraph+import ProjectM36.RelationalExpression+import ProjectM36.DatabaseContext.Types+import ProjectM36.RelationVariablesMentioned+import ProjectM36.IsomorphicSchema+import ProjectM36.Function+import Control.Monad.Except+import Control.Monad (when, forM_)++applyACLRelationalExpr :: Show a => [RoleId] -> RelVarAccessControlList -> RelationalExprBase a -> Either RelationalError ()+applyACLRelationalExpr roleIds acl' expr = do+  let checkPerm perm =+        if hasAccess roleIds perm acl' then+          pure ()+          else+          Left (AccessDeniedError (SomeRelVarPermission perm))+  when (mentionsRelVar expr) (checkPerm AccessRelVarsPermission)++applyACLAlterTransGraphExpr :: [RoleId] -> AlterTransGraphAccessControlList -> AlterTransactionGraphExpr -> Either RelationalError ()+applyACLAlterTransGraphExpr roleIds acl' _alterExpr =+  if hasAccess roleIds CommitTransactionPermission acl' then+    pure ()+    else+    Left (AccessDeniedError (SomeAlterTransGraphPermission CommitTransactionPermission))++-- we should probably have finer-grained permission here+applyACLDatabaseContextIOExpr :: [RoleId] -> DatabaseContextIOExpr -> DatabaseContextIOEvalMonad ()+applyACLDatabaseContextIOExpr roleIds _expr = do+   acl' <- resolveIODBC acl  +   if hasAccess roleIds AlterFunctionPermission (dbcFunctionsACL acl') then+     pure ()+     else+     throwError (AccessDeniedError (SomeFunctionPermission AlterFunctionPermission))++applyACLDatabaseContextExpr :: [RoleId] -> DatabaseContextExpr' -> DatabaseContextEvalMonad ()+applyACLDatabaseContextExpr roleIds expr = do+  dbcAcl <- resolveDBC acl+  dbcFuncs <- resolveDBC dbcFunctions+  let checkRVPerm perm acl' =+        if hasAccess roleIds perm acl' then+          pure ()+        else+          dbErr (AccessDeniedError (SomeRelVarPermission perm))+{-      checkFuncPerm perm acl' =+        if hasAccess roleIds perm acl' then+          pure ()+        else+          dbErr (AccessDeniedError (SomeFunctionPermission perm))-}+      checkACLPerm perm acl' =+          if hasAccess roleIds perm acl' then+            pure ()+          else+            dbErr (AccessDeniedError (SomeACLPermission perm))+      checkFuncPerm perm acl' = do +        -- check dbc-function-level permissions+        if hasAccess roleIds perm acl' then+          pure ()+          else+          dbErr (AccessDeniedError (SomeFunctionPermission perm))+      checkDBCFuncPerm fname perm = do+        -- check specific function-level permissions+          case functionForName fname dbcFuncs of+            Left err -> dbErr err+            Right func ->+              if hasAccess roleIds perm (funcACL func) then +                pure ()+              else+                dbErr (AccessDeniedError (SomeDBCFunctionPermission perm))+      rvAcl = relvarsACL dbcAcl+      aclAcl = aclACL dbcAcl+      funcAcl = dbcFunctionsACL dbcAcl+  case expr of+    NoOperation -> pure ()+    Define{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    Undefine{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    Assign{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    Insert{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    Delete{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    Update{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    AddInclusionDependency{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    RemoveInclusionDependency{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    AddNotification{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    RemoveNotification{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    AddTypeConstructor{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    RemoveTypeConstructor{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    RemoveAtomFunction{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    RemoveDatabaseContextFunction fname -> do+      checkFuncPerm AlterFunctionPermission funcAcl+      checkDBCFuncPerm fname AlterDBCFunctionPermission +    ExecuteDatabaseContextFunction fname _args -> do+      checkFuncPerm ExecuteFunctionPermission funcAcl+      checkDBCFuncPerm fname ExecuteDBCFunctionPermission       +    AddRegisteredQuery{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    RemoveRegisteredQuery{} ->+      checkRVPerm AccessRelVarsPermission rvAcl+    AlterACL{} ->+      checkACLPerm AlterACLPermission aclAcl+    MultipleExpr exprs ->+      mapM_ (applyACLDatabaseContextExpr roleIds) exprs+{-+applyACLTransGraphRelationalExpr :: [RoleId] -> RelVarAccessControlList -> TransGraphRelationalExpr -> Either RelationalError ()+applyACLTransGraphRelationalExpr roleIds acl' expr =+  when (mentionsRelVar expr) $+    if hasAccess roleIds AccessRelVarsPermission acl' then+      pure ()+      else+      Left (AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission))+-}++-- | Validate that any reference to a relvar within a transaction has AccessRelVarsPermission at that transactionId. Pass relevant access control lists extracted from the original expression which reference relvars at specific transaction ids.+applyACLGraphRefRelationalExpr :: [RoleId] -> [RelVarAccessControlList] -> Either RelationalError ()+applyACLGraphRefRelationalExpr roleIds transACLs =+  forM_ transACLs $ \acl' -> applyACLRelationalExpr roleIds acl' (RelationVariable "true" ())+    +applyACLSchemaExpr :: [RoleId] -> SchemaAccessControlList -> SchemaExpr -> Either RelationalError ()+applyACLSchemaExpr roleIds acl' _expr =+  if hasAccess roleIds AlterSchemaPermission acl' then+    pure ()+    else+    Left (AccessDeniedError (SomeAlterSchemaPermission AlterSchemaPermission))+
+ src/lib/ProjectM36/AccessControlList.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE DeriveGeneric, DeriveAnyClass, RankNTypes, TupleSections #-}+module ProjectM36.AccessControlList where+import qualified Data.Set as S+import Data.UUID+import Control.DeepSeq (NFData)+import GHC.Generics+import Data.Hashable+import qualified Data.Map as M+import Data.Maybe (fromMaybe)+import Data.Default++newtype AccessControlList role' permission =+    AccessControlList (M.Map role' (RoleAccess permission))+   deriving (Show, NFData, Generic, Read, Eq)++instance Ord r => Default (AccessControlList r p) where+  def = AccessControlList mempty++instance (Ord p, Eq r, Ord r) => Semigroup (AccessControlList r p) where+  a <> b = merge a b+  +instance (Ord p, Eq r, Ord r) => Monoid (AccessControlList r p) where+  mempty = empty++type MayGrant = Bool++type RoleAccess permission = M.Map permission MayGrant++type RoleId = UUID++-- | Determine if the role can view or alter underlying relation variables.+type RelVarAccessControlList = AccessControlList RoleId RelVarPermission++type FunctionAccessControlList = AccessControlList RoleId FunctionPermission++type AlterTransGraphAccessControlList = AccessControlList RoleId AlterTransGraphPermission++type SchemaAccessControlList = AccessControlList RoleId AlterSchemaPermission++type ACLAccessControlList = AccessControlList RoleId ACLPermission++type DBCFunctionAccessControlList = AccessControlList RoleId DBCFunctionPermission++grantable :: perm -> M.Map perm MayGrant+grantable perm = M.singleton perm True++notGrantable :: perm -> M.Map perm MayGrant+notGrantable perm = M.singleton perm False++class AllPermissions a where+  allPermissions :: S.Set a++data RelVarPermission = AccessRelVarsPermission {-| AccessTypeConstructorsPermission -}+  deriving (Eq, Show, Ord, Generic, NFData, Read, Hashable)++instance AllPermissions RelVarPermission where+  allPermissions = S.fromList [AccessRelVarsPermission]++data FunctionPermission =+  ExecuteFunctionPermission -- ^ allow the role to execute the function+  | ViewFunctionPermission -- ^ allow the role to see that the function exists+  | AlterFunctionPermission -- ^ allow the role to load functions from outside the database or replace existing functions+  deriving (Eq, Show, Ord, Generic, NFData, Hashable)++instance AllPermissions FunctionPermission where+  allPermissions = S.fromList [ExecuteFunctionPermission, ViewFunctionPermission, AlterFunctionPermission]++data AlterTransGraphPermission =+  CommitTransactionPermission -- ^ allow the role to commit a transaction to the graph+  deriving (Eq, Show, Ord, Generic, NFData, Hashable)++instance AllPermissions AlterTransGraphPermission where+  allPermissions = S.fromList [CommitTransactionPermission]++data AlterSchemaPermission =+  AlterSchemaPermission -- ^ allow the role to add or remove a schema+  deriving (Eq, Show, Ord, Generic, NFData, Hashable)++instance AllPermissions AlterSchemaPermission where+  allPermissions = S.fromList [AlterSchemaPermission]++data ACLPermission =+  ViewACLPermission |+  AlterACLPermission+  deriving (Eq, Show, Ord, Generic, NFData, Hashable)++instance AllPermissions ACLPermission where+  allPermissions = S.fromList [ViewACLPermission, AlterACLPermission]++data DBCFunctionPermission =+  ViewDBCFunctionPermission |+  ExecuteDBCFunctionPermission |+  AlterDBCFunctionPermission -- ^ change or delete this function+  deriving (Eq, Show, Ord, Generic, NFData, Hashable)++instance AllPermissions DBCFunctionPermission where+  allPermissions = S.fromList [ViewDBCFunctionPermission,+                               ExecuteDBCFunctionPermission,+                               AlterDBCFunctionPermission]++hasAccess :: (Ord perm, Ord role', Eq role', Eq perm) => [role'] -> perm -> AccessControlList role' perm -> Bool+hasAccess hasRoles checkPerm (AccessControlList acl) =+  any (\role -> M.member checkPerm (fromMaybe mempty (M.lookup role acl))) hasRoles++addAccess :: (Eq role', Ord role', Eq perm, Ord perm) => role' -> perm -> MayGrant -> AccessControlList role' perm -> AccessControlList role' perm+addAccess targetRole newPerm mayGrant (AccessControlList ras) =+  AccessControlList $ M.insertWith M.union targetRole (M.singleton newPerm mayGrant) ras++removeAccess :: (Eq role', Ord role', Eq perm, Ord perm) => role' -> perm -> AccessControlList role' perm -> AccessControlList role' perm+removeAccess targetRole targetPerm (AccessControlList acl) =+  normalize $ AccessControlList $ M.alter alterFunc targetRole acl+  where+    alterFunc (Just ras) = Just $ M.delete targetPerm ras+    alterFunc Nothing = Nothing++-- | Remove empty dictionaries (roles without permissions).+normalize :: AccessControlList r p -> AccessControlList r p+normalize (AccessControlList acl) = AccessControlList $ M.filter (not . M.null) acl++empty :: Ord r => AccessControlList r p+empty = AccessControlList mempty++allPermissionsForRoleId :: (AllPermissions p, Ord p) => r -> AccessControlList r p+allPermissionsForRoleId roleid = AccessControlList (M.singleton roleid (M.fromList (map (,True) (S.toList allPermissions))))++merge :: (Ord p, Eq r, Ord r) => AccessControlList r p -> AccessControlList r p -> AccessControlList r p+merge (AccessControlList acl1) (AccessControlList acl2) =+  normalize $ AccessControlList $ M.unionWith mergeFunc acl1 acl2+  where+    mergeFunc = M.unionWith (||)++data DatabaseContextACL =+  DatabaseContextACL {+  relvarsACL :: RelVarAccessControlList,+  dbcFunctionsACL :: FunctionAccessControlList,+  transGraphACL :: AlterTransGraphAccessControlList,+  schemaACL :: SchemaAccessControlList,+  aclACL :: ACLAccessControlList+      }+  deriving (Show, NFData, Generic)++instance Semigroup DatabaseContextACL where+  a <> b = DatabaseContextACL {+    relvarsACL = relvarsACL a <> relvarsACL b,+    dbcFunctionsACL = dbcFunctionsACL a <> dbcFunctionsACL b,+    transGraphACL = transGraphACL a <> transGraphACL b,+    schemaACL = schemaACL a <> schemaACL b,+    aclACL = aclACL a <> aclACL b+    }++instance Monoid DatabaseContextACL where+  mempty = DatabaseContextACL {+    relvarsACL = mempty,+    dbcFunctionsACL = mempty,+    transGraphACL = mempty,+    schemaACL = mempty,+    aclACL = mempty+    }++basic :: DatabaseContextACL+basic = DatabaseContextACL {+  relvarsACL = AccessControlList (M.singleton adminRoleId+                                   (grantableL allPermissions)+                                 ),+  dbcFunctionsACL = AccessControlList (M.singleton adminRoleId+                                        (grantableL allPermissions)+                                      ),+  transGraphACL = AccessControlList (M.singleton adminRoleId+                                      (grantableL allPermissions)+                                    ),+  schemaACL = AccessControlList (M.singleton adminRoleId+                                  (grantableL allPermissions)+                                ),+  aclACL = AccessControlList (M.singleton adminRoleId+                               (grantableL allPermissions)+                             )+  }+  where+    grantableL :: forall perm. Ord perm => S.Set perm -> RoleAccess perm+    grantableL perms = M.fromList (map (, True) (S.toList perms))++adminRoleId :: RoleId+adminRoleId = nil++-- used in access control error reporting+data SomePermission = SomeRelVarPermission RelVarPermission |+                      SomeFunctionPermission FunctionPermission |+                      SomeAlterSchemaPermission AlterSchemaPermission |+                      SomeAlterTransGraphPermission AlterTransGraphPermission |+                      SomeACLPermission ACLPermission |+                      SomeDBCFunctionPermission DBCFunctionPermission+                    deriving (Show, NFData, Generic, Eq, Hashable)++
src/lib/ProjectM36/AtomFunction.hs view
@@ -30,7 +30,8 @@                                     funcBody = FunctionBuiltInBody $                                                \case                                                  x:_ -> pure x-                                                 _ -> Left AtomFunctionTypeMismatchError+                                                 _ -> Left AtomFunctionTypeMismatchError,+                                    funcACL = ()                                   }                                                                                      @@ -38,7 +39,8 @@ compiledAtomFunction :: FunctionName -> [AtomType] -> AtomFunctionBodyType -> AtomFunction compiledAtomFunction name aType body = Function { funcName = name,                                                   funcType = aType,-                                                  funcBody = FunctionBuiltInBody body }+                                                  funcBody = FunctionBuiltInBody body,+                                                  funcACL = () }  --the atom function really should offer some way to return an error evalAtomFunction :: AtomFunction -> [Atom] -> Either AtomFunctionError Atom
src/lib/ProjectM36/AtomFunctions/Primitive.hs view
@@ -19,64 +19,82 @@              funcType = [IntegerAtomType, IntegerAtomType, IntegerAtomType],              funcBody = body (\case                                  IntegerAtom i1:IntegerAtom i2:_ -> pure (IntegerAtom (i1 + i2))-                                 _ -> Left AtomFunctionTypeMismatchError)},+                                 _ -> Left AtomFunctionTypeMismatchError),+             funcACL = ()},     Function { funcName = "abs",                funcType = [IntegerAtomType, IntegerAtomType],                funcBody = body (\case                                    IntegerAtom i:_ -> pure $ IntegerAtom (abs i)                                    _ -> Left AtomFunctionTypeMismatchError-                               )+                               ),+               funcACL = ()              },     Function { funcName = "id",                funcType = [TypeVariableType "a", TypeVariableType "a"],                funcBody = body (\case                                    x:_ -> pure x                                    _ -> Left AtomFunctionTypeMismatchError-                               )},+                               ),+               funcACL = ()},         Function { funcName = "sum",                funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,-               funcBody = body $ relationFoldFunc relationSum+               funcBody = body $ relationFoldFunc relationSum,+               funcACL = ()                             },     Function { funcName = "count",                funcType = [anyRelationAtomType,                            IntegerAtomType],-               funcBody = body $ relationAtomFunc relationCount+               funcBody = body $ relationAtomFunc relationCount,+               funcACL = ()                             },     Function { funcName = "max",                funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,-               funcBody = body $ relationFoldFunc relationMax +               funcBody = body $ relationFoldFunc relationMax,+               funcACL = ()                             },     Function { funcName = "min",                funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,-               funcBody = body $ relationFoldFunc relationMin+               funcBody = body $ relationFoldFunc relationMin,+               funcACL = ()                             },     Function { funcName = "mean",                funcType = foldAtomFuncType IntegerAtomType IntegerAtomType,-               funcBody = body $ relationFoldFunc relationMean+               funcBody = body $ relationFoldFunc relationMean,+               funcACL = ()                             },     Function { funcName = "eq",                funcType = [TypeVariableType "a", TypeVariableType "a", BoolAtomType],                funcBody = body $ \case                                          [i1,i2] -> pure (BoolAtom (i1 == i2))-                                         _ -> Left AtomFunctionTypeMismatchError+                                         _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "lt",                funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],-               funcBody = body $ integerAtomFuncLessThan False},+               funcBody = body $ integerAtomFuncLessThan False,+               funcACL = ()+             },     Function { funcName = "lte",                funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],-               funcBody = body $ integerAtomFuncLessThan True},+               funcBody = body $ integerAtomFuncLessThan True,+               funcACL = ()+               },     Function { funcName = "gte",                funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],-               funcBody = body $ integerAtomFuncLessThan False >=> boolAtomNot},+               funcBody = body $ integerAtomFuncLessThan False >=> boolAtomNot,+               funcACL = ()+             },     Function { funcName = "gt",                funcType = [IntegerAtomType, IntegerAtomType, BoolAtomType],-               funcBody = body $ integerAtomFuncLessThan True >=> boolAtomNot},+               funcBody = body $ integerAtomFuncLessThan True >=> boolAtomNot,+               funcACL = ()+             },     Function { funcName = "not",                funcType = [BoolAtomType, BoolAtomType],                funcBody = body $ \case                                          [b] -> boolAtomNot b-                                         _ -> Left AtomFunctionTypeMismatchError+                                         _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "int",                funcType = [IntegerAtomType, IntAtomType],@@ -87,13 +105,15 @@                                     pure (IntAtom (fromIntegral v))                                   else                                     Left InvalidIntBoundError-                                _ -> Left AtomFunctionTypeMismatchError+                                _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "integer",                funcType = [IntAtomType, IntegerAtomType],                funcBody = body $ \case                  [IntAtom v] -> Right $ IntegerAtom $ fromIntegral v-                 _ -> Left AtomFunctionTypeMismatchError+                 _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "uuid",                funcType = [TextAtomType, UUIDAtomType],@@ -103,27 +123,38 @@                      case mUUID of                        Just u -> pure $ UUIDAtom u                        Nothing -> Left $ InvalidUUIDString v-                 _ -> Left AtomFunctionTypeMismatchError+                 _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "and",                funcType = [BoolAtomType, BoolAtomType, BoolAtomType],                funcBody = body $ \case                  [BoolAtom b1, BoolAtom b2] ->                    Right $ BoolAtom (b1 && b2)-                 _ -> Left AtomFunctionTypeMismatchError+                 _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "or",                funcType = [BoolAtomType, BoolAtomType, BoolAtomType],                funcBody = body $ \case                  [BoolAtom b1, BoolAtom b2] ->                    Right $ BoolAtom (b1 || b2)-                 _ -> Left AtomFunctionTypeMismatchError                   +                 _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()                             },     Function { funcName = "increment",                funcType = [IntegerAtomType, IntegerAtomType],                funcBody = body $ \case                  [IntegerAtom i] -> pure (IntegerAtom (i+1))-                 _ -> Left AtomFunctionTypeMismatchError+                 _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()               +             },+    Function { funcName = "text_length",+               funcType = [TextAtomType, IntegerAtomType],+               funcBody = body $ \case+                 [TextAtom t] -> pure (IntegerAtom (toInteger (T.length t)))+                 _ -> Left AtomFunctionTypeMismatchError,+               funcACL = ()              }        ] <> scientificAtomFunctions@@ -198,7 +229,6 @@ castInteger (IntegerAtom i) = i  castInteger _ = error "attempted to cast non-IntegerAtom to Integer" - scientificAtomFunctions :: AtomFunctions scientificAtomFunctions = HS.fromList [   Function { funcName = "read_scientific",@@ -208,29 +238,35 @@                  case APT.parseOnly (APT.scientific <* APT.endOfInput) t of                    Left err -> Left (AtomFunctionParseError err)                    Right sci -> pure (ScientificAtom sci)-               _ -> Left AtomFunctionTypeMismatchError+               _ -> Left AtomFunctionTypeMismatchError,+             funcACL = ()                         },   Function { funcName = "scientific",              funcType = [IntegerAtomType, IntAtomType, ScientificAtomType],              funcBody = body $ \case                [IntegerAtom c,IntAtom e] -> pure (ScientificAtom $ scientific c e)-               _ -> Left AtomFunctionTypeMismatchError+               _ -> Left AtomFunctionTypeMismatchError,+             funcACL = ()                         },   Function { funcName = "scientific_add",              funcType = binaryFuncType,-             funcBody = binaryFuncBody (+)+             funcBody = binaryFuncBody (+),+             funcACL = ()                         },   Function { funcName = "scientific_subtract",              funcType = binaryFuncType,-             funcBody = binaryFuncBody (-)+             funcBody = binaryFuncBody (-),+             funcACL = ()                         },   Function { funcName = "scientific_multiply",              funcType = binaryFuncType,-             funcBody = binaryFuncBody (*)+             funcBody = binaryFuncBody (*),+             funcACL = ()                         },   Function { funcName = "scientific_divide",              funcType = binaryFuncType,-             funcBody = binaryFuncBody (/)+             funcBody = binaryFuncBody (/),+             funcACL = ()            }   ]   where body = FunctionBuiltInBody
+ src/lib/ProjectM36/AtomFunctions/SelfTest.hs view
@@ -0,0 +1,36 @@+module ProjectM36.AtomFunctions.SelfTest where+import ProjectM36.Base+import ProjectM36.AtomFunctionError+import qualified Data.HashSet as HS+import Control.Concurrent (threadDelay)+import System.IO.Unsafe (unsafePerformIO)+import Crypto.KDF.BCrypt (bcrypt)+import qualified Data.Text.Encoding as TE+import Debug.Trace++-- functions which should only exist for testing Project:M36+selfTestAtomFunctions :: AtomFunctions+selfTestAtomFunctions = HS.fromList [+  Function { funcName = "test_expensive" --returns the first argument after pausing X microseconds in the second argument to simulate a time-consuming function+           , funcType = [TypeVariableType "a", IntegerAtomType, TypeVariableType "a"]+           , funcBody = FunctionBuiltInBody (+             \case+               atom:(IntegerAtom microseconds):_ ->+                 unsafePerformIO $ do+                   traceShowM ("test_expensive threadDelay"::String, fromIntegral microseconds::Int)+                   threadDelay (fromIntegral microseconds)+                   pure (Right atom)+               _ -> Left AtomFunctionTypeMismatchError)+           , funcACL = ()+           },+    Function { funcName = "test_bcrypt", -- pass a value through but calculate something expensive, not for actual encryption use since it uses a fixed seed+               funcType = [IntegerAtomType, TextAtomType, TypeVariableType "a", TypeVariableType "a"],+               funcBody = FunctionBuiltInBody (+                 \case+                   (IntegerAtom costVal):(TextAtom plaintextPassword):atom:_ ->+                     let hashed = bcrypt (fromIntegral costVal) (TE.encodeUtf8 "1234567890123456") (TE.encodeUtf8 plaintextPassword) in+                       Right (ByteStringAtom hashed `seq` atom)+                   _ -> Left AtomFunctionTypeMismatchError)+             , funcACL = ()+             }+  ]
src/lib/ProjectM36/Atomable.hs view
@@ -12,18 +12,16 @@ import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Vector as V-import Control.DeepSeq (NFData) import Control.Applicative import Data.Time.Calendar import Data.ByteString (ByteString) import Data.Time.Clock import Data.Proxy import qualified Data.List.NonEmpty as NE-import Codec.Winery import Data.UUID  -- | All database values ("atoms") adhere to the 'Atomable' typeclass. This class is derivable allowing new datatypes to be easily marshaling between Haskell values and database values.-class (Eq a, NFData a, Serialise a, Show a) => Atomable a where+class Atomable a where   toAtom :: a -> Atom   default toAtom :: (Generic a, AtomableG (Rep a)) => a -> Atom   toAtom v = toAtomG (from v) (toAtomTypeG (from v))
src/lib/ProjectM36/Attribute.hs view
@@ -104,6 +104,21 @@       --,attributesSet = attributesSet attrs1 <> attributesSet attrs2       } +{-+-- | Return the intersection of attributes. If the attributes share the same name, but not the same types, return an error.+intersection :: Attributes -> Attributes -> Either Relation Attributes+intersection attrsA attrsB =+  if overlappingAttrs attrsA  == overlappingAttrs attrsB then+    pure $ overlappingAttrs attrA+  else+    Left (TupleAttributeTypeMismatchError (attributesForNames overlappingNames attrsA))+  where+    nameSet1 = attributeNameSet attrsA+    nameSet2 = attributeNameSet attrsB+    overlappingNames = S.intersection nameSet1 nameSet2+    overlappingAttrs = attributesForNames overlappingNames+-}    + addAttributes :: Attributes -> Attributes -> Attributes addAttributes = (<>) @@ -284,3 +299,14 @@   where     hset = HS.intersection (attributesSet attrsA) (attributesSet attrsB)     vec = HS.foldr (flip V.snoc) mempty hset++-- | reorder the attributes according to the order of the attributes input. Useful during typechecking because the incoming attributes may be unresolved.+reorderAttributes :: Attributes -> Attributes -> Either RelationalError Attributes+reorderAttributes attrsOrder attrs =+  if attributesAndOrderEqual attrsOrder attrs then+    pure attrs+    else do+    Attributes <$> V.mapM mapper (attributesVec attrsOrder)+  where+    mapper attrOrder = do+      attributeForName (attributeName attrOrder) attrs
src/lib/ProjectM36/AttributeExpr.hs view
@@ -5,3 +5,8 @@ attributeName :: AttributeExprBase a -> AttributeName attributeName (AttributeAndTypeNameExpr nam _ _) = nam attributeName (NakedAttributeExpr attr) = A.attributeName attr++-- | Identify all type constructors mentioned in the expression.+typeConstructor :: AttributeExprBase a -> Maybe TypeConstructor+typeConstructor NakedAttributeExpr{} = Nothing+typeConstructor (AttributeAndTypeNameExpr _ tCons _) = Just tCons
+ src/lib/ProjectM36/BTree.hs view
@@ -0,0 +1,239 @@+-- implements a write-once, read-many B+tree layed out using the eytzinger format for less CPU cache churn+{-# LANGUAGE TypeApplications, DeriveAnyClass, DeriveGeneric #-}+module ProjectM36.BTree (build, member, BTree(..), Branches, Level, Index, size, totalBytes) where+import qualified Data.Vector.Unboxed as V+import qualified Data.Vector.Unboxed.Mutable as VM+import Control.Monad+import Control.DeepSeq+import Data.Maybe+--import Text.Printf+import GHC.Generics++import Debug.Trace++data BTree = BTree {+  branches :: Branches,+  elemCount :: Int,+  vec :: V.Vector Int }+  deriving (Show, Generic, NFData)++type Branches = Int+type Level = Int+type Index = Int++-- | return slicepoints for the b slices (returns b-1 indexes so that b nearly-equal slices are made)+sliceIndexes :: Branches -> Int -> [Int]+sliceIndexes b n | n < b = []+sliceIndexes b n | n == b = [0..b-2]+sliceIndexes b n = map islice [1 .. nodec]+  where+    nodec = b - 1+    islice x = (n * x) `divr` b+     +divr :: Integral a => a -> a -> a    +divr a b =+  --the `round` function rounds based even/odd integers- we just want dumb, unbiased rounding+  -- warning: this does not round properly+  if res - fromIntegral intres < 0.5 then+    intres+    else+    intres + 1+  where+    intres = floor res+    res :: Double+    res = fromIntegral a / fromIntegral b++-- | Calculate the size needed to flatten a sorted list into an Eytzinger b-tree (which may be sparse).+vectorSize :: Branches -> Int -> (Int, Int)+vectorSize b n = firstFit (zip (availableSizes b) [0..])+  where+    firstFit [] = (0,0) -- impossible+    firstFit ((size',i):ss) =+      if size' >= n then+        (size', i)+        else+        firstFit ss++-- | return list of vector sizes available for tree of node-branch factor b.+availableSizes :: Int -> [Int]+availableSizes b = takeWhile (>0) $ scanl (\acc x -> acc + (b - 1) * b ^ x) (b - 1) [(1 :: Int) ..]++-- | slice a vector into b+1 equally-sized slices and return the b roots+treeSlicer :: Branches -> V.Vector Int -> ([Int], [V.Vector Int])+treeSlicer b vin =+  if n >= b then+    (roots, slicesThroughIndexes sliceindexes vin)+  else+    (V.toList vin, [])+  where+    n = V.length vin+    sliceindexes = sliceIndexes b n+    roots = map (vin V.!) sliceindexes++-- cut up a vector into new vectors at the indexes but do not include the indexed value+slicesThroughIndexes :: [Int] -> V.Vector Int -> [V.Vector Int]+slicesThroughIndexes [] vin = [vin]+slicesThroughIndexes (i:is) vin =+  case V.splitAt i vin of+    (s1, sr) -> s1 : slicesThroughIndexes (map (\x -> x - i - 1) is) (V.tail sr)++sparseVal :: Int+sparseVal = -1 -- maxBound++-- the logical offset path within a tree, starting with the leaves (not the root).+type TreePath = [Int]++{-0+3 6 9 12+15 18 21 24+-}+-- | Returns the offset in the eytzinger encoding of the b-tree to the start of the (b-1) roots.+offsetForPath :: Branches -> TreePath -> Int+offsetForPath _ [] = 0+offsetForPath b path =+  levelOffset + --base offset to get to correct level in tree+  foldr (\(o, ri) acc -> +           acc ++           intraLevelOffset o ri+           ) 0 (zip path [pathLen, pathLen - 1 .. 1])+  where+    pathLen = length path+    kBlockSize l = (0 : availableSizes b) !! l+    kRootSize l = (b - 1) * (b ^ (l - 1)) -- 0, 3, 3*4, 3*4^2+    levelOffset = kBlockSize pathLen+    intraLevelOffset o ri = --traceShow ("o", o, "ri", ri, "i", i, "kRootSize", kRootSize ri)+      o * kRootSize ri+                       +-- | uses a sorted list to create a static Eytzinger representation of the b-tree- this structure does not support mutability. Since Project:M36 writes data using a WORM strategy, the Eytzinger is ideal since the structure emphasizes cache locality, reduces pointer indirection, and is quite compact with no need for extraneous padding.++--should build take a streamly stream or a list instead so that we won't need the sorted list in memory?+build :: Branches -> V.Vector Int -> BTree+build b sortedList = --input list should be de-duplicated to reduce b-tree size, but is otherwise a non-fatal construction of a b-tree (with some duplicate nodes)+  BTree {+    branches = b,+    elemCount = V.length sortedList,+    vec = vout}+  where+    n = V.length sortedList+    ksize = ceiling (logBase @Double (fromIntegral (b - 1)) (fromIntegral n))+    newArrSize = availableSizes b !! (ksize - 1)+    vout =+     V.create $ do+      --traceShowM ("ksize" :: String, ksize, "newArrSize" :: String, newArrSize, "n" :: String, n)+      v <- VM.replicate newArrSize sparseVal+      --split input list into `b` slices, extract the b roots by slicing the array into three equal-sized slices+      let writelevel path items = do+            let (roots, childSlices) = treeSlicer b items+                childSliceSizes = map V.length childSlices+                almostEqualSized = foldr (\x acc ->+                                            case acc of+                                              Nothing -> Nothing+                                              Just acc' -> if abs (acc' - x) > 2 then Nothing else Just x) (Just (head childSliceSizes)) childSliceSizes+            --first lay out all root-k values, then, k+1, etc.+            --space needed for each k level is (b-1) * b^k elements+            --traceShowM ("vsize" :: String, V.length items, "k" :: String, (k:: Int), "ksize" :: String, ksize, "roots" :: String, roots, "childSizes" :: String , map V.length childSlices, "childVecs" :: String, childSlices, "path" :: String, path)+            when (isNothing almostEqualSized) $ error ("uneven slices" <> show childSliceSizes)+            --write roots+            forM_ (zip roots [0..]) $ \(r,i) -> do+              let pos = offsetForPath b path + i+                  --dbgStr :: String+                  -- dbgStr = printf "%d + %d * (%d - 1) * (%d ^ %d) + %d" kOffset offset b b k i+                  -- dbgStr = printf "offset %d blockOffset %d k %d" offset blockOffset k+                  --dbgStr = printf "path %s pos %d" (show path) pos+              --traceShowM ("write" :: String, dbgStr)+              oldval <- VM.read v pos+              when (oldval /= sparseVal) $ traceShowM ("overwrite pos" :: String,+                                                        pos, "oldval" :: String,+                                                        oldval, "newval" :: String,+                                                        r)+              --traceShowM ("write val " <> show r <> " at " <> show pos)+              VM.write v pos r+            forM_ (zip childSlices [0..]) $ \(childVec, i) -> do+{-              let childRootOffset = kOffset + i * (b - 1) ^ k+                  kOffset = (0 : availableSizes b) !! (k + 1)-}+              unless (V.null childVec) $+                writelevel (i : path) childVec+      writelevel [] sortedList+      pure v++{- 0..16+5 should be written to pos+   [   ]/ [     ]  [     ]  [       ]  [       ] / [      ]  [      ] [      ] [      ] [      ]+k  0 1 2  3  4  5  6  7  8  9  10  11  12 13 14    15 16 17  18 19 20 21 22 23 24 25 26 27 28 29+0  4 9 13 +1         0  1  2  5  6  7  10 11  12  14 15 16+2                                                            3yes++4 * 3 ^ k++                          0                           n=1+     3              6          9            12        n=4+15 18 21 24    27 30 33 36  39 42 45 48  51 54 57 60  n=16 k+1 offset + (3 * 4 ^ 1) * parentOffset + 3 * childOffset+++                          0                           k=0+     1              2          3            4         k=1 offset+5  6  7  8       9 10 11 12  13 14 15 16  17 18 19 20 k=2 +-}++{-indexForRoots :: Branches -> Level -> Index -> Int+indexForRoots b k i = ((0 : availableSizes b) !! k) + i * (b - 1) +  -}+  +member :: Int -> BTree -> Bool+member needle bt = memberk 0 []+  where+    b = branches bt+    vin = vec bt+    (_,maxk) = vectorSize b (V.length vin) -- we don't care about the actual number of items in the tree+    memberk :: Level -> TreePath -> Bool+    memberk k _ | k > maxk = False+    memberk k path =+      let startIndex = offsetForPath b path+          roots = V.slice startIndex (b - 1) vin+      in +        case gteBinarySearch needle roots of+          Nothing -> -- follow right-most branch+            memberk (k+1) (b - 1 : path)+          Just gteIndex ->+            (roots V.! gteIndex == needle) || -- found it!+            memberk (k + 1) (gteIndex : path) -- search left branch of this root++-- | Search a sorted vector using binary search to find the first index >= to the needle, but takes into account the sparseVal.+gteBinarySearch :: Int -> V.Vector Int -> Maybe Int+gteBinarySearch needle haystack =+  if V.null haystack then+    Nothing+    else+    bsearch 0 (V.length haystack - 1)+  where+    bsearch low high+      | low == high =+        if haystack V.! low >= needle then+          Just low+          else+          Nothing+      | otherwise = +        let index = (high + low) `div` 2+            item = haystack V.! index+        in+          if item == needle then+            Just index+          else if item < needle && item /= sparseVal then do+            --we need to search to higher indexes+            bsearch (index + 1) high+          else+            --search lower indexes+            bsearch low index++size :: BTree -> Int+size = elemCount++totalBytes :: BTree -> Int+totalBytes bt = V.length (vec bt)+{-+-- | Useful for joins using two btrees.+intersect :: BTree -> BTree -> [Int]+intersect bta btb =+  go = +-}
src/lib/ProjectM36/Base.hs view
@@ -1,11 +1,9 @@-{-# LANGUAGE ExistentialQuantification,DeriveGeneric,DeriveAnyClass,FlexibleInstances,OverloadedStrings, DeriveTraversable, DerivingVia, TemplateHaskell, TypeFamilies, BangPatterns #-}+{-# LANGUAGE ExistentialQuantification,DeriveGeneric,DeriveAnyClass,FlexibleInstances,OverloadedStrings, DeriveTraversable, DerivingVia, TemplateHaskell, TypeFamilies, BangPatterns, DuplicateRecordFields #-} {-# OPTIONS_GHC -fno-warn-orphans #-}  module ProjectM36.Base where-import ProjectM36.DatabaseContextFunctionError import ProjectM36.AtomFunctionError-import ProjectM36.MerkleHash-+import ProjectM36.AccessControlList import Data.Functor.Foldable.TH import qualified Data.Map as M import qualified Data.HashSet as HS@@ -23,7 +21,6 @@ import Data.Time.Calendar (Day(..)) import Data.Typeable import Data.ByteString (ByteString)-import qualified Data.List.NonEmpty as NE import Data.Vector.Instances () import Data.Scientific @@ -31,17 +28,9 @@  type DatabaseName = String -#if !(MIN_VERSION_hashable(1,3,4))---support for hashable < 1.3, hashable 1.3+ includes instance for containers-instance Hashable (M.Map TypeVarName AtomType) where -  hashWithSalt salt tvmap = hashWithSalt salt (M.keys tvmap)--instance Hashable (M.Map AttributeName AtomExpr) where-  hashWithSalt salt m = salt `hashWithSalt` M.toList m+type RoleName = Text -instance Hashable (S.Set AttributeName) where-  hashWithSalt salt s = salt `hashWithSalt` S.toList s-#endif+type Permission = Text  -- time-compat includes these instances but time-compat is a dependency that is problematic, so just copy the instances here instance Hashable Day where@@ -124,7 +113,7 @@ type AttributeName = StringType  -- | A relation's type is composed of attribute names and types.-data Attribute = Attribute AttributeName AtomType deriving (Eq, Show, Read, Generic, NFData)+data Attribute = Attribute AttributeName !AtomType deriving (Eq, Show, Read, Generic, NFData)  instance Hashable Attribute where   hashWithSalt salt (Attribute attrName _) = hashWithSalt salt attrName@@ -223,6 +212,8 @@  type RelationalExpr = RelationalExprBase () +type RenameAssocs = S.Set (AttributeName, AttributeName)+ -- | A relational expression represents query (read) operations on a database. data RelationalExprBase a =   --- | Create a relation from tuple expressions.@@ -245,7 +236,7 @@   --- | Create a join of two relational expressions. The join occurs on attributes which are identical. If the expressions have no overlapping attributes, the join becomes a cross-product of both tuple sets.   Join (RelationalExprBase a) (RelationalExprBase a)  |   --- | Rename an attribute (first argument) to another (second argument).-  Rename (S.Set (AttributeName, AttributeName)) (RelationalExprBase a) | -- should the rename be a Map?+  Rename RenameAssocs (RelationalExprBase a) | -- should the rename be a Map?   --- | Return a relation containing all tuples of the first argument which do not appear in the second argument (minus).   Difference (RelationalExprBase a) (RelationalExprBase a) |   --- | Create a sub-relation composed of the first argument's attributes which will become an attribute of the result expression. The unreferenced attributes are not altered in the result but duplicate tuples in the projection of the expression minus the attribute names are compressed into one. For more information, <https://github.com/agentm/project-m36/blob/master/docs/introduction_to_the_relational_algebra.markdown#group read the relational algebra tutorial.>@@ -265,6 +256,13 @@  instance Hashable RelationalExpr +-- | Used for fixed relational expressions (useful for caching).+type PinnedRelationalExpr = RelationalExprBase TransactionId++instance Hashable PinnedRelationalExpr+    +type TransactionId = UUID+ type WithNamesAssocs = WithNamesAssocsBase ()  type WithNamesAssocsBase a = [(WithNameExprBase a, RelationalExprBase a)]@@ -335,41 +333,12 @@ -- a fundamental relational expr to which other relational expressions compile type GraphRefRelationalExpr = RelationalExprBase GraphRefTransactionMarker -type SchemaName = StringType                         --type Subschemas = M.Map SchemaName Schema---- | Every transaction has one concrete database context and any number of isomorphic subschemas.-data Schemas = Schemas DatabaseContext Subschemas-  deriving (Generic)---- | The DatabaseContext is a snapshot of a database's evolving state and contains everything a database client can change over time.--- I spent some time thinking about whether the VirtualDatabaseContext/Schema and DatabaseContext data constructors should be the same constructor, but that would allow relation variables to be created in a "virtual" context which would appear to defeat the isomorphisms of the contexts. It should be possible to switch to an alternative schema to view the same equivalent information without information loss. However, allowing all contexts to reference another context while maintaining its own relation variables, new types, etc. could be interesting from a security perspective. For example, if a user creates a new relvar in a virtual context, then does it necessarily appear in all linked contexts? After deliberation, I think the relvar should appear in *all* linked contexts to retain the isomorphic properties, even when the isomorphism is for a subset of the context. This hints that the IsoMorphs should allow for "fall-through"; that is, when a relvar is not defined in the virtual context (for morphing), then the lookup should fall through to the underlying context.-newtype Schema = Schema SchemaIsomorphs-              deriving (Generic)-                              -data SchemaIsomorph = IsoRestrict RelVarName RestrictionPredicateExpr (RelVarName, RelVarName) | -                      IsoRename RelVarName RelVarName |-                      IsoUnion (RelVarName, RelVarName) RestrictionPredicateExpr RelVarName  --maps two relvars to one relvar-                      -- IsoTypeConstructor in morphAttrExpr-                      deriving (Generic, Show)-                      -type SchemaIsomorphs = [SchemaIsomorph]+type HeadName = StringType  type RegisteredQueryName = StringType  type RegisteredQueries = M.Map RegisteredQueryName RelationalExpr-                              -data DatabaseContext = DatabaseContext {-  inclusionDependencies :: InclusionDependencies,-  relationVariables :: RelationVariables,-  atomFunctions :: AtomFunctions,-  dbcFunctions :: DatabaseContextFunctions,-  notifications :: Notifications,-  typeConstructorMapping :: TypeConstructorMapping,-  registeredQueries :: RegisteredQueries-  } deriving (NFData, Generic)-             +   type IncDepName = StringType               -- | Inclusion dependencies represent every possible database constraint. Constraints enforce specific, arbitrarily-complex rules to which the database context's relation variables must adhere unconditionally.@@ -380,14 +349,18 @@ --used for returning information about individual expressions type DatabaseContextExprName = StringType -type DatabaseContextExpr = DatabaseContextExprBase ()+type DatabaseContextExpr = DatabaseContextExprBase () RoleName +type DatabaseContextExpr' = DatabaseContextExprBase () RoleId+ instance Hashable DatabaseContextExpr  -type GraphRefDatabaseContextExpr = DatabaseContextExprBase GraphRefTransactionMarker+type GraphRefDatabaseContextExpr = DatabaseContextExprBase GraphRefTransactionMarker RoleName +type GraphRefDatabaseContextExpr' = DatabaseContextExprBase GraphRefTransactionMarker RoleId+ -- | Database context expressions modify the database context.-data DatabaseContextExprBase a = +data DatabaseContextExprBase a r =    NoOperation |   Define RelVarName [AttributeExprBase a] |   Undefine RelVarName | --forget existence of relvar X@@ -414,9 +387,11 @@    AddRegisteredQuery RegisteredQueryName RelationalExpr |   RemoveRegisteredQuery RegisteredQueryName |++  AlterACL (AlterDBCACLExprBase r) |   -  MultipleExpr [DatabaseContextExprBase a]-  deriving (Show, Read, Eq, Generic, NFData)+  MultipleExpr [DatabaseContextExprBase a r]+  deriving (Show, Eq, Generic, NFData)  type ObjModuleName = StringType type ObjFunctionName = StringType@@ -438,6 +413,8 @@  instance Hashable RestrictionPredicateExpr +instance Hashable (RestrictionPredicateExprBase TransactionId)+ type GraphRefRestrictionPredicateExpr = RestrictionPredicateExprBase GraphRefTransactionMarker  -- | Restriction predicates are boolean algebra components which, when composed, indicate whether or not a tuple should be retained during a restriction (filtering) operation.@@ -451,65 +428,16 @@   AttributeEqualityPredicate AttributeName (AtomExprBase a) -- relationalexpr must result in relation with single tuple   deriving (Show, Read, Eq, Generic, NFData, Foldable, Functor, Traversable) --- child + parent links--- | A transaction graph's head name references the leaves of the transaction graph and can be used during session creation to indicate at which point in the graph commits should persist.-type HeadName = StringType--type TransactionHeads = M.Map HeadName Transaction---- | The transaction graph is the global database's state which references every committed transaction.-data TransactionGraph = TransactionGraph TransactionHeads (S.Set Transaction)-  deriving Generic--transactionHeadsForGraph :: TransactionGraph -> TransactionHeads-transactionHeadsForGraph (TransactionGraph hs _) = hs--transactionsForGraph :: TransactionGraph -> S.Set Transaction-transactionsForGraph (TransactionGraph _ ts) = ts--transactionIdsForGraph :: TransactionGraph -> S.Set TransactionId-transactionIdsForGraph = S.map transactionId . transactionsForGraph---- | Every transaction has context-specific information attached to it.--- The `TransactionDiff`s represent child/edge relationships to previous transactions (branches or continuations of the same branch).-data TransactionInfo = TransactionInfo {-  parents :: TransactionParents,-  stamp :: UTCTime,-  merkleHash :: MerkleHash-  } deriving (Show, Generic)--type TransactionParents = NE.NonEmpty TransactionId---- | Every set of modifications made to the database are atomically committed to the transaction graph as a transaction.-type TransactionId = UUID--data Transaction = Transaction TransactionId TransactionInfo Schemas-  deriving Generic-                            --- | The disconnected transaction represents an in-progress workspace used by sessions before changes are committed. This is similar to git's "index". After a transaction is committed, it is "connected" in the transaction graph and can no longer be modified.-data DisconnectedTransaction = DisconnectedTransaction TransactionId Schemas DirtyFlag---the database context expression represents a difference between the disconnected transaction and its immutable parent transaction- is this diff expr used at all?- type DirtyFlag = Bool  type TransactionDiffExpr = DatabaseContextExpr-                            -transactionId :: Transaction -> TransactionId-transactionId (Transaction tid _ _) = tid -transactionInfo :: Transaction -> TransactionInfo-transactionInfo (Transaction _ info _) = info--instance Eq Transaction where                            -  (Transaction uuidA _ _) == (Transaction uuidB _ _) = uuidA == uuidB-                   -instance Ord Transaction where                            -  compare (Transaction uuidA _ _) (Transaction uuidB _ _) = compare uuidA uuidB- type AtomExpr = AtomExprBase ()  instance Hashable AtomExpr +instance Hashable (AtomExprBase TransactionId)+ type GraphRefAtomExpr = AtomExprBase GraphRefTransactionMarker  type AggAtomFuncExprInfo = (AttributeName, AttributeName) -- (relvar attribute name, subrel attribute name)@@ -532,6 +460,8 @@ type ExtendTupleExpr = ExtendTupleExprBase ()  instance Hashable ExtendTupleExpr++instance Hashable (ExtendTupleExprBase TransactionId)    type GraphRefExtendTupleExpr = ExtendTupleExprBase GraphRefTransactionMarker @@ -558,6 +488,8 @@  instance Hashable AttributeNames +instance Hashable (AttributeNamesBase TransactionId)+ type GraphRefAttributeNames = AttributeNamesBase GraphRefTransactionMarker  -- | The persistence strategy is a global database option which represents how to persist the database in the filesystem, if at all.@@ -585,6 +517,8 @@  instance Hashable TupleExpr +instance Hashable (TupleExprBase TransactionId)+ type TupleExpr = TupleExprBase ()  type GraphRefTupleExpr = TupleExprBase GraphRefTransactionMarker@@ -594,6 +528,8 @@  instance Hashable TupleExprs +instance Hashable (TupleExprsBase TransactionId)+ type GraphRefTupleExprs = TupleExprsBase GraphRefTransactionMarker  type TupleExprs = TupleExprsBase ()@@ -603,30 +539,26 @@   UnionMergeStrategy |   -- | Similar to a union merge, but, on conflict, prefer the unmerged section (relvar, function, etc.) from the branch named as the argument.   UnionPreferMergeStrategy HeadName |-  -- | Similar to the our/theirs merge strategy in git, the merge transaction's context is identical to that of the last transaction in the selected branch.+  -- | Similar to the our/theirs merge strategy in git, the merge transaction's context is identical to that of the head of the selected branch.   SelectedBranchMergeStrategy HeadName                      deriving (Eq, Show, Generic, NFData) ---type DatabaseContextFunctionBodyType = [Atom] -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext-type DatabaseContextFunctions = HS.HashSet DatabaseContextFunction- type FunctionName = StringType type FunctionBodyScript = StringType  -- | Represents stored, user-created or built-in functions which can operates of types such as Atoms or DatabaseContexts.-data Function a = Function {+data Function a acl = Function {   funcName :: FunctionName,   funcType :: [AtomType],-  funcBody :: FunctionBody a+  funcBody :: FunctionBody a,+  funcACL :: acl   }   deriving (Generic, NFData) -instance Eq (Function a) where                           +instance Eq (Function a acl) where                              f1 == f2 = funcName f1 == funcName f2 -instance Hashable (Function a) where+instance Hashable (Function a acl) where   hashWithSalt salt func = salt `hashWithSalt` funcName func `hashWithSalt` funcType func `hashWithSalt` hashfuncbody     where     hashfuncbody =@@ -646,12 +578,9 @@   rnf (FunctionBuiltInBody _) = rnf ()   rnf (FunctionObjectLoadedBody fp mod' entryf _) = rnf (fp, mod', entryf) -type AtomFunction = Function AtomFunctionBodyType+type AtomFunction = Function AtomFunctionBodyType () type AtomFunctionBody = FunctionBody AtomFunctionBodyType -type DatabaseContextFunction = Function DatabaseContextFunctionBodyType-type DatabaseContextFunctionBody = FunctionBody DatabaseContextFunctionBodyType- attrTypeVars :: Attribute -> S.Set TypeVarName attrTypeVars (Attribute _ aType) = case aType of   IntAtomType -> S.empty@@ -699,7 +628,20 @@  unimplemented :: HasCallStack => a unimplemented = error "unimplemented"++data AlterDBCACLExprBase r =+  GrantAccessExpr r SomePermission MayGrant |+  RevokeAccessExpr r SomePermission |+  GrantDBCFunctionAccessExpr r FunctionName DBCFunctionPermission MayGrant | -- refactor to support other function types; for example, an AtomFunction could contain sensitive business logic which only some users can run+  RevokeDBCFunctionAccessExpr r FunctionName DBCFunctionPermission+  deriving (Show, Generic, Eq, NFData, Hashable)++type AlterDBCACLExpr = AlterDBCACLExprBase RoleName++-- | Variant of ACL expression with RoleName resolved to RoleId.+type AlterDBCACLRoleIdExpr = AlterDBCACLExprBase RoleId             makeBaseFunctor ''RelationalExprBase-+makeBaseFunctor ''DatabaseContextExprBase+makeBaseFunctor ''AlterDBCACLExprBase 
+ src/lib/ProjectM36/Cache/RelationalExprCache.hs view
@@ -0,0 +1,202 @@+-- an in-memory cache for relational expression results keyed off of the expressions+module ProjectM36.Cache.RelationalExprCache where+import ProjectM36.Base+import Data.Time.Clock.POSIX+import Data.Time.Clock+import Data.Int+import qualified StmContainers.Map as STMMap+import Control.Concurrent.STM+import GHC.Conc (unsafeIOToSTM)+import System.Random+import Control.Monad+import qualified ProjectM36.RelExprSize as RE+import ProjectM36.SystemMemory+import ProjectM36.RelExprSize (ByteCount)+import qualified Data.List.NonEmpty as NE+import ListT+import Data.List (sortBy)++--caching for uncommitted transactions may be a useful, future extension, but cannot be supported here since they are not (yet) uniquely identified++{- both the key and value of this cache are relational expressions, allowing for maximum flexibility++* if a key is a partial match of a large rel expr, then it can be used+* if a value is something other than a set of tuples, it can allow for a natural compression++Project:M36 passes all results to the cache, which decides if it is worth caching based on the time it took to calculate the result and how large the result is (if it won't blow out the cache maximum level).++In the future, the cache can be populated by predicting which queries are likely to be issued.+-}+++data RelExprCache = RelExprCache {+  upperBound :: TVar ByteCount,+  currentSize :: TVar ByteCount,+  cacheMap :: STMMap.Map PinnedRelationalExpr RelExprCacheInfo+  }++-- | Use all available RAM. In the future, some sort of memory heuristics engine could juggle how much memory is allocated to caching vs. processing.+defaultUpperBound :: IO ByteCount+defaultUpperBound = do+  mem <- getMemoryStats+  case mem of+    Left _err -> pure 0+    Right (_,totalMem) -> pure totalMem++empty :: ByteCount -> IO RelExprCache+empty upper = do+  maxSize <- newTVarIO upper+  curSize <- newTVarIO 0+  newMap <- STMMap.newIO+  pure $ RelExprCache {+    upperBound = maxSize,+    currentSize = curSize,+    cacheMap = newMap+    }++-- | Relational results can be represented using multiple representations such as+-- * unsorted tupleset+-- * tuples sorted by some ordering+-- * pinned relational expression (which may have been partially evaluated and could refer to other potentially-cached expressions)+-- * b+tree with tuples+-- All representations are immutable and pegged to specific transactions.+-- These representations are used to cache evaluated relational expressions out of the transaction graph+data RelationRepresentation =+  PinnedExpressionRep PinnedRelationalExpr |+  UnsortedTupleSetRep Attributes RelationTupleSet |+  SortedTuplesRep [RelationTuple] (NE.NonEmpty (AttributeName, SortOrder))++instance RE.Size RelationRepresentation where+  size (PinnedExpressionRep pRelExpr) = RE.size pRelExpr+  size (UnsortedTupleSetRep _ tupSet) = RE.size tupSet+  size (SortedTuplesRep tups _) = RE.size tups++data SortOrder = AscSortOrder | DescSortOrder  +    +data RelExprCacheInfo =+  RelExprCacheInfo { calculatedInTime :: !NominalDiffTime, -- ^ the duration of time it took to compute the relational expression without this cache entry. This can be used to determine if using the cache is worthwhile.+                     result :: RelationRepresentation, -- ^ the cached relational expr (in memory)+                     createTime :: !UTCTime, -- ^  when this entry was added to the cache+                     lastRequestTime :: !(Maybe UTCTime), -- when this entry was last used+                     size :: !ByteCount+                   }++-- identify the least-recently-used entries whose size sum to the target size or more.+leastRecentlyUsedEntries :: ByteCount -> RelExprCache -> STM [PinnedRelationalExpr]+leastRecentlyUsedEntries targetSize cache = do+  cacheMapAssoc <- toList $ STMMap.listT (cacheMap cache)+  let lrusorted = sortBy lrusort cacheMapAssoc+      lrusort (_, cacheInfoA) (_,cacheInfoB) =+        lastRequestTime cacheInfoB `compare` lastRequestTime cacheInfoA -- lru should be at the front of the list+      keysToRemove = snd $ foldr sumToTargetSize (0,[]) lrusorted+      sumToTargetSize (k,cacheInfo) acc@(bytesAcc, keysAcc) = +        if bytesAcc >= targetSize then+          acc+          else+          (size cacheInfo + bytesAcc, k : keysAcc)+  pure keysToRemove++-- | Delete the least-important cache items until the target size for this cache is reached.+purgeToSize :: RelExprCache -> ByteCount -> STM ()+purgeToSize = undefined++lookup :: PinnedRelationalExpr -> RelExprCache -> STM (Maybe RelExprCacheInfo)+lookup key cache = STMMap.lookup key (cacheMap cache)++type HitCount = Int64+type Probability = Double++-- | Decide probabalistically which cache entries to expunge depending on cache pressure. Any entry has a non-zero chance of being expunged.+--trimCache :: RelExprCache -> STM ()+--trimCache = do+  --attribute probability to all cache entries based on cache size, time to compute the entry, last request time++type MemoryPressure = Double++-- | A simple LRU-based cache where the upper-bound is the available memory.+executeLRUStrategy :: ByteCount -> -- ^ size of new, potential cache entry+                      NominalDiffTime -> -- ^ time it took to calculate this cache entry+                      RelExprCache ->+                      FreeMemBytes ->+                      STM (Probability, [PinnedRelationalExpr]) -- ^ return the probability that the cache should retain this entry and, if so, which entries to purge to make room for it+executeLRUStrategy entrySize _calcTime cache freeMem = do+    upperBound' <- readTVar (upperBound cache)+    currentSize' <- readTVar (currentSize cache)+    let proposedFreeMem = freeMem - entrySize+        prob = normalizedLogProb 1.0 (fromIntegral proposedFreeMem) (fromIntegral upperBound')+        --prob = logisticProb (fromIntegral proposedFreeMem) (fromIntegral upperBound')+--    traceShowM ("cache prob"::String, proposedFreeMem, upperBound', prob)+    if entrySize + currentSize' < upperBound' then+      pure (prob, []) -- should we probabilistically remove cache entries before the cache is full?+      else do+      -- evict entries were least-recently used+      entriesToEvict <- leastRecentlyUsedEntries entrySize cache+      pure (prob, entriesToEvict)++type IsRegisteredQuery = Bool++--allow the cache to decide if this result or one of it constituents should be cached+add :: RandomGen g +    => g+    -> PinnedRelationalExpr+    -> RelationRepresentation+    -> NominalDiffTime -- ^ time it took to calculate this value+    -> IsRegisteredQuery -- ^ Used to determine if the result to cache may potentially be used to evaluate a registered query, which should increase the result's likelihood of being cached.+    -> MemoryStats+    -> RelExprCache+    -> STM g+add rgen expr exprResult calcTime _isRegisteredQuery memStats cache = do+  -- if the time to calculate is less than a certain threshold, don't bother caching it+  now <- unsafeIOToSTM getCurrentTime+  let newCacheInfo = RelExprCacheInfo { calculatedInTime = calcTime,+                                        result = exprResult,+                                        createTime = now,+                                        lastRequestTime = Nothing,+                                        size = RE.size exprResult + RE.size expr}+  mCacheInfo <- STMMap.lookup expr (cacheMap cache) --opt: replace with `focus`+  case mCacheInfo of+        Nothing -> do+          -- calculate new entry size+          let keySize = RE.size expr+              valSize = RE.size exprResult+          -- calculate probability of retention and, if retaining, which entries to evict+          upperBound' <- readTVar (upperBound cache)          +          (probRetain, entriesToEvict) <- executeLRUStrategy (keySize + valSize) calcTime cache (min (fst memStats) upperBound')+          let (rand, rgen') = uniformR (0.0, 1.0) rgen+          --traceShowM ("probRetain"::String, probRetain, "rand"::String, rand, probRetain >= rand)+          when (probRetain >= rand) $ do+            forM_ entriesToEvict $ \key -> do+              mval <- STMMap.lookup key (cacheMap cache)+              let delSize = maybe 0 size mval+              currentSize' <- readTVar (currentSize cache)                    +              STMMap.delete key (cacheMap cache)+              writeTVar (currentSize cache) (currentSize' - delSize)+            --traceShowM ("adding to cache"::String, expr)+            STMMap.insert newCacheInfo expr (cacheMap cache)+            currentSize'' <- readTVar (currentSize cache)             +            writeTVar (currentSize cache) (keySize + valSize + currentSize'')+          pure rgen'+        Just _ -> do -- then entry is already cached, nothing to do+          --traceShowM ("key already cached"::String)+          pure rgen++-- p(m) = log(1 + α*m) / log(1 + α*Mmax)+normalizedLogProb :: Double -> Double -> Double -> Double+normalizedLogProb alpha m mmax+  | m <= 0    = 0+  | m >= mmax = 1+  | otherwise = logBase (1 + alpha * mmax) (1 + alpha * m) ++logistic :: Double -> Double -> Double -> Double+logistic k m x0 =+  1.0 / (1.0 + (euler ** ((-k) * (m - x0))))++stdlogistic :: Double -> Double+stdlogistic m =+  logistic 1.0 m 0.0++logisticProb :: Double -> Double -> Double+logisticProb freeMem memMax = logistic 1.0 freeMem (memMax / 2.0)++euler :: Double+euler = 2.718281828459045        
+ src/lib/ProjectM36/Cache/Tuple.hs view
@@ -0,0 +1,170 @@+-- | Define a file which can be used to cache tuples for arbitrary, flat tuple sets.+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ScopedTypeVariables #-}++module ProjectM36.Cache.Tuple where+import ProjectM36.Base+import ProjectM36.Serialise.Base ()+import ProjectM36.RelExprSize+import qualified Data.Vector as V+import qualified Data.ByteString as BS+import Network.ByteOrder+import Data.Time.Clock+import Codec.Winery as W+import System.IO+import GHC.Generics+import Control.Monad (when, foldM)+import Control.Exception+import qualified Streamly.Data.Stream.Prelude as SP+import qualified Streamly.Internal.Data.Stream as SD+import Control.Monad.IO.Class (liftIO, MonadIO)+import Data.Bifunctor+import Data.Proxy++{- file format for tuple cache+-bytecount for info at end of file+TupleCacheInfo++-use blockSizes to get the next block of bytes+-}+type FileMagic = BS.ByteString++newtype InvalidFileMagicException = InvalidFileMagicException BS.ByteString deriving (Show)+newtype TruncatedFileException = TruncatedFileException ByteCount deriving (Show)+data SerialisationSchemaMismatch = SerialisationSchemaMismatch deriving (Show)++instance Exception InvalidFileMagicException+instance Exception TruncatedFileException+instance Exception SerialisationSchemaMismatch++fileMagic :: FileMagic+fileMagic = "PM36CacheTuple_v000"++type ByteOffset = ByteCount++data TupleCacheInfo =+  TupleCacheInfo { blockSizes :: V.Vector (ByteOffset, ByteCount),+                   representing :: PinnedRelationalExpr,+                   created :: UTCTime,+                   tupleSchema :: W.Schema -- the schema is shared amongst all tuples+                 }+  deriving (Generic, Show)+  deriving Serialise via WineryRecord TupleCacheInfo++blockCount :: TupleCacheInfo -> Int+blockCount i = V.length (blockSizes i)++readByteCount :: Handle -> IO Int+readByteCount h = do+  bytes <- BS.hGet h 8+  when (BS.length bytes /= 8) $ throw (TruncatedFileException 8)+  pure (fromIntegral $ word64 bytes)++-- | Runs checks such as confirming that the file size can accommodate the blockSizes in the cache info, but without reading the entire file so that this can be done routinely.+validateQuickly :: Handle -> IO ()+validateQuickly = undefined++--read and deserialize block of bytes prefixed by a byte count+readBlock :: Serialise a => Handle -> IO a+readBlock h = do+  bc <- readByteCount h+  bytes <- BS.hGetNonBlocking h bc+  when (BS.length bytes /= bc) $ throw (InvalidFileMagicException bytes)+  case deserialise bytes of+    Left err -> throw err+    Right val -> pure val+  +-- | get the cache info from a tuple cache file, may throw IO exception or InvalidFileMagicException+readCacheInfo :: Handle -> IO TupleCacheInfo+readCacheInfo h = do+  --look for magic+  potentialMagic <- BS.hGetNonBlocking h (BS.length fileMagic)+  when (potentialMagic /= fileMagic) $ throw (InvalidFileMagicException potentialMagic)+  readBlock h ++-- since this is just for caching, IO errors can be logged and ignored due to cache creation failure+writeTupleStream :: Handle -> PinnedRelationalExpr -> ByteCount -> [RelationTuple] -> IO ()+writeTupleStream h expr groupSize tuples = do+  --how can I write the tuple info if I only have a stream of the tuples :/ should the tuple info be at the back of the file then?+  --create slices and record sizes for metadata+  --write tuple info+  --write metadata plus metadata size at end+  now <- getCurrentTime+  BS.hPutStr h fileMagic+  let writeTupleBlock tuples' = do+          let tupleListBytes = serialiseOnly tuples'+              bytesCount = fromIntegral (BS.length tupleListBytes)+          BS.hPutStr h tupleListBytes+          pure bytesCount+      tupleBlockWriter (accsize, offset', accTuples, metadata) nextTuple = do+        -- add a new grouping, if we go over the size limit+        let estimatedTupleSize = size nextTuple+        if accsize + estimatedTupleSize >= groupSize then do+          tupleListByteCount <- writeTupleBlock (accTuples <> [nextTuple])+          pure (0,+                offset' + tupleListByteCount,+                [],+                metadata <> [(offset', tupleListByteCount)])+          else do+          pure (accsize + estimatedTupleSize,+                offset',+                accTuples <> [nextTuple],+                metadata)+      writeFinalBlock x@(_, _, [], _) = pure x+      writeFinalBlock (acc, offset', accTuples, metadata) = do+        bytesWritten <- writeTupleBlock accTuples+        pure (acc, offset', [], metadata <> [(offset', bytesWritten)])+        +  (_, _, _, blockSizeMetadata) <- foldM tupleBlockWriter (0,0,mempty,mempty) tuples >>= writeFinalBlock+  --write remaining tuples which didn't get get us over the last threshold+  --create block metadata at end of file, offset by file magic at beginning of file+  let tupleCacheInfo = TupleCacheInfo { blockSizes = V.fromList (map (first offsetByMagic) blockSizeMetadata),+                                        representing = expr,+                                        created = now,+                                        tupleSchema = schema (Proxy :: Proxy RelationTuple)+                                        }+      offsetByMagic = (+) (fromIntegral (BS.length fileMagic))+      tInfoData = serialise tupleCacheInfo+  BS.hPutStr h tInfoData+  BS.hPutStr h (bytestring64 (fromIntegral (BS.length tInfoData)))+  pure ()++readTupleStream :: MonadIO m => Handle -> SP.Stream m RelationTuple+readTupleStream h = SD.unCross $ do+  --skip filemagic+  --jump to end to read tuple cache info+  --deserialise blocks from metadata+  tupleCacheInfo <- liftIO $ do+    potentialMagic <- BS.hGetNonBlocking h (BS.length fileMagic)+    when (potentialMagic /= fileMagic) $ throw (InvalidFileMagicException potentialMagic)+    --get size of tuple cache info+    hSeek h SeekFromEnd (-8)+    infoSize <- readByteCount h+    hSeek h SeekFromEnd (-(fromIntegral (8 + infoSize)))+    tcacheInfo <- deserialise <$> BS.hGet h infoSize+    case tcacheInfo of+      Left err -> throw err+      Right info -> do+        when (tupleSchema info /= schema (Proxy :: Proxy RelationTuple)) $ throw SerialisationSchemaMismatch+        --print (blockSizes info)+        pure info+  let readTupleBlock (offset', byteLength) = liftIO $ do+--        print ("readTupleBlock", offset', byteLength)+        hSeek h AbsoluteSeek (fromIntegral offset')+        tuples <- deserialiseOnly' <$> BS.hGet h (fromIntegral byteLength)+        case tuples of+          Left err -> throw err+          Right tuples' -> do+--            print ("readTupleBlock", tuples')+            pure $ SP.fromList tuples'+  SD.mkCross $ SP.concatMapM readTupleBlock (SP.fromList (V.toList (blockSizes tupleCacheInfo)))+  +deserialiseOnly' :: forall s. Serialise s => BS.ByteString -> Either WineryException s+deserialiseOnly' bytes = do+  dec <- getDecoder (schema (Proxy :: Proxy s))+  pure (evalDecoder dec bytes)++    +          
src/lib/ProjectM36/Client.hs view
@@ -11,6 +11,7 @@        Hostname,        ServiceName,        DatabaseName,+       TlsConfig(..),        ConnectionError(..),        connectProjectM36,        close,@@ -19,7 +20,8 @@        executeDatabaseContextExpr,        executeDatabaseContextIOExpr,        executeDataFrameExpr,-       executeGraphExpr,+       executeTransactionGraphExpr,+       executeAlterTransactionGraphExpr,        executeSchemaExpr,        executeTransGraphRelationalExpr,        commit,@@ -29,6 +31,7 @@        ProjectM36.Client.typeConstructorMapping,        ProjectM36.Client.databaseContextFunctionsAsRelation,              planForDatabaseContextExpr,+       planForRelationalExpr,        currentSchemaName,        SchemaName,        HeadName,@@ -40,7 +43,7 @@        ddlAsRelation,        ProjectM36.Client.atomFunctionsAsRelation,        disconnectedTransactionIsDirty,-       headName,+       currentHead,        remoteDBLookupName,        defaultServerPort,        headTransactionId,@@ -56,17 +59,19 @@        RelationalExprBase(..),        DatabaseContextExprBase(..),        DatabaseContextExpr,+       ExtendTupleExprBase(..),        DatabaseContextIOExprBase(..),        DatabaseContextIOExpr,        Attribute(..),        MergeStrategy(..),        attributesFromList,-       createSessionAtCommit,+       createSessionAtTransactionId,        createSessionAtHead,        closeSession,        callTestTimeout_,        RelationCardinality(..),-       TransactionGraphOperator(..),+       TransactionGraphExpr(..),+       AlterTransactionGraphExpr(..),        ProjectM36.Client.autoMergeToHead,        transactionGraph_,        disconnectedTransaction_,@@ -84,7 +89,6 @@        inclusionDependencyForKey,        databaseContextExprForUniqueKey,        databaseContextExprForForeignKey,-       createScriptedAtomFunction,        ProjectM36.Client.validateMerkleHashes,        AttributeExprBase(..),        TypeConstructorBase(..),@@ -113,13 +117,30 @@        RemoteServerAddress(..),        resolveRemoteServerAddress,        defaultRemoteServerAddress,-       defaultServerHostname+       defaultServerHostname,+       Discon.CurrentHead(..),+       LoginRoles.adminRoleName,+       SRPC.ClientAuth(..),+       ClientInfo(..),+       InProcessConnectionConf(..),+       executeAlterLoginRolesExpr,+       setRoleName,+       LoginRoles.AlterLoginRolesExpr(..),+       LoginRoles.SuccessResult(..),+       SomePermission(..),+       FunctionPermission(..),+       DBCFunctionPermission(..),       +       AlterDBCACLExprBase(..),+       RelVarPermission(..),+       RoleName        ) where-import ProjectM36.Base hiding (inclusionDependencies) --defined in this module as well-import qualified ProjectM36.Base as B+import ProjectM36.Base import ProjectM36.Serialise.Error () import ProjectM36.Error-import ProjectM36.DatabaseContext+import qualified ProjectM36.DatabaseContext.Types as DBC+import ProjectM36.DatabaseContext.Basic+import qualified ProjectM36.DatabaseContext as DBC+import ProjectM36.DatabaseContext.Types (DatabaseContext, notifications) import ProjectM36.Atomable import ProjectM36.AtomFunction as AF import ProjectM36.StaticOptimizer@@ -127,23 +148,23 @@ import qualified ProjectM36.DataFrame as DF import ProjectM36.DatabaseContextFunction as DCF import qualified ProjectM36.IsomorphicSchema as Schema-#if MIN_VERSION_base(4,16,0)-import Control.Monad (forever, forM, forM_, unless, void)-#endif---import Control.Monad.State+import Control.Monad (forever, forM, forM_, unless, void, when) import qualified ProjectM36.RelationalExpression as RE import qualified ProjectM36.TransactionGraph as Graph import ProjectM36.TransactionGraph as TG-import qualified ProjectM36.Transaction as Trans+import qualified ProjectM36.Transaction.Types as Trans+import ProjectM36.IsomorphicSchema.Types hiding (subschemas, concreteDatabaseContext) import ProjectM36.TransactionGraph.Persist import ProjectM36.Attribute-import ProjectM36.TransGraphRelationalExpression as TGRE (TransGraphRelationalExpr)+import ProjectM36.TransGraphRelationalExpression as TGRE (TransGraphRelationalExpr, process, TransGraphEvalEnv(..)) import ProjectM36.Persist (DiskSync(..)) import ProjectM36.FileLock import ProjectM36.DDLType import ProjectM36.NormalizeExpr import ProjectM36.Notifications import ProjectM36.Server.RemoteCallTypes+import ProjectM36.GraphRefRelationalExpr+import ProjectM36.Streaming.RelationalExpression (planGraphRefRelationalExpr, renderPretty) import qualified ProjectM36.DisconnectedTransaction as Discon import ProjectM36.Relation (typesAsRelation) import ProjectM36.ScriptSession (initScriptSession, ScriptSession)@@ -152,37 +173,47 @@ import Control.Concurrent.STM import Control.Concurrent.Async -import Data.Either (isRight) import Data.UUID.V4 (nextRandom)+import Data.UUID (nil) import Data.Word import Data.Hashable import Control.Concurrent.MVar import Codec.Winery hiding (Schema, schema) import qualified Data.Map as M-#if MIN_VERSION_stm_containers(1,0,0) import qualified StmContainers.Map as StmMap-import qualified StmContainers.Set as StmSet-#else-import qualified STMContainers.Map as StmMap-import qualified STMContainers.Set as StmSet-#endif import qualified ProjectM36.Session as Sess import ProjectM36.Session+import ProjectM36.ValueMarker+import ProjectM36.AccessControl import ProjectM36.Sessions+import ProjectM36.AccessControlList import ProjectM36.HashSecurely (SecureHash) import ProjectM36.RegisteredQuery+import qualified ProjectM36.Cache.RelationalExprCache as RelExprCache+import ProjectM36.Cache.RelationalExprCache (RelExprCache) import GHC.Generics (Generic) import Control.DeepSeq (force) import System.IO+import qualified Data.Text as T import Data.Time.Clock-import qualified Network.RPC.Curryer.Client as RPC-import qualified Network.RPC.Curryer.Server as RPC-import Network.Socket (Socket, AddrInfo(..), getAddrInfo, defaultHints, SocketType(..), ServiceName, SockAddr, Family(..), SockAddr(..))+import qualified Network.RPC.Curryer.Client as CRPC+import qualified Network.RPC.Curryer.Server as SRPC+import Network.Socket (AddrInfo(..), getAddrInfo, defaultHints, SocketType(..), ServiceName, SockAddr, Family(..), SockAddr(..)) import GHC.Conc (unsafeIOToSTM) import ProjectM36.SQL.Select as SQL import ProjectM36.SQL.DBUpdate as SQL import ProjectM36.SQL.Convert+import ProjectM36.TransactionGraph.Types+import ProjectM36.DisconnectedTransaction (DisconnectedTransaction(..))+import ProjectM36.DatabaseContextExpr+import qualified Data.Set as S+import qualified ProjectM36.LoginRoles as LoginRoles import Streamly.Internal.Network.Socket (SockSpec(..))+import System.FilePath ((</>))+import Data.Maybe (catMaybes)+import qualified Network.Socket as Socket+import qualified Data.HashSet as HS+import System.Random (StdGen)  type Hostname = String type Port = Word16@@ -211,8 +242,8 @@ instance Exception RequestTimeoutException  -- | Construct a 'ConnectionInfo' to describe how to make the 'Connection'. The database can be run within the current process or running remotely via RPC.-data ConnectionInfo = InProcessConnectionInfo PersistenceStrategy NotificationCallback [GhcPkgPath] DatabaseContext |-                      RemoteConnectionInfo DatabaseName RemoteServerAddress NotificationCallback+data ConnectionInfo = InProcessConnectionInfo PersistenceStrategy NotificationCallback [GhcPkgPath] DBC.ResolvedDatabaseContext StdGen RoleName |+                      RemoteConnectionInfo DatabaseName RemoteServerAddress CRPC.ClientConnectionConfig NotificationCallback RoleName                        type EvaluatedNotifications = M.Map NotificationName EvaluatedNotification @@ -248,14 +279,14 @@ defaultRemoteServerAddress = RemoteServerHostAddress "127.0.0.1" defaultServerPort  -- | Create a connection configuration which connects to the localhost on the default server port and default server database name. The configured notification callback is set to ignore all events.-defaultRemoteConnectionInfo :: ConnectionInfo+defaultRemoteConnectionInfo :: RoleName -> ConnectionInfo defaultRemoteConnectionInfo =-  RemoteConnectionInfo defaultDatabaseName defaultRemoteServerAddress emptyNotificationCallback+  RemoteConnectionInfo defaultDatabaseName defaultRemoteServerAddress CRPC.defaultClientConnectionConfig emptyNotificationCallback  defaultServerHostname :: Hostname defaultServerHostname = "localhost" -newtype RemoteConnectionConf = RemoteConnectionConf RPC.Connection+newtype RemoteConnectionConf = RemoteConnectionConf CRPC.Connection    data Connection = InProcessConnection InProcessConnectionConf |                   RemoteConnection RemoteConnectionConf@@ -299,47 +330,54 @@       sockAddr = SockAddrUnix sockPath   pure (sockSpec, sockAddr) --- | To create a 'Connection' to a remote or local database, create a 'ConnectionInfo' and call 'connectProjectM36'.+-- | To create a 'Connection' to a remote or local database, create a 'ConnectionInfo' and call 'connectProjectM36'. Requires "login" permission to connect successfully. connectProjectM36 :: ConnectionInfo -> IO (Either ConnectionError Connection) --create a new in-memory database/transaction graph-connectProjectM36 (InProcessConnectionInfo strat notificationCallback ghcPkgPaths bootstrapDatabaseContext) = do+connectProjectM36 (InProcessConnectionInfo strat notificationCallback ghcPkgPaths bootstrapDatabaseContext rando roleName) = do   freshId <- nextRandom   tstamp <- getCurrentTime-  let freshGraph = bootstrapTransactionGraph tstamp freshId bootstrapDatabaseContext+  let freshGraph = bootstrapTransactionGraph tstamp freshId (DBC.toDatabaseContext bootstrapDatabaseContext)   case strat of-    --create date examples graph for now- probably should be empty context in the future     NoPersistence -> do         graphTvar <- newTVarIO freshGraph-        clientNodes <- StmSet.newIO+        clientNodes <- StmMap.newIO         sessions <- StmMap.newIO         mScriptSession <- createScriptSession ghcPkgPaths         notifAsync <- startNotificationListener clientNodes notificationCallback+        maxCacheSize <- RelExprCache.defaultUpperBound+        cache <- RelExprCache.empty maxCacheSize+        loginRoles <- LoginRoles.openNoPersistence+        LoginRoles.setupDatabaseIfNecessary loginRoles         let conn = InProcessConnection InProcessConnectionConf {-                                           ipPersistenceStrategy = strat, -                                           ipClientNodes = clientNodes, -                                           ipSessions = sessions, -                                           ipTransactionGraph = graphTvar, -                                           ipScriptSession = mScriptSession,-                                           ipLocks = Nothing,-                                           ipCallbackAsync = notifAsync-                                           }+                    ipPersistenceStrategy = strat, +                    ipClientNodes = clientNodes, +                    ipSessions = sessions, +                    ipTransactionGraph = graphTvar, +                    ipScriptSession = mScriptSession,+                    ipLocks = Nothing,+                    ipCallbackAsync = notifAsync,+                    ipRelExprCache = cache,+                    ipLoginRoles = loginRoles,+                    ipRoleName = roleName,+                    ipRandomGen = rando+                  }         pure (Right conn)-    MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph notificationCallback ghcPkgPaths-    CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshGraph notificationCallback ghcPkgPaths+    MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph notificationCallback ghcPkgPaths rando roleName+    CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshGraph notificationCallback ghcPkgPaths rando roleName         -connectProjectM36 (RemoteConnectionInfo dbName remoteAddress notificationCallback) = do+connectProjectM36 (RemoteConnectionInfo dbName remoteAddress connConfig notificationCallback roleName) = do   (sockSpec, sockAddr) <- resolveRemoteServerAddress remoteAddress   let notificationHandlers =-        [RPC.ClientAsyncRequestHandler $+        [CRPC.ClientAsyncRequestHandler $           \(NotificationMessage notifications') ->             forM_ (M.toList notifications') (uncurry notificationCallback)         ]       connectExcHandler (e :: IOException) = pure $ Left (IOExceptionError e)-  eConn <- (Right <$> RPC.connect notificationHandlers sockSpec sockAddr) `catch` connectExcHandler+  eConn <- (Right <$> CRPC.connect notificationHandlers connConfig sockSpec sockAddr) `catch` connectExcHandler   case eConn of     Left err -> pure (Left err)     Right conn -> do-      eRet <- RPC.call conn (Login dbName)+      eRet <- CRPC.call conn (Login dbName roleName)       case eRet of         Left err -> error (show err)         Right False -> error "wtf"@@ -348,27 +386,43 @@           pure (Right (RemoteConnection (RemoteConnectionConf conn)))  --convert RPC errors into exceptions-convertRPCErrors :: RPC.ConnectionError -> IO a+convertRPCErrors :: SRPC.ConnectionError -> IO a convertRPCErrors err =   case err of-    RPC.TimeoutError -> throw RequestTimeoutException-    RPC.CodecError msg -> error $ "decoding message failed on server: " <> msg-    RPC.ExceptionError msg -> error $ "server threw exception: " <> msg+    SRPC.TimeoutError -> throw RequestTimeoutException+    SRPC.CodecError msg -> error $ "decoding message failed on server: " <> msg+    SRPC.ExceptionError msg -> error $ "server threw exception: " <> msg -addClientNode :: Connection -> RPC.Locking Socket -> IO ()-addClientNode (RemoteConnection _) _ = error "addClientNode called on remote connection"-addClientNode (InProcessConnection conf) lockSock = atomically (StmSet.insert clientInfo (ipClientNodes conf))-  where-    clientInfo = RemoteClientInfo lockSock+addClientNode :: Connection -> SRPC.ClientConnectionId -> SRPC.SocketContext -> RoleName -> IO ()+addClientNode (RemoteConnection _) _ _ _ = error "addClientNode called on remote connection"+addClientNode (InProcessConnection conf) clientId sockCtx roleName = do+  let lrdb = ipLoginRoles conf+      dropConn = SRPC.withLock (SRPC.lockingSocket sockCtx) $ \sock -> Socket.close sock+  LoginRoles.withTransaction lrdb $ do+    --check the role has login privilege+    eMayLogin <- LoginRoles.roleNameMayLogin roleName lrdb+    case eMayLogin of+      Left{} -> dropConn+      Right False -> dropConn+      Right True -> do+        -- collect roleids for rolename to pass to ACL validation functions+        eRoleIds <- LoginRoles.roleIdsForRoleName roleName lrdb+        case eRoleIds of+          Left{} -> dropConn+          Right _roleIds -> do+            let clientInfo = RemoteClientInfo sockCtx roleName+            atomically (StmMap.insert clientInfo clientId (ipClientNodes conf))  connectPersistentProjectM36 :: PersistenceStrategy ->                                DiskSync ->                                FilePath ->                                 TransactionGraph ->                                NotificationCallback ->-                               [GhcPkgPath] -> +                               [GhcPkgPath] ->+                               StdGen ->+                               RoleName ->                                IO (Either ConnectionError Connection)      -connectPersistentProjectM36 strat sync dbdir freshGraph notificationCallback ghcPkgPaths = do+connectPersistentProjectM36 strat sync dbdir freshGraph notificationCallback ghcPkgPaths rando roleName = do   err <- setupDatabaseDir sync dbdir freshGraph    case err of     Left err' -> return $ Left (SetupDatabaseDirectoryError err')@@ -383,52 +437,62 @@             Right _ -> do               tvarGraph <- newTVarIO graph'               sessions <- StmMap.newIO-              clientNodes <- StmSet.newIO+              clientNodes <- StmMap.newIO               lockMVar <- newMVar digest               notifAsync <- startNotificationListener clientNodes notificationCallback+              maxCacheSize <- RelExprCache.defaultUpperBound+              cache <- RelExprCache.empty maxCacheSize+              loginRoles <- LoginRoles.open (dbdir </> "loginroles.sqlite3")               let conn = InProcessConnection InProcessConnectionConf {-                                             ipPersistenceStrategy = strat,-                                             ipClientNodes = clientNodes,-                                             ipSessions = sessions,-                                             ipTransactionGraph = tvarGraph,-                                             ipScriptSession = mScriptSession,-                                             ipLocks = Just (lockFileH, lockMVar),-                                             ipCallbackAsync = notifAsync-                                             }+                          ipPersistenceStrategy = strat,+                          ipClientNodes = clientNodes,+                          ipSessions = sessions,+                          ipTransactionGraph = tvarGraph,+                          ipScriptSession = mScriptSession,+                          ipLocks = Just (lockFileH, lockMVar),+                          ipCallbackAsync = notifAsync,+                          ipRelExprCache = cache,+                          ipLoginRoles = loginRoles,+                          ipRoleName = roleName,+                          ipRandomGen = rando+                        }               pure (Right conn)  --startup local async process to handle notification callbacks startNotificationListener :: ClientNodes -> NotificationCallback -> IO (Async ()) startNotificationListener cNodes notificationCallback = do   inProcessClientInfo@(InProcessClientInfo notifMVar) <- InProcessClientInfo <$> newEmptyMVar          -  atomically $ StmSet.insert inProcessClientInfo cNodes +  atomically $ StmMap.insert inProcessClientInfo nil cNodes    async $ forever $ do     notifs <- takeMVar notifMVar     forM_ (M.toList notifs) $ uncurry notificationCallback --- | Create a new session at the transaction id and return the session's Id.-createSessionAtCommit :: Connection -> TransactionId -> IO (Either RelationalError SessionId)-createSessionAtCommit conn@(InProcessConnection _) commitId = do+-- | Create a new session at the transaction id and return the session's Id. +createSessionAtTransactionId :: Connection -> TransactionId -> IO (Either RelationalError SessionId)+createSessionAtTransactionId conn@(InProcessConnection _) commitId = do    newSessionId <- nextRandom-   atomically $ createSessionAtCommit_ commitId newSessionId conn-createSessionAtCommit conn@(RemoteConnection _) uuid = remoteCall conn (CreateSessionAtCommit uuid)+   atomically $ createSessionAtTransactionId_ Nothing commitId newSessionId conn+createSessionAtTransactionId conn@(RemoteConnection _) uuid = remoteCall conn (CreateSessionAtTransactionId uuid) -createSessionAtCommit_ :: TransactionId -> SessionId -> Connection -> STM (Either RelationalError SessionId)-createSessionAtCommit_ commitId newSessionId (InProcessConnection conf) = do+createSessionAtTransactionId_ :: Maybe HeadName -> TransactionId -> SessionId -> Connection -> STM (Either RelationalError SessionId)+createSessionAtTransactionId_ mHeadName commitId newSessionId (InProcessConnection conf) = do     let sessions = ipSessions conf         graphTvar = ipTransactionGraph conf     graph <- readTVar graphTvar     case RE.transactionForId commitId graph of         Left err -> pure (Left err)         Right transaction -> do-            let freshDiscon = DisconnectedTransaction commitId (Discon.loadGraphRefRelVarsOnly commitId (Trans.schemas transaction)) False+            let freshDiscon = Discon.freshTransaction' currentHead' commitId (Trans.schemas transaction)+                currentHead' = case mHeadName of+                                Just hname -> Discon.CurrentHeadBranch hname+                                Nothing -> Discon.CurrentHeadTransactionId commitId             keyDuplication <- StmMap.lookup newSessionId sessions             case keyDuplication of                 Just _ -> pure $ Left (SessionIdInUseError newSessionId)                 Nothing -> do                    StmMap.insert (Session freshDiscon defaultSchemaName) newSessionId sessions                    pure $ Right newSessionId-createSessionAtCommit_ _ _ (RemoteConnection _) = error "createSessionAtCommit_ called on remote connection"+createSessionAtTransactionId_ _ _ _ (RemoteConnection _) = error "createSessionAtTransactionId_ called on remote connection"    -- | Call 'createSessionAtHead' with a transaction graph's head's name to create a new session pinned to that head. This function returns a 'SessionId' which can be used in other function calls to reference the point in the transaction graph. createSessionAtHead :: Connection -> HeadName -> IO (Either RelationalError SessionId)@@ -439,10 +503,11 @@         graph <- readTVar graphTvar         case transactionForHead headn graph of             Nothing -> pure $ Left (NoSuchHeadNameError headn)-            Just trans -> createSessionAtCommit_ (transactionId trans) newSessionId conn+            Just trans -> createSessionAtTransactionId_ (Just headn) (Trans.transactionId trans) newSessionId conn createSessionAtHead conn@(RemoteConnection _) headn = remoteCall conn (CreateSessionAtHead headn)  -- | Discards a session, eliminating any uncommitted changes present in the session.+-- TODO: should sessions only be closed by the role which opened it? Yes. Otherwise we risk DoS attacks. closeSession :: SessionId -> Connection -> IO () closeSession sessionId (InProcessConnection conf) =      atomically $ StmMap.delete sessionId (ipSessions conf)@@ -466,12 +531,12 @@     Just (lockFileH, _) -> closeLockFile lockFileH  close (RemoteConnection (RemoteConnectionConf conn)) =-  RPC.close conn+  CRPC.close conn  --used only by the server EntryPoints closeRemote_ :: Connection -> IO () closeRemote_ (InProcessConnection _) = error "invalid call of closeRemote_ on InProcessConnection"-closeRemote_ (RemoteConnection (RemoteConnectionConf conn)) = RPC.close conn+closeRemote_ (RemoteConnection (RemoteConnectionConf conn)) = CRPC.close conn    --we need to actually close the localNode's connection to the remote --within the database server, we must catch and handle all exception lest they take down the database process- this handling might be different for other use-cases@@ -487,7 +552,7 @@ remoteCall :: (Serialise a, Serialise b) => Connection -> a -> IO b remoteCall (InProcessConnection _ ) _ = error "remoteCall called on local connection" remoteCall (RemoteConnection (RemoteConnectionConf rpcConn)) arg = do-  eRet <- RPC.call rpcConn arg+  eRet <- CRPC.call rpcConn arg   case eRet of     Left err -> convertRPCErrors err     Right val -> pure val@@ -496,23 +561,28 @@ sessionForSessionId sessionId sessions =    maybe (Left $ NoSuchSessionError sessionId) Right <$> StmMap.lookup sessionId sessions   -schemaForSessionId :: Session -> STM (Either RelationalError Schema)  -schemaForSessionId session = do+schemaForSessionId :: Session -> InProcessConnectionConf -> STM (Either RelationalError Schema)+schemaForSessionId session conf = do   let sname = schemaName session   if sname == defaultSchemaName then     pure (Right (Schema [])) -- the main schema includes no transformations (but neither do empty schemas :/ )-    else-    case M.lookup sname (subschemas session) of-      Nothing -> pure (Left (SubschemaNameNotInUseError sname))-      Just schema -> pure (Right schema)+    else do+    graph <- readTVar (ipTransactionGraph conf)      +    case resolveSubschemas session graph of+      Left err -> pure (Left err)+      Right sschemas ->+        case M.lookup sname sschemas of+          Nothing -> pure (Left (SubschemaNameNotInUseError sname))+          Just schema -> pure (Right schema)   -sessionAndSchema :: SessionId -> Sessions -> STM (Either RelationalError (Session, Schema))-sessionAndSchema sessionId sessions = do+sessionAndSchema :: SessionId -> InProcessConnectionConf -> STM (Either RelationalError (Session, Schema))+sessionAndSchema sessionId conf = do+  let sessions = ipSessions conf     eSession <- sessionForSessionId sessionId sessions   case eSession of     Left err -> pure (Left err)-    Right session -> do  -      eSchema <- schemaForSessionId session+    Right session -> do+      eSchema <- schemaForSessionId session conf       case eSchema of         Left err -> pure (Left err)         Right schema -> pure (Right (session, schema))@@ -534,70 +604,116 @@   eSession <- sessionForSessionId sessionId sessions   case eSession of     Left err -> pure (Left err)-    Right session -> case Sess.setSchemaName sname session of-      Left err -> pure (Left err)-      Right newSession -> StmMap.insert newSession sessionId sessions >> pure (Right ())+    Right session -> do+      graph <- readTVar (ipTransactionGraph conf)  +      case Sess.setSchemaName sname session graph of+        Left err -> pure (Left err)+        Right newSession -> StmMap.insert newSession sessionId sessions >> pure (Right ()) setCurrentSchemaName sessionId conn@(RemoteConnection _) sname = remoteCall conn (ExecuteSetCurrentSchema sessionId sname) --- | Execute a relational expression in the context of the session and connection. Relational expressions are queries and therefore cannot alter the database.+-- | Execute a relational expression in the context of the session and connection. Relational expressions are queries and therefore cannot alter the database. Requires `AccessRelVars` permission if the relational expression includes a reference to a relation variable. executeRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Relation)-executeRelationalExpr sessionId (InProcessConnection conf) expr = excEither $ atomically $ do-  let sessions = ipSessions conf-  eSession <- sessionAndSchema sessionId sessions-  case eSession of+executeRelationalExpr sessionId (InProcessConnection conf) expr = do+  res <- excEither $ atomically $ do+   eSession <- sessionAndSchema sessionId conf+   case eSession of     Left err -> pure $ Left err     Right (session, schema) -> do       let expr' = if schemaName session /= defaultSchemaName then                     Schema.processRelationalExprInSchema schema expr                   else-                    Right expr+                    Right expr                           case expr' of         Left err -> pure (Left err)         Right expr'' -> do           let graphTvar = ipTransactionGraph conf           graph <- readTVar graphTvar-          let reEnv = RE.mkRelationalExprEnv (Sess.concreteDatabaseContext session) graph-          case optimizeAndEvalRelationalExpr reEnv expr'' of-            Right rel -> pure (force (Right rel)) -- this is necessary so that any undefined/error exceptions are spit out here +          let reEnv = RE.mkRelationalExprEnv dbctx graph+              dbctx = Sess.concreteDatabaseContext session+          pure (Right (graph, reEnv, expr''))+  case res of+    Left err -> pure (Left err)+    Right (graph, reEnv, rexpr) -> do+      case RE.resolveDBC' graph (RE.re_context reEnv) DBC.acl of+        Left err -> pure (Left err)+        Right acl' -> do+          roleIds <- roleIdsForRoleName conf+          case applyACLRelationalExpr roleIds (relvarsACL acl') rexpr of             Left err -> pure (Left err)+            Right () -> do+              qres <- optimizeAndEvalRelationalExpr' reEnv rexpr (ipRelExprCache conf)+              case qres of+                Right rel -> pure (force (Right rel)) -- this is necessary so that any undefined/error exceptions are spit out here +                Left err -> pure (Left err)  executeRelationalExpr sessionId conn@(RemoteConnection _) relExpr = remoteCall conn (ExecuteRelationalExpr sessionId relExpr) --- | Execute a database context expression in the context of the session and connection. Database expressions modify the current session's disconnected transaction but cannot modify the transaction graph.+-- | Execute a database context expression in the context of the session and connection. Database expressions modify the current session's disconnected transaction but cannot modify the transaction graph. Requires AccessRelVarsPermission. Requires ExecuteFunctionPermission if a function is executed. executeDatabaseContextExpr :: SessionId -> Connection -> DatabaseContextExpr -> IO (Either RelationalError ())-executeDatabaseContextExpr sessionId (InProcessConnection conf) expr = excEither $ atomically $ do+executeDatabaseContextExpr sessionId (InProcessConnection conf) expr = do+ roleIds <- roleIdsForRoleName conf+ roles <- LoginRoles.allRoles (ipLoginRoles conf)+ excEither $ atomically $ do   let sessions = ipSessions conf-  eSession <- sessionAndSchema sessionId sessions+  eSession <- sessionAndSchema sessionId conf   case eSession of     Left err -> pure (Left err)     Right (session, schema) -> do-      let expr' = if schemaName session == defaultSchemaName then-                    Right expr-                  else-                    Schema.processDatabaseContextExprInSchema schema expr+      let expr' = if schemaName session == defaultSchemaName then do+                    resolveRoleIds roleNameResolver expr+                  else +                    resolveRoleIds roleNameResolver expr >>= Schema.processDatabaseContextExprInSchema schema+          roleNameResolver nam = fst <$> lookup nam roles       case expr' of          Left err -> pure (Left err)         Right expr'' -> do           graph <- readTVar (ipTransactionGraph conf)           let ctx = Sess.concreteDatabaseContext session-              env = RE.mkDatabaseContextEvalEnv transId graph+              dbcfuncutils = DBC.DatabaseContextFunctionUtils {+                DBC.executeDatabaseContextExpr = \ctx' dbexpr' -> +                    case resolveRoleIds roleNameResolver dbexpr' of+                      Left err -> Left err+                      Right dbexpr'' ->+                        case RE.runDatabaseContextEvalMonad ctx' env (optimizeAndEvalDatabaseContextExpr True dbexpr'') of+                          Left err -> Left err+                          Right reState -> pure (RE.dbc_context reState)+                      ,+                DBC.executeRelationalExpr = \ctx' relExpr ->+                          let reEnv = RE.mkRelationalExprEnv ctx' graph in +                          optimizeAndEvalRelationalExpr reEnv relExpr+                      }+              env = RE.mkDatabaseContextEvalEnv transId graph dbcfuncutils               transId = Sess.parentId session-          case RE.runDatabaseContextEvalMonad ctx env (optimizeAndEvalDatabaseContextExpr True expr'') of+              runExpr = do+                applyACLDatabaseContextExpr roleIds expr''+                optimizeAndEvalDatabaseContextExpr True expr''+          case RE.runDatabaseContextEvalMonad ctx env runExpr of             Left err -> pure (Left err)             Right newState ->-              if not (RE.dbc_dirty newState) then --nothing dirtied, nothing to do+              if not (DBC.isUpdated (RE.dbc_context newState)) then do --nothing dirtied, nothing to do                 pure (Right ())               else do-                let newDiscon = DisconnectedTransaction (Sess.parentId session) newSchemas True-                    context' = RE.dbc_context newState-                    newSubschemas = Schema.processDatabaseContextExprSchemasUpdate (Sess.subschemas session) expr-                    newSchemas = Schemas context' newSubschemas-                    newSession = Session newDiscon (Sess.schemaName session)-                StmMap.insert newSession sessionId sessions-                pure (Right ())+                case resolveSubschemas session graph of+                  Left err -> pure (Left err)+                  Right sschemas -> do+                    let newDiscon = DisconnectedTransaction {+                          disconTransactionId = Sess.parentId session,+                          disconSchemas = newSchemas,+                          disconCurrentHead = Discon.disconCurrentHead (Sess.disconnectedTransaction session)+                          }+                        ctcontext = RE.dbc_context newState+                        newSubschemas = Schema.processDatabaseContextExprSchemasUpdate sschemas expr+                        newSubschemas' = if newSubschemas == sschemas then+                                           NotChangedSinceMarker transId+                                         else+                                           ValueMarker newSubschemas+                        newSchemas = Schemas ctcontext newSubschemas' -- the schemas in the disconnectedtransaction need to pass the ctdbc around until commit time+                        newSession = Session newDiscon (Sess.schemaName session)+                    StmMap.insert newSession sessionId sessions+                    pure (Right ()) executeDatabaseContextExpr sessionId conn@(RemoteConnection _) dbExpr = remoteCall conn (ExecuteDatabaseContextExpr sessionId dbExpr) --- | Similar to a git rebase, 'autoMergeToHead' atomically creates a temporary branch and merges it to the latest commit of the branch referred to by the 'HeadName' and commits the merge. This is useful to reduce incidents of 'TransactionIsNotAHeadError's but at the risk of merge errors (thus making it similar to rebasing). Alternatively, as an optimization, if a simple commit is possible (meaning that the head has not changed), then a fast-forward commit takes place instead.+-- | Similar to a git rebase, 'autoMergeToHead' atomically creates a temporary branch and merges it to the latest commit of the branch referred to by the 'HeadName' and commits the merge. This is useful to reduce incidents of 'TransactionIsNotAHeadError's but at the risk of merge errors (thus making it similar to rebasing). Alternatively, as an optimization, if a simple commit is possible (meaning that the head has not changed), then a fast-forward commit takes place instead. Requires CommitTransactionPermission. autoMergeToHead :: SessionId -> Connection -> MergeStrategy -> HeadName -> IO (Either RelationalError ()) autoMergeToHead sessionId (InProcessConnection conf) strat headName' = do   let sessions = ipSessions conf@@ -605,6 +721,7 @@   id2 <- nextRandom   id3 <- nextRandom   tstamp <- getCurrentTime+  roleIds <- roleIdsForRoleName conf   commitLock_ sessionId conf $ \graph -> do     eSession <- sessionForSessionId sessionId sessions       case eSession of@@ -614,22 +731,37 @@           Nothing -> pure (Left (NoSuchHeadNameError headName'))           Just headTrans -> do             --attempt fast-forward commit, if possible-            let graphInfo = if Sess.parentId session == transactionId headTrans then do-                              ret <- Graph.evalGraphOp tstamp id1 (Sess.disconnectedTransaction session) graph Commit-                              pure (ret, [id1])-                            else do-                              ret <- Graph.autoMergeToHead tstamp (id1, id2, id3) (Sess.disconnectedTransaction session) headName' strat graph -                              pure (ret, [id1,id2,id3])-            case graphInfo of+            let disconIn = Sess.disconnectedTransaction session+                dbctx = Sess.concreteDatabaseContext session+            --check permission+            case RE.resolveDBC' graph dbctx DBC.acl of               Left err -> pure (Left err)-              Right ((discon', graph'), transactionIdsAdded) ->-                pure (Right (discon', graph', transactionIdsAdded))+              Right acl' -> do+                let alterTGACL = transGraphACL acl'+                case applyACLAlterTransGraphExpr roleIds alterTGACL Commit of+                  Left err -> pure (Left err)+                  Right () -> do+                    if Sess.parentId session == Trans.transactionId headTrans then do+                      let ret = Graph.evalAlterTransactionGraphExpr tstamp id1 disconIn graph Commit+                      case ret of+                        Left err -> pure (Left err)+                        Right (discon', mtrans', tGraph) ->+                          pure (Right (discon', TransactionGraphIncrementalWriteInfo {+                                          uncommittedTransactions = S.fromList (catMaybes [mtrans']),+                                          newGraph = tGraph+                                          }))+                      else do+                      pure $ Graph.autoMergeToHead tstamp (id1, id2, id3) disconIn headName' strat graph autoMergeToHead sessionId conn@(RemoteConnection _) strat headName' = remoteCall conn (ExecuteAutoMergeToHead sessionId strat headName')       --- | Execute a database context IO-monad-based expression for the given session and connection. `DatabaseContextIOExpr`s modify the DatabaseContext but cannot be purely implemented.+-- | Execute a database context IO-monad-based expression for the given session and connection. `DatabaseContextIOExpr`s modify the DatabaseContext but cannot be purely implemented. Requires ExecuteFunctionPermission. --this is almost completely identical to executeDatabaseContextExpr above executeDatabaseContextIOExpr :: SessionId -> Connection -> DatabaseContextIOExpr -> IO (Either RelationalError ())-executeDatabaseContextIOExpr sessionId (InProcessConnection conf) expr = excEither $ do+executeDatabaseContextIOExpr sessionId (InProcessConnection conf) expr = do+ roleIds <- roleIdsForRoleName conf+ roles <- LoginRoles.allRoles (ipLoginRoles conf) + myRoleId <- primaryRoleIdForRoleName conf+ excEither $ do   let sessions = ipSessions conf       scriptSession = ipScriptSession conf   eSession <- atomically $ sessionForSessionId sessionId sessions --potentially race condition due to interleaved IO?@@ -637,22 +769,45 @@     Left err -> pure (Left err)     Right session -> do       graph <- readTVarIO (ipTransactionGraph conf)-      let env = RE.DatabaseContextIOEvalEnv transId graph scriptSession objFilesPath+      let env = RE.DatabaseContextIOEvalEnv transId graph scriptSession myRoleId objFilesPath dbcFuncUtils+          dbcEnv = RE.mkDatabaseContextEvalEnv transId graph dbcFuncUtils+          roleNameResolver nam = fst <$> lookup nam roles      +          dbcFuncUtils = DBC.DatabaseContextFunctionUtils {+            DBC.executeDatabaseContextExpr = \ctx' expr' ->+                    case resolveRoleIds roleNameResolver expr' of+                      Left err -> Left err+                      Right expr'' ->+                        case RE.runDatabaseContextEvalMonad ctx' dbcEnv (optimizeAndEvalDatabaseContextExpr True expr'') of+                          Left err -> Left err+                          Right reState -> pure (RE.dbc_context reState)+                      ,+            DBC.executeRelationalExpr = \ctx' relExpr ->+                          let reEnv = RE.mkRelationalExprEnv ctx' graph in +                          optimizeAndEvalRelationalExpr reEnv relExpr+            }           objFilesPath = objectFilesPath <$> persistenceDirectory (ipPersistenceStrategy conf)           transId = Sess.parentId session           context = Sess.concreteDatabaseContext session-      res <- RE.runDatabaseContextIOEvalMonad env context (optimizeAndEvalDatabaseContextIOExpr expr)+          runExpr = do+            --check perms+            applyACLDatabaseContextIOExpr roleIds expr+            optimizeAndEvalDatabaseContextIOExpr expr+      res <- RE.runDatabaseContextIOEvalMonad env context runExpr       case res of         Left err -> pure (Left err)         Right newState -> do-          let newDiscon = DisconnectedTransaction (Sess.parentId session) newSchemas False+          let newDiscon = DisconnectedTransaction {+                disconTransactionId = Sess.parentId session,+                disconSchemas = newSchemas,+                disconCurrentHead = Discon.disconCurrentHead (Sess.disconnectedTransaction session)+                }               newSchemas = Schemas context' (Sess.subschemas session)               newSession = Session newDiscon (Sess.schemaName session)               context' = RE.dbc_context newState           atomically $ StmMap.insert newSession sessionId sessions           pure (Right ()) executeDatabaseContextIOExpr sessionId conn@(RemoteConnection _) dbExpr = remoteCall conn (ExecuteDatabaseContextIOExpr sessionId dbExpr)-         +                                                                           -- process notifications for commits executeCommitExprSTM_   :: TransactionGraph@@ -661,8 +816,10 @@   -> ClientNodes   -> STM (EvaluatedNotifications, ClientNodes) executeCommitExprSTM_ graph oldContext newContext nodes = do-  let nots = notifications oldContext-      fireNots = notificationChanges nots graph oldContext newContext +  nots <- case RE.resolveDBC' graph oldContext notifications of+            Left err -> throwSTM err+            Right nots' -> pure nots'+  let fireNots = notificationChanges nots graph oldContext newContext       evaldNots = M.map mkEvaldNot fireNots       evalInContext expr ctx = optimizeAndEvalRelationalExpr (RE.mkRelationalExprEnv ctx graph) expr  @@ -671,99 +828,211 @@                                                  reportNewRelation = evalInContext (reportNewExpr notif) newContext}   pure (evaldNots, nodes)   --- | Execute a transaction graph expression in the context of the session and connection. Transaction graph operators modify the transaction graph state.+-- | Execute a transaction graph expression in the context of the session and connection. Transaction graph operators cannot modify the transaction graph state. Does not require any permission.+executeTransactionGraphExpr :: SessionId -> Connection -> TransactionGraphExpr -> IO (Either RelationalError ())+executeTransactionGraphExpr sessionId (InProcessConnection conf) graphExpr = excEither $ do+  let sessions = ipSessions conf+  commitLock_ sessionId conf $ \updatedGraph -> do+    eSession <- sessionForSessionId sessionId sessions+    case eSession of+      Left err -> pure (Left err)+      Right session -> do+        let discon = Sess.disconnectedTransaction session+        case evalTransactionGraphExpr discon updatedGraph graphExpr of+          Left err -> pure (Left err)+          Right discon' -> do+            pure (Right (discon', TransactionGraphIncrementalWriteInfo {+                            uncommittedTransactions = mempty,+                            newGraph = updatedGraph+                            })) --- OPTIMIZATION OPPORTUNITY: no locks are required to write new transaction data, only to update the transaction graph id file--- if writing data is re-entrant, we may be able to use unsafeIOtoSTM--- perhaps keep hash of data file instead of checking if our head was updated on every write-executeGraphExpr :: SessionId -> Connection -> TransactionGraphOperator -> IO (Either RelationalError ())-executeGraphExpr sessionId (InProcessConnection conf) graphExpr = excEither $ do+executeTransactionGraphExpr sessionId conn@(RemoteConnection _) graphExpr = remoteCall conn (ExecuteGraphExpr sessionId graphExpr)++-- | Execute an operator which alters the transaction graph such as commit, branching, or merge. Requires CommitTransactionPermission.+executeAlterTransactionGraphExpr :: SessionId -> Connection -> AlterTransactionGraphExpr -> IO (Either RelationalError ())+executeAlterTransactionGraphExpr sessionId (InProcessConnection conf) alterGraphExpr = do   let sessions = ipSessions conf   freshId <- nextRandom   tstamp <- getCurrentTime+  roleIds <- roleIdsForRoleName conf   commitLock_ sessionId conf $ \updatedGraph -> do     eSession <- sessionForSessionId sessionId sessions     case eSession of       Left err -> pure (Left err)       Right session -> do         let discon = Sess.disconnectedTransaction session-        case evalGraphOp tstamp freshId discon updatedGraph graphExpr of+            dbctx = Sess.concreteDatabaseContext session        +        case RE.resolveDBC' updatedGraph dbctx DBC.acl of           Left err -> pure (Left err)-          Right (discon', graph') -> do-            --if freshId appears in the graph, then we need to pass it on-            let transIds = [freshId | isRight (RE.transactionForId freshId graph')]-            pure (Right (discon', graph', transIds))+          Right acl' -> do+            let alterTGACL = transGraphACL acl'+            case applyACLAlterTransGraphExpr roleIds alterTGACL alterGraphExpr of+              Left err -> pure (Left err)+              Right () -> do -executeGraphExpr sessionId conn@(RemoteConnection _) graphExpr = remoteCall conn (ExecuteGraphExpr sessionId graphExpr)+                case evalAlterTransactionGraphExpr tstamp freshId discon updatedGraph alterGraphExpr of+                  Left err -> pure (Left err)+                  Right (discon', mtrans', graph') -> do+                    pure (Right (discon', TransactionGraphIncrementalWriteInfo {+                                    uncommittedTransactions = S.fromList (catMaybes [mtrans']),+                                    newGraph = graph'+                                    }))+executeAlterTransactionGraphExpr sessionId conn@RemoteConnection{} alterGraphExpr =+  remoteCall conn (ExecuteAlterTransactionGraphExpr sessionId alterGraphExpr) --- | A trans-graph expression is a relational query executed against the entirety of a transaction graph.+-- | A trans-graph expression is a relational query executed against the entirety of a transaction graph. Requires AccessRelVarsPermission if the expression mentions a relation variable. executeTransGraphRelationalExpr :: SessionId -> Connection -> TransGraphRelationalExpr -> IO (Either RelationalError Relation)-executeTransGraphRelationalExpr _ (InProcessConnection conf) tgraphExpr = excEither . atomically $ do-  let graphTvar = ipTransactionGraph conf-  graph <- readTVar graphTvar-  pure $ force $ optimizeAndEvalTransGraphRelationalExpr graph tgraphExpr+executeTransGraphRelationalExpr sessionId (InProcessConnection conf) tgraphExpr = do+  roleIds <- roleIdsForRoleName conf+  eGraph <- atomically $ do+    eSession <- sessionAndSchema sessionId conf    +    case eSession of+      Left err -> pure $ Left err+      Right (session, _schema) -> do+        let dbctx = Sess.concreteDatabaseContext session+            graphTvar = ipTransactionGraph conf+        graph <- readTVar graphTvar+        case RE.resolveDBC' graph dbctx DBC.acl of+          Left err -> pure (Left err)+          Right acl' -> do+            let rvACL = relvarsACL acl'+            case TGRE.process (TransGraphEvalEnv graph) tgraphExpr of+              Left err -> pure (Left err)+              Right gfExpr -> do+                -- security check- get rvs mentioned at any transaction ids+                let rvMentionedMarkers = decomposeGraphRefTransactionMarkers gfExpr+                    -- collect acls from those transactions+                    lookupACL UncommittedContextMarker = do+                      applyACLRelationalExpr roleIds rvACL (RelationVariable "true" ())+                    lookupACL (TransactionMarker tid) = do+                      trans <- TG.lookupTransaction graph (TransactionIdLookup tid)+                      let dbctx' = Trans.concreteDatabaseContext trans+                      rvacl <- relvarsACL <$> RE.resolveDBC' graph dbctx' DBC.acl+                      applyACLRelationalExpr roleIds rvacl (RelationVariable "true" ())+                case forM_ rvMentionedMarkers (\(marker, _rvname) -> lookupACL marker) of+                  Left err -> pure (Left err)+                  Right () -> pure (Right graph)+  case eGraph of+    Left err -> pure (Left err)+    Right graph -> +      optimizeAndEvalTransGraphRelationalExprWithCache (ipRandomGen conf) graph tgraphExpr (ipRelExprCache conf)+   executeTransGraphRelationalExpr sessionId conn@(RemoteConnection _) tgraphExpr = remoteCall conn (ExecuteTransGraphRelationalExpr sessionId tgraphExpr)   --- | Schema expressions manipulate the isomorphic schemas for the current 'DatabaseContext'.+-- | Schema expressions manipulate the isomorphic schemas for the current 'DatabaseContext'. Requires AlterSchemaPermission. executeSchemaExpr :: SessionId -> Connection -> Schema.SchemaExpr -> IO (Either RelationalError ())-executeSchemaExpr sessionId (InProcessConnection conf) schemaExpr = atomically $ do+executeSchemaExpr sessionId (InProcessConnection conf) schemaExpr = do+ roleIds <- roleIdsForRoleName conf+ roles <- LoginRoles.allRoles (ipLoginRoles conf)  + atomically $ do   let sessions = ipSessions conf-  eSession <- sessionAndSchema sessionId sessions  +  eSession <- sessionAndSchema sessionId conf+  graph <- readTVar (ipTransactionGraph conf)   case eSession of     Left err -> pure (Left err)     Right (session, _) -> do-      let subschemas' = subschemas session-          transId = Sess.parentId session-          context = Sess.concreteDatabaseContext session-      graph <- readTVar (ipTransactionGraph conf)-      case Schema.evalSchemaExpr schemaExpr context transId graph subschemas' of+      case resolveSubschemas session graph of         Left err -> pure (Left err)-        Right (newSubschemas, newContext) -> do-          --hm- maybe we should start using lenses-          let discon = Sess.disconnectedTransaction session -              newSchemas = Schemas newContext newSubschemas-              newSession = Session (DisconnectedTransaction (Discon.parentId discon) newSchemas False) (Sess.schemaName session)-          StmMap.insert newSession sessionId sessions-          pure (Right ())+        Right subschemas' -> do+          let transId = Sess.parentId session+              context = Sess.concreteDatabaseContext session+              roleNameResolver nam = fst <$> lookup nam roles+          case RE.resolveDBC' graph context DBC.acl of+            Left err -> pure (Left err)+            Right acl' ->+              case applyACLSchemaExpr roleIds (schemaACL acl') schemaExpr of+                Left err -> pure (Left err)+                Right () -> do+                  let dbcEnv = RE.mkDatabaseContextEvalEnv transId graph dbcFunctionUtils+                      dbcFunctionUtils = DBC.DatabaseContextFunctionUtils {+                        DBC.executeDatabaseContextExpr =+                            \ctx' dbexpr ->+                              case resolveRoleIds roleNameResolver dbexpr of+                                Left err -> Left err+                                Right expr'' ->+                                  case RE.runDatabaseContextEvalMonad ctx' dbcEnv (optimizeAndEvalDatabaseContextExpr True expr'') of+                                    Left err -> Left err+                                    Right reState -> pure (RE.dbc_context reState)+                              ,+                        DBC.executeRelationalExpr =+                        \ctx' relExpr ->+                          let reEnv = RE.mkRelationalExprEnv ctx' graph in +                          optimizeAndEvalRelationalExpr reEnv relExpr+                        }+                  case Schema.evalSchemaExpr schemaExpr context transId graph dbcFunctionUtils subschemas' of+                    Left err -> pure (Left err)+                    Right (newSubschemas, newContext) -> do+                      --hm- maybe we should start using lenses+                      let discon = Sess.disconnectedTransaction session +                          newSchemas = Schemas newContext (ValueMarker newSubschemas)+                          newSession = Session (DisconnectedTransaction {+                                                   disconTransactionId = Discon.parentId discon,+                                                   disconSchemas = newSchemas,+                                                   disconCurrentHead = Discon.disconCurrentHead (Sess.disconnectedTransaction session) }) (Sess.schemaName session)+                      StmMap.insert newSession sessionId sessions+                      pure (Right ()) executeSchemaExpr sessionId conn@(RemoteConnection _) schemaExpr = remoteCall conn (ExecuteSchemaExpr sessionId schemaExpr)            -- | After modifying a 'DatabaseContext', 'commit' the transaction to the transaction graph at the head which the session is referencing. This will also trigger checks for any notifications which need to be propagated. commit :: SessionId -> Connection -> IO (Either RelationalError ())-commit sessionId conn@(InProcessConnection _) = executeGraphExpr sessionId conn Commit -commit sessionId conn@(RemoteConnection _) = remoteCall conn (ExecuteGraphExpr sessionId Commit)+commit sessionId conn@(InProcessConnection _) = executeAlterTransactionGraphExpr sessionId conn Commit +commit sessionId conn@(RemoteConnection _) = remoteCall conn (ExecuteAlterTransactionGraphExpr sessionId Commit) -sendNotifications :: [ClientInfo] -> EvaluatedNotifications -> IO ()-sendNotifications clients notifs =+-- | Sends notifications to client who have registered notifications. Only sends notifications to roles which have the AccessRelVarsPermission.+sendNotifications :: InProcessConnectionConf -> RelVarAccessControlList -> [ClientInfo] -> EvaluatedNotifications -> IO ()+sendNotifications conf acl' clients notifs =   unless (M.null notifs) $ forM_ clients sender  where-  sender (RemoteClientInfo sock) = RPC.sendMessage sock (NotificationMessage notifs)+   --check if role has access to relvars based on the context- make sure that roles which have had permission in the past but have had it revoked no longer get notifications- find a solution to send notifications for changes in DBC function results.+  sender (RemoteClientInfo sock roleName) = do+    eRoles <- LoginRoles.roleIdsForRoleName roleName (ipLoginRoles conf)+    case eRoles of+      Left _err -> pure ()+      Right roleIds ->+        when (hasAccess roleIds AccessRelVarsPermission acl') $+          SRPC.sendMessage sock (NotificationMessage notifs)   sender (InProcessClientInfo tvar) = putMVar tvar notifs --- | Discard any changes made in the current 'Session' and 'DatabaseContext'. This resets the disconnected transaction to reference the original database context of the parent transaction and is a very cheap operation.+-- | Discard any changes made in the current 'Session' and 'DatabaseContext'. This resets the disconnected transaction to reference the original database context of the parent transaction and is a very cheap operation. Requires CommitTransactionPermission. rollback :: SessionId -> Connection -> IO (Either RelationalError ())-rollback sessionId conn@(InProcessConnection _) = executeGraphExpr sessionId conn Rollback      -rollback sessionId conn@(RemoteConnection _) = remoteCall conn (ExecuteGraphExpr sessionId Rollback)+rollback sessionId conn@(InProcessConnection _) = executeAlterTransactionGraphExpr sessionId conn Rollback >> pure (Right ())+rollback sessionId conn@(RemoteConnection _) = remoteCall conn (ExecuteAlterTransactionGraphExpr sessionId Rollback)  -- | Write the transaction graph to disk. This function can be used to incrementally write new transactions to disk.-processTransactionGraphPersistence :: PersistenceStrategy -> [TransactionId] -> TransactionGraph -> IO ()-processTransactionGraphPersistence NoPersistence _ _ = pure ()-processTransactionGraphPersistence (MinimalPersistence dbdir) transIds graph = void $ transactionGraphPersist NoDiskSync dbdir transIds graph-processTransactionGraphPersistence (CrashSafePersistence dbdir) transIds graph = void $ transactionGraphPersist FsyncDiskSync dbdir transIds graph+processTransactionGraphPersistence :: PersistenceStrategy -> TransactionGraphIncrementalWriteInfo -> IO ()+processTransactionGraphPersistence NoPersistence _ = pure ()+processTransactionGraphPersistence (MinimalPersistence dbdir) tWriteInfo = void $ transactionGraphPersistIncremental NoDiskSync dbdir tWriteInfo+processTransactionGraphPersistence (CrashSafePersistence dbdir) tWriteInfo = void $ transactionGraphPersistIncremental FsyncDiskSync dbdir tWriteInfo  readGraphTransactionIdDigest :: PersistenceStrategy -> IO LockFileHash readGraphTransactionIdDigest NoPersistence = error "attempt to read digest from transaction log without persistence enabled" readGraphTransactionIdDigest (MinimalPersistence dbdir) = readGraphTransactionIdFileDigest dbdir  readGraphTransactionIdDigest (CrashSafePersistence dbdir) = readGraphTransactionIdFileDigest dbdir  --- | Return a relation whose type would match that of the relational expression if it were executed. This is useful for checking types and validating a relational expression's types.+-- | Return a relation whose type would match that of the relational expression if it were executed. This is useful for checking types and validating a relational expression's types. If relvars are mentioned, requires AccessRelVarsPermission. If functions are mentioned, requires ViewFunctionPermission. typeForRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Relation)-typeForRelationalExpr sessionId conn@(InProcessConnection _) relExpr = atomically $ typeForRelationalExprSTM sessionId conn relExpr+typeForRelationalExpr sessionId (InProcessConnection conf) relExpr = do+  roleIds <- roleIdsForRoleName conf+  atomically $ do+    eSession <- sessionAndSchema sessionId conf+    case eSession of+      Left err -> pure $ Left err+      Right (session, _schema) -> do+        graph <- readTVar (ipTransactionGraph conf)+        let dbctx = Sess.concreteDatabaseContext session+            reEnv = RE.mkRelationalExprEnv dbctx graph+        case RE.resolveDBC' graph (RE.re_context reEnv) DBC.acl of+          Left err -> pure (Left err)+          Right acl' -> do+            case applyACLRelationalExpr roleIds (relvarsACL acl') relExpr of+              Left err -> pure (Left err)+              Right () ->+                typeForRelationalExprSTM sessionId conf relExpr typeForRelationalExpr sessionId conn@(RemoteConnection _) relExpr = remoteCall conn (ExecuteTypeForRelationalExpr sessionId relExpr)     -typeForRelationalExprSTM :: SessionId -> Connection -> RelationalExpr -> STM (Either RelationalError Relation)    -typeForRelationalExprSTM sessionId (InProcessConnection conf) relExpr = do-  let sessions = ipSessions conf-  eSession <- sessionAndSchema sessionId sessions+typeForRelationalExprSTM :: SessionId -> InProcessConnectionConf -> RelationalExpr -> STM (Either RelationalError Relation)    +typeForRelationalExprSTM sessionId conf relExpr = do+  eSession <- sessionAndSchema sessionId conf   case eSession of     Left err -> pure $ Left err     Right (session, schema) -> do@@ -776,45 +1045,50 @@         Right relExpr' -> do           graph <- readTVar (ipTransactionGraph conf)                     let reEnv = RE.mkRelationalExprEnv (Sess.concreteDatabaseContext session) graph-          pure $ RE.runRelationalExprM reEnv (RE.typeForRelationalExpr relExpr') -    -typeForRelationalExprSTM _ _ _ = error "typeForRelationalExprSTM called on non-local connection"-+          pure $ RE.runRelationalExprM reEnv (RE.typeForRelationalExpr relExpr')+           -- | Return a 'Map' of the database's constraints at the context of the session and connection. inclusionDependencies :: SessionId -> Connection -> IO (Either RelationalError InclusionDependencies) inclusionDependencies sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf   atomically $ do-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure $ Left err        Right (session, schema) -> do+            graph <- readTVar (ipTransactionGraph conf)                     let context = Sess.concreteDatabaseContext session-            if schemaName session == defaultSchemaName then-              pure $ Right (B.inclusionDependencies context)-              else-              pure (Schema.inclusionDependenciesInSchema schema (B.inclusionDependencies context))+            case RE.resolveDBC' graph context DBC.inclusionDependencies of+              Left err -> pure (Left err)+              Right incDeps -> do+                if schemaName session == defaultSchemaName then+                  pure (Right incDeps)+                  else+                    pure $ Schema.inclusionDependenciesInSchema schema incDeps  inclusionDependencies sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveInclusionDependencies sessionId)  typeConstructorMapping :: SessionId -> Connection -> IO (Either RelationalError TypeConstructorMapping) typeConstructorMapping sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf   atomically $ do-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure $ Left err -      Right (session, _) -> --warning, no schema support for typeconstructors-        pure (Right (B.typeConstructorMapping (Sess.concreteDatabaseContext session)))+      Right (session, _) -> do --warning, no schema support for typeconstructors+        let context = Sess.concreteDatabaseContext session+        graph <- readTVar (ipTransactionGraph conf)            +        case RE.resolveDBC' graph context DBC.typeConstructorMapping of+          Left err -> pure (Left err)+          Right tConsMap ->+            pure (Right tConsMap) typeConstructorMapping sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveTypeConstructorMapping sessionId)    -- | Return an optimized database expression which is logically equivalent to the input database expression. This function can be used to determine which expression will actually be evaluated. planForDatabaseContextExpr :: SessionId -> Connection -> DatabaseContextExpr -> IO (Either RelationalError GraphRefDatabaseContextExpr)   planForDatabaseContextExpr sessionId (InProcessConnection conf) dbExpr = do-  let sessions = ipSessions conf+  roles <- LoginRoles.allRoles (ipLoginRoles conf)   atomically $ do     graph <- readTVar (ipTransactionGraph conf)    -    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure $ Left err        Right (session, _) ->@@ -822,12 +1096,45 @@           let ctx = Sess.concreteDatabaseContext session               transId = Sess.parentId session               gfExpr = runProcessExprM UncommittedContextMarker (processDatabaseContextExpr dbExpr)-          pure $ runGraphRefSOptDatabaseContextExprM transId ctx graph (optimizeGraphRefDatabaseContextExpr gfExpr)+              roleNameResolver nam = fst <$> lookup nam roles+              roleIdResolver roleId = lookup roleId (map (\(a,(b,_c)) -> (b,a)) roles)+          case resolveRoleIds roleNameResolver gfExpr of+            Left err -> pure (Left err)+            Right gfExpr' -> do+              let dbcFuncUtils = DBC.DatabaseContextFunctionUtils {+                    DBC.executeDatabaseContextExpr = undefined,+                    DBC.executeRelationalExpr = undefined+                    }+              case runGraphRefSOptDatabaseContextExprM transId ctx graph dbcFuncUtils (optimizeGraphRefDatabaseContextExpr gfExpr') of+                Left err -> pure (Left err)+                Right optExpr -> +              -- convert roleIds back roleNames to avoid leaking role ids to the client+                  case resolveRoleNames roleIdResolver optExpr of+                    Left err -> pure (Left err)+                    Right optExpr' -> pure (Right optExpr')         else -- don't show any optimization because the current optimization infrastructure relies on access to the base context- this probably underscores the need for each schema to have its own DatabaseContext, even if it is generated on-the-fly-}           pure (Left NonConcreteSchemaPlanError)  planForDatabaseContextExpr sessionId conn@(RemoteConnection _) dbExpr = remoteCall conn (RetrievePlanForDatabaseContextExpr sessionId dbExpr)-             ++planForRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError T.Text)+planForRelationalExpr sessionId (InProcessConnection conf) rexpr = do+  atomically $ do+    graph <- readTVar (ipTransactionGraph conf)    +    eSession <- sessionAndSchema sessionId conf+    case eSession of+      Left err -> pure $ Left err +      Right (session, schema) ->+        case  Schema.processRelationalExprInSchema schema rexpr of+          Left err -> pure (Left err)+          Right rexpr' -> do+            let ctx = Sess.concreteDatabaseContext session+                gfExpr = runProcessExprM UncommittedContextMarker (processRelationalExpr rexpr')+                gfEnv = RE.freshGraphRefRelationalExprEnv (Just ctx) graph+            pure (renderPretty <$> planGraphRefRelationalExpr gfExpr gfEnv)++planForRelationalExpr sessionId conn@RemoteConnection{} expr = remoteCall conn (RetrievePlanForRelationalExpr sessionId expr)+ -- | Return a relation which represents the current state of the global transaction graph. The attributes are  --    * current- boolean attribute representing whether or not the current session references this transaction --    * head- text attribute which is a non-empty 'HeadName' iff the transaction references a head.@@ -846,13 +1153,19 @@      transactionGraphAsRelation sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveTransactionGraph sessionId)  +-- | Alter roles (users) which can login to the database.+executeAlterLoginRolesExpr :: SessionId -> Connection -> LoginRoles.AlterLoginRolesExpr -> IO (Either LoginRoles.LoginRoleError LoginRoles.SuccessResult)+executeAlterLoginRolesExpr _sessionId (InProcessConnection conf) expr = do+  LoginRoles.executeAlterLoginRolesExpr (ipRoleName conf) (ipLoginRoles conf) expr+executeAlterLoginRolesExpr sessionId conn@RemoteConnection{} expr = do+  remoteCall conn (ExecuteAlterLoginRolesExpr sessionId expr)+ -- | Returns the names and types of the relation variables in the current 'Session'. relationVariablesAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) relationVariablesAsRelation sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf   atomically $ do     graph <- readTVar (ipTransactionGraph conf)-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)       Right (session, schema) -> do@@ -864,10 +1177,9 @@ -- | Returns a relation representing the complete DDL of the current `DatabaseContext`. ddlAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) ddlAsRelation sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf   atomically $ do     graph <- readTVar (ipTransactionGraph conf)-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)       Right (session, schema) -> do@@ -878,37 +1190,71 @@ -- | Returns the names and types of the atom functions in the current 'Session'. atomFunctionsAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) atomFunctionsAsRelation sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf   atomically $ do-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)-      Right (session, _) -> -        pure (AF.atomFunctionsAsRelation (atomFunctions (concreteDatabaseContext session)))+      Right (session, _) -> do+        graph <- readTVar (ipTransactionGraph conf)+        let context = concreteDatabaseContext session+        case RE.resolveDBC' graph context DBC.atomFunctions of+          Left err -> pure (Left err)+          Right afuncs ->+            pure (AF.atomFunctionsAsRelation afuncs)          atomFunctionsAsRelation sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveAtomFunctionSummary sessionId)         +-- | Return a relation representing all database context functions. Requires ViewFunctionPermission and ViewDBCFunctionPermission per function. databaseContextFunctionsAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) databaseContextFunctionsAsRelation sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf+  roleIds <- roleIdsForRoleName conf   atomically $ do-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)-      Right (session, _) ->-        pure (DCF.databaseContextFunctionsAsRelation (dbcFunctions (concreteDatabaseContext session)))+      Right (session, _) -> do+        graph <- readTVar (ipTransactionGraph conf)+        let context = concreteDatabaseContext session+            reEnv = RE.mkRelationalExprEnv context graph+            requiredPerm = ViewFunctionPermission+        case RE.resolveDBC' graph (RE.re_context reEnv) DBC.acl of+          Left err -> pure (Left err)+          Right acl' -> do+            if hasAccess roleIds requiredPerm (dbcFunctionsACL acl') then+                case RE.resolveDBC' graph context DBC.dbcFunctions of+                  Left err -> pure (Left err)+                  Right dbcFuncs -> do+                    -- filter out dbcFuncs to which this user does not have ViewDBCFunctionPermission+                    let dbcFuncsFiltered = HS.filter dbcFuncFilter dbcFuncs+                        dbcFuncFilter f = hasAccess roleIds ViewDBCFunctionPermission (funcACL f)+                    pure (DCF.databaseContextFunctionsAsRelation dbcFuncsFiltered)+            else+               pure (Left (AccessDeniedError (SomeFunctionPermission requiredPerm)))  databaseContextFunctionsAsRelation sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveDatabaseContextFunctionSummary sessionId) +-- | Show notifications in a relation format. Requires RelVarsAccessPermission. notificationsAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) notificationsAsRelation sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf+  roleIds <- roleIdsForRoleName conf       atomically $ do-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)-      Right (session, schema) ->-        pure (Schema.notificationsAsRelationInSchema (notifications (concreteDatabaseContext session)) schema)+      Right (session, schema) -> do+        graph <- readTVar (ipTransactionGraph conf)+        let context = concreteDatabaseContext session+        case RE.resolveDBC' graph context DBC.notifications of+          Left err -> pure (Left err)+          Right notifs ->+            case relvarsACL <$> RE.resolveDBC' graph context DBC.acl of+              Left err -> pure (Left err)+              Right rvacl' ->+                if hasAccess roleIds AccessRelVarsPermission rvacl' then+                  pure (Schema.notificationsAsRelationInSchema notifs schema)+                else+                  pure (Left (AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission)))                  + notificationsAsRelation sessionId conn@RemoteConnection{} = remoteCall conn (RetrieveNotificationsAsRelation sessionId)  -- | Returns the transaction id for the connection's disconnected transaction committed parent transaction.  @@ -922,27 +1268,22 @@       Right session -> pure $ Right (Sess.parentId session) headTransactionId sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveHeadTransactionId sessionId)     -headNameSTM_ :: SessionId -> Sessions -> TVar TransactionGraph -> STM (Either RelationalError HeadName)  -headNameSTM_ sessionId sessions graphTvar = do-    graph <- readTVar graphTvar+currentHeadSTM_ :: SessionId -> Sessions -> STM (Either RelationalError Discon.CurrentHead)  +currentHeadSTM_ sessionId sessions = do     eSession <- sessionForSessionId sessionId sessions     case eSession of       Left err -> pure (Left err)-      Right session -> case RE.transactionForId (Sess.parentId session) graph of-        Left err -> pure (Left err)-        Right parentTrans -> case headNameForTransaction parentTrans graph of-          Nothing -> pure (Left UnknownHeadError)-          Just headName' -> pure (Right headName')+      Right session ->+        pure (Right (Discon.disconCurrentHead (Sess.disconnectedTransaction session)))   --- | Returns Just the name of the head of the current disconnected transaction or Nothing.    -headName :: SessionId -> Connection -> IO (Either RelationalError HeadName)-headName sessionId (InProcessConnection conf) = do+-- | Returns Just the current head of the current disconnected transaction which may be a branch name or a transaction id.+currentHead :: SessionId -> Connection -> IO (Either RelationalError Discon.CurrentHead)+currentHead sessionId (InProcessConnection conf) = do   let sessions = ipSessions conf-      graphTvar = ipTransactionGraph conf-  atomically (headNameSTM_ sessionId sessions graphTvar)-headName sessionId conn@(RemoteConnection _) = remoteCall conn (ExecuteHeadName sessionId)+  atomically (currentHeadSTM_ sessionId sessions)+currentHead sessionId conn@(RemoteConnection _) = remoteCall conn (ExecuteHeadName sessionId) --- | Returns a listing of all available atom types.+-- | Returns a listing of all available atom types. Requires no specific permission. atomTypesAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) atomTypesAsRelation sessionId (InProcessConnection conf) = do   let sessions = ipSessions conf@@ -950,10 +1291,15 @@     eSession <- sessionForSessionId sessionId sessions     case eSession of       Left err -> pure (Left err)-      Right session ->-        case typesAsRelation (B.typeConstructorMapping (Sess.concreteDatabaseContext session)) of+      Right session -> do+        graph <- readTVar (ipTransactionGraph conf)+        let context = concreteDatabaseContext session+        case RE.resolveDBC' graph context DBC.typeConstructorMapping of           Left err -> pure (Left err)-          Right rel -> pure (Right rel)+          Right tConsMap ->+            case typesAsRelation tConsMap of+              Left err -> pure (Left err)+              Right rel -> pure (Right rel) atomTypesAsRelation sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveAtomTypesAsRelation sessionId)  disconnectedTransactionIsDirty :: SessionId -> Connection -> IO (Either RelationalError Bool)@@ -964,7 +1310,7 @@     case eSession of       Left err -> pure (Left err)       Right session ->-        pure (Right (isDirty session))+        pure (Right (Sess.isUpdated session)) disconnectedTransactionIsDirty sessionId conn@(RemoteConnection _) = remoteCall conn (RetrieveSessionIsDirty sessionId)          --used only for testing- we expect this to throw an exception@@ -988,10 +1334,11 @@ disconnectedTransaction_ _ _= error "remote connection used"  -- wrap a graph evaluation in file locking+-- the wrapped function returns multiple disconnected transaction and transaction ids because we anticipate coalescing multiple in-flight commits into one big fsync commitLock_ :: SessionId ->                 InProcessConnectionConf ->                 (TransactionGraph -> -                STM (Either RelationalError (DisconnectedTransaction, TransactionGraph, [TransactionId]))) -> +                STM (Either RelationalError (DisconnectedTransaction, TransactionGraphIncrementalWriteInfo))) ->                 IO (Either RelationalError ()) commitLock_ sessionId conf stmBlock = do   let sessions = ipSessions conf@@ -1033,29 +1380,35 @@         case eRefreshedGraph of           Left err -> pure (Left (DatabaseLoadError err))           Right refreshedGraph -> do-            eGraph <- stmBlock refreshedGraph-            case eGraph of+            eTransWriteInfo <- stmBlock refreshedGraph+            case eTransWriteInfo of               Left err -> pure (Left err)-              Right (discon', graph', transactionIdsToPersist) -> do-                writeTVar graphTvar graph'-                let newSession = Session discon' (Sess.schemaName session)-                StmMap.insert newSession sessionId sessions-                case RE.transactionForId (Sess.parentId session) oldGraph of-                  Left err -> pure $ Left err-                  Right previousTrans ->-                    if not (Prelude.null transactionIdsToPersist) then do-                      (evaldNots, nodes) <- executeCommitExprSTM_ graph' (Trans.concreteDatabaseContext previousTrans) (Sess.concreteDatabaseContext session) clientNodes-                      nodesToNotify <- stmSetToList nodes-                      pure $ Right (evaldNots, nodesToNotify, graph', transactionIdsToPersist)-                    else pure (Right (M.empty, [], graph', []))+              Right (discon, tWriteInfo) -> do+                writeTVar graphTvar (newGraph tWriteInfo)+                let newSession = Session discon (Sess.schemaName session)+                    dbctx = Discon.concreteDatabaseContext discon+                case RE.resolveDBC' (newGraph tWriteInfo) dbctx DBC.acl of+                  Left err -> pure (Left err)+                  Right acl' -> do+                    let rvacl = relvarsACL acl'+                    StmMap.insert newSession sessionId sessions+                    case RE.transactionForId (Sess.parentId session) oldGraph of+                      Left err -> pure $ Left err+                      Right previousTrans ->+                        if not (Prelude.null (uncommittedTransactions tWriteInfo)) then do+                          (evaldNots, nodes) <- executeCommitExprSTM_ (newGraph tWriteInfo) (Trans.concreteDatabaseContext previousTrans) (Sess.concreteDatabaseContext session) clientNodes+                          clientNodeMap <- stmMapToList nodes+                          let nodesToNotify = map snd clientNodeMap+                          pure $ Right (evaldNots, nodesToNotify, tWriteInfo, rvacl)+                        else pure (Right (M.empty, [], tWriteInfo, rvacl))        --handle notification firing                   case manip of      Left err -> pure (Left err)-    Right (notsToFire, nodesToNotify, newGraph, transactionIdsToPersist) -> do+    Right (notsToFire, nodesToNotify, tWriteInfo, acl') -> do       --update filesystem database, if necessary-      processTransactionGraphPersistence strat transactionIdsToPersist newGraph-      sendNotifications nodesToNotify notsToFire+      processTransactionGraphPersistence strat tWriteInfo+      sendNotifications conf acl' nodesToNotify notsToFire       pure (Right ())  -- | Runs an IO monad, commits the result when the monad returns no errors, otherwise, rolls back the changes and the error.@@ -1082,6 +1435,7 @@                   else -- no updates executed, so don't create a commit                   pure (Right val) +-- | Execute an expression which returns a dataframe. Requires permissions to run the underlying relational expression. executeDataFrameExpr :: SessionId -> Connection -> DF.DataFrameExpr -> IO (Either RelationalError DF.DataFrame) executeDataFrameExpr sessionId conn@(InProcessConnection _) dfExpr = do   eRel <- executeRelationalExpr sessionId conn (DF.convertExpr dfExpr)@@ -1109,7 +1463,8 @@                   dFrame'' = maybe dFrame' (`DF.take'` dFrame') (DF.limit dfExpr)               pure (Right dFrame'') executeDataFrameExpr sessionId conn@(RemoteConnection _) dfExpr = remoteCall conn (ExecuteDataFrameExpr sessionId dfExpr)-        ++-- | Run a check on the transaction graph to validate all merkle hashes. Requires no specific permission. validateMerkleHashes :: SessionId -> Connection -> IO (Either RelationalError ()) validateMerkleHashes sessionId (InProcessConnection conf) = do   let sessions = ipSessions conf@@ -1124,28 +1479,34 @@           Right () -> pure (Right ()) validateMerkleHashes sessionId conn@RemoteConnection{} = remoteCall conn (ExecuteValidateMerkleHashes sessionId) --- | Calculate a hash on the DDL of the current database context (not the graph). This is useful for validating on the client that the database schema meets the client's expectation. Any DDL change will change this hash. This hash does not change based on the current isomorphic schema being examined. This function is not affected by the current schema (since they are all isomorphic anyway, they should return the same hash).+-- | Calculate a hash on the DDL of the current database context (not the graph). This is useful for validating on the client that the database schema meets the client's expectation. Any DDL change will change this hash. This hash does not change based on the current isomorphic schema being examined. This function is not affected by the current schema (since they are all isomorphic anyway, they should return the same hash). Requires AccessRelVarsPermission. getDDLHash :: SessionId -> Connection -> IO (Either RelationalError SecureHash) getDDLHash sessionId (InProcessConnection conf) = do+  roleIds <- roleIdsForRoleName conf         let sessions = ipSessions conf   atomically $ do     eSession <- sessionForSessionId sessionId sessions     case eSession of       Left err -> pure (Left err)       Right session -> do-        let ctx = Sess.concreteDatabaseContext session            +        let ctx = Sess.concreteDatabaseContext session         graph <- readTVar (ipTransactionGraph conf)-        pure (ddlHash ctx graph)+        case RE.resolveDBC' graph ctx DBC.acl of+          Left err -> pure (Left err)+          Right acl' -> +            if hasAccess roleIds AccessRelVarsPermission (relvarsACL acl') then+              pure (ddlHash ctx graph)+            else+               pure (Left (AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission)))               getDDLHash sessionId conn@RemoteConnection{} = remoteCall conn (GetDDLHash sessionId)  -- | Convert a SQL Query expression into a DataFrameExpr. Because the conversion process requires substantial database metadata access (such as retrieving types for various subexpressions), we cannot process SQL client-side. However, the underlying DBMS is completely unaware that the resultant DataFrameExpr has come from SQL. convertSQLQuery :: SessionId -> Connection -> Query -> IO (Either RelationalError DF.DataFrameExpr) convertSQLQuery sessionId (InProcessConnection conf) query = do-  let sessions = ipSessions conf-      graphTvar = ipTransactionGraph conf  +  let graphTvar = ipTransactionGraph conf     atomically $ do     transGraph <- readTVar graphTvar    -    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)       Right (session, _schema) -> do -- TODO: enable SQL to leverage isomorphic schemas@@ -1161,11 +1522,10 @@  convertSQLDBUpdates :: SessionId -> Connection -> [SQL.DBUpdate] -> IO (Either RelationalError DatabaseContextExpr) convertSQLDBUpdates sessionId (InProcessConnection conf) updates = do-  let sessions = ipSessions conf-      graphTvar = ipTransactionGraph conf  +  let graphTvar = ipTransactionGraph conf     atomically $ do     transGraph <- readTVar graphTvar    -    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)       Right (session, _schema) -> do -- TODO: enable SQL to leverage isomorphic schemas@@ -1178,40 +1538,86 @@           Right updateExpr -> pure (Right updateExpr) convertSQLDBUpdates sessionId conn@RemoteConnection{} ups = remoteCall conn (ConvertSQLUpdates sessionId ups)    +-- | Registered queries are queries must always typecheck. Return a relation of them. Requires AccessRelVarsPermission. registeredQueriesAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation) registeredQueriesAsRelation sessionId (InProcessConnection conf) = do-  let sessions = ipSessions conf+  roleIds <- roleIdsForRoleName conf     atomically $ do-    eSession <- sessionAndSchema sessionId sessions+    eSession <- sessionAndSchema sessionId conf     case eSession of       Left err -> pure (Left err)       Right (session, schema) -> do-        let ctx = Sess.concreteDatabaseContext session        -        pure $ registeredQueriesAsRelationInSchema schema (registeredQueries ctx)+        graph <- readTVar (ipTransactionGraph conf)        +        let context = Sess.concreteDatabaseContext session+        case RE.resolveDBC' graph context DBC.acl of+          Left err -> pure (Left err)+          Right acl' -> +            if hasAccess roleIds AccessRelVarsPermission (relvarsACL acl') then+              case RE.resolveDBC' graph context DBC.registeredQueries of+                Left err -> pure (Left err)+                Right regQs ->+                  pure $ registeredQueriesAsRelationInSchema schema regQs+            else+               pure (Left (AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission)))                             registeredQueriesAsRelation sessionId conn@RemoteConnection{} = remoteCall conn (RetrieveRegisteredQueries sessionId)         -type ClientNodes = StmSet.Set ClientInfo+type ClientNodes = StmMap.Map SRPC.ClientConnectionId ClientInfo  -- internal structure specific to in-process connections data InProcessConnectionConf = InProcessConnectionConf {   ipPersistenceStrategy :: PersistenceStrategy,    ipClientNodes :: ClientNodes, -  ipSessions :: Sessions, +  ipSessions :: Sessions,   ipTransactionGraph :: TVar TransactionGraph,   ipScriptSession :: Maybe ScriptSession,   ipLocks :: Maybe (LockFile, MVar LockFileHash), -- nothing when NoPersistence-  ipCallbackAsync :: Async ()+  ipCallbackAsync :: Async (),+  ipRelExprCache :: RelExprCache, -- can the remote client also include such a pinned expr cache? should that cache be controlled by the server or client?+  ipLoginRoles :: LoginRoles.LoginRolesDB, -- ^ roles allowed to connect to the database, access control is otherwise handled by ACLs in the database context+  ipRoleName :: RoleName, -- ^ role name for the user accessing the database+  ipRandomGen :: StdGen -- ^ random number generator   }  -- clients may connect associate one socket/mvar with the server to register for change callbacks-data ClientInfo = RemoteClientInfo (RPC.Locking Socket) |+data ClientInfo = RemoteClientInfo SRPC.SocketContext RoleName |                   InProcessClientInfo (MVar EvaluatedNotifications)  instance Eq ClientInfo where-  (RemoteClientInfo a) == (RemoteClientInfo b) = RPC.lockless a == RPC.lockless b+  (RemoteClientInfo a roleNameA) == (RemoteClientInfo b roleNameB) = a == b && roleNameA == roleNameB   (InProcessClientInfo a) == (InProcessClientInfo b) = a == b   _ == _ = False  instance Hashable ClientInfo where-  hashWithSalt salt (RemoteClientInfo sock) = hashWithSalt salt (show (RPC.lockless sock))+  hashWithSalt salt (RemoteClientInfo sockCtx roleName) = salt `hashWithSalt` show (SRPC.lockless (SRPC.lockingSocket sockCtx)) `hashWithSalt` roleName   hashWithSalt salt (InProcessClientInfo _) = hashWithSalt salt (1::Int)++data TlsConfig = TlsConfig+  {+    serverTlsHostName :: String,+    serverTlsServiceName :: String,+    serverX509PublicPrivateKeyPaths :: Maybe (FilePath, FilePath),+    serverX509CertificatePath :: Maybe FilePath -- ^ Using Nothing indicates referencing the system certificate store.+  }+  deriving Show++roleIdsForRoleName :: InProcessConnectionConf -> IO [RoleId]+roleIdsForRoleName conf = do+  eRoles <- LoginRoles.roleIdsForRoleName (ipRoleName conf) (ipLoginRoles conf)+  --ignore errors and expect ACL functions to handle empty role id list+  case eRoles of+    Left _err -> pure []+    Right roleIds -> pure roleIds++primaryRoleIdForRoleName :: InProcessConnectionConf -> IO RoleId+primaryRoleIdForRoleName conf = do+  eRoleId <- LoginRoles.roleIdForRoleName (ipRoleName conf) (ipLoginRoles conf)+  case eRoleId of+    Left _err -> error ("role id for role name \"" <> T.unpack (ipRoleName conf) <> "\" no longer exists")+    Right roleId -> pure roleId++-- | Useful for in-process connections to change the role related to access control. Remote connections authenticate via TLS, so require new connections to change roles.+setRoleName :: RoleName -> Connection -> Connection+setRoleName newRoleName (InProcessConnection conf) =+  InProcessConnection (conf { ipRoleName = newRoleName })+setRoleName _ RemoteConnection{} = error "setRoleName can only be used with in-process connections"+  
src/lib/ProjectM36/Client/Simple.hs view
@@ -78,10 +78,11 @@ -- | Same as 'withTransaction' except that the merge strategy can be specified. withTransactionUsing :: DbConn -> MergeStrategy -> Db a -> IO (Either DbError a) withTransactionUsing (sess, conn) strat dbm = do-  eHeadName <- C.headName sess conn+  eHeadName <- C.currentHead sess conn   case eHeadName of     Left err -> pure (Left (RelError err))-    Right headName -> do+    Right (C.CurrentHeadTransactionId tid) -> pure (Left (RelError (MergeTransactionError (DisconnectedTransactionNotAMergeHeadError tid))))+    Right (C.CurrentHeadBranch headName) -> do       let successFunc = C.autoMergeToHead sess conn strat headName           block = runReaderT (runDb dbm) (sess, conn)           handler :: TransactionCancelled -> IO (Either DbError a)
src/lib/ProjectM36/DDLType.hs view
@@ -10,32 +10,45 @@ import ProjectM36.AtomFunction import ProjectM36.DatabaseContextFunction import ProjectM36.IsomorphicSchema+import ProjectM36.DatabaseContext.Types+import ProjectM36.TransactionGraph.Types+import ProjectM36.IsomorphicSchema.Types --- | Return a hash of just DDL-specific (schema) attributes. This is useful for determining if a client has the appropriate updates needed to work with the current schema.+-- | Return a hash of just DDL-specific (schema) attributes. This is useful for determining if a client has the appropriate updates needed to work with the current schema. The database context is fully resolved and materialized so that no references to previous transactions exist in the hash calculation. ddlHash :: DatabaseContext -> TransactionGraph -> Either RelationalError SecureHash ddlHash ctx tgraph = do   -- we cannot merely hash the relational representation of the type because the order of items matters when hashing   -- registered queries are not included here because a client could be compatible with a schema even if the queries are not registered. The client should validate registered query state up-front. Perhaps there should be another hash for registered queries.   rvtypemap <- typesForRelationVariables ctx tgraph-  pure $ mkDDLHash ctx rvtypemap+  resDBContext <- toResolvedDatabaseContext ctx tgraph+  pure $ mkDDLHash resDBContext rvtypemap  -- | Process all relations within the context of the transaction graph to extract the relation variables types. typesForRelationVariables :: DatabaseContext -> TransactionGraph -> Either RelationalError (M.Map RelVarName Relation) typesForRelationVariables ctx tgraph = do+  rvs <- resolveDBC' tgraph ctx relationVariables   let gfEnv = freshGraphRefRelationalExprEnv (Just ctx) tgraph   M.fromList <$> mapM (\(rvname, rvexpr) -> do            rvtype <- runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr rvexpr)            pure (rvname, rvtype)-                      ) (M.toList (relationVariables ctx))+                      ) (M.toList rvs)   -- | Return a Relation which represents the database context's current DDL schema. ddlType :: Schema -> DatabaseContext -> TransactionGraph -> Either RelationalError Relation ddlType schema ctx tgraph = do-  incDepsRel <- inclusionDependenciesInSchema schema (inclusionDependencies ctx) >>= inclusionDependenciesAsRelation-  atomFuncsRel <- atomFunctionsAsRelation (atomFunctions ctx)-  dbcFuncsRel <- databaseContextFunctionsAsRelation (dbcFunctions ctx)-  typesRel <- typesAsRelation (typeConstructorMapping ctx)+  incDeps <- resolveDBC' tgraph ctx inclusionDependencies+  incDepsRel <- inclusionDependenciesInSchema schema incDeps >>= inclusionDependenciesAsRelation++  atomFuncs <- resolveDBC' tgraph ctx atomFunctions+  atomFuncsRel <- atomFunctionsAsRelation atomFuncs++  dbcFuncs <- resolveDBC' tgraph ctx dbcFunctions+  dbcFuncsRel <- databaseContextFunctionsAsRelation dbcFuncs++  tConsMap <- resolveDBC' tgraph ctx typeConstructorMapping+  typesRel <- typesAsRelation tConsMap+     relvarTypesRel <- relationVariablesAsRelationInSchema ctx schema tgraph   let attrsAssocs = [("inclusion_dependencies", incDepsRel),                      ("atom_functions", atomFuncsRel),
src/lib/ProjectM36/DataTypes/ByteString.hs view
@@ -15,7 +15,8 @@                TextAtom textIn:_ -> case B64.decode (TE.encodeUtf8 textIn) of                    Left err -> Left (AtomFunctionBytesDecodingError err)                    Right bs -> pure (ByteStringAtom bs)-               _ -> Left AtomFunctionTypeMismatchError+               _ -> Left AtomFunctionTypeMismatchError,+             funcACL = ()                }   ]        
src/lib/ProjectM36/DataTypes/DateTime.hs view
@@ -12,7 +12,8 @@                                      funcBody = compiledAtomFunctionBody $                                        \case                                          IntegerAtom epoch:_ -> pure (DateTimeAtom (posixSecondsToUTCTime (realToFrac epoch)))-                                         _ -> Left AtomFunctionTypeMismatchError+                                         _ -> Left AtomFunctionTypeMismatchError,+                                     funcACL = ()                                      }]                                                   
src/lib/ProjectM36/DataTypes/Day.hs view
@@ -13,13 +13,15 @@                  funcBody = compiledAtomFunctionBody $                             \case                               IntegerAtom year:IntegerAtom month:IntegerAtom day:_ -> pure $ DayAtom (fromGregorian (fromIntegral year) (fromIntegral month) (fromIntegral day))-                              _ -> Left AtomFunctionTypeMismatchError+                              _ -> Left AtomFunctionTypeMismatchError,+                 funcACL = ()                  },   Function { funcName = "dayEarlierThan",                  funcType = [DayAtomType, DayAtomType, BoolAtomType],                  funcBody = compiledAtomFunctionBody $                             \case                               ConstructedAtom _ _ (IntAtom dayA:_):ConstructedAtom _ _ (IntAtom dayB:_):_ -> pure (BoolAtom (dayA < dayB))-                              _ -> Left AtomFunctionTypeMismatchError+                              _ -> Left AtomFunctionTypeMismatchError,+                funcACL = ()                }   ]
src/lib/ProjectM36/DataTypes/Interval.hs view
@@ -112,7 +112,8 @@                      createInterval atom1 atom2 bopen eopen                      else                      Left (AtomTypeDoesNotSupportIntervalError (prettyAtomType aType))-               _ -> Left AtomFunctionTypeMismatchError+               _ -> Left AtomFunctionTypeMismatchError,+             funcACL = ()                },   Function {     funcName = "interval_overlaps",@@ -123,7 +124,8 @@       \case         i1@ConstructedAtom{}:i2@ConstructedAtom{}:_ ->            BoolAtom <$> intervalOverlaps i1 i2-        _ -> Left AtomFunctionTypeMismatchError+        _ -> Left AtomFunctionTypeMismatchError,+    funcACL = ()           }]                          isIntervalAtomType :: AtomType -> Bool
src/lib/ProjectM36/DataTypes/List.hs view
@@ -41,7 +41,8 @@        \case                (listAtom:_) ->                  IntAtom . fromIntegral <$> listLength listAtom-               _ -> Left AtomFunctionTypeMismatchError+               _ -> Left AtomFunctionTypeMismatchError,+     funcACL = ()      },   Function {     funcName = "maybeHead",@@ -49,7 +50,8 @@     funcBody = FunctionBuiltInBody $                \case                  (listAtom:_) -> listMaybeHead listAtom-                 _ -> Left AtomFunctionTypeMismatchError+                 _ -> Left AtomFunctionTypeMismatchError,+    funcACL = ()     }   ]                     
src/lib/ProjectM36/DataTypes/Maybe.hs view
@@ -22,7 +22,8 @@      funcBody = FunctionBuiltInBody $        \case                ConstructedAtom dConsName _ _:_ -> pure $ BoolAtom (dConsName /= "Nothing")-               _ -> Left AtomFunctionTypeMismatchError+               _ -> Left AtomFunctionTypeMismatchError,+     funcACL = ()      },   Function {      funcName = "fromMaybe",@@ -30,7 +31,8 @@      funcBody = FunctionBuiltInBody $        \case          (defaultAtom:ConstructedAtom dConsName _ (atomVal:_):_) -> if atomTypeForAtom defaultAtom /= atomTypeForAtom atomVal then Left AtomFunctionTypeMismatchError else if dConsName == "Nothing" then pure defaultAtom else pure atomVal-         _ ->Left AtomFunctionTypeMismatchError         +         _ -> Left AtomFunctionTypeMismatchError,+     funcACL = ()      }   ] 
src/lib/ProjectM36/DataTypes/NonEmptyList.hs view
@@ -46,7 +46,8 @@        \case          (nonEmptyListAtom:_) ->            IntAtom . fromIntegral <$> nonEmptyListLength nonEmptyListAtom-         _ -> Left AtomFunctionTypeMismatchError+         _ -> Left AtomFunctionTypeMismatchError,+     funcACL = ()      },   Function {     funcName = "nonEmptyListHead",@@ -54,6 +55,7 @@     funcBody = FunctionBuiltInBody $       \case         (nonEmptyListAtom:_) -> nonEmptyListHead nonEmptyListAtom-        _ -> Left AtomFunctionTypeMismatchError+        _ -> Left AtomFunctionTypeMismatchError,+    funcACL = ()     }   ]
src/lib/ProjectM36/DataTypes/SQL/Null.hs view
@@ -31,67 +31,79 @@       funcType = [TypeVariableType "a",                   TypeVariableType "b", -- either type could be SQLNullable or a NakedAtom                   nullAtomType BoolAtomType],-      funcBody = FunctionBuiltInBody sqlEquals+      funcBody = FunctionBuiltInBody sqlEquals,+      funcACL = ()       },     Function {       funcName = "sql_and",       funcType = [TypeVariableType "a", TypeVariableType "b", nullAtomType BoolAtomType], -- for a more advanced typechecker, this should be BoolAtomType or SQLNullable BoolAtomType-      funcBody = FunctionBuiltInBody nullAnd+      funcBody = FunctionBuiltInBody nullAnd,+      funcACL = ()       },     Function {       funcName = "sql_or",       funcType = [TypeVariableType "a", TypeVariableType "b", nullAtomType BoolAtomType], -- for a more advanced typechecker, this should be BoolAtomType or SQLNullable BoolAtomType-      funcBody = FunctionBuiltInBody nullOr+      funcBody = FunctionBuiltInBody nullOr,+      funcACL = ()              },     Function {       funcName = "sql_coalesce_bool", -- used in where clause so that NULLs are filtered out       funcType = [TypeVariableType "a",                   BoolAtomType],-      funcBody = FunctionBuiltInBody coalesceBool+      funcBody = FunctionBuiltInBody coalesceBool,+      funcACL = ()       },     Function {       funcName = "sql_add",       funcType = [TypeVariableType "a",                    TypeVariableType "b",                   nullAtomType IntegerAtomType],-      funcBody = FunctionBuiltInBody (sqlIntegerBinaryFunction IntegerAtomType (\a b -> IntegerAtom (a + b)))+      funcBody = FunctionBuiltInBody (sqlIntegerBinaryFunction IntegerAtomType (\a b -> IntegerAtom (a + b))),+      funcACL = ()       },     Function {       funcName = "sql_abs",       funcType = [TypeVariableType "a",                   nullAtomType IntegerAtomType],-      funcBody = FunctionBuiltInBody sqlAbs+      funcBody = FunctionBuiltInBody sqlAbs,+      funcACL = ()       },     Function {       funcName = "sql_negate",       funcType = [TypeVariableType "a",                   nullAtomType IntegerAtomType],-      funcBody = FunctionBuiltInBody (sqlIntegerUnaryFunction IntegerAtomType (\a -> IntegerAtom (- a)))+      funcBody = FunctionBuiltInBody (sqlIntegerUnaryFunction IntegerAtomType (\a -> IntegerAtom (- a))),+      funcACL = ()       },     Function {       funcName = "sql_max",       funcType = foldAtomFuncType (TypeVariableType "a") (nullAtomType IntegerAtomType),-      funcBody = FunctionBuiltInBody sqlMax+      funcBody = FunctionBuiltInBody sqlMax,+      funcACL = ()       },     Function {       funcName = "sql_min",       funcType = foldAtomFuncType (TypeVariableType "a") (nullAtomType IntegerAtomType),-      funcBody = FunctionBuiltInBody sqlMin+      funcBody = FunctionBuiltInBody sqlMin,+      funcACL = ()       },     Function {       funcName = "sql_count",       funcType = foldAtomFuncType (TypeVariableType "a") IntegerAtomType,-      funcBody = FunctionBuiltInBody sqlCount+      funcBody = FunctionBuiltInBody sqlCount,+      funcACL = ()       },     Function {       funcName = "sql_sum",       funcType = foldAtomFuncType (TypeVariableType "a") (nullAtomType IntegerAtomType),-      funcBody = FunctionBuiltInBody sqlSum+      funcBody = FunctionBuiltInBody sqlSum,+      funcACL = ()       },     Function {       funcName = "sql_isnull",       funcType = [TypeVariableType "a", BoolAtomType],-      funcBody = FunctionBuiltInBody sqlIsNull+      funcBody = FunctionBuiltInBody sqlIsNull,+      funcACL = ()       }     ] <> sqlCompareFunctions @@ -104,7 +116,8 @@       Function {       funcName = sql_func,         funcType = [TypeVariableType "a", TypeVariableType "b", nullAtomType BoolAtomType],-        funcBody = FunctionBuiltInBody (sqlCompareFunc (opi, opt))+        funcBody = FunctionBuiltInBody (sqlCompareFunc (opi, opt)),+        funcACL = ()       }     boolNull = nullAtom BoolAtomType Nothing     sqlCompareFunc :: (Integer -> Integer -> Bool, Text -> Text -> Bool) -> [Atom] -> Either AtomFunctionError Atom
src/lib/ProjectM36/DatabaseContext.hs view
@@ -1,51 +1,76 @@-{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RankNTypes, MultiParamTypeClasses, ExistentialQuantification #-} module ProjectM36.DatabaseContext where import ProjectM36.Base+import ProjectM36.DatabaseContext.Types as DBT+import ProjectM36.ValueMarker as VM import Control.Monad (void)-import qualified Data.Map as M-import qualified Data.HashSet as HS-import ProjectM36.DataTypes.Basic-import ProjectM36.AtomFunctions.Basic-import ProjectM36.Relation-import ProjectM36.DatabaseContextFunction+import Data.Functor.Identity -empty :: DatabaseContext-empty = DatabaseContext { inclusionDependencies = M.empty, -                          relationVariables = M.empty, -                          notifications = M.empty,-                          atomFunctions = HS.empty,-                          dbcFunctions = HS.empty,-                          typeConstructorMapping = mempty,-                          registeredQueries = mempty }+-- | The DatabaseContext is a snapshot of a database's evolving state and contains everything a database client can change over time.+-- I spent some time thinking about whether the VirtualDatabaseContext/Schema and DatabaseContext data constructors should be the same constructor, but that would allow relation variables to be created in a "virtual" context which would appear to defeat the isomorphisms of the contexts. It should be possible to switch to an alternative schema to view the same equivalent information without information loss. However, allowing all contexts to reference another context while maintaining its own relation variables, new types, etc. could be interesting from a security perspective. For example, if a user creates a new relvar in a virtual context, then does it necessarily appear in all linked contexts? After deliberation, I think the relvar should appear in *all* linked contexts to retain the isomorphic properties, even when the isomorphism is for a subset of the context. This hints that the IsoMorphs should allow for "fall-through"; that is, when a relvar is not defined in the virtual context (for morphing), then the lookup should fall through to the underlying context.+-- | Used for read-only database context within a transaction. +-- | Indicate whether a record field has been changed in this transaction. This is used in the DatabaseContext ot mark fields which have been modified relative to the previous transactions' state as an optimization when persisting transactions on disk.++empty :: DatabaseContext+empty = DatabaseContext { inclusionDependencies = emptyValue, +                          relationVariables = emptyValue,+                          notifications = emptyValue,+                          atomFunctions = emptyValue,+                          dbcFunctions = emptyValue,+                          typeConstructorMapping = emptyValue,+                          registeredQueries = emptyValue,+                          acl = emptyValue            +                          }    -- | Remove TransactionId markers on GraphRefRelationalExpr stripGraphRefRelationalExpr :: GraphRefRelationalExpr -> RelationalExpr stripGraphRefRelationalExpr = void-        --- | convert an existing database context into its constituent expression.   -databaseContextAsDatabaseContextExpr :: DatabaseContext -> DatabaseContextExpr-databaseContextAsDatabaseContextExpr context = MultipleExpr $ relVarsExprs ++ incDepsExprs ++ funcsExprs-  where-    relVarsExprs = map (\(name, rel) -> Assign name (stripGraphRefRelationalExpr rel)) (M.toList (relationVariables context))-    incDepsExprs :: [DatabaseContextExpr]-    incDepsExprs = map (uncurry AddInclusionDependency) (M.toList (inclusionDependencies context))-    funcsExprs = [] -- map (\func -> ) (HS.toList funcs) -- there are no databaseExprs to add atom functions yet-} -basicDatabaseContext :: DatabaseContext-basicDatabaseContext = DatabaseContext { inclusionDependencies = M.empty,-                                         relationVariables = M.fromList [("true", ExistingRelation relationTrue),-                                                                         ("false", ExistingRelation relationFalse)],-                                         atomFunctions = basicAtomFunctions,-                                         dbcFunctions = basicDatabaseContextFunctions,-                                         notifications = M.empty,-                                         typeConstructorMapping = basicTypeConstructorMapping,-                                         registeredQueries = M.singleton "booleans" (Union (RelationVariable "true" ()) (RelationVariable "false" ()))-                                         }+-- | If the database context has any values which do *not* reference previous transactions, it must be new data.+isUpdated :: DatabaseContext -> Bool+isUpdated ctx = or [VM.valueIsUpdated (inclusionDependencies ctx),+                     VM.valueIsUpdated (relationVariables ctx),+                     VM.valueIsUpdated (atomFunctions ctx),+                     VM.valueIsUpdated (dbcFunctions ctx),+                     VM.valueIsUpdated (notifications ctx),+                     VM.valueIsUpdated (typeConstructorMapping ctx),+                     VM.valueIsUpdated (registeredQueries ctx),+                     VM.valueIsUpdated (acl ctx)+                   ]  someDatabaseContextExprs :: [DatabaseContextExpr] -> DatabaseContextExpr someDatabaseContextExprs [s] = s someDatabaseContextExprs (s:ss) = MultipleExpr (s:ss) someDatabaseContextExprs [] = NoOperation +-- | The "fresh" database context is created after a commit, so all values will refer to previous transactions.+freshDatabaseContext :: TransactionId -> DatabaseContext -> DatabaseContext+freshDatabaseContext previousTransactionId ctx =+  DatabaseContext { inclusionDependencies = freshen (inclusionDependencies ctx),+                    relationVariables = freshen (relationVariables ctx),+                    atomFunctions = freshen (atomFunctions ctx),+                    dbcFunctions = freshen (dbcFunctions ctx),+                    notifications = freshen (notifications ctx),+                    typeConstructorMapping = freshen (typeConstructorMapping ctx),+                    registeredQueries = freshen (registeredQueries ctx),+                    acl = freshen (acl ctx)+                  }+  where+    freshen :: forall a. ValueMarker a -> ValueMarker a+    freshen (ValueMarker _) = NotChangedSinceMarker previousTransactionId+    freshen m@NotChangedSinceMarker{} = m+                    +toDatabaseContext :: ResolvedDatabaseContext -> DatabaseContext+toDatabaseContext r =+  DatabaseContext {+    inclusionDependencies = ValueMarker (runIdentity (inclusionDependencies r)),+    relationVariables = ValueMarker (runIdentity (relationVariables r)),+    atomFunctions = ValueMarker (runIdentity (atomFunctions r)),+    dbcFunctions = ValueMarker (runIdentity (dbcFunctions r)),+    notifications = ValueMarker (runIdentity (notifications r)),+    typeConstructorMapping = ValueMarker (runIdentity (typeConstructorMapping r)),+    registeredQueries = ValueMarker (runIdentity (registeredQueries r)),+    acl = ValueMarker (runIdentity (acl r))+    } 
+ src/lib/ProjectM36/DatabaseContext/Basic.hs view
@@ -0,0 +1,23 @@+module ProjectM36.DatabaseContext.Basic where+import ProjectM36.DatabaseContext.Types+import ProjectM36.Base+import ProjectM36.Relation+import ProjectM36.DataTypes.Basic+import ProjectM36.AtomFunctions.Basic+import ProjectM36.DatabaseContextFunctions.Basic+import ProjectM36.AccessControlList as ACL+import qualified Data.Map as M+import Data.Functor.Identity++basicDatabaseContext :: ResolvedDatabaseContext+basicDatabaseContext = DatabaseContext { inclusionDependencies = Identity mempty,+                                         relationVariables =+                                           Identity $ M.fromList [("true", ExistingRelation relationTrue),+                                                                      ("false", ExistingRelation relationFalse)],+                                         atomFunctions = Identity basicAtomFunctions,+                                         dbcFunctions = Identity basicDatabaseContextFunctions,+                                         notifications = Identity mempty,+                                         typeConstructorMapping = Identity basicTypeConstructorMapping,+                                         registeredQueries = Identity $ M.singleton "booleans" (Union (RelationVariable "true" ()) (RelationVariable "false" ())),+                                         acl = Identity ACL.basic+                                         }
+ src/lib/ProjectM36/DatabaseContext/Fields.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}+module ProjectM36.DatabaseContext.Fields where+import GHC.Generics+import Control.DeepSeq (NFData)++data DatabaseContextField =+  InclusionDependenciesField |+  RelationVariablesField |+  NotificationsField |+  AtomFunctionsField |+  DbcFunctionsField |+  TypeConstructorMappingField |+  RegisteredQueriesField+  deriving (Show, Eq, NFData, Generic)+
+ src/lib/ProjectM36/DatabaseContext/SelfTest.hs view
@@ -0,0 +1,12 @@+-- | Include some test utility functions on top of the basic database context.+module ProjectM36.DatabaseContext.SelfTest where+import ProjectM36.DatabaseContext.Types+import ProjectM36.DatabaseContext.Basic+import ProjectM36.AtomFunctions.Basic+import ProjectM36.AtomFunctions.SelfTest+import Data.Functor.Identity++selfTestDatabaseContext :: ResolvedDatabaseContext+selfTestDatabaseContext = basicDatabaseContext {+  atomFunctions = Identity (basicAtomFunctions <> selfTestAtomFunctions)+  }
+ src/lib/ProjectM36/DatabaseContext/Types.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE RankNTypes, DeriveGeneric, DeriveAnyClass, MultiParamTypeClasses, ExistentialQuantification, StandaloneDeriving, FlexibleInstances #-}+module ProjectM36.DatabaseContext.Types where+import GHC.Generics+import Control.DeepSeq (NFData)+import ProjectM36.Base+import ProjectM36.Error+import ProjectM36.AccessControlList+import ProjectM36.ValueMarker+import qualified Data.HashSet as HS+import Data.Functor.Identity++data DatabaseContextBase a = DatabaseContext {+  inclusionDependencies :: a InclusionDependencies,+  relationVariables :: a RelationVariables,+  atomFunctions :: a AtomFunctions,+  dbcFunctions :: a DatabaseContextFunctions,+  notifications :: a Notifications,+  typeConstructorMapping :: a TypeConstructorMapping,+  registeredQueries :: a RegisteredQueries,+  acl :: a DatabaseContextACL+  } ++-- | The type of the database context when stored in the graph. It can reference data from other transactions in the graph.+type DatabaseContext = DatabaseContextBase ValueMarker++deriving instance NFData DatabaseContext+deriving instance Generic DatabaseContext++-- | The type of the database context when it is fully resolved (of transaction markers such as in the TransactionRefDatabaseContext); standalone, ready-to-use.+type ResolvedDatabaseContext = DatabaseContextBase Identity++data TransactionIdMarker a = TransactionIdMarker TransactionId a+  deriving (NFData, Generic)++-- | Utility functions to pass to Haskell-written database context functions.+data DatabaseContextFunctionUtils =+  DatabaseContextFunctionUtils {+  executeDatabaseContextExpr :: DatabaseContext -> DatabaseContextExpr -> Either RelationalError DatabaseContext,+  executeRelationalExpr :: DatabaseContext -> RelationalExpr -> Either RelationalError Relation+                               }++type DatabaseContextFunctionBodyType = DatabaseContextFunctionUtils -> [Atom] -> DatabaseContext -> Either RelationalError DatabaseContext+type DatabaseContextFunctions = HS.HashSet DatabaseContextFunction++type DatabaseContextFunction = Function DatabaseContextFunctionBodyType DBCFunctionAccessControlList+type DatabaseContextFunctionBody = FunctionBody DatabaseContextFunctionBodyType+
+ src/lib/ProjectM36/DatabaseContextExpr.hs view
@@ -0,0 +1,110 @@+module ProjectM36.DatabaseContextExpr where+import ProjectM36.DatabaseContext.Types+import ProjectM36.DatabaseContext+import ProjectM36.Base+import ProjectM36.AccessControlList+import ProjectM36.Error+import ProjectM36.RelationalExpression+import ProjectM36.TransactionGraph.Types+import qualified Data.Map as M+import Data.Functor.Identity+import qualified Data.Functor.Foldable as Fold++resolvedDatabaseContextAsDatabaseContextExpr :: ResolvedDatabaseContext -> DatabaseContextExpr+resolvedDatabaseContextAsDatabaseContextExpr context = do+  let relVarsExprs = map (\(name, rel) -> Assign name (stripGraphRefRelationalExpr rel)) (M.toList relVars)+      relVars = runIdentity (relationVariables context)+      incDeps = runIdentity (inclusionDependencies context)+      incDepsExprs = map (uncurry AddInclusionDependency) (M.toList incDeps)+      funcsExprs = []+  MultipleExpr $ relVarsExprs ++ incDepsExprs ++ funcsExprs+  +-- | convert an existing database context into its constituent expression.   +databaseContextAsDatabaseContextExpr :: DatabaseContext -> TransactionGraph -> Either RelationalError DatabaseContextExpr+databaseContextAsDatabaseContextExpr context graph = do+  relVars <- resolveDBC' graph context relationVariables+  incDeps <- resolveDBC' graph context inclusionDependencies+  let relVarsExprs = map (\(name, rel) -> Assign name (stripGraphRefRelationalExpr rel)) (M.toList relVars)+      incDepsExprs = map (uncurry AddInclusionDependency) (M.toList incDeps)+      funcsExprs = []+  pure $ MultipleExpr $ relVarsExprs ++ incDepsExprs ++ funcsExprs++resolveRoleIds :: (RoleName -> Maybe RoleId) -> DatabaseContextExprBase a RoleName -> Either RelationalError (DatabaseContextExprBase a RoleId)+resolveRoleIds resolver expr = do+  case expr of+    NoOperation -> pure NoOperation+    Define rv exprs -> pure (Define rv exprs)+    Undefine rv -> pure (Undefine rv)+    Assign rv expr' -> pure (Assign rv expr')+    Insert rv expr' -> pure (Insert rv expr')+    Delete rv expr' -> pure (Delete rv expr')+    Update rv atoms expr' -> pure (Update rv atoms expr')+    AddInclusionDependency iname idep -> pure (AddInclusionDependency iname idep)+    RemoveInclusionDependency iname -> pure (RemoveInclusionDependency iname)+    AddNotification notName exprA exprB exprC -> pure (AddNotification notName exprA exprB exprC)+    RemoveNotification notName -> pure (RemoveNotification notName)+    AddTypeConstructor tDef dDefs -> pure (AddTypeConstructor tDef dDefs)+    RemoveTypeConstructor tName -> pure (RemoveTypeConstructor tName)+    RemoveAtomFunction fName -> pure (RemoveAtomFunction fName)+    RemoveDatabaseContextFunction fname -> pure (RemoveDatabaseContextFunction fname)+    ExecuteDatabaseContextFunction fname exprs -> pure (ExecuteDatabaseContextFunction fname exprs)+    AddRegisteredQuery qName expr' -> pure (AddRegisteredQuery qName expr')+    RemoveRegisteredQuery qName -> pure (RemoveRegisteredQuery qName)+    AlterACL expr' -> AlterACL <$> resolveRoleIdsDBCACLExpr resolver expr'+    MultipleExpr exprs -> MultipleExpr <$> mapM (resolveRoleIds resolver) exprs++resolveRoleNames :: (RoleId -> Maybe RoleName) -> DatabaseContextExprBase a RoleId -> Either RelationalError (DatabaseContextExprBase a RoleName)+resolveRoleNames resolver expr = do+  case expr of+    NoOperation -> pure NoOperation+    Define rv exprs -> pure (Define rv exprs)+    Undefine rv -> pure (Undefine rv)+    Assign rv expr' -> pure (Assign rv expr')+    Insert rv expr' -> pure (Insert rv expr')+    Delete rv expr' -> pure (Delete rv expr')+    Update rv atoms expr' -> pure (Update rv atoms expr')+    AddInclusionDependency iname idep -> pure (AddInclusionDependency iname idep)+    RemoveInclusionDependency iname -> pure (RemoveInclusionDependency iname)+    AddNotification notName exprA exprB exprC -> pure (AddNotification notName exprA exprB exprC)+    RemoveNotification notName -> pure (RemoveNotification notName)+    AddTypeConstructor tDef dDefs -> pure (AddTypeConstructor tDef dDefs)+    RemoveTypeConstructor tName -> pure (RemoveTypeConstructor tName)+    RemoveAtomFunction fName -> pure (RemoveAtomFunction fName)+    RemoveDatabaseContextFunction fname -> pure (RemoveDatabaseContextFunction fname)+    ExecuteDatabaseContextFunction fname exprs -> pure (ExecuteDatabaseContextFunction fname exprs)+    AddRegisteredQuery qName expr' -> pure (AddRegisteredQuery qName expr')+    RemoveRegisteredQuery qName -> pure (RemoveRegisteredQuery qName)+    AlterACL alterACLExpr -> AlterACL <$> resolveRoleNamesDBCACLExpr resolver alterACLExpr+    MultipleExpr exprs -> MultipleExpr <$> mapM (resolveRoleNames resolver) exprs++resolveRoleIdsDBCACLExpr :: (RoleName -> Maybe RoleId) -> AlterDBCACLExprBase RoleName -> Either RelationalError (AlterDBCACLExprBase RoleId)+resolveRoleIdsDBCACLExpr resolver =+  recurseRoles resolver'+  where+    resolver' roleName = maybe (Left (NoSuchRoleNameError roleName)) pure (resolver roleName)++-- | convert role ids into role names or vice versa+recurseRoles :: (a -> Either RelationalError b) -> AlterDBCACLExprBase a -> Either RelationalError (AlterDBCACLExprBase b)+recurseRoles resolver =+  Fold.cataA recurse+  where+--    recurse :: AlterDBCACLExprBaseF RoleId (Either RelationalError (AlterDBCACLExprBase RoleName)) -> Either RelationalError (AlterDBCACLExprBase RoleName)+    recurse (GrantAccessExprF roleInfo perm mgrant) = do+      roleOut <- resolver roleInfo+      pure (GrantAccessExpr roleOut perm mgrant)+    recurse (RevokeAccessExprF roleInfo perm) = do+      roleOut <- resolver roleInfo+      pure (RevokeAccessExpr roleOut perm)+    recurse (GrantDBCFunctionAccessExprF roleInfo funcName' perm mgrant) = do+      roleOut <- resolver roleInfo+      pure (GrantDBCFunctionAccessExpr roleOut funcName' perm mgrant)+    recurse (RevokeDBCFunctionAccessExprF roleInfo funcName' perm) = do+      roleOut <- resolver roleInfo+      pure (RevokeDBCFunctionAccessExpr roleOut funcName' perm)++resolveRoleNamesDBCACLExpr :: (RoleId -> Maybe RoleName) -> AlterDBCACLExprBase RoleId -> Either RelationalError (AlterDBCACLExprBase RoleName)+resolveRoleNamesDBCACLExpr resolver = recurseRoles resolver'+  where+    resolver' roleId = maybe (Left NoSuchRoleIdError) pure (resolver roleId)+  +
src/lib/ProjectM36/DatabaseContextFunction.hs view
@@ -1,17 +1,16 @@-{-# LANGUAGE CPP #-} module ProjectM36.DatabaseContextFunction where --implements functions which operate as: [Atom] -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr import ProjectM36.Base import ProjectM36.Error-import ProjectM36.Serialise.Base () import ProjectM36.Attribute as A import ProjectM36.Relation import ProjectM36.AtomType import ProjectM36.Function+import ProjectM36.DatabaseContext.Types import qualified Data.HashSet as HS-import qualified Data.Map as M import qualified Data.Text as T import Data.Maybe (isJust)+import Data.Default  externalDatabaseContextFunction :: DatabaseContextFunctionBodyType -> DatabaseContextFunctionBody externalDatabaseContextFunction = FunctionBuiltInBody@@ -20,7 +19,8 @@ emptyDatabaseContextFunction name = Function {    funcName = name,   funcType = [],-  funcBody = FunctionBuiltInBody (\_ ctx -> pure ctx)+  funcBody = FunctionBuiltInBody (\_ _ ctx -> pure ctx),+  funcACL = def   }  databaseContextFunctionForName :: FunctionName -> DatabaseContextFunctions -> Either RelationalError DatabaseContextFunction@@ -29,36 +29,26 @@     [] -> Left $ NoSuchFunctionError funcName'     x : _ -> Right x -evalDatabaseContextFunction :: DatabaseContextFunction -> [Atom] -> DatabaseContext -> Either RelationalError DatabaseContext-evalDatabaseContextFunction func args ctx =-  case f args ctx of-    Left err -> Left (DatabaseContextFunctionUserError err)+evalDatabaseContextFunction :: DatabaseContextFunction -> DatabaseContextFunctionUtils -> [Atom] -> DatabaseContext -> Either RelationalError DatabaseContext+evalDatabaseContextFunction func utils args ctx =+  case f utils args ctx of+    Left err -> Left err     Right c -> pure c   where    f = function (funcBody func)-   -basicDatabaseContextFunctions :: DatabaseContextFunctions-basicDatabaseContextFunctions = HS.fromList [-  Function { funcName = "deleteAll",-             funcType = [],-             funcBody = FunctionBuiltInBody (\_ ctx -> pure $ ctx { relationVariables = M.empty })-           }-  ]-                                ---the precompiled functions are special because they cannot be serialized. Their names are therefore used in perpetuity so that the functions can be "serialized" (by name).-precompiledDatabaseContextFunctions :: DatabaseContextFunctions-precompiledDatabaseContextFunctions = HS.filter (not . isScriptedDatabaseContextFunction) basicDatabaseContextFunctions+                                                                     isScriptedDatabaseContextFunction :: DatabaseContextFunction -> Bool isScriptedDatabaseContextFunction func = isJust (functionScript func)    databaseContextFunctionReturnType :: TypeConstructor -> TypeConstructor databaseContextFunctionReturnType tCons = ADTypeConstructor "Either" [-  ADTypeConstructor "DatabaseContextFunctionError" [],+  ADTypeConstructor "RelationalError" [],   tCons]                                           -createScriptedDatabaseContextFunction :: FunctionName -> [TypeConstructor] -> TypeConstructor -> FunctionBodyScript -> DatabaseContextIOExpr+{-createScriptedDatabaseContextFunction :: FunctionName -> [TypeConstructor] -> TypeConstructor -> FunctionBodyScript -> DatabaseContextIOExpr createScriptedDatabaseContextFunction funcName' argsIn retArg = AddDatabaseContextFunction funcName' (argsIn ++ [databaseContextFunctionReturnType retArg])+-}  databaseContextFunctionsAsRelation :: DatabaseContextFunctions -> Either RelationalError Relation databaseContextFunctionsAsRelation dbcFuncs = mkRelationFromList attrs tups
− src/lib/ProjectM36/DatabaseContextFunctionError.hs
@@ -1,8 +0,0 @@-{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}-module ProjectM36.DatabaseContextFunctionError where-import GHC.Generics-import Control.DeepSeq--{-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-}-data DatabaseContextFunctionError = DatabaseContextFunctionUserError String-                                  deriving (Generic, Eq, Show, NFData)
src/lib/ProjectM36/DatabaseContextFunctionUtils.hs view
@@ -1,17 +1,18 @@ module ProjectM36.DatabaseContextFunctionUtils where import ProjectM36.RelationalExpression import ProjectM36.Base-import ProjectM36.DatabaseContextFunctionError import ProjectM36.Error+import ProjectM36.TransactionGraph.Types +import ProjectM36.DatabaseContext.Types as T import ProjectM36.StaticOptimizer -executeDatabaseContextExpr :: DatabaseContextExpr -> TransactionId -> TransactionGraph -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext-executeDatabaseContextExpr expr transId graph context' =+executeDatabaseContextExpr :: DatabaseContextExpr' -> TransactionId -> TransactionGraph -> DatabaseContext -> DatabaseContextFunctionUtils -> Either RelationalError DatabaseContext+executeDatabaseContextExpr expr transId graph context' dbcFuncUtils =   case run of     Right st -> pure (dbc_context st)     Left err -> error (show err)   where-    env = mkDatabaseContextEvalEnv transId graph+    env = mkDatabaseContextEvalEnv transId graph dbcFuncUtils     run = runDatabaseContextEvalMonad context' env (optimizeAndEvalDatabaseContextExpr True expr)    executeRelationalExpr :: RelationalExpr -> DatabaseContext -> TransactionGraph -> Either RelationalError Relation@@ -20,3 +21,11 @@   where     env = mkRelationalExprEnv context graph     run = optimizeAndEvalRelationalExpr env expr++-- used in tests where the utils don't matter+dud :: DatabaseContextFunctionUtils+dud = DatabaseContextFunctionUtils+  {+    T.executeDatabaseContextExpr = error "dud executeDatabaseContextExpr",+    T.executeRelationalExpr = error "dud executeRelationalExpr"+  }
+ src/lib/ProjectM36/DatabaseContextFunctions/Basic.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE TupleSections #-}+module ProjectM36.DatabaseContextFunctions.Basic where+import ProjectM36.DatabaseContextFunction+import ProjectM36.DatabaseContext.Types+import ProjectM36.ValueMarker+import ProjectM36.Base+import ProjectM36.AccessControlList+import qualified Data.HashSet as HS+import qualified Data.Map as M+import qualified Data.Set as S++basicDatabaseContextFunctions :: DatabaseContextFunctions+basicDatabaseContextFunctions = HS.fromList [+  Function { funcName = "deleteAll",+             funcType = [],+             funcBody = FunctionBuiltInBody (\_utils _ ctx -> pure $ ctx { relationVariables = emptyValue }),+             funcACL = defaultACL+           }+  ]++defaultACL :: DBCFunctionAccessControlList+defaultACL = AccessControlList (M.singleton adminRoleId (M.fromList (map (, True) (S.toList allPermissions))))++--the precompiled functions are special because they cannot be serialized. Their names are therefore used in perpetuity so that the functions can be "serialized" (by name).+precompiledDatabaseContextFunctions :: DatabaseContextFunctions+precompiledDatabaseContextFunctions = HS.filter (not . isScriptedDatabaseContextFunction) basicDatabaseContextFunctions
src/lib/ProjectM36/DateExamples.hs view
@@ -1,20 +1,32 @@ module ProjectM36.DateExamples where import ProjectM36.Base+import ProjectM36.Error+import ProjectM36.DatabaseContextExpr+import ProjectM36.DatabaseContext+import ProjectM36.TransactionGraph import qualified ProjectM36.Attribute as A import ProjectM36.Key+import ProjectM36.DatabaseContext.Basic+import ProjectM36.DatabaseContext.Types import ProjectM36.AtomFunctions.Basic import ProjectM36.DataTypes.Basic-import ProjectM36.DatabaseContext import ProjectM36.Relation+import ProjectM36.AccessControlList as ACL import qualified Data.Map as M import qualified Data.Set as S+import Data.Functor.Identity -dateExamples :: DatabaseContext-dateExamples = empty { inclusionDependencies = dateIncDeps,-                                 relationVariables = M.union (relationVariables basicDatabaseContext) dateRelVars,-                                 notifications = M.empty,-                                 atomFunctions = basicAtomFunctions,-                                 typeConstructorMapping = basicTypeConstructorMapping }+dateExamples :: ResolvedDatabaseContext+dateExamples = DatabaseContext {+  inclusionDependencies = Identity dateIncDeps,+  relationVariables = relationVariables basicDatabaseContext <> Identity dateRelVars,+  notifications = Identity M.empty,+  dbcFunctions = Identity mempty,+  atomFunctions = Identity basicAtomFunctions,+  registeredQueries = Identity mempty,+  typeConstructorMapping = Identity basicTypeConstructorMapping,+  acl = Identity ACL.basic+  }   where -- these must be lower case now that data constructors are in play     dateRelVars = M.fromList [("s", ExistingRelation suppliers),                               ("p", ExistingRelation products),@@ -88,3 +100,7 @@       [TextAtom "P5", TextAtom "Cam", TextAtom "Blue", IntegerAtom 12, TextAtom "Paris"],       [TextAtom "P6", TextAtom "Cog", TextAtom "Red", IntegerAtom 19, TextAtom "London"]       ]++dateExamplesDatabaseContextExpr :: Either RelationalError DatabaseContextExpr+dateExamplesDatabaseContextExpr =+  databaseContextAsDatabaseContextExpr (toDatabaseContext dateExamples) emptyTransactionGraph
src/lib/ProjectM36/DisconnectedTransaction.hs view
@@ -1,26 +1,57 @@+{-# LANGUAGE DeriveGeneric #-} module ProjectM36.DisconnectedTransaction where import ProjectM36.Base-import Data.Map+import qualified ProjectM36.IsomorphicSchema.Types as Schema+import ProjectM36.IsomorphicSchema.Types (Schemas)+import ProjectM36.DatabaseContext as DBC+import ProjectM36.DatabaseContext.Types as DBC+import GHC.Generics -concreteDatabaseContext :: DisconnectedTransaction -> DatabaseContext-concreteDatabaseContext (DisconnectedTransaction _ (Schemas context _) _) = context+-- | Every set of modifications made to the database are atomically committed to the transaction graph as a transaction. -schemas :: DisconnectedTransaction -> Schemas-schemas (DisconnectedTransaction _ s _) = s+-- | The disconnected transaction represents an in-progress workspace used by sessions before changes are committed. This is similar to git's "index". After a transaction is committed, it is "connected" in the transaction graph and can no longer be modified. -loadGraphRefRelVarsOnly :: TransactionId -> Schemas -> Schemas-loadGraphRefRelVarsOnly commitId (Schemas concreteCtx subschemas) = -  let f k _ = RelationVariable k (TransactionMarker commitId)-      ctx' = concreteCtx { relationVariables = mapWithKey f (relationVariables concreteCtx)}-  in Schemas ctx' subschemas+data DisconnectedTransaction = DisconnectedTransaction {+  disconTransactionId :: TransactionId,+  disconSchemas :: Schemas DatabaseContext,+  disconCurrentHead :: CurrentHead+  } +data CurrentHead = CurrentHeadBranch HeadName | -- ^ track a branch+                   CurrentHeadTransactionId TransactionId -- ^ peg to a transaction which is not a head+                   deriving (Show, Eq, Generic) +type TransactionRefSchemas = Schemas DatabaseContext +--the database context expression represents a difference between the disconnected transaction and its immutable parent transaction- is this diff expr used at all?++concreteDatabaseContext :: DisconnectedTransaction -> DatabaseContext+concreteDatabaseContext discon = Schema.concreteDatabaseContext (disconSchemas discon)+ parentId :: DisconnectedTransaction -> TransactionId-parentId (DisconnectedTransaction pid _ _) = pid+parentId = disconTransactionId -isDirty :: DisconnectedTransaction -> Bool-isDirty (DisconnectedTransaction _ _ flag) = flag+isUpdated :: DisconnectedTransaction -> Bool+isUpdated disconTrans = DBC.isUpdated (concreteDatabaseContext disconTrans) -freshTransaction :: TransactionId -> Schemas -> DisconnectedTransaction-freshTransaction tid schemas' = DisconnectedTransaction tid schemas' False+-- | Create a fresh (unchanged marker-ed) disconnected transaction- used after a commit so all database context values should refer to previous transactions.+freshTransaction :: CurrentHead -> TransactionId -> TransactionRefSchemas -> DisconnectedTransaction+freshTransaction head' tid schemas =+  DisconnectedTransaction {+  disconTransactionId = tid,+  disconSchemas = schemas { Schema.concreteDatabaseContext = freshDatabaseContext tid (Schema.concreteDatabaseContext schemas) },+  disconCurrentHead = head'+  }+++freshTransaction' :: CurrentHead -> TransactionId -> Schemas DatabaseContext -> DisconnectedTransaction+freshTransaction' head' tid schemas =+  DisconnectedTransaction {+  disconTransactionId = tid,+  disconSchemas = schemas',+  disconCurrentHead = head'+  }+  where+    schemas' = schemas { Schema.concreteDatabaseContext = freshDatabaseContext tid (Schema.concreteDatabaseContext schemas)+                       }+
src/lib/ProjectM36/Error.hs view
@@ -2,8 +2,9 @@ module ProjectM36.Error where import ProjectM36.Base import ProjectM36.MerkleHash-import ProjectM36.DatabaseContextFunctionError import ProjectM36.AtomFunctionError+import ProjectM36.IsomorphicSchema.Types+import ProjectM36.DatabaseContext.Fields (DatabaseContextField) import qualified Data.Set as S import Control.DeepSeq (NFData, rnf) import Control.DeepSeq.Generics (genericRnf)@@ -12,6 +13,7 @@ import Data.Typeable import Control.Exception import ProjectM36.SQL.Select+import ProjectM36.AccessControlList  data RelationalError = NoSuchAttributeNamesError (S.Set AttributeName)                      | TupleAttributeCountMismatchError Int --attribute name@@ -26,6 +28,7 @@                      | CouldNotInferAttributes                      | RelVarNotDefinedError RelVarName                      | RelVarAlreadyDefinedError RelVarName+                     | RelVarNameShadowingForbiddenError RelVarName -- can happen with with macro expressions                      | RelationTypeMismatchError Attributes Attributes --expected, found                      | InclusionDependencyCheckError IncDepName (Maybe RelationalError)                      | InclusionDependencyNameInUseError IncDepName@@ -37,6 +40,7 @@                      | RootTransactionTraversalError                       | HeadNameSwitchingHeadProhibitedError HeadName                      | NoSuchHeadNameError HeadName+                     | HeadNameAlreadyInUseError HeadName                      | UnknownHeadError                      | NewTransactionMayNotHaveChildrenError TransactionId                      | ParentCountTraversalError Int Int --maximum, requested@@ -87,12 +91,13 @@                      | NotificationValidationError NotificationName NotificationExpression RelationalError                      | ImportError ImportError'                      | ExportError T.Text+                     | SystemError String                      | UnhandledExceptionError String                      | MergeTransactionError MergeError                      | ScriptError ScriptCompilationError                      | LoadFunctionError                      | SecurityLoadFunctionError-                     | DatabaseContextFunctionUserError DatabaseContextFunctionError+                     | DatabaseContextFunctionUserError String                      | DatabaseLoadError PersistenceError                                              | SubschemaNameInUseError SchemaName@@ -112,9 +117,13 @@                      | RegisteredQueryValidationError RegisteredQueryName RelationalError                      | RegisteredQueryNameInUseError RegisteredQueryName                      | RegisteredQueryNameNotInUseError RegisteredQueryName-+                     | AccessDeniedError SomePermission+                                           | SQLConversionError SQLError +                     | NoSuchRoleNameError RoleName+                     | NoSuchRoleIdError -- don't leak role ids+                      | MultipleErrors [RelationalError]                        deriving (Show,Eq,Generic,Typeable, NFData)  @@ -134,7 +143,7 @@                   StrategyViolatesConstraintMergeError |                   InvalidMergeStrategyError MergeStrategy | -- this is an internal coding error                   DisconnectedTransactionNotAMergeHeadError TransactionId |-                  StrategyViolatesComponentMergeError | --failed merge in inc deps, relvars, etc.+                  StrategyViolatesComponentMergeError DatabaseContextField StringType | --failed merge in inc deps, relvars, etc.                   StrategyViolatesRelationVariableMergeError RelationalError |                   StrategyWithoutPreferredBranchResolutionMergeError |                   StrategyViolatesTypeConstructorMergeError |@@ -149,7 +158,8 @@                               OtherScriptCompilationError String                             deriving (Show, Eq, Generic, Typeable, NFData)                                      -instance Exception ScriptCompilationError                                     +instance Exception ScriptCompilationError+instance Exception RelationalError                                                 data SchemaError = RelVarReferencesMissing (S.Set RelVarName) |                    RelVarInReferencedMoreThanOnce RelVarName |
src/lib/ProjectM36/FSType.hs view
@@ -67,22 +67,15 @@ import Foreign.C.String import Foreign.C.Types -#include "MachDeps.h" --Linux cannot report journaling, so we just check the filesystem type as a proxy type CStatFS = () foreign import ccall unsafe "sys/vfs.h statfs"    c_statfs :: CString -> Ptr CStatFS -> IO CInt-  -#if WORD_SIZE_IN_BITS == 64++-- warning: hardcoded to 64-bit statfs type CFSType = Word64 sizeofStructStatFS :: Int sizeofStructStatFS = 120-#else-#error 32-bit not supported due to sizeof struct statfs missing-type CFSType = Word32-sizeofStructStatFS :: Int-sizeofStructStatFS = undefined-#endif  fsTypeSupportsJournaling :: FilePath -> IO Bool fsTypeSupportsJournaling path = do@@ -97,6 +90,8 @@                          0x58465342, --XFS                          0x3153464a --JFS                          ]-      pure (elem cfstype journaledFS)+      pure (cfstype `elem` journaledFS)+#else+#error Failed to match OS. #endif 
src/lib/ProjectM36/Function.hs view
@@ -2,7 +2,7 @@ module ProjectM36.Function where import ProjectM36.Base import ProjectM36.Error-import ProjectM36.Serialise.Base ()+--import ProjectM36.Serialise.Base () import ProjectM36.ScriptSession import qualified Data.HashSet as HS @@ -15,7 +15,7 @@ function (FunctionObjectLoadedBody _ _ _ f) = f  -- | Return the text-based Haskell script, if applicable.-functionScript :: Function a -> Maybe FunctionBodyScript+functionScript :: Function a acl -> Maybe FunctionBodyScript functionScript func = case funcBody func of   FunctionScriptBody script _ -> Just script   _ -> Nothing@@ -27,11 +27,11 @@   where     f = function body -processObjectLoadedFunctions :: Functor f => ObjectModuleName -> ObjectFileEntryFunctionName -> FilePath -> f (Function a) -> f (Function a)+processObjectLoadedFunctions :: Functor f => ObjectModuleName -> ObjectFileEntryFunctionName -> FilePath -> f (Function a acl) -> f (Function a acl) processObjectLoadedFunctions modName entryName path =   fmap (\f -> f { funcBody = processObjectLoadedFunctionBody modName entryName path (funcBody f) } ) -loadFunctions :: ModName -> FuncName -> Maybe FilePath -> FilePath -> IO (Either LoadSymbolError [Function a])+loadFunctions :: ModName -> FuncName -> Maybe FilePath -> FilePath -> IO (Either LoadSymbolError [Function a acl]) #ifdef PM36_HASKELL_SCRIPTING loadFunctions modName funcName' mModDir objPath =   case mModDir of@@ -51,7 +51,7 @@ loadFunctions _ _ _ _ = pure (Left LoadSymbolError) #endif -functionForName :: FunctionName -> HS.HashSet (Function a) -> Either RelationalError (Function a)+functionForName :: FunctionName -> HS.HashSet (Function a acl) -> Either RelationalError (Function a acl) functionForName funcName' funcSet =   case HS.toList $ HS.filter (\f -> funcName f == funcName') funcSet of     [] -> Left $ NoSuchFunctionError funcName'
src/lib/ProjectM36/GraphRefRelationalExpr.hs view
@@ -2,6 +2,7 @@ --evaluate relational expressions across the entire transaction graph to support cross-transaction referencing import ProjectM36.Base import qualified Data.Set as S+import qualified Data.Functor.Foldable as Fold  data SingularTransactionRef = SingularTransactionRef GraphRefTransactionMarker |                               MultipleTransactionsRef |@@ -40,3 +41,11 @@  singularTransactions :: (Foldable f, Foldable t) => f (t GraphRefTransactionMarker) -> SingularTransactionRef singularTransactions = foldMap singularTransaction++-- | Decompose a GraphRefRelationalExpr into its constituent references to relvars at transactionIds. Useful for determining which relvars in the transaction graph the expression references; for example, for security checks.+decomposeGraphRefTransactionMarkers :: GraphRefRelationalExpr -> S.Set (GraphRefTransactionMarker, RelVarName)+decomposeGraphRefTransactionMarkers = Fold.cata decomp+  where+    decomp :: RelationalExprBaseF GraphRefTransactionMarker (S.Set (GraphRefTransactionMarker, RelVarName)) -> S.Set (GraphRefTransactionMarker, RelVarName)+    decomp (RelationVariableF rvname marker) = S.singleton (marker, rvname)+    decomp other = foldr S.union mempty other
src/lib/ProjectM36/HashSecurely.hs view
@@ -8,11 +8,14 @@ import ProjectM36.Base import ProjectM36.Tuple (tupleAttributes, tupleAtoms) import ProjectM36.Serialise.Base ()+import ProjectM36.IsomorphicSchema.Types hiding (subschemas, concreteDatabaseContext) import ProjectM36.IsomorphicSchema-import ProjectM36.Transaction+import ProjectM36.Transaction.Types+import ProjectM36.DatabaseContext.Types import qualified Data.HashSet as HS import qualified ProjectM36.DataConstructorDef as DC import ProjectM36.MerkleHash+import ProjectM36.ValueMarker import Data.List (sortOn) import qualified Data.Map as M import qualified ProjectM36.TypeConstructorDef as TCons@@ -25,6 +28,7 @@ import Data.Time.Calendar import Data.Time.Clock import Codec.Winery (Serialise)+import Data.Functor.Identity  newtype SecureHash = SecureHash { _unSecureHash :: B.ByteString }   deriving (Serialise, Show, Eq)@@ -226,7 +230,7 @@   hashBytes (Schema morphs) ctx =     hashBytesL ctx "Schema" (map SHash (sortOn sortIso morphs))     where-      sortIso iso = mconcat (isomorphInRelVarNames iso)+      sortIso iso' = mconcat (isomorphInRelVarNames iso')                               instance HashBytes SchemaIsomorph where@@ -264,8 +268,15 @@                                       SHash (notifications db),                                       SHash (typeConstructorMapping db),                                       SHash (atomFunctions db),-                                      SHash (dbcFunctions db)]-+                                      SHash (dbcFunctions db)+                                     ]+  +instance HashBytes a => HashBytes (ValueMarker a) where +  hashBytes (ValueMarker a) ctx =+    hashBytesL ctx "ValueMarker" [SHash a]+  hashBytes (NotChangedSinceMarker tid) ctx =+    hashBytesL ctx "NotChangedSinceMarker" [SHash tid]+     instance HashBytes InclusionDependencies where   hashBytes incDeps ctx =     hashBytesL ctx "InclusionDependencies" (map SHash (M.toAscList incDeps))@@ -341,14 +352,18 @@   hashBytes m ctx =     hashBytesL ctx "rvtypes" (map SHash (M.toAscList m)) +instance HashBytes Subschemas where+  hashBytes m ctx =+    hashBytesL ctx "subschemas" (map SHash (M.toAscList m))+ -- | Hash a transaction within its graph context to create a Merkle hash for it. hashTransaction :: Transaction -> S.Set Transaction -> MerkleHash hashTransaction trans parentTranses = MerkleHash (SHA256.finalize newHash)   where     newHash = hashBytesL SHA256.init "Transaction" (map SHash transIds <>-                                         map SHash (M.toAscList (subschemas trans)) <>                                          map SHash parentMerkleHashes <>                                          [SHash tstamp,+                                         SHash (subschemas trans),                                          SHash (concreteDatabaseContext trans)]                                                    )     tstamp = stamp (transactionInfo trans)@@ -356,13 +371,15 @@     getMerkleHash t = merkleHash (transactionInfo t)     transIds = transactionId trans : S.toAscList (parentIds trans) --- | Return a hash of just DDL-specific (schema) attributes. This is useful for determining if a client has the appropriate updates needed to work with the current schema.-mkDDLHash :: DatabaseContext -> M.Map RelVarName Relation -> SecureHash+-- | Return a hash of just DDL-specific (schema) attributes. This is useful for determining if a client has the appropriate updates needed to work with the current schema. DDL hashing only works on a fully-resolved database context so that the hash is valida even across different servers which may converge on the same database DDL.+mkDDLHash :: ResolvedDatabaseContext -> M.Map RelVarName Relation -> SecureHash mkDDLHash ctx rvtypemap = do   -- we cannot merely hash the relational representation of the type because the order of items matters when hashing   -- registered queries are not included here because a client could be compatible with a schema even if the queries are not registered. The client should validate registered query state up-front. Perhaps there should be another hash for registered queries.-  SecureHash $ SHA256.finalize $ hashBytesL SHA256.init "DDLHash" [SHash (inclusionDependencies ctx),-                                                                    SHash (atomFunctions ctx),-                                                                    SHash (dbcFunctions ctx),-                                                                    SHash (typeConstructorMapping ctx),-                                                                    SHash rvtypemap]+  SecureHash $ SHA256.finalize $+    hashBytesL SHA256.init "DDLHash" [SHash (runIdentity $ inclusionDependencies ctx),+                                      SHash (runIdentity $ atomFunctions ctx),+                                      SHash (runIdentity $ dbcFunctions ctx),+                                      SHash (runIdentity $ typeConstructorMapping ctx),+                                      SHash rvtypemap+                                     ]
src/lib/ProjectM36/IsomorphicSchema.hs view
@@ -5,6 +5,9 @@ import ProjectM36.MiscUtils import ProjectM36.Relation import ProjectM36.NormalizeExpr+import ProjectM36.DatabaseContext.Types+import ProjectM36.TransactionGraph.Types+import ProjectM36.IsomorphicSchema.Types import ProjectM36.RelationalExpression import qualified ProjectM36.AttributeNames as AN import Control.Monad@@ -40,8 +43,8 @@ -- | Return an error if the schema is not isomorphic to the base database context. -- A schema is fully isomorphic iff all relvars in the base context are in the "out" relvars, but only once. --TODO: add relvar must appear exactly once constraint-validateSchema :: Schema -> DatabaseContext -> Maybe SchemaError-validateSchema potentialSchema baseContext+validateSchema :: Schema -> S.Set RelVarName -> Maybe SchemaError+validateSchema potentialSchema expectedRelVars   | not (S.null rvDiff) = Just (RelVarReferencesMissing rvDiff)   | otherwise = case outDupes of       x : _ -> Just $ RelVarOutReferencedMoreThanOnce x@@ -54,7 +57,6 @@     inDupes = duplicateNames (namesList isomorphInRelVarNames)     duplicateNames = dupes . L.sort     namesList isoFunc = concatMap isoFunc (isomorphs potentialSchema)-    expectedRelVars = M.keysSet (relationVariables baseContext)     schemaRelVars = isomorphsOutRelVarNames (isomorphs potentialSchema)     rvDiff = S.difference expectedRelVars schemaRelVars @@ -102,10 +104,10 @@   validateRelationalExprInSchema schema relExprIn   foldM processRelExpr relExprIn (isomorphs schema) -validateDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError ()+validateDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr' -> Either RelationalError () validateDatabaseContextExprInSchema schema dbExpr = mapM_ (\morph -> databaseContextExprMorph morph (\e -> validateRelationalExprInSchema schema e >> pure e) dbExpr) (isomorphs schema) -processDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr+processDatabaseContextExprInSchema :: Schema -> DatabaseContextExpr' -> Either RelationalError DatabaseContextExpr' processDatabaseContextExprInSchema schema@(Schema morphs) dbExpr = do   let relExprMogrifier = processRelationalExprInSchema schema   --validate that all mentioned relvars are in the valid set@@ -126,7 +128,7 @@     passthru rvname = Schema (morphs ++ [IsoRename rvname rvname])  processDatabaseContextExprSchemasUpdate :: Subschemas -> DatabaseContextExpr -> Subschemas-processDatabaseContextExprSchemasUpdate subschemas expr = M.map (`processDatabaseContextExprSchemaUpdate` expr) subschemas+processDatabaseContextExprSchemasUpdate subschemas' expr = M.map (`processDatabaseContextExprSchemaUpdate` expr) subschemas'  -- 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! -- 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.@@ -194,8 +196,8 @@ spam2 = relExprMogrify (relExprMorph (IsoUnion ("boss", Just "nonboss") TruePredicate "emp")) (RelationVariable "boss" ()) -} -databaseContextExprMorph :: SchemaIsomorph  -> (RelationalExpr -> Either RelationalError RelationalExpr) -> DatabaseContextExpr -> Either RelationalError DatabaseContextExpr-databaseContextExprMorph iso@(IsoRestrict rvIn filt (rvTrue, rvFalse)) relExprFunc expr = case expr of+databaseContextExprMorph :: SchemaIsomorph  -> (RelationalExpr -> Either RelationalError RelationalExpr) -> DatabaseContextExpr' -> Either RelationalError DatabaseContextExpr'+databaseContextExprMorph iso'@(IsoRestrict rvIn filt (rvTrue, rvFalse)) relExprFunc expr = case expr of   Assign rv relExpr | rv == rvIn -> do     ex <- relExprFunc relExpr     let trueExpr n = Assign n (Restrict filt ex)@@ -211,9 +213,9 @@     let trueExpr n = Update n attrMap (AndPredicate predi filt)         falseExpr n = Update n attrMap (AndPredicate predi (NotPredicate filt))     pure (MultipleExpr [trueExpr rvTrue, falseExpr rvFalse])-  MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso relExprFunc) exprs+  MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso' relExprFunc) exprs   orig -> pure orig-databaseContextExprMorph iso@(IsoUnion (rvTrue, rvFalse) filt rvOut) relExprFunc expr = case expr of+databaseContextExprMorph iso'@(IsoUnion (rvTrue, rvFalse) filt rvOut) relExprFunc expr = case expr of   --assign: replace all instances in the portion of the target relvar with the new tuples from the relExpr   --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?   -- if any tuples are filtered out of the insert/assign, we need to simulate a constraint violation@@ -226,14 +228,14 @@   Delete rv delPred | rv == rvFalse -> pure $ Delete rvOut (AndPredicate delPred (NotPredicate filt))   Update rv attrMap predi | rv == rvTrue -> pure $ Update rvOut attrMap (AndPredicate predi filt)   Update rv attrMap predi | rv == rvFalse -> pure $ Update rvOut attrMap (AndPredicate (NotPredicate filt) predi)-  MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso relExprFunc) exprs+  MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso' relExprFunc) exprs   orig -> pure orig-databaseContextExprMorph iso@(IsoRename relIn relOut) relExprFunc expr = case expr of+databaseContextExprMorph iso'@(IsoRename relIn relOut) relExprFunc expr = case expr of   Assign rv relExpr | rv == relIn -> relExprFunc relExpr >>= \ex -> pure (Assign relOut ex)   Insert rv relExpr | rv == relIn -> relExprFunc relExpr >>= \ex -> pure $ Insert relOut ex   Delete rv delPred | rv == relIn -> pure $ Delete relOut delPred   Update rv attrMap predi | rv == relIn -> pure $ Update relOut attrMap predi-  MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso relExprFunc) exprs+  MultipleExpr exprs -> MultipleExpr <$> mapM (databaseContextExprMorph iso' relExprFunc) exprs   orig -> pure orig  -- | Apply the isomorphism transformations to the relational expression to convert the relational expression from operating on one schema to a disparate, isomorphic schema.@@ -347,24 +349,25 @@ createIncDepsForIsomorph _ _ = M.empty  -- 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-evalSchemaExpr :: SchemaExpr -> DatabaseContext -> TransactionId -> TransactionGraph -> Subschemas -> Either RelationalError (Subschemas, DatabaseContext)-evalSchemaExpr (AddSubschema sname morphs) context transId graph sschemas =+evalSchemaExpr :: SchemaExpr -> DatabaseContext -> TransactionId -> TransactionGraph -> DatabaseContextFunctionUtils -> Subschemas -> Either RelationalError (Subschemas, DatabaseContext)+evalSchemaExpr (AddSubschema sname morphs) context transId graph dbcfuncutils sschemas =   if M.member sname sschemas then     Left (SubschemaNameInUseError sname)-    else-    case validateSchema (Schema morphs) context of+    else do+    relVars <- resolveDBC' graph context relationVariables+    case validateSchema (Schema morphs) (M.keysSet relVars) of       Just err -> Left (SchemaCreationError err)       Nothing -> do         let newSchemas = M.insert sname newSchema sschemas             newSchema = Schema morphs             moreIncDeps = foldr (\morph acc -> M.union acc (createIncDepsForIsomorph sname morph)) M.empty morphs             incDepExprs = MultipleExpr (map (uncurry AddInclusionDependency) (M.toList moreIncDeps))-            dbenv = mkDatabaseContextEvalEnv transId graph+            dbenv = mkDatabaseContextEvalEnv transId graph dbcfuncutils         dbstate <- runDatabaseContextEvalMonad context dbenv (evalGraphRefDatabaseContextExpr incDepExprs)         pure (newSchemas, dbc_context dbstate) --need to propagate dirty flag here -evalSchemaExpr (RemoveSubschema sname) context _ _ sschemas = if M.member sname sschemas then+evalSchemaExpr (RemoveSubschema sname) context _ _ _ sschemas = if M.member sname sschemas then                                            pure (M.delete sname sschemas, context)                                          else                                            Left (SubschemaNameNotInUseError sname)
+ src/lib/ProjectM36/IsomorphicSchema/Types.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE DeriveGeneric #-}+module ProjectM36.IsomorphicSchema.Types where+import GHC.Generics+import ProjectM36.Base+import ProjectM36.ValueMarker+import qualified Data.Map as M++-- | Every transaction has one concrete database context and any number of isomorphic subschemas.+data Schemas ctx = Schemas {+  concreteDatabaseContext :: ctx,+  subschemas :: ValueMarker Subschemas+  }+  deriving (Generic)++type Subschemas = M.Map SchemaName Schema++type SchemaName = StringType                         ++newtype Schema = Schema SchemaIsomorphs+              deriving (Generic, Eq)+                              +data SchemaIsomorph = IsoRestrict RelVarName RestrictionPredicateExpr (RelVarName, RelVarName) | +                      IsoRename RelVarName RelVarName |+                      IsoUnion (RelVarName, RelVarName) RestrictionPredicateExpr RelVarName  --maps two relvars to one relvar+                      -- IsoTypeConstructor in morphAttrExpr+                      deriving (Generic, Show, Eq)+                      +type SchemaIsomorphs = [SchemaIsomorph]
+ src/lib/ProjectM36/LoginRoles.hs view
@@ -0,0 +1,347 @@+-- | Provides a simple database mapping login names to role IDs (UUIDs). Only role names which map to a role ID are allowed to log in.+{-# LANGUAGE ScopedTypeVariables, TypeApplications, DeriveGeneric #-}+module ProjectM36.LoginRoles where+import ProjectM36.AccessControlList+import qualified Database.SQLite.Simple as SQL+import Data.UUID (fromString, toText, fromText)+import Data.UUID.V4 (nextRandom)+import Data.Text (Text)+import Data.Maybe+import GHC.Generics+import ProjectM36.Base+import qualified Data.Text as T+import Control.Monad (forM_, void)++type LoginRolesDB = SQL.Connection++type MayLogin = Bool++-- | Used by console programs to change this database of roles which are allowed to login.+data AlterLoginRolesExpr = ShowRolesForRoleExpr RoleName | -- ^ show roles available to the given role+                           AddLoginRoleExpr RoleName MayLogin | -- ^ add a login role, requires admin role+                           RemoveLoginRoleExpr RoleName | -- ^ remove a login role, requires admin role+                           AlterLoginRoleExpr RoleName (Maybe RoleName) (Maybe MayLogin) | -- ^ alter an existing role's name or login right+                           AddRoleToRoleExpr RoleName RoleName MayGrant | -- ^ add a role to an existing role+                           RemoveRoleFromRoleExpr RoleName RoleName | -- ^ remove a role from an existing role+                           AddPermissionToRoleExpr RoleName Permission MayGrant |+                           RemovePermissionFromRoleExpr RoleName Permission |+                           ShowAllRolesExpr -- ^ display all available roles+                           deriving (Eq, Generic, Show)++data LoginRoleError = PermissionDeniedError |+                      NoSuchRoleNameError RoleName |+                      InvalidRoleIdError Text |+                      NoSuchPermissionError Permission |+                      RoleNameAlreadyExistsError RoleName |+                      RoleNameConflictError RoleName+                      deriving (Eq, Generic, Show)++viewLoginRolesPerm :: Permission+viewLoginRolesPerm = "view_login_roles"++alterLoginRolesPerm :: Permission+alterLoginRolesPerm = "alter_login_roles"++openNoPersistence :: IO LoginRolesDB+openNoPersistence = open ":memory:"++open :: String -> IO LoginRolesDB+open openText = do+  db <- SQL.open openText+  setupDatabaseIfNecessary db+  pure db++adminRoleName :: RoleName  +adminRoleName = "admin"++withTransaction :: LoginRolesDB -> IO a -> IO a+withTransaction = SQL.withTransaction++setupDatabaseIfNecessary :: LoginRolesDB -> IO ()+setupDatabaseIfNecessary db = do+  SQL.execute_ db "PRAGMA foreign_keys = ON;"+  SQL.withTransaction db $ do+    res <- SQL.query_ @(SQL.Only Int) db "SELECT 1 FROM sqlite_master WHERE type='table' AND name='login_role_name'"+    case res of+      [SQL.Only _x] -> pure ()+      _ -> do+        let dbsetup =+              ["CREATE TABLE login_role_name(rolename TEXT PRIMARY KEY, roleid TEXT NOT NULL UNIQUE, maylogin BOOLEAN NOT NULL, UNIQUE(rolename), UNIQUE(roleid))",+                "CREATE TABLE permission(name TEXT PRIMARY KEY)",+                "CREATE TABLE login_role_permission(roleid TEXT NOT NULL REFERENCES login_role_name(roleid), permission TEXT NOT NULL REFERENCES permission(name), may_grant BOOLEAN NOT NULL, UNIQUE(roleid, permission, may_grant))",+                "CREATE TABLE login_role_member (roleid TEXT NOT NULL REFERENCES login_role_name(roleid), memberof TEXT NOT NULL, maygrant BOOLEAN NOT NULL, UNIQUE(roleid, memberof))",+              -- create login permissions+              "INSERT INTO permission(name) VALUES ('login'),('alter_login_roles'),('view_login_roles')"]+        forM_ dbsetup $ \sql ->+          SQL.execute_ db sql+        -- add admin role+        SQL.execute db "INSERT INTO login_role_name(rolename, roleid, maylogin) VALUES ('admin',?, true)" (SQL.Only (toText adminRoleId))+        -- grant full permission to admin role+        SQL.execute db "INSERT INTO login_role_permission(roleid, permission,may_grant) VALUES (?,'login',true), (?, 'alter_login_roles',true), (?, 'view_login_roles',true)" (toText adminRoleId, toText adminRoleId, toText adminRoleId)++-- | Determine if the role name has the privilege to login to the database. Some roles are prohibited from logging in.+roleNameMayLogin :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError Bool)+roleNameMayLogin roleName db = do+  res <- SQL.query db "SELECT maylogin FROM login_role_name WHERE rolename = ?" (SQL.Only roleName)+  case res of+    [val] -> pure (Right (SQL.fromOnly val))+    _ -> pure (Left (NoSuchRoleNameError roleName))+  +-- | Test for a role name to role ID mapping. If a role id is found, the role is allowed to login to the database.+roleIdsForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError [RoleId])+roleIdsForRoleName roleName db = do+  --with recursive query to extract all roles+  let q = "with recursive cte as (select NULL as roleid,roleid as memberof FROM login_role_name WHERE rolename=? union all select lrm.roleid,lrm.memberof FROM login_role_member as lrm JOIN cte AS c ON c.memberof = lrm.roleid) select memberof from cte;"+  res <- SQL.query db q (SQL.Only roleName)+  pure (Right (map (\x -> case fromString (SQL.fromOnly x) of+                     Nothing -> error "invalid uuid"+                     Just val -> val) res))++roleNamesForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError [(RoleName, MayGrant)])+roleNamesForRoleName roleName db = do+  let q = "with recursive cte as (select lrm.roleid,lrm.memberof,lrm.maygrant FROM login_role_member as lrm WHERE roleid=(SELECT roleid FROM login_role_name WHERE rolename=?) union all select lrm.roleid,lrm.memberof,lrm.maygrant FROM login_role_member as lrm JOIN cte AS c ON c.memberof = lrm.roleid) select lrn.rolename,cte.maygrant from cte join login_role_name AS lrn ON lrn.roleid = cte.memberof union all select rolename,false as maygrant FROM login_role_name WHERE rolename=?;"+  eTargetId <- roleIdForRoleName roleName db+  case eTargetId of+    Left err -> pure (Left err)+    Right _ -> do+      roleNamesAndGrants <- SQL.query db q (roleName, roleName)+      pure (Right roleNamesAndGrants)++permissionsForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError [Permission])+permissionsForRoleName roleName db = do+  -- collect all roles for the role name+  -- then collect all the permissions for those roles  +  let q = "with recursive cte as (select NULL as roleid,roleid as memberof FROM login_role_name WHERE rolename=? union all select lrm.roleid,lrm.memberof FROM login_role_member as lrm JOIN cte AS c ON c.memberof = lrm.roleid) select permission from login_role_permission WHERE roleid IN (SELECT memberof FROM cte);"+  perms' <- SQL.query db q (SQL.Only roleName)+  pure (Right (map SQL.fromOnly perms'))+  +roleHasPermission :: RoleName -> Permission -> LoginRolesDB -> IO (Either LoginRoleError Bool)+roleHasPermission roleName perm db = do+  eTargetId <- roleIdForRoleName roleName db+  case eTargetId of+    Left err -> pure (Left err)+    Right _ -> do+      let q = "with recursive cte as (select NULL as roleid,roleid as memberof FROM login_role_name WHERE rolename=? union all select lrm.roleid,lrm.memberof FROM login_role_member as lrm JOIN cte AS c ON c.memberof = lrm.roleid),perms AS (select permission from login_role_permission WHERE roleid IN (SELECT memberof FROM cte)) SELECT 1 FROM perms WHERE permission = ?;"+      perms' <- SQL.query @_ @(SQL.Only Int) db q (roleName, perm)+      pure $ case perms' of+        [SQL.Only 1] -> Right True+        _ -> Right False++-- | Add new role name to the database with optional login privilege.+addRoleName :: RoleName -> RoleId -> MayLogin -> LoginRolesDB -> IO (Either LoginRoleError ())+addRoleName roleName newRoleId maylogin db = do+  SQL.execute db "INSERT INTO login_role_name(rolename, roleid, maylogin) VALUES (?,?,?) ON CONFLICT(rolename) DO NOTHING" (roleName, show newRoleId, maylogin)+  c <- SQL.changes db+  if c > 0 then+    pure (Right ())+    else+    pure (Left (RoleNameAlreadyExistsError roleName))++-- Change and existing role's name or login ability.+alterRoleName :: RoleName -> Maybe RoleName -> Maybe MayLogin -> LoginRolesDB -> IO (Either LoginRoleError ())+alterRoleName origRoleName mNewRoleName mMayLogin db = do+  eTargetId <- roleIdForRoleName origRoleName db+  case eTargetId of+      Left err -> pure (Left err)+      Right targetId -> do+        case mNewRoleName of+          Nothing -> pure ()+          Just newRoleName -> +            void $ SQL.execute db "UPDATE login_role_name SET rolename=? WHERE roleid=?" (newRoleName, toText targetId)+        case mMayLogin of+          Nothing -> pure ()+          Just mayLogin ->+            void $ SQL.execute db "UPDATE login_role_name SET maylogin=? WHERE roleid=?" (mayLogin, toText targetId)+        pure (Right ())++-- | Grant role access to other role.+addRoleToRoleName :: RoleName -> RoleName -> MayGrant -> LoginRolesDB -> IO (Either LoginRoleError ())+addRoleToRoleName roleNameTarget addToRoleName mayGrant db = do+  -- TODO: prevent infinite loops in roles+    eTargetId <- roleIdForRoleName roleNameTarget db+    eAddId <- roleIdForRoleName addToRoleName db+    case eTargetId of+      Left err -> pure (Left err)+      Right targetId ->+        case eAddId of+          Left err -> pure (Left err)+          Right addId -> do+            let q = "INSERT INTO login_role_member(roleid,memberof,maygrant) VALUES (?,?,?)-- ON CONFLICT DO NOTHING"+            SQL.execute db q (toText targetId, toText addId, mayGrant)+            c <- SQL.changes db+            if c == 0 then+              pure (Left (RoleNameConflictError addToRoleName))+              else+              pure (Right ())++removeRoleFromRoleName :: RoleName -> RoleName -> LoginRolesDB -> IO (Either LoginRoleError ())+removeRoleFromRoleName roleNameTarget fromRole db = do+  eTargetId <- roleIdForRoleName roleNameTarget db+  eDelId <- roleIdForRoleName fromRole db+  case eTargetId of+    Left err -> pure (Left err)+    Right targetId ->+      case eDelId of+        Left err -> pure (Left err)+        Right delId -> do+          let q = "DELETE FROM login_role_member WHERE roleid=? AND memberof=?"+          SQL.execute db q (toText targetId, toText delId)+          c <- SQL.changes db +          if c > 0 then+            pure (Right ())+            else+            pure (Left (NoSuchRoleNameError roleNameTarget))++addPermissionToRoleName :: RoleName -> Permission -> Bool -> LoginRolesDB -> IO (Either LoginRoleError ())+addPermissionToRoleName roleName perm mayGrant db = do+  eTargetId <- roleIdForRoleName roleName db+  case eTargetId of+    Left err -> pure (Left err)+    Right targetId -> do+      let q = "INSERT INTO login_role_permission(roleid,permission,may_grant) VALUES (?,?,?)"+      SQL.execute db q (toText targetId, perm, mayGrant)+      pure (Right ())++-- | Whether or not the granting role has permission to grant the role. To whom is irrelevant.+roleMayGrantRole :: RoleName -> RoleName -> LoginRolesDB -> IO (Either LoginRoleError Bool)+roleMayGrantRole grantingRole roleToGrant db = do+  eroles <- roleNamesForRoleName grantingRole db+  case eroles of+    Left err -> pure (Left err)+    Right roles -> do+      let matches = filter (\(role', maygrant) -> role' == roleToGrant && maygrant) roles+      pure (Right (not (null matches)))++removePermissionFromRoleName :: RoleName -> Permission -> LoginRolesDB -> IO (Either LoginRoleError ())+removePermissionFromRoleName roleName perm db = do+  eTargetId <- roleIdForRoleName roleName db  +  case eTargetId of+    Left err -> pure (Left err)+    Right targetId -> do+      let q = "DELETE FROM login_role_permission WHERE roleid=? AND permission=?"+      SQL.execute db q (toText targetId, perm)+      pure (Right ())+      +-- | Return the primary key for the given role name or Nothing.+roleIdForRoleName :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError RoleId)+roleIdForRoleName rolename db = do+  let q = "SELECT roleid FROM login_role_name where rolename=?"+  res <- SQL.query db q (SQL.Only rolename)+  case res of+   [SQL.Only roleId] ->+     case fromText roleId of+       Nothing -> pure (Left (InvalidRoleIdError roleId))+       Just roleuuid -> pure (Right roleuuid)+   _ -> pure (Left (NoSuchRoleNameError rolename))++-- | Remove a role. Returns True if the role was present and was removed.+removeRole :: RoleName -> LoginRolesDB -> IO (Either LoginRoleError ())+removeRole rolename db = do+  SQL.execute db "DELETE FROM login_role_name WHERE rolename = ?" (SQL.Only rolename)+  c <- SQL.changes db+  if c > 0 then+    pure (Right ())+    else+    pure (Left (NoSuchRoleNameError rolename))++allRoles :: LoginRolesDB -> IO [(RoleName, (RoleId, MayLogin))]+allRoles db = do+  res <- SQL.query_ db "SELECT rolename,roleid, maylogin from login_role_name"+  pure (map (\(rn, rid, maylogin) ->+               (rn,+                (fromMaybe (error "invalid uuid in login_role_name") (fromString rid),+                maylogin))) res)++close :: LoginRolesDB -> IO ()+close = SQL.close++data SuccessResult = QuietSuccessResult |+                     InfoResult T.Text+                   deriving (Eq, Show, Generic)++executeAlterLoginRolesExpr :: RoleName -> LoginRolesDB -> AlterLoginRolesExpr -> IO (Either LoginRoleError SuccessResult)+executeAlterLoginRolesExpr currentRole db expr = do+  let ok = pure (Right QuietSuccessResult)+      checkPerm expr' =+        case expr' of+          ShowRolesForRoleExpr roleName ->+            if currentRole == roleName then+              pure (Right True)+            else+              hasPerm viewLoginRolesPerm+          AddLoginRoleExpr{} -> hasPerm alterLoginRolesPerm+          RemoveLoginRoleExpr{} -> hasPerm alterLoginRolesPerm+          AddRoleToRoleExpr _target other _mayGrant -> do+            eperm <- hasPerm alterLoginRolesPerm+            case eperm of+              Left err -> pure (Left err)+              Right True -> pure (Right True)+              Right False ->+                roleMayGrantRole currentRole other db+          AlterLoginRoleExpr _target _mNewName _mMayLogin -> +            hasPerm alterLoginRolesPerm+          RemoveRoleFromRoleExpr _target other ->+            roleMayGrantRole currentRole other db+          AddPermissionToRoleExpr{} -> hasPerm alterLoginRolesPerm+          RemovePermissionFromRoleExpr{} -> hasPerm alterLoginRolesPerm+          ShowAllRolesExpr{} -> hasPerm viewLoginRolesPerm+      hasPerm perm = do+        roleHasPermission currentRole perm db++  SQL.withTransaction db $ do+    check <- checkPerm expr+    case check of+      Left err -> pure (Left err)+      Right False -> pure (Left PermissionDeniedError)+      Right True -> do+        case expr of+          ShowRolesForRoleExpr roleName -> do+            eRoles <- roleNamesForRoleName roleName db+            case eRoles of+              Left err ->+                pure (Left err)+              Right roles ->+                pure (Right (InfoResult (T.intercalate "\n" (map (\(r,g) -> r <> if g then ":maygrant" else "") roles))))+          AddLoginRoleExpr roleName maylogin -> do+            newRoleId <- nextRandom+            result <- addRoleName roleName newRoleId maylogin db+            case result of+              Left err -> pure (Left err)+              Right () -> ok+          ShowAllRolesExpr -> do+            roleInfos <- allRoles db+            pure (Right (InfoResult (T.intercalate "\n" (map (\(roleName, (roleId, maylogin)) -> T.pack (show roleId) <> ":" <> roleName <> ":" <> if maylogin then "maylogin" else "maynotlogin") roleInfos))))+          RemoveLoginRoleExpr roleName -> do+            result <- removeRole roleName db+            case result of+              Left err -> pure (Left err)+              Right () -> ok+          AddRoleToRoleExpr targetRole otherRole mayGrant -> do+            -- who can do this? the current user granting his permission or alter_login_roles perm+            res <- addRoleToRoleName targetRole otherRole mayGrant db+            case res of+              Left err -> pure (Left err)+              Right () -> ok+          AlterLoginRoleExpr targetRole mNewRoleName mMayLogin -> do+            res <- alterRoleName targetRole mNewRoleName mMayLogin db+            case res of+              Left err -> pure (Left err)+              Right () -> ok+          RemoveRoleFromRoleExpr targetRole otherRole -> do+            res <- removeRoleFromRoleName targetRole otherRole db+            case res of+              Left err -> pure (Left err)+              Right () -> ok+          AddPermissionToRoleExpr targetRole perm mayGrant -> do+            res <- addPermissionToRoleName targetRole perm mayGrant db+            case res of+              Left err -> pure (Left err)+              Right () -> ok+          RemovePermissionFromRoleExpr targetRole perm -> do+            res <- removePermissionFromRoleName targetRole perm db+            case res of+              Left err -> pure (Left err)+              Right () -> ok++     +     
src/lib/ProjectM36/NormalizeExpr.hs view
@@ -57,7 +57,7 @@ processAttributeNames (IntersectAttributeNames attrNamesA attrNamesB) = IntersectAttributeNames <$> processAttributeNames attrNamesA <*> processAttributeNames attrNamesB processAttributeNames (RelationalExprAttributeNames expr) = RelationalExprAttributeNames <$> processRelationalExpr expr -processDatabaseContextExpr :: DatabaseContextExpr -> ProcessExprM GraphRefDatabaseContextExpr+processDatabaseContextExpr :: DatabaseContextExprBase () r -> ProcessExprM (DatabaseContextExprBase GraphRefTransactionMarker r) processDatabaseContextExpr expr =   case expr of     NoOperation -> pure NoOperation@@ -80,6 +80,7 @@     ExecuteDatabaseContextFunction funcName' atomExprs -> ExecuteDatabaseContextFunction funcName' <$> mapM processAtomExpr atomExprs     AddRegisteredQuery n q -> pure (AddRegisteredQuery n q)     RemoveRegisteredQuery n -> pure (RemoveRegisteredQuery n)+    AlterACL e -> pure (AlterACL e)     MultipleExpr exprs -> MultipleExpr <$> mapM processDatabaseContextExpr exprs  processDatabaseContextIOExpr :: DatabaseContextIOExpr -> ProcessExprM GraphRefDatabaseContextIOExpr
src/lib/ProjectM36/Notifications.hs view
@@ -2,11 +2,15 @@ import ProjectM36.Base import ProjectM36.Error import ProjectM36.RelationalExpression+import ProjectM36.TransactionGraph.Types+import ProjectM36.DatabaseContext.Types import ProjectM36.StaticOptimizer import qualified Data.Map as M import Data.Either (isRight)  -- | Returns the notifications which should be triggered based on the transition from the first 'DatabaseContext' to the second 'DatabaseContext'.++-- TODO fix to use new optimizer (relies on IO) notificationChanges :: Notifications -> TransactionGraph -> DatabaseContext -> DatabaseContext -> Notifications notificationChanges nots graph context1 context2 = M.filter notificationFilter nots   where
src/lib/ProjectM36/Persist.hs view
@@ -1,14 +1,19 @@-{-# LANGUAGE ForeignFunctionInterface, CPP #-}+{-# LANGUAGE ForeignFunctionInterface, CPP, RankNTypes, ScopedTypeVariables #-} --this module is related to persisting Project:M36 structures to disk and not related to the persistent library module ProjectM36.Persist (writeFileSync,                             writeSerialiseSync,+                           readDeserialise,                            renameSync,                            printFdCount,                            DiskSync(..)) where -- on Windows, use FlushFileBuffers and MoveFileEx import qualified Data.Text as T import Codec.Winery+import Data.Proxy import qualified Data.ByteString.FastBuilder as BB++-- serialising with winery with the schema intact, while safer, writes a lot of redundant data while writing transactions.+#define SERIALISE_WITH_SCHEMA 0 #if defined(linux_HOST_OS) # define FDCOUNTSUPPORTED 1 # define FDDIR "/proc/self/fd"@@ -43,19 +48,38 @@ #if defined(mingw32_HOST_OS) import ProjectM36.Win32Handle #else+-- maybe this could be "safe" for GC since we only use it with withCString+-- https://www.reddit.com/r/haskell/comments/xlm4qv/haskell_ffi_call_safety_and_garbage_collection/ foreign import ccall unsafe "cDirectoryFsync" cHSDirectoryFsync :: CString -> IO CInt #endif  data DiskSync = NoDiskSync | FsyncDiskSync  --using withFile here is OK because we use a WORM strategy- the file is written but not read until after the handle is synced, closed, and unhidden (moved from ".X" to "X") at the top level in the transaction directory -writeFileSync :: DiskSync -> FilePath -> T.Text -> IO()+writeFileSync :: DiskSync -> FilePath -> T.Text -> IO () writeFileSync sync path strOut = withFile path WriteMode handler   where     handler handle = do       BS.hPut handle (TE.encodeUtf8 strOut)       syncHandle sync handle +--use winery to decode only the data structure and skip the schema+deserialiseOnly' :: forall s. Serialise s => BS.ByteString -> Either WineryException s+deserialiseOnly' bytes = do+  dec <- getDecoder (schema (Proxy :: Proxy s))+  pure (evalDecoder dec bytes)++readDeserialise :: Serialise a => FilePath -> IO a+readDeserialise path = do+#if SERIALISE_WITH_SCHEMA+  readFileDeserialise path+#else+  bsin <- BS.readFile path+  case deserialiseOnly' bsin of+    Left err -> error ("readDeserialise: " <> show err)+    Right val -> pure val+#endif+ renameSync :: DiskSync -> FilePath -> FilePath -> IO () renameSync sync srcPath dstPath = do   atomicRename srcPath dstPath@@ -104,7 +128,12 @@ writeSerialiseSync :: Serialise a => DiskSync -> FilePath -> a -> IO () writeSerialiseSync sync path val =    withBinaryFile path WriteMode $ \handle -> do-    BB.hPutBuilder handle $ toBuilderWithSchema val+    BB.hPutBuilder handle $+#if SERIALISE_WITH_SCHEMA      +      toBuilderWithSchema val+#else+     toBuilder val+#endif     syncHandle sync handle    directoryFsync :: FilePath -> IO ()@@ -130,6 +159,6 @@ fdCount :: IO Int fdCount = do   fds <- getDirectoryContents FDDIR-  pure ((length fds) - 2)+  pure (length fds - 2) --not supported on non-linux #endif
+ src/lib/ProjectM36/PinnedRelationalExpr.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE FlexibleInstances, ScopedTypeVariables #-}+module ProjectM36.PinnedRelationalExpr where+import ProjectM36.Base++{-+-- | Convert an ADT which may reference the uncommitted transaction to the version of the ADT which does not include the ADT or +class ToPinned a where+  toPinned :: a GraphRefTransactionMarker -> Maybe (a TransactionId)+++instance ToPinned RelationalExprBase where+  toPinned expr =+    case expr of+      MakeRelationFromExprs mAttrExprs tupleExprs -> do+        tupleExprs' <- toPinnedRelationalExpr tupleExprs+        mAttrExprs' <- case mAttrExprs of+                         Nothing -> pure Nothing+                         Just es -> Just <$> mapM toPinnedRelationalExpr es+        pure (MakeRelationFromExprs mAttrExprs' tupleExprs')++      MakeStaticRelation attrs tupSet ->+        pure (MakeStaticRelation attrs tupSet)++      ExistingRelation rel ->+        ExistingRelation rel++      RelationVariable rv m ->+        RelationVariable rv <$> toPinned+-}++toPinnedRelationalExpr :: GraphRefRelationalExpr -> Maybe PinnedRelationalExpr+toPinnedRelationalExpr = mapM mapper+  where+    mapper UncommittedContextMarker = Nothing+    mapper (TransactionMarker tid) = Just tid+  +toGraphRefRelationalExpr :: PinnedRelationalExpr -> GraphRefRelationalExpr+toGraphRefRelationalExpr = fmap mapper+  where+    mapper = TransactionMarker
+ src/lib/ProjectM36/PrettyBytes.hs view
@@ -0,0 +1,13 @@+module ProjectM36.PrettyBytes where+import Data.Int++units :: [String]+units = ["B","KB","MB","GB","TB","PB","EB","ZB","YB"]++prettyBytes' :: [String] -> Int64 -> String+prettyBytes' remainingUnits n | null remainingUnits = show n+                              | n < 1000 = show n <> head remainingUnits+                              | otherwise = prettyBytes' (tail remainingUnits) (n `div` 1000)++prettyBytes :: Int64 -> String+prettyBytes = prettyBytes' units
src/lib/ProjectM36/ReferencedTransactionIds.hs view
@@ -1,8 +1,11 @@ {-# LANGUAGE FlexibleInstances #-} module ProjectM36.ReferencedTransactionIds where import ProjectM36.Base+import ProjectM36.ValueMarker import ProjectM36.Error-import qualified ProjectM36.Transaction as T+import ProjectM36.DatabaseContext.Types+import ProjectM36.Transaction.Types+import ProjectM36.TransactionGraph.Types import ProjectM36.RelationalExpression import qualified Data.Map as M import qualified Data.Set as S@@ -121,6 +124,10 @@     --referencedTransactionIds (registeredQueries dbc)     ] +instance ReferencedTransactionIds a => ReferencedTransactionIds (ValueMarker a) where+  referencedTransactionIds (ValueMarker a) = referencedTransactionIds a+  referencedTransactionIds (NotChangedSinceMarker tid) = S.singleton tid+ instance ReferencedTransactionIds RelationVariables where   referencedTransactionIds relVars =     S.unions (referencedTransactionIds <$> M.elems relVars)@@ -130,11 +137,11 @@ -- should be trim merge parents that don't contribute to the relvars? maybe referencedTransactionIdsForTransaction :: Transaction -> TransactionGraph -> Either RelationalError (S.Set Transaction) referencedTransactionIdsForTransaction trans graph-  | parentIds == T.rootParent = pure (S.singleton trans)+  | parentIds' == rootParent = pure (S.singleton trans)   | otherwise = -    foldM folder (S.singleton trans) parentIds+    foldM folder (S.singleton trans) parentIds'   where-    parentIds = parents (transactionInfo trans)+    parentIds' = parents (transactionInfo trans)     folder acc transId' = do       trans' <- transactionForId transId' graph       transSet <- referencedTransactionIdsForTransaction trans' graph
src/lib/ProjectM36/RegisteredQuery.hs view
@@ -3,6 +3,7 @@ import ProjectM36.Attribute import ProjectM36.Error import ProjectM36.IsomorphicSchema+import ProjectM36.IsomorphicSchema.Types import ProjectM36.Relation import qualified Data.Map as M 
+ src/lib/ProjectM36/RelExprSize.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE MagicHash, FlexibleInstances #-}+module ProjectM36.RelExprSize where+import ProjectM36.Base+import Data.Int+#ifdef MIN_VERSION_ghc_bignum+import GHC.Num.Integer+import GHC.Num.BigNat+#else+import GHC.Integer.GMP.Internals+import GHC.Exts+#endif+import qualified Data.Text as T+import Data.Time.Clock+import qualified Data.ByteString as BS+import Data.Time.Calendar+import qualified Data.Map as M+import Data.Scientific++type ByteCount = Int64++-- | Provide reasonably accurate but fast estimates for size of the data.+class Size a where+  size :: a -> ByteCount++instance Size (RelationalExprBase a) where+  size expr =   +    case expr of+      MakeRelationFromExprs _ tupleExprs -> size tupleExprs+      MakeStaticRelation _ tupSet -> size tupSet+      ExistingRelation (Relation _ tupSet) -> size tupSet+      RelationVariable _ _ -> 0+      RelationValuedAttribute _ -> 0+      Project _ expr' -> size expr'+      Union exprA exprB -> size exprA + size exprB+      Join exprA exprB -> size exprA + size exprB+      Rename _ expr' -> size expr'+      Difference exprA exprB -> size exprA + size exprB+      Group _ _ expr' -> size expr'+      Ungroup _ expr' -> size expr'+      Restrict _ expr' -> size expr'+      Equals exprA exprB -> size exprA + size exprB+      NotEquals exprA exprB -> size exprA + size exprB+      Extend _ expr' -> size expr'+      With macros expr' -> size expr' + sum (fmap (size . snd) macros)++ptrSize :: Int -> Int64+ptrSize l = fromIntegral $ 8 * l++instance Size RelationTupleSet where+  size (RelationTupleSet tups) = sum (fmap size tups) + ptrSize (length tups)++instance Size RelationTuple where+  size (RelationTuple _ atoms) = sum (fmap size atoms) + ptrSize (length atoms)+                                     +instance Size (TupleExprsBase a) where+  size (TupleExprs _ exprs) = sum (fmap size exprs) + ptrSize (length exprs)++instance Size (TupleExprBase a) where+  size (TupleExpr tupMap) = sum (fmap size tupMap) + ptrSize (M.size tupMap) * 2++instance Size (AtomExprBase a) where+  size expr =+    case expr of+      AttributeAtomExpr{} -> 0+      NakedAtomExpr a -> size a+      FunctionAtomExpr _ exprs _ -> sum (fmap size exprs) + ptrSize (length exprs)+      RelationAtomExpr relexpr -> size relexpr+      ConstructedAtomExpr _ exprs _ -> sum (fmap size exprs) + ptrSize (length exprs)+      SubrelationAttributeAtomExpr{} -> 0+      IfThenAtomExpr if' then' else' -> size if' + size then' + size else'++instance Size Atom where+  size atom =+    case atom of+      IntegerAtom i -> size i+      IntAtom _ -> 8+      ScientificAtom s -> size s+      DoubleAtom _ -> 8+      TextAtom t -> fromIntegral $ T.length t * 2 --UTF-16 until text-2.0 is widespread+      DayAtom d -> size (toModifiedJulianDay d)+      DateTimeAtom (UTCTime d dt) -> size d + size dt+      ByteStringAtom bs -> fromIntegral $ BS.length bs+      BoolAtom _ -> 1+      UUIDAtom _ -> 16+      RelationAtom rel -> size rel+      RelationalExprAtom expr -> size expr+      ConstructedAtom _ _ atoms -> 8 + sum (fmap size atoms)+      SubrelationFoldAtom rel _attr -> 8 + size rel++instance Size Relation where+  size (Relation _ tupSet) = size tupSet + ptrSize (length (asList tupSet))++instance Size Integer where+#ifdef MIN_VERSION_ghc_bignum+  size (IS _) = 8+  size (IP bn) = fromIntegral (8 + bigNatSize bn)+  size (IN bn) = fromIntegral (8 + bigNatSize bn)+#else  +  size (S# _) = 8+  size (Jp# (BN# bytearray)) = fromIntegral (I# (sizeofByteArray# bytearray))+  size (Jn# (BN# bytearray)) = fromIntegral (I# (sizeofByteArray# bytearray))+#endif++instance Size Day where+  size d = size $ toModifiedJulianDay d++instance Size DiffTime where+  size d = size $ diffTimeToPicoseconds d++instance Size Scientific where+  size s = 8 + size (coefficient s)++instance Size [RelationTuple] where+  size = foldr (\t acc -> acc + size t) 0+
src/lib/ProjectM36/Relation.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE GADTs,ExistentialQuantification #-}+{-# LANGUAGE GADTs,ExistentialQuantification,GeneralizedNewtypeDeriving #-} module ProjectM36.Relation where import qualified Data.Set as S import qualified Data.HashSet as HS@@ -6,6 +6,7 @@ import qualified Data.Vector as V import ProjectM36.Base import ProjectM36.Tuple+import qualified ProjectM36.TupleSet as TS import qualified ProjectM36.Attribute as A import ProjectM36.TupleSet import ProjectM36.Error@@ -16,10 +17,14 @@ import Data.Either (isRight) import System.Random.Shuffle import Control.Monad.Random+import Data.List (foldl')  attributes :: Relation -> Attributes attributes (Relation attrs _ ) = attrs +tupleSet :: Relation -> RelationTupleSet+tupleSet (Relation _ tupSet) = tupSet+ attributeNames :: Relation -> S.Set AttributeName attributeNames (Relation attrs _) = A.attributeNameSet attrs @@ -37,13 +42,13 @@   Relation attrs <$> mkTupleSetFromList attrs atomMatrix    emptyRelationWithAttrs :: Attributes -> Relation  -emptyRelationWithAttrs attrs = Relation attrs emptyTupleSet+emptyRelationWithAttrs attrs = Relation attrs TS.empty  mkRelation :: Attributes -> RelationTupleSet -> Either RelationalError Relation-mkRelation attrs tupleSet =+mkRelation attrs tupleSet' =     --check that all tuples have the same keys     --check that all tuples have keys (1-N) where N is the attribute count-    case verifyTupleSet attrs tupleSet of+    case verifyTupleSet attrs tupleSet' of       Left err -> Left err       Right verifiedTupleSet -> return $ Relation attrs verifiedTupleSet     @@ -52,7 +57,7 @@ --instead of returning a Left RelationalError, if a tuple does not match the relation's attributes, the tuple is simply removed --duplicate tuples are NOT filtered by this creation method mkRelationDeferVerify :: Attributes -> RelationTupleSet -> Either RelationalError Relation-mkRelationDeferVerify attrs tupleSet = return $ Relation attrs (RelationTupleSet (filter tupleFilter (asList tupleSet)))+mkRelationDeferVerify attrs tupleSet' = return $ Relation attrs (RelationTupleSet (filter tupleFilter (asList tupleSet')))   where     tupleFilter tuple = isRight (verifyTuple attrs tuple)     @@ -66,17 +71,17 @@    mkRelation attrs tupSet  relationTrue :: Relation-relationTrue = Relation A.emptyAttributes singletonTupleSet+relationTrue = Relation A.emptyAttributes TS.emptySingleTuple  relationFalse :: Relation-relationFalse = Relation A.emptyAttributes emptyTupleSet+relationFalse = Relation A.emptyAttributes TS.empty  --if the relation contains one tuple, return it, otherwise Nothing singletonTuple :: Relation -> Maybe RelationTuple-singletonTuple rel@(Relation _ tupleSet) =+singletonTuple rel@(Relation _ tupleSet') =   case cardinality rel of     Countable -> Nothing-    _ -> case asList tupleSet of+    _ -> case asList tupleSet' of       [] -> Nothing       x : _ -> Just x @@ -168,16 +173,16 @@ restrictEq :: RelationTuple -> Relation -> Either RelationalError Relation restrictEq tuple = restrict rfilter   where-    rfilter :: RelationTuple -> Either RelationalError Bool-    rfilter tupleIn = do-      pure (tupleIntersection tuple tupleIn == tuple)+    rfilter :: RestrictionFilter+    rfilter tupIn _ = do+      pure (tupleIntersection tuple tupIn == tuple)  -- unwrap relation-valued attribute -- return error if relval attrs and nongroup attrs overlap ungroup :: AttributeName -> Relation -> Either RelationalError Relation ungroup relvalAttrName rel = case attributesForRelval relvalAttrName rel of   Left err -> Left err-  Right relvalAttrs -> relFold relFolder (Right $ Relation newAttrs emptyTupleSet) rel+  Right relvalAttrs -> relFold relFolder (Right $ Relation newAttrs TS.empty) rel    where     newAttrs = A.addAttributes relvalAttrs nonGroupAttrs     nonGroupAttrs = A.deleteAttributeName relvalAttrName (attributes rel)@@ -198,7 +203,7 @@         Left err -> Left err         Right accRel ->           union accRel $ Relation newAttrs (RelationTupleSet [tupleExtend nonGroupTupleProjection tupleIn])-  relFold folder (Right $ Relation newAttrs emptyTupleSet) relvalRelation+  relFold folder (Right $ Relation newAttrs TS.empty) relvalRelation  attributesForRelval :: AttributeName -> Relation -> Either RelationalError Attributes attributesForRelval relvalAttrName (Relation attrs _) = do@@ -207,10 +212,35 @@     (RelationAtomType relAttrs) -> Right relAttrs     _ -> Left $ AttributeIsNotRelationValuedError relvalAttrName -type RestrictionFilter = RelationTuple -> Either RelationalError Bool+type RestrictionFilter = RelationTuple -> ContextTuples -> Either RelationalError Bool++-- | ContextTuples allow restriction to receive additional context from which the restriction evaluation may read. This is useful when a restriction occurs in a sub-expression so there is a higher-level context which may be referenced using attribute names. The tuples are scanned from first to last in order to find a matching attribute name, when necessary+newtype ContextTuples = ContextTuples [RelationTuple]+  deriving (Semigroup, Monoid, Show, Eq)++singletonContextTuple :: RelationTuple -> ContextTuples+singletonContextTuple tup = ContextTuples [tup]++addContextTuple :: ContextTuples -> RelationTuple -> ContextTuples+addContextTuple (ContextTuples tups) tup = ContextTuples (tup : tups)++contextTupleAtomForAttributeName :: ContextTuples -> AttributeName -> Either RelationalError Atom+contextTupleAtomForAttributeName (ContextTuples tups) attrName =+  foldl' tupFolder (Left (NoSuchAttributeNamesError (S.singleton attrName))) tups+  where+    tupFolder acc tup | isRight acc =+                        acc+                      | otherwise =+      atomForAttributeName attrName tup++-- | Scan the context tuples, but first scan an additional tuple.+contextTupleAtomForAttributeName' :: RelationTuple -> ContextTuples -> AttributeName -> Either RelationalError Atom+contextTupleAtomForAttributeName' tup ctx =+  contextTupleAtomForAttributeName (addContextTuple ctx tup)+ restrict :: RestrictionFilter -> Relation -> Either RelationalError Relation restrict rfilter (Relation attrs tupset) = do-  tuples <- filterM rfilter (asList tupset)+  tuples <- filterM (`rfilter` mempty) (asList tupset)   Right $ Relation attrs (RelationTupleSet tuples)  --joins on columns with the same name- use rename to avoid this- base case: cartesian product@@ -236,12 +266,13 @@   where     attrsA = attributes relA     attrsB = attributes relB-    rfilter tupInA = relFold (\tupInB acc -> if acc == Right False then pure False else pure (tupInB /= tupInA)) (Right True) relB+    rfilter tupInA _ =+      relFold (\tupInB acc -> if acc == Right False then pure False else pure (tupInB /= tupInA)) (Right True) relB        --a map should NOT change the structure of a relation, so attributes should be constant relMap :: (RelationTuple -> Either RelationalError RelationTuple) -> Relation -> Either RelationalError Relation-relMap mapper (Relation attrs tupleSet) = -  case forM (asList tupleSet) typeMapCheck of+relMap mapper (Relation attrs tupleSet') = +  case forM (asList tupleSet') typeMapCheck of     Right remappedTupleSet -> mkRelation attrs (RelationTupleSet remappedTupleSet)     Left err -> Left err   where@@ -258,7 +289,7 @@   mkRelationFromTuples newAttributes newTuples  relFold :: (RelationTuple -> a -> a) -> a -> Relation -> a-relFold folder acc (Relation _ tupleSet) = foldr folder acc (asList tupleSet)+relFold folder acc (Relation _ tupleSet') = foldr folder acc (asList tupleSet')  -- | Generate a randomly-ordered list of tuples from the relation. toList :: Relation -> IO [RelationTuple]@@ -318,4 +349,4 @@ oneTuple (Relation _ (RelationTupleSet (x:_))) = Just x  tuplesList :: Relation -> [RelationTuple]-tuplesList (Relation _ tupleSet) = asList tupleSet+tuplesList (Relation _ tupleSet') = asList tupleSet'
src/lib/ProjectM36/Relation/Show/HTML.hs view
@@ -20,7 +20,7 @@  relationAsHTML :: Relation -> Text -- web browsers don't display tables with empty cells or empty headers, so we have to insert some placeholders- it's not technically the same, but looks as expected in the browser-relationAsHTML rel@(Relation attrNameSet tupleSet) +relationAsHTML rel@(Relation attrNameSet tupleSet')    | rel == relationTrue = pm36relcss <>                           tablestart <>                           "<tr><th></th></tr>" <>@@ -34,7 +34,7 @@   | otherwise = pm36relcss <>                 tablestart <>                  attributesAsHTML attrNameSet <> -                tupleSetAsHTML tupleSet <> +                tupleSetAsHTML tupleSet' <>                  tablefooter <>                  "</table>"   where
src/lib/ProjectM36/Relation/Show/Term.hs view
@@ -77,13 +77,13 @@     allRows = header : body      relationAsTable :: Relation -> Table-relationAsTable rel@(Relation _ tupleSet) = (header, body)+relationAsTable rel@(Relation _ tupleSet') = (header, body)   where     oAttrs = orderedAttributes (attributes rel)     oAttrNames = orderedAttributeNames (attributes rel)     header = map prettyAttribute oAttrs     body :: [[Cell]]-    body = L.foldr tupleFolder [] (asList tupleSet)+    body = L.foldr tupleFolder [] (asList tupleSet')     tupleFolder tuple acc = map (\attrName -> case atomForAttributeName attrName tuple of                                             Left _ -> "?"                                             Right atom -> showAtom 0 atom
+ src/lib/ProjectM36/RelationVariablesMentioned.hs view
@@ -0,0 +1,90 @@+-- | Return Relation Variables mentioned in the data structure. Useful for access control.+module ProjectM36.RelationVariablesMentioned where+import ProjectM36.Base+import ProjectM36.WithNameExpr+import qualified Data.Set as S+import qualified Data.Map as M+import qualified Data.Vector as V++mentionsRelVar :: RelationVariablesMentioned a => a -> Bool+mentionsRelVar x =+  not (S.null (relVarsMentioned x))++class RelationVariablesMentioned a where+  relVarsMentioned :: a -> S.Set RelVarName++instance RelationVariablesMentioned (AtomExprBase a) where+  relVarsMentioned expr = +   case expr of+    AttributeAtomExpr{} -> mempty+    SubrelationAttributeAtomExpr{} -> mempty+    NakedAtomExpr{} -> mempty+    FunctionAtomExpr _ args _ ->+      S.unions (map relVarsMentioned args)+    RelationAtomExpr relExpr ->+      relVarsMentioned relExpr+    IfThenAtomExpr exprA exprB exprC ->+      S.unions (map relVarsMentioned [exprA, exprB, exprC])+    ConstructedAtomExpr _ args _ ->+      S.unions (map relVarsMentioned args)++instance RelationVariablesMentioned (RelationalExprBase a) where+  relVarsMentioned expr =+    case expr of+      MakeRelationFromExprs _ tupleExprs ->+        relVarsMentioned tupleExprs+      MakeStaticRelation _ tupSet ->+        relVarsMentioned tupSet+      ExistingRelation{} -> mempty+      RelationVariable nam _ -> S.singleton nam+      RelationValuedAttribute{} -> mempty+      Project _ exprA -> relVarsMentioned exprA+      Union exprA exprB -> relVarsMentioned exprA <> relVarsMentioned exprB+      Join exprA exprB -> relVarsMentioned exprA <> relVarsMentioned exprB+      Rename _ exprA -> relVarsMentioned exprA+      Difference exprA exprB -> relVarsMentioned exprA <> relVarsMentioned exprB+      Group _ _ exprA -> relVarsMentioned exprA+      Ungroup _ exprA -> relVarsMentioned exprA+      Restrict _ exprA -> relVarsMentioned exprA+      Equals exprA exprB -> relVarsMentioned exprA <> relVarsMentioned exprB+      NotEquals exprA exprB -> relVarsMentioned exprA <> relVarsMentioned exprB+      Extend extendExpr exprA -> relVarsMentioned extendExpr <> relVarsMentioned exprA+      With withNames exprA -> relVarsMentioned exprA `S.difference` macroNames withNames++instance RelationVariablesMentioned (ExtendTupleExprBase a) where+  relVarsMentioned (AttributeExtendTupleExpr _attrName atomExpr) =+    relVarsMentioned atomExpr++instance RelationVariablesMentioned (TupleExprsBase a) where+  relVarsMentioned (TupleExprs _ tExprs) =+    S.unions (map relVarsMentioned tExprs)++instance RelationVariablesMentioned (TupleExprBase a) where+  relVarsMentioned (TupleExpr tupmap) =+    S.unions (M.map relVarsMentioned tupmap)+    +instance RelationVariablesMentioned RelationTupleSet where+  relVarsMentioned (RelationTupleSet tups) =+    S.unions (map relVarsMentioned tups)++instance RelationVariablesMentioned RelationTuple where+  relVarsMentioned (RelationTuple _ atoms) =+    S.unions (V.toList (V.map relVarsMentioned atoms))++instance RelationVariablesMentioned Atom where+  relVarsMentioned atom =+    case atom of+      IntAtom{} -> mempty+      IntegerAtom{} -> mempty+      ScientificAtom{} -> mempty+      DoubleAtom{} -> mempty+      TextAtom{} -> mempty+      DayAtom{} -> mempty+      DateTimeAtom{} -> mempty+      ByteStringAtom{} -> mempty+      BoolAtom{} -> mempty+      UUIDAtom{} -> mempty+      RelationAtom{} -> mempty+      RelationalExprAtom expr -> relVarsMentioned expr+      SubrelationFoldAtom{} -> mempty+      ConstructedAtom _ _ atoms' -> S.unions (map relVarsMentioned atoms')
src/lib/ProjectM36/RelationalExpression.hs view
@@ -2,10 +2,11 @@ {-# LANGUAGE FlexibleInstances  #-} {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE MultiParamTypeClasses #-} module ProjectM36.RelationalExpression where import ProjectM36.Relation import ProjectM36.Tuple-import ProjectM36.TupleSet+import qualified ProjectM36.TupleSet as TS import ProjectM36.Base import qualified Data.UUID as U import ProjectM36.Error@@ -14,10 +15,14 @@ import ProjectM36.ScriptSession import ProjectM36.DataTypes.Primitive import ProjectM36.AtomFunction+import ProjectM36.ValueMarker import ProjectM36.DatabaseContextFunction+import ProjectM36.TransactionGraph.Types+import ProjectM36.Transaction.Types import ProjectM36.Arbitrary+import ProjectM36.IsomorphicSchema.Types hiding (concreteDatabaseContext, subschemas)+import ProjectM36.DatabaseContext.Types import ProjectM36.GraphRefRelationalExpr-import ProjectM36.Transaction import qualified ProjectM36.Attribute as A import qualified Data.Map as M import qualified Data.HashSet as HS@@ -33,6 +38,7 @@ import Data.Maybe import Data.Tuple (swap) import Data.Either+import Data.List (foldl') import Data.Char (isUpper) import Data.Time import qualified Data.List.NonEmpty as NE@@ -46,7 +52,9 @@ import ProjectM36.NormalizeExpr import ProjectM36.WithNameExpr import ProjectM36.Function+import ProjectM36.AccessControlList as ACL import Test.QuickCheck+import Data.Functor (void) import qualified Data.Functor.Foldable as Fold import Control.Applicative #ifdef PM36_HASKELL_SCRIPTING@@ -65,7 +73,7 @@     existingCount = case V.head (tupleAtoms existingTuple) of       IntAtom v -> v       _ -> error "impossible counting error in tupleAtoms"-    newTups = case mkTupleSetFromList attrs [[IntAtom (existingCount + 1)]] of+    newTups = case TS.mkTupleSetFromList attrs [[IntAtom (existingCount + 1)]] of       Left err -> error ("impossible counting error in " ++ show err)       Right ts -> ts       @@ -73,9 +81,8 @@ mkDatabaseContextEvalState :: DatabaseContext -> DatabaseContextEvalState mkDatabaseContextEvalState context = DatabaseContextEvalState {   dbc_context = context,-  dbc_accum = M.empty,-  dbc_dirty = False-  } --future work: propagate return accumulator+  dbc_accum = M.empty+  }   -- we need to pass around a higher level RelationTuple and Attributes in order to solve #52 data RelationalExprEnv = RelationalExprEnv {@@ -88,7 +95,11 @@ envTuple e = fromLeft emptyTuple (fromMaybe (Left emptyTuple) (gre_extra e))  envAttributes :: GraphRefRelationalExprEnv -> Attributes-envAttributes e = fromRight emptyAttributes (fromMaybe (Right emptyAttributes) (gre_extra e))+envAttributes e =+  case gre_extra e of+    Nothing -> mempty+    Just (Left tup) -> tupleAttributes tup+    Just (Right attrs) -> attrs    instance Show RelationalExprEnv where   show e@RelationalExprEnv{} = "RelationalExprEnv " ++ show (re_extra e)@@ -137,17 +148,22 @@                                  }  data DatabaseContextEvalState = DatabaseContextEvalState {-  dbc_context :: DatabaseContext, --new, alterable context for a new transaction-  dbc_accum :: M.Map ResultAccumName ResultAccum,-  dbc_dirty :: DirtyFlag+  dbc_context :: DatabaseContext,+  dbc_accum :: M.Map ResultAccumName ResultAccum   } +-- | To break the circular dependency loop between StaticOptimizer and RelationalExpression modules, we pass in the optimizer as a function.+--type DatabaseContextExprOptimizer = TransactionId -> DatabaseContext -> TransactionGraph -> DatabaseContextExpr -> GraphRefDatabaseContextExpr++--type DatabaseContextIOExprOptimizer = TransactionId -> DatabaseContext -> TransactionGraph -> DatabaseContextIOExpr -> GraphRefDatabaseContextIOExpr+ data DatabaseContextEvalEnv = DatabaseContextEvalEnv   { dce_transId :: TransactionId,-    dce_graph :: TransactionGraph+    dce_graph :: TransactionGraph,+    dce_dbcfuncutils :: DatabaseContextFunctionUtils   } -mkDatabaseContextEvalEnv :: TransactionId -> TransactionGraph -> DatabaseContextEvalEnv+mkDatabaseContextEvalEnv :: TransactionId -> TransactionGraph -> DatabaseContextFunctionUtils -> DatabaseContextEvalEnv mkDatabaseContextEvalEnv = DatabaseContextEvalEnv  type DatabaseContextEvalMonad a = RWST DatabaseContextEvalEnv () DatabaseContextEvalState (ExceptT RelationalError Identity) a@@ -174,7 +190,7 @@ putStateContext :: DatabaseContext -> DatabaseContextEvalMonad ()  putStateContext ctx' = do   s <- get-  put (s {dbc_context = ctx', dbc_dirty = True}) +  put (s {dbc_context = ctx'})   -- | The context is optionally passed down along in cases where the current context is uncommitted. data GraphRefRelationalExprEnv =@@ -191,7 +207,7 @@   graph <- gfGraph   lift $ except $ transactionForId tid graph -gfDatabaseContextForMarker :: GraphRefTransactionMarker -> GraphRefRelationalExprM DatabaseContext+gfDatabaseContextForMarker :: GraphRefTransactionMarker -> GraphRefRelationalExprM DatabaseContext  gfDatabaseContextForMarker (TransactionMarker transId) = concreteDatabaseContext <$> gfTransForId transId gfDatabaseContextForMarker UncommittedContextMarker = do   mctx <- gre_context <$> askEnv@@ -224,14 +240,15 @@   currentContext <- getStateContext   --prevent recursive relvar definition by resolving references to relvars in previous states   relExpr' <- resolve relExpr-  let newRelVars = M.insert relVarName relExpr' $ relationVariables currentContext+  graph <- dbcGraph+  relVars <- resolveDBC relationVariables+  let newRelVars = ValueMarker $ M.insert relVarName relExpr' relVars       potentialContext = currentContext { relationVariables = newRelVars }   --optimization: if the relexpr is unchanged, skip the update      -  if M.lookup relVarName (relationVariables currentContext) == Just relExpr then+  if M.lookup relVarName relVars == Just relExpr then     pure ()     else do     --determine when to check constraints-    graph <- dbcGraph     tid <- dbcTransId     case checkConstraints potentialContext tid graph of       Left err -> dbErr err@@ -244,54 +261,56 @@ deleteRelVar :: RelVarName -> DatabaseContextEvalMonad () deleteRelVar relVarName = do   currContext <- getStateContext-  let relVars = relationVariables currContext+  graph <- dbcGraph+  relVars <- resolveDBC relationVariables   if M.notMember relVarName relVars then     pure ()     else do-    let newRelVars = M.delete relVarName relVars+    let newRelVars = ValueMarker $ M.delete relVarName relVars         newContext = currContext { relationVariables = newRelVars }-    graph <- dbcGraph     tid <- dbcTransId     case checkConstraints newContext tid graph of       Left err -> dbErr err       Right _ ->         putStateContext newContext -evalGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr -> DatabaseContextEvalMonad ()+evalGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr' -> DatabaseContextEvalMonad () evalGraphRefDatabaseContextExpr NoOperation = pure ()    evalGraphRefDatabaseContextExpr (Define relVarName attrExprs) = do   context <- getStateContext-  relvars <- fmap relationVariables getStateContext-  tConss <- fmap typeConstructorMapping getStateContext   graph <- dbcGraph+  relVars <- resolveDBC relationVariables+  tConss <- resolveDBC typeConstructorMapping+   let eAttrs = runGraphRefRelationalExprM gfEnv (mapM evalGraphRefAttrExpr attrExprs)       gfEnv = freshGraphRefRelationalExprEnv (Just context) graph   case eAttrs of     Left err -> dbErr err     Right attrsList -> do       lift $ except $ validateAttributes tConss (A.attributesFromList attrsList)-      case M.member relVarName relvars of+      case M.member relVarName relVars of           True -> dbErr (RelVarAlreadyDefinedError relVarName)           False -> setRelVar relVarName (ExistingRelation emptyRelation)             where               attrs = A.attributesFromList attrsList-              emptyRelation = Relation attrs emptyTupleSet+              emptyRelation = Relation attrs TS.empty  evalGraphRefDatabaseContextExpr (Undefine relVarName) = deleteRelVar relVarName  evalGraphRefDatabaseContextExpr (Assign relVarName expr) = do   graph <- re_graph <$> dbcRelationalExprEnv   context <- getStateContext-  let existingRelVar = M.lookup relVarName (relationVariables context)+  relVars <- resolveDBC relationVariables+  tConsMapping <- resolveDBC typeConstructorMapping  +  let existingRelVar = M.lookup relVarName relVars       reEnv = freshGraphRefRelationalExprEnv (Just context) graph-   case existingRelVar of     Nothing -> do       case runGraphRefRelationalExprM reEnv (typeForGraphRefRelationalExpr expr) of         Left err -> dbErr err         Right reltype -> do-          lift $ except $ validateAttributes (typeConstructorMapping context) (attributes reltype)+          lift $ except $ validateAttributes tConsMapping (attributes reltype)           setRelVar relVarName expr     Just existingRel -> do       let eExpectedType = runGraphRefRelationalExprM reEnv (typeForGraphRefRelationalExpr existingRel)@@ -305,9 +324,9 @@             Left err -> dbErr err             Right newExprType -> do               if newExprType == expectedType then do-                lift $ except $ validateAttributes (typeConstructorMapping context) (attributes newExprType)+                lift $ except $ validateAttributes tConsMapping (attributes newExprType)                 setRelVar relVarName hintedExpr -              else+              else do                 dbErr (RelationTypeMismatchError (attributes expectedType) (attributes newExprType))  evalGraphRefDatabaseContextExpr (Insert relVarName relExpr) = do@@ -357,8 +376,8 @@   currContext <- getStateContext   transId <- dbcTransId   graph <- dbcGraph-  let currDeps = inclusionDependencies currContext-      newDeps = M.insert newDepName newDep currDeps+  currDeps <- resolveDBC inclusionDependencies+  let newDeps = ValueMarker $ M.insert newDepName newDep currDeps   if M.member newDepName currDeps then     dbErr (InclusionDependencyNameInUseError newDepName)     else do@@ -372,26 +391,26 @@  evalGraphRefDatabaseContextExpr (RemoveInclusionDependency depName) = do   currContext <- getStateContext-  let currDeps = inclusionDependencies currContext-      newDeps = M.delete depName currDeps+  currDeps <- resolveDBC inclusionDependencies+  let newDeps = ValueMarker $ M.delete depName currDeps   if M.notMember depName currDeps then     dbErr (InclusionDependencyNameNotInUseError depName)     else -    putStateContext $ currContext {inclusionDependencies = newDeps }+    putStateContext $ currContext { inclusionDependencies = newDeps }      -- | Add a notification which will send the resultExpr when triggerExpr changes between commits. evalGraphRefDatabaseContextExpr (AddNotification notName triggerExpr resultOldExpr resultNewExpr) = do   currentContext <- getStateContext-  graph <- dbcGraph+  nots <- resolveDBC notifications   transId <- dbcTransId-  let nots = notifications currentContext+  graph <- dbcGraph   if M.member notName nots then     dbErr (NotificationNameInUseError notName)     else do-      let newNotification = Notification { changeExpr = triggerExpr,+      let newNotifications = ValueMarker $ M.insert notName newNotification nots+          newNotification = Notification { changeExpr = triggerExpr,                                            reportOldExpr = resultOldExpr,                                             reportNewExpr = resultNewExpr}-          newNotifications = M.insert notName newNotification nots           potentialContext = currentContext { notifications = newNotifications }       case checkConstraints potentialContext transId graph of         Left err -> dbErr err@@ -399,37 +418,36 @@    evalGraphRefDatabaseContextExpr (RemoveNotification notName) = do   currentContext <- getStateContext-  let nots = notifications currentContext+  nots <- resolveDBC notifications   if M.notMember notName nots then     dbErr (NotificationNameNotInUseError notName)     else do-    let newNotifications = M.delete notName nots+    let newNotifications = ValueMarker $ M.delete notName nots     putStateContext $ currentContext { notifications = newNotifications } - -- | Adds type and data constructors to the database context. -- validate that the type *and* constructor names are unique! not yet implemented! evalGraphRefDatabaseContextExpr (AddTypeConstructor tConsDef dConsDefList) = do   currentContext <- getStateContext-  let oldTypes = typeConstructorMapping currentContext-      tConsName = TCD.name tConsDef+  oldTypes <- resolveDBC typeConstructorMapping+  let tConsName = TCD.name tConsDef   -- validate that the constructor's types exist   case validateTypeConstructorDef tConsDef dConsDefList oldTypes of     Left err -> throwError err     Right () | T.null tConsName || not (isUpper (T.head tConsName)) -> dbErr (InvalidAtomTypeName tConsName)        | isJust (findTypeConstructor tConsName oldTypes) -> dbErr (AtomTypeNameInUseError tConsName)        | otherwise -> do-      let newTypes = oldTypes ++ [(tConsDef, dConsDefList)]+      let newTypes = ValueMarker $ oldTypes ++ [(tConsDef, dConsDefList)]       putStateContext $ currentContext { typeConstructorMapping = newTypes }  -- | 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. evalGraphRefDatabaseContextExpr (RemoveTypeConstructor tConsName) = do   currentContext <- getStateContext-  let oldTypes = typeConstructorMapping currentContext+  oldTypes <- resolveDBC typeConstructorMapping   if isNothing (findTypeConstructor tConsName oldTypes) then     dbErr (AtomTypeNameNotInUseError tConsName)     else do-      let newTypes = filter (\(tCons, _) -> TCD.name tCons /= tConsName) oldTypes+      let newTypes = ValueMarker $ filter (\(tCons, _) -> TCD.name tCons /= tConsName) oldTypes       putStateContext $ currentContext { typeConstructorMapping = newTypes }  evalGraphRefDatabaseContextExpr (MultipleExpr exprs) =@@ -438,34 +456,36 @@  evalGraphRefDatabaseContextExpr (RemoveAtomFunction funcName') = do   currentContext <- getStateContext-  let atomFuncs = atomFunctions currentContext+  atomFuncs <- resolveDBC atomFunctions   case atomFunctionForName funcName' atomFuncs of     Left err -> dbErr err     Right realFunc ->       if isScriptedAtomFunction realFunc then do-        let updatedFuncs = HS.delete realFunc atomFuncs-        putStateContext (currentContext {atomFunctions = updatedFuncs })+        let updatedFuncs = ValueMarker $ HS.delete realFunc atomFuncs+        putStateContext (currentContext { atomFunctions = updatedFuncs })       else         dbErr (PrecompiledFunctionRemoveError funcName')        evalGraphRefDatabaseContextExpr (RemoveDatabaseContextFunction funcName') = do         context <- getStateContext-  let dbcFuncs = dbcFunctions context+  dbcFuncs <- resolveDBC dbcFunctions   case databaseContextFunctionForName funcName' dbcFuncs of     Left err -> dbErr err     Right realFunc ->       if isScriptedDatabaseContextFunction realFunc then do-        let updatedFuncs = HS.delete realFunc dbcFuncs-        putStateContext (context { dbcFunctions = updatedFuncs })+        let updatedFuncs = ValueMarker $ HS.delete realFunc dbcFuncs+        putStateContext (context { dbcFunctions = updatedFuncs})       else         dbErr (PrecompiledFunctionRemoveError funcName')        evalGraphRefDatabaseContextExpr (ExecuteDatabaseContextFunction funcName' atomArgExprs) = do   context <- getStateContext   graph <- dbcGraph+  dbcfuncutils <- dce_dbcfuncutils <$> RWS.ask+  funcs <- resolveDBC dbcFunctions   --resolve atom arguments   let eAtomTypes = mapM (runGraphRefRelationalExprM gfEnv . typeForGraphRefAtomExpr emptyAttributes) atomArgExprs-      eFunc = databaseContextFunctionForName funcName' (dbcFunctions context)+      eFunc = databaseContextFunctionForName funcName' funcs       gfEnv = freshGraphRefRelationalExprEnv (Just context) graph   case eFunc of       Left err -> dbErr err@@ -491,7 +511,7 @@                 else if not (null typeErrors) then                      dbErr (someErrors typeErrors)                    else-                     case evalDatabaseContextFunction func (rights eAtomArgs) context of+                     case evalDatabaseContextFunction func dbcfuncutils (rights eAtomArgs) context of                        Left err -> dbErr err                        Right newContext -> putStateContext newContext @@ -499,49 +519,53 @@   context <- getStateContext   tgraph <- dbcGraph   tid <- dbcTransId-  case M.lookup regName (registeredQueries context) of+  regQueries <- resolveDBC registeredQueries+  case M.lookup regName regQueries of     Just _ -> dbErr (RegisteredQueryNameInUseError regName)     Nothing -> do-      let context' = context { registeredQueries = M.insert regName regExpr (registeredQueries context) }+      let context' = context { registeredQueries = ValueMarker $ M.insert regName regExpr regQueries }       case checkConstraints context' tid tgraph of         Left err -> dbErr err         Right _ -> putStateContext context' evalGraphRefDatabaseContextExpr (RemoveRegisteredQuery regName) = do-  context <- getStateContext  -  case M.lookup regName (registeredQueries context) of+  context <- getStateContext+  regQueries <- resolveDBC registeredQueries+  case M.lookup regName regQueries of     Nothing -> dbErr (RegisteredQueryNameNotInUseError regName)-    Just _ -> putStateContext (context { registeredQueries = M.delete regName (registeredQueries context) })+    Just _ -> putStateContext (context { registeredQueries = ValueMarker $ M.delete regName regQueries})+evalGraphRefDatabaseContextExpr (AlterACL alterACLExpr) = do+  evalAlterDBCACLRoleIdExpr alterACLExpr   - data DatabaseContextIOEvalEnv = DatabaseContextIOEvalEnv   { dbcio_transId :: TransactionId,     dbcio_graph :: TransactionGraph,     dbcio_mScriptSession :: Maybe ScriptSession,-    dbcio_mModulesDirectory :: Maybe FilePath -- ^ when running in persistent mode, this must be a Just value to a directory containing .o/.so/.dynlib files which the user has placed there for access to compiled functions+    dbcio_roleId :: RoleId,+    dbcio_mModulesDirectory :: Maybe FilePath, -- ^ when running in persistent mode, this must be a Just value to a directory containing .o/.so/.dynlib files which the user has placed there for access to compiled functions+    dbcio_dbcfunctionUtils :: DatabaseContextFunctionUtils   } -type DatabaseContextIOEvalMonad a = RWST DatabaseContextIOEvalEnv () DatabaseContextEvalState IO a+type DatabaseContextIOEvalMonad a = RWST DatabaseContextIOEvalEnv () DatabaseContextEvalState (ExceptT RelationalError IO) a -runDatabaseContextIOEvalMonad :: DatabaseContextIOEvalEnv -> DatabaseContext -> DatabaseContextIOEvalMonad (Either RelationalError ()) -> IO (Either RelationalError DatabaseContextEvalState)+runDatabaseContextIOEvalMonad :: DatabaseContextIOEvalEnv -> DatabaseContext -> DatabaseContextIOEvalMonad () -> IO (Either RelationalError DatabaseContextEvalState) runDatabaseContextIOEvalMonad env ctx m = do-  res <- runRWST m env freshState+  res <- runExceptT $ runRWST m env freshState   case res of-    (Left err,_,_) -> pure (Left err)-    (Right (),s,_) -> pure (Right s)+    Left err -> pure (Left err)+    Right (_,s,_) -> pure (Right s)   where     freshState = mkDatabaseContextEvalState ctx -requireScriptSession :: DatabaseContextIOEvalMonad (Either RelationalError ScriptSession)+requireScriptSession :: DatabaseContextIOEvalMonad ScriptSession requireScriptSession = do   env <- RWS.ask   case dbcio_mScriptSession env of-    Nothing -> pure $ Left $ ScriptError ScriptCompilationDisabledError-    Just ss -> pure (Right ss)+    Nothing -> throwError (ScriptError ScriptCompilationDisabledError)+    Just ss -> pure ss -putDBCIOContext :: DatabaseContext -> DatabaseContextIOEvalMonad (Either RelationalError ())+putDBCIOContext :: DatabaseContext -> DatabaseContextIOEvalMonad () putDBCIOContext ctx = do   RWS.modify (\dbstate -> dbstate { dbc_context = ctx})-  pure (Right ())  getDBCIOContext :: DatabaseContextIOEvalMonad DatabaseContext getDBCIOContext = dbc_context <$> RWS.get@@ -549,24 +573,23 @@ getDBCIORelationalExprEnv :: DatabaseContextIOEvalMonad RelationalExprEnv getDBCIORelationalExprEnv = do   context <- getDBCIOContext-  mkRelationalExprEnv context . dbcio_graph <$> RWS.ask+  graph <- dbcio_graph <$> RWS.ask+  pure (mkRelationalExprEnv context graph) -evalGraphRefDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> DatabaseContextIOEvalMonad (Either RelationalError ())+evalGraphRefDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> DatabaseContextIOEvalMonad () #if !defined(PM36_HASKELL_SCRIPTING)-evalGraphRefDatabaseContextIOExpr AddAtomFunction{} = pure (Left (ScriptError ScriptCompilationDisabledError))-evalGraphRefDatabaseContextIOExpr AddDatabaseContextFunction{} = pure (Left (ScriptError ScriptCompilationDisabledError))-evalGraphRefDatabaseContextIOExpr LoadAtomFunctions{} = pure (Left (ScriptError ScriptCompilationDisabledError))-evalGraphRefDatabaseContextIOExpr LoadDatabaseContextFunctions{} = pure (Left (ScriptError ScriptCompilationDisabledError))+evalGraphRefDatabaseContextIOExpr AddAtomFunction{} = throwError (ScriptError ScriptCompilationDisabledError)+evalGraphRefDatabaseContextIOExpr AddDatabaseContextFunction{} = throwError (ScriptError ScriptCompilationDisabledError)+evalGraphRefDatabaseContextIOExpr LoadAtomFunctions{} = throwError (ScriptError ScriptCompilationDisabledError)+evalGraphRefDatabaseContextIOExpr LoadDatabaseContextFunctions{} = throwError (ScriptError ScriptCompilationDisabledError) #else evalGraphRefDatabaseContextIOExpr (AddAtomFunction funcName' funcType' script) = do-  eScriptSession <- requireScriptSession+  scriptSession <- requireScriptSession   currentContext <- getDBCIOContext-  case eScriptSession of-    Left err -> pure (Left err)-    Right scriptSession -> do-      res <- liftIO $ try $ runGhc (Just libdir) $ do+  atomFuncs <- resolveIODBC atomFunctions+  tConsMap <- resolveIODBC typeConstructorMapping+  res <- liftIO $ try $ runGhc (Just libdir) $ do         setSession (hscEnv scriptSession)-        let atomFuncs = atomFunctions currentContext         case extractAtomFunctionType funcType' of           Left err -> pure (Left err)           Right adjustedAtomTypeCons -> do@@ -575,37 +598,38 @@             pure $ case eCompiledFunc of               Left err -> Left (ScriptError err)               Right compiledFunc -> do-                funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructorValidate False funcTypeArg (typeConstructorMapping currentContext) M.empty) adjustedAtomTypeCons-                let updatedFuncs = HS.insert newAtomFunc atomFuncs+                funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructorValidate False funcTypeArg tConsMap M.empty) adjustedAtomTypeCons+                let updatedFuncs = ValueMarker $ HS.insert newAtomFunc atomFuncs                     newContext = currentContext { atomFunctions = updatedFuncs }                     newAtomFunc = Function { funcName = funcName',                                              funcType = funcAtomType,-                                             funcBody = FunctionScriptBody script compiledFunc }+                                             funcBody = FunctionScriptBody script compiledFunc,+                                             funcACL = () }                -- check if the name is already in use                 if HS.member funcName' (HS.map funcName atomFuncs) then                   Left (FunctionNameInUseError funcName')                   else                    Right newContext-      case res of-        Left (exc :: SomeException) -> pure $ Left (ScriptError (OtherScriptCompilationError (show exc)))+  case res of+        Left (exc :: SomeException) -> throwError (ScriptError (OtherScriptCompilationError (show exc)))         Right eContext -> case eContext of-          Left err -> pure (Left err)+          Left err -> throwError err           Right context' -> putDBCIOContext context' evalGraphRefDatabaseContextIOExpr (AddDatabaseContextFunction funcName' funcType' script) = do-  eScriptSession <- requireScriptSession+  scriptSession <- requireScriptSession   currentContext <- getDBCIOContext-  case eScriptSession of-    Left err -> pure (Left err)-    Right scriptSession -> do-      --validate that the function signature is of the form x -> y -> ... -> DatabaseContext -> DatabaseContext-      let last2Args = reverse (take 2 (reverse funcType'))-          atomArgs = take (length funcType' - 2) funcType'-          dbContextTypeCons = ADTypeConstructor "Either" [ADTypeConstructor "DatabaseContextFunctionError" [], ADTypeConstructor "DatabaseContext" []]-          expectedType = "DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext"-          actualType = show funcType'-      if last2Args /= [ADTypeConstructor "DatabaseContext" [], dbContextTypeCons] then -        pure (Left (ScriptError (TypeCheckCompilationError expectedType actualType)))+  myRoleId <- dbcio_roleId <$> RWS.ask+  --validate that the function signature is of the form x -> y -> ... -> DatabaseContext -> DatabaseContext+  let last2Args = reverse (take 2 (reverse funcType'))+      atomArgs = take (length funcType' - 2) funcType'+      dbContextTypeCons = ADTypeConstructor "Either" [ADTypeConstructor "RelationalError" [], ADTypeConstructor "DatabaseContext" []]+      expectedType = "DatabaseContext -> Either RelationalError DatabaseContext"+      actualType = show funcType'+  if last2Args /= [ADTypeConstructor "DatabaseContext" [], dbContextTypeCons] then +        throwError (ScriptError (TypeCheckCompilationError expectedType actualType))         else do+        tConsMap <- resolveIODBC typeConstructorMapping+        dbcFuncs <- resolveIODBC dbcFunctions         res <- liftIO $ try $ runGhc (Just libdir) $ do           setSession (hscEnv scriptSession)           eCompiledFunc  <- compileScript (dbcFunctionBodyType scriptSession) script@@ -613,52 +637,54 @@             Left err -> Left (ScriptError err)             Right compiledFunc -> do               --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-              funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg (typeConstructorMapping currentContext) M.empty) atomArgs-              let updatedDBCFuncs = HS.insert newDBCFunc (dbcFunctions currentContext)+              funcAtomType <- mapM (\funcTypeArg -> atomTypeForTypeConstructor funcTypeArg tConsMap M.empty) atomArgs+              let updatedDBCFuncs = ValueMarker $ HS.insert newDBCFunc dbcFuncs                   newContext = currentContext { dbcFunctions = updatedDBCFuncs }-                  dbcFuncs = dbcFunctions currentContext                   newDBCFunc = Function {                     funcName = funcName',                     funcType = funcAtomType,-                    funcBody = FunctionScriptBody script compiledFunc+                    funcBody = FunctionScriptBody script compiledFunc,+                    funcACL = allPermissionsForRoleId myRoleId                     }                 -- check if the name is already in use               if HS.member funcName' (HS.map funcName dbcFuncs) then-                Left (FunctionNameInUseError funcName')+                throwError (FunctionNameInUseError funcName')                 else -                Right newContext+                pure newContext         case res of-          Left (exc :: SomeException) -> pure $ Left (ScriptError (OtherScriptCompilationError (show exc)))+          Left (exc :: SomeException) -> throwError (ScriptError (OtherScriptCompilationError (show exc)))           Right eContext -> case eContext of-            Left err -> pure (Left err)+            Left err -> throwError err             Right context' -> putDBCIOContext context' evalGraphRefDatabaseContextIOExpr (LoadAtomFunctions modName entrypointName modPath) = do    -- when running an in-memory database, we are willing to load object files from any path- when running in persistent mode, we load modules only from the modules directory so that we can be reasonbly sure that these same modules will exist when the database is restarted from the same directory   mModDir <- dbcio_mModulesDirectory <$> ask   currentContext <- getDBCIOContext+  atomFuncs <- resolveIODBC atomFunctions   let sModName = T.unpack modName       sEntrypointName = T.unpack entrypointName   eLoadFunc <- liftIO $ loadFunctions sModName sEntrypointName mModDir modPath   case eLoadFunc of-    Left LoadSymbolError -> pure (Left LoadFunctionError)-    Left SecurityLoadSymbolError -> pure (Left SecurityLoadFunctionError)+    Left LoadSymbolError -> throwError LoadFunctionError+    Left SecurityLoadSymbolError -> throwError SecurityLoadFunctionError     Right atomFunctionListFunc -> do       let newContext = currentContext { atomFunctions = mergedFuncs }           processedAtomFunctions = processObjectLoadedFunctions sModName sEntrypointName modPath atomFunctionListFunc-          mergedFuncs = HS.union (atomFunctions currentContext) (HS.fromList processedAtomFunctions)+          mergedFuncs = ValueMarker $ HS.union atomFuncs (HS.fromList processedAtomFunctions)       putDBCIOContext newContext evalGraphRefDatabaseContextIOExpr (LoadDatabaseContextFunctions modName entrypointName modPath) = do   currentContext <- getDBCIOContext+  dbcFuncs <- resolveIODBC dbcFunctions   let sModName = T.unpack modName       sEntrypointName = T.unpack entrypointName   mModDir <- dbcio_mModulesDirectory <$> ask         eLoadFunc <- liftIO $ loadFunctions sModName sEntrypointName mModDir modPath   case eLoadFunc of-    Left LoadSymbolError -> pure (Left LoadFunctionError)-    Left SecurityLoadSymbolError -> pure (Left SecurityLoadFunctionError)+    Left LoadSymbolError -> throwError LoadFunctionError+    Left SecurityLoadSymbolError -> throwError SecurityLoadFunctionError     Right dbcListFunc -> let newContext = currentContext { dbcFunctions = mergedFuncs }-                             mergedFuncs = HS.union (dbcFunctions currentContext) (HS.fromList processedDBCFuncs)+                             mergedFuncs = ValueMarker $ HS.union dbcFuncs (HS.fromList processedDBCFuncs)                              processedDBCFuncs = processObjectLoadedFunctions sModName sEntrypointName modPath dbcListFunc                                   in putDBCIOContext newContext #endif@@ -668,49 +694,57 @@   env <- RWS.ask   --create graph ref expr   let gfExpr = Define relVarName attrExprs-      evalEnv = mkDatabaseContextEvalEnv (dbcio_transId env) (dbcio_graph env)+      evalEnv = mkDatabaseContextEvalEnv (dbcio_transId env) (dbcio_graph env) (dbcio_dbcfunctionUtils env)       graph = dbcio_graph env   case runDatabaseContextEvalMonad currentContext evalEnv (evalGraphRefDatabaseContextExpr gfExpr) of-    Left err -> pure (Left err)+    Left err -> throwError err     Right dbstate -> do          --Assign-           let existingRelVar = M.lookup relVarName relVarTable-               relVarTable = relationVariables (dbc_context dbstate)-           case existingRelVar of-                Nothing -> pure $ Left (RelVarNotDefinedError relVarName)-                Just existingRel -> do+           let context' = dbc_context dbstate      +           case resolveDBC' graph context' relationVariables of+             Left err -> throwError err+             Right relVars -> do+               let existingRelVar = M.lookup relVarName relVars+               case existingRelVar of+                 Nothing -> throwError (RelVarNotDefinedError relVarName)+                 Just existingRel -> do                   let gfEnv = freshGraphRefRelationalExprEnv (Just currentContext) graph                   case runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr existingRel) of-                    Left err -> pure (Left err)+                    Left err -> throwError err                     Right relType -> do-                      let expectedAttributes = attributes relType-                          tcMap = typeConstructorMapping (dbc_context dbstate)-                      eitherRel <- liftIO $ generate $ runReaderT (arbitraryRelation expectedAttributes range) tcMap-                      case eitherRel of-                        Left err -> pure $ Left err-                        Right rel ->-                          case runDatabaseContextEvalMonad currentContext evalEnv (setRelVar relVarName (ExistingRelation rel)) of-                            Left err -> pure (Left err)-                            Right dbstate' -> putDBCIOContext (dbc_context dbstate')+                      case resolveDBC' graph context' typeConstructorMapping of+                        Left err -> throwError err+                        Right tcMap -> do+                          let expectedAttributes = attributes relType+                          eitherRel <- liftIO $ generate $ runReaderT (arbitraryRelation expectedAttributes range) tcMap+                          case eitherRel of+                            Left err -> throwError err+                            Right rel ->+                              case runDatabaseContextEvalMonad currentContext evalEnv (setRelVar relVarName (ExistingRelation rel)) of+                                Left err -> throwError err+                                Right dbstate' -> putDBCIOContext (dbc_context dbstate') ---run verification on all constraints+-- | run verification of all constraints+-- needs DatabaseContext to create dummy Transaction checkConstraints :: DatabaseContext -> TransactionId -> TransactionGraph -> Either RelationalError () checkConstraints context transId graph@(TransactionGraph graphHeads transSet) = do+  deps <- resolveDBC' graph context inclusionDependencies     mapM_ (uncurry checkIncDep) (M.toList deps)-  mapM_ checkRegisteredQuery (M.toList (registeredQueries context))-  mapM_ checkNotification (M.toList (notifications context))+  regQueries <- resolveDBC' graph context registeredQueries+  mapM_ checkRegisteredQuery (M.toList regQueries)+  notifs <- resolveDBC' graph context notifications+  mapM_ checkNotification (M.toList notifs)+   where     potentialGraph = TransactionGraph graphHeads (S.insert tempTrans transSet)     tempStamp = UTCTime { utctDay = fromGregorian 2000 1 1,                           utctDayTime = secondsToDiffTime 0 }-    tempSchemas = Schemas context M.empty-    tempTrans = Transaction U.nil  tempTransInfo tempSchemas+    tempSchemas = Schemas context emptyValue+    tempTrans = Transaction U.nil tempTransInfo tempSchemas     tempTransInfo = TransactionInfo { parents = transId NE.:| [],                                       stamp = tempStamp,                                       merkleHash = mempty                                       }-    -    deps = inclusionDependencies context     process = runProcessExprM UncommittedContextMarker     gfEnv = freshGraphRefRelationalExprEnv (Just context) graph       -- no optimization available here, really? perhaps the optimizer should be passed down to here or the eval function should be passed through the environment@@ -767,45 +801,39 @@       runGf = runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr gfExpr)   lift $ except runGf -liftE :: (Monad m) => m (Either a b) -> ExceptT a m b-liftE v = do-  y <- lift v-  case y of-    Left err -> throwError err-    Right val -> pure val- {- used for restrictions- take the restrictionpredicate and return the corresponding filter function -} predicateRestrictionFilter :: Attributes -> GraphRefRestrictionPredicateExpr -> GraphRefRelationalExprM RestrictionFilter predicateRestrictionFilter attrs (AndPredicate expr1 expr2) = do   expr1v <- predicateRestrictionFilter attrs expr1   expr2v <- predicateRestrictionFilter attrs expr2-  pure (\x -> do-           ev1 <- expr1v x -           ev2 <- expr2v x+  pure (\tup ctx -> do+           ev1 <- expr1v tup ctx+           ev2 <- expr2v tup ctx            pure (ev1 && ev2))  predicateRestrictionFilter attrs (OrPredicate expr1 expr2) = do     expr1v <- predicateRestrictionFilter attrs expr1     expr2v <- predicateRestrictionFilter attrs expr2-    pure (\x -> do-                ev1 <- expr1v x -                ev2 <- expr2v x-                pure (ev1 || ev2))+    pure (\tup ctx -> do+             ev1 <- expr1v tup ctx+             ev2 <- expr2v tup ctx+             pure (ev1 || ev2)) -predicateRestrictionFilter _ TruePredicate = pure (\_ -> pure True)+predicateRestrictionFilter _ TruePredicate = pure (\_ _ -> pure True)  predicateRestrictionFilter attrs (NotPredicate expr) = do   exprv <- predicateRestrictionFilter attrs expr-  pure (fmap not . exprv)+  pure (\tup ctx ->+          not <$> exprv tup ctx)  --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 predicateRestrictionFilter _ (RelationalExprPredicate relExpr) = do   renv <- askEnv-  let eval :: RelationTuple -> Either RelationalError Relation-      eval tup = +  let eval :: RelationTuple -> ContextTuples -> Either RelationalError Relation+      eval tup _ = -- context is ignored because the context is available via a closure         let gfEnv = mergeTuplesIntoGraphRefRelationalExprEnv tup renv in         runGraphRefRelationalExprM gfEnv (evalGraphRefRelationalExpr relExpr)-  pure (\tup -> case eval tup of+  pure (\tup ctx -> case eval tup ctx of     Left err -> Left err     Right rel -> if arity rel /= 0 then                    Left (PredicateExpressionError "Relational restriction filter must evaluate to 'true' or 'false'")@@ -827,17 +855,15 @@   if atomExprType /= A.atomType attr then do       throwError (TupleAttributeTypeMismatchError (A.attributesFromList [attr]))     else-      pure $ \tupleIn -> let evalAndCmp atomIn = case atomEvald of-                               Right atomCmp -> atomCmp == atomIn-                               Left _ -> False-                             atomEvald = runGraphRefRelationalExprM env (evalGraphRefAtomExpr tupleIn atomExpr)+      pure $ \tupIn ctx -> let evalAndCmp atomIn = case atomEvald of+                                                     Right atomCmp -> atomCmp == atomIn+                                                     Left _ -> False+                               atomEvald = runGraphRefRelationalExprM env (evalGraphRefAtomExpr tupIn atomExpr)+                               ctx' = addContextTuple ctx ctxtup'                          in-                          pure $ case atomForAttributeName attrName tupleIn of-                            Left (NoSuchAttributeNamesError _) -> case atomForAttributeName attrName ctxtup' of-                              Left _ -> False-                              Right ctxatom -> evalAndCmp ctxatom-                            Left _ -> False-                            Right atomIn -> evalAndCmp atomIn+                          case contextTupleAtomForAttributeName' tupIn ctx' attrName of+                            Left err -> Left err+                            Right atomIn -> pure $ evalAndCmp atomIn -- in the future, it would be useful to do typechecking on the attribute and atom expr filters in advance predicateRestrictionFilter attrs (AtomExprPredicate atomExpr) = do   --merge attrs into the state attributes@@ -846,37 +872,38 @@   if aType /= BoolAtomType then       throwError (AtomTypeMismatchError aType BoolAtomType)     else-      pure (\tupleIn ->-             case runGraphRefRelationalExprM renv (evalGraphRefAtomExpr tupleIn atomExpr) of+      pure (\tupIn _ ->+             case runGraphRefRelationalExprM renv (evalGraphRefAtomExpr tupIn atomExpr) of                Left err -> Left err                Right boolAtomValue -> pure (boolAtomValue == BoolAtom True)) -tupleExprCheckNewAttrName :: AttributeName -> Relation -> Either RelationalError Relation-tupleExprCheckNewAttrName attrName rel = if isRight (attributeForName attrName rel) then+tupleExprCheckNewAttrName :: AttributeName -> Attributes -> Either RelationalError Attributes+tupleExprCheckNewAttrName attrName attrs = if isRight (A.attributeForName attrName attrs) then                                            Left (AttributeNameInUseError attrName)                                          else-                                           Right rel+                                           Right attrs+                                           +type ExtendTupleProcessor = (Attributes, RelationTuple -> ContextTuples -> Either RelationalError RelationTuple) -extendGraphRefTupleExpressionProcessor :: Relation -> GraphRefExtendTupleExpr -> GraphRefRelationalExprM (Attributes, RelationTuple -> Either RelationalError RelationTuple)-extendGraphRefTupleExpressionProcessor relIn (AttributeExtendTupleExpr newAttrName atomExpr) = +-- | Pass in existing attributes of the relational expression and the extend expr, return the new attributes and the function which will add the attribute to each tuples+extendGraphRefTupleExpressionProcessor :: Attributes -> GraphRefExtendTupleExpr -> GraphRefRelationalExprM ExtendTupleProcessor+extendGraphRefTupleExpressionProcessor attrsIn (AttributeExtendTupleExpr newAttrName atomExpr) =  --  renv <- askEnv   -- check that the attribute name is not in use-  case tupleExprCheckNewAttrName newAttrName relIn of+  case tupleExprCheckNewAttrName newAttrName attrsIn of     Left err -> throwError err     Right _ -> do-      atomExprType <- typeForGraphRefAtomExpr (attributes relIn) atomExpr-      atomExprType' <- verifyGraphRefAtomExprTypes relIn atomExpr atomExprType+      atomExprType <- typeForGraphRefAtomExpr attrsIn atomExpr+      atomExprType' <- verifyGraphRefAtomExprTypes attrsIn atomExpr atomExprType       let newAttrs = A.attributesFromList [Attribute newAttrName atomExprType']-          newAndOldAttrs = A.addAttributes (attributes relIn) newAttrs+          newAndOldAttrs = A.addAttributes attrsIn newAttrs       env <- ask-      pure (newAndOldAttrs, \tup -> do-               let gfEnv = mergeTuplesIntoGraphRefRelationalExprEnv tup env-               atom <- runGraphRefRelationalExprM gfEnv (evalGraphRefAtomExpr tup atomExpr)-               Right (tupleAtomExtend newAttrName atom tup)+      pure (newAndOldAttrs, \tupIn (ContextTuples tupsIn) -> do+               let gfEnv = foldl' (flip mergeTuplesIntoGraphRefRelationalExprEnv) env tupsIn+               atom <- runGraphRefRelationalExprM gfEnv (evalGraphRefAtomExpr tupIn atomExpr)+               Right (tupleAtomExtend newAttrName atom tupIn)                ) -  - evalGraphRefAtomExpr :: RelationTuple -> GraphRefAtomExpr -> GraphRefRelationalExprM Atom evalGraphRefAtomExpr tupIn (AttributeAtomExpr attrName) =   case atomForAttributeName attrName tupIn of@@ -893,8 +920,9 @@ -- first argumentr is starting value, second argument is relationatom evalGraphRefAtomExpr tupIn (FunctionAtomExpr funcName' arguments tid) = do   argTypes <- mapM (typeForGraphRefAtomExpr (tupleAttributes tupIn)) arguments+  graph <- gfGraph   context <- gfDatabaseContextForMarker tid-  let functions = atomFunctions context+  functions <- lift $ except $ resolveDBC' graph context atomFunctions   func <- lift $ except (atomFunctionForName funcName' functions)   let expectedArgCount = length (funcType func) - 1       actualArgCount = length argTypes@@ -934,9 +962,9 @@ evalGraphRefAtomExpr _ (ConstructedAtomExpr tOrF [] _)   | tOrF == "True" = pure (BoolAtom True)   | tOrF == "False" = pure (BoolAtom False)-evalGraphRefAtomExpr tupIn cons@(ConstructedAtomExpr dConsName dConsArgs _) = do --why is the tid unused here? suspicious+evalGraphRefAtomExpr tupIn consE@(ConstructedAtomExpr dConsName dConsArgs _) = do --why is the tid unused here? suspicious   let mergeEnv = mergeTuplesIntoGraphRefRelationalExprEnv tupIn-  aType <- local mergeEnv (typeForGraphRefAtomExpr (tupleAttributes tupIn) cons)+  aType <- local mergeEnv (typeForGraphRefAtomExpr (tupleAttributes tupIn) consE)   argAtoms <- local mergeEnv $     mapM (evalGraphRefAtomExpr tupIn) dConsArgs   pure (ConstructedAtom dConsName aType argAtoms) @@ -966,25 +994,29 @@     _ -> throwError (AttributeIsNotRelationValuedError relAttr) typeForGraphRefAtomExpr _ (NakedAtomExpr atom) = pure (atomTypeForAtom atom) typeForGraphRefAtomExpr attrs (FunctionAtomExpr funcName' atomArgs transId) = do-  funcs <- atomFunctions <$> gfDatabaseContextForMarker transId-  case atomFunctionForName funcName' funcs of+  ctx <- gfDatabaseContextForMarker transId+  graph <- gfGraph+  case resolveDBC' graph ctx atomFunctions of     Left err -> throwError err-    Right func -> do-      let funcRetType = last (funcType func)-          funcArgTypes = init (funcType func)-          funArgCount = length funcArgTypes-          inArgCount = length atomArgs-      when (funArgCount /= inArgCount) (throwError (FunctionArgumentCountMismatchError funArgCount inArgCount))-      argTypes <- mapM (typeForGraphRefAtomExpr attrs) atomArgs-      mapM_ (\(fArg,arg,argCount) -> do+    Right funcs -> do+      case atomFunctionForName funcName' funcs of+        Left err -> throwError err+        Right func -> do+          let funcRetType = last (funcType func)+              funcArgTypes = init (funcType func)+              funArgCount = length funcArgTypes+              inArgCount = length atomArgs+          when (funArgCount /= inArgCount) (throwError (FunctionArgumentCountMismatchError funArgCount inArgCount))+          argTypes <- mapM (typeForGraphRefAtomExpr attrs) atomArgs+          mapM_ (\(fArg,arg,argCount) -> do                 let handler :: RelationalError -> GraphRefRelationalExprM AtomType                     handler (AtomTypeMismatchError expSubType actSubType) = do                       throwError (AtomFunctionTypeError funcName' argCount expSubType actSubType)                     handler err = throwError err                 lift (except $ atomTypeVerify fArg arg) `catchError` handler             ) (zip3 funcArgTypes argTypes [1..])-      let eTvMap = resolveTypeVariables funcArgTypes argTypes-      case eTvMap of+          let eTvMap = resolveTypeVariables funcArgTypes argTypes+          case eTvMap of             Left err -> throwError err             Right tvMap ->               lift $ except $ resolveFunctionReturnValue funcName' tvMap funcRetType@@ -1006,14 +1038,18 @@ typeForGraphRefAtomExpr attrs (ConstructedAtomExpr dConsName dConsArgs tid) =   do     argsTypes <- mapM (typeForGraphRefAtomExpr attrs) dConsArgs-    tConsMap <- typeConstructorMapping <$> gfDatabaseContextForMarker tid-    lift $ except $ atomTypeForDataConstructor tConsMap dConsName argsTypes+    ctx <- gfDatabaseContextForMarker tid+    graph <- gfGraph+    case resolveDBC' graph ctx typeConstructorMapping of+      Left err -> throwError err+      Right tConsMap -> +        lift $ except $ atomTypeForDataConstructor tConsMap dConsName argsTypes  -- | Validate that the type of the AtomExpr matches the expected type.-verifyGraphRefAtomExprTypes :: Relation -> GraphRefAtomExpr -> AtomType -> GraphRefRelationalExprM AtomType-verifyGraphRefAtomExprTypes relIn (AttributeAtomExpr attrName) expectedType = do+verifyGraphRefAtomExprTypes :: Attributes -> GraphRefAtomExpr -> AtomType -> GraphRefRelationalExprM AtomType+verifyGraphRefAtomExprTypes attrsIn (AttributeAtomExpr attrName) expectedType = do   env <- askEnv-  case A.atomTypeForAttributeName attrName (attributes relIn) of+  case A.atomTypeForAttributeName attrName attrsIn of     Right aType -> lift $ except $ atomTypeVerify expectedType aType     (Left err@(NoSuchAttributeNamesError _)) ->       let attrs' = envAttributes env in@@ -1027,14 +1063,15 @@  verifyGraphRefAtomExprTypes _ (NakedAtomExpr atom) expectedType =   lift $ except $ atomTypeVerify expectedType (atomTypeForAtom atom)-verifyGraphRefAtomExprTypes relIn (SubrelationAttributeAtomExpr relAttr subAttr) expectedType = do-    let mergedAttrsEnv = mergeAttributesIntoGraphRefRelationalExprEnv (attributes relIn)+verifyGraphRefAtomExprTypes attrsIn (SubrelationAttributeAtomExpr relAttr subAttr) expectedType = do+    let mergedAttrsEnv = mergeAttributesIntoGraphRefRelationalExprEnv attrsIn     (Relation relAttrs _) <- R.local mergedAttrsEnv (typeForGraphRefRelationalExpr (RelationValuedAttribute relAttr))     subAttrType <- lift $ except $ A.atomTypeForAttributeName subAttr relAttrs     lift $ except $ atomTypeVerify expectedType (SubrelationFoldAtomType subAttrType)-verifyGraphRefAtomExprTypes relIn (FunctionAtomExpr funcName' funcArgExprs tid) expectedType = do+verifyGraphRefAtomExprTypes attrsIn (FunctionAtomExpr funcName' funcArgExprs tid) expectedType = do   context <- gfDatabaseContextForMarker tid-  let functions = atomFunctions context+  graph <- gfGraph+  functions <- lift $ except $ resolveDBC' graph context atomFunctions   func <- lift $ except $ atomFunctionForName funcName' functions   let expectedArgTypes = funcType func       funcArgVerifier (atomExpr, expectedType2, argCount) = do@@ -1042,33 +1079,37 @@             handler (AtomTypeMismatchError expSubType actSubType) = do               throwError (AtomFunctionTypeError funcName' argCount expSubType actSubType)             handler err = throwError err-        verifyGraphRefAtomExprTypes relIn atomExpr expectedType2 `catchError` handler   +        verifyGraphRefAtomExprTypes attrsIn atomExpr expectedType2 `catchError` handler      funcArgTypes <- mapM funcArgVerifier $ zip3 funcArgExprs expectedArgTypes [1..]   if length funcArgTypes /= length expectedArgTypes - 1 then       throwError (AtomTypeCountError funcArgTypes expectedArgTypes)       else        lift $ except $ atomTypeVerify expectedType (last expectedArgTypes)-verifyGraphRefAtomExprTypes relIn (RelationAtomExpr relationExpr) expectedType =+verifyGraphRefAtomExprTypes attrsIn (RelationAtomExpr relationExpr) expectedType =   do-    let mergedAttrsEnv = mergeAttributesIntoGraphRefRelationalExprEnv (attributes relIn)+    let mergedAttrsEnv = mergeAttributesIntoGraphRefRelationalExprEnv attrsIn     relType <- R.local mergedAttrsEnv (typeForGraphRefRelationalExpr relationExpr)     lift $ except $ atomTypeVerify expectedType (RelationAtomType (attributes relType))-verifyGraphRefAtomExprTypes relIn (IfThenAtomExpr _ifExpr thenExpr elseExpr) expectedType = do-  thenType <- typeForGraphRefAtomExpr (attributes relIn) thenExpr-  elseType <- typeForGraphRefAtomExpr (attributes relIn) elseExpr+verifyGraphRefAtomExprTypes attrsIn consE@ConstructedAtomExpr{} expectedType = do+  cType <- typeForGraphRefAtomExpr attrsIn consE+  lift $ except $ atomTypeVerify expectedType cType+verifyGraphRefAtomExprTypes attrsIn (IfThenAtomExpr _ifExpr thenExpr elseExpr) expectedType = do+  thenType <- typeForGraphRefAtomExpr attrsIn thenExpr+  elseType <- typeForGraphRefAtomExpr attrsIn elseExpr   when (thenType /= elseType) $ throwError (AtomTypeMismatchError thenType elseType)     lift $ except $ atomTypeVerify expectedType thenType-verifyGraphRefAtomExprTypes rel cons@ConstructedAtomExpr{} expectedType = do-  cType <- typeForGraphRefAtomExpr (attributes rel) cons-  lift $ except $ atomTypeVerify expectedType cType  -- | Look up the type's name and create a new attribute. evalGraphRefAttrExpr :: GraphRefAttributeExpr -> GraphRefRelationalExprM Attribute evalGraphRefAttrExpr (AttributeAndTypeNameExpr attrName tCons transId) = do-  tConsMap <- typeConstructorMapping <$> gfDatabaseContextForMarker transId-  aType <- lift $ except $ atomTypeForTypeConstructorValidate True tCons tConsMap M.empty-  lift $ except $ validateAtomType aType tConsMap-  pure $ Attribute attrName aType+  ctx <- gfDatabaseContextForMarker transId+  graph <- gfGraph+  case resolveDBC' graph ctx typeConstructorMapping of+    Left err -> throwError err+    Right tConsMap -> do+      aType <- lift $ except $ atomTypeForTypeConstructorValidate True tCons tConsMap M.empty+      lift $ except $ validateAtomType aType tConsMap+      pure $ Attribute attrName aType    evalGraphRefAttrExpr (NakedAttributeExpr attr) = pure attr @@ -1077,6 +1118,7 @@ evalGraphRefTupleExprs _ (TupleExprs _ []) = pure [] evalGraphRefTupleExprs mAttrs (TupleExprs fixedMarker tupleExprL) = do   tuples <- mapM (evalGraphRefTupleExpr mAttrs) tupleExprL+  graph <- gfGraph   finalAttrs <- case mAttrs of     Just attrs -> pure attrs     Nothing ->@@ -1107,11 +1149,17 @@                       ) (V.toList . attributesVec $ tupleAttributes headTuple) tailTuples           pure (A.attributesFromList mostResolvedTypes)   --strategy: if all the tuple expr transaction markers refer to one location, then we can pass the type constructor mapping from that location, otherwise, we cannot assume that the types are the same+  let getTypeConsMap ctx =+        case resolveDBC' graph ctx typeConstructorMapping of+          Left err -> throwError err+          Right m -> pure m   tConsMap <- case singularTransactions tupleExprL of-                   SingularTransactionRef commonTransId -> -                     typeConstructorMapping <$> gfDatabaseContextForMarker commonTransId-                   NoTransactionsRef -> -                     typeConstructorMapping <$> gfDatabaseContextForMarker fixedMarker+                   SingularTransactionRef commonTransId -> do+                     ctx <- gfDatabaseContextForMarker commonTransId+                     getTypeConsMap ctx+                   NoTransactionsRef -> do +                     ctx <- gfDatabaseContextForMarker fixedMarker+                     getTypeConsMap ctx   -- if there are multiple transaction markers in the TupleExprs, then we can't assume a single type constructor mapping- this could be improved in the future, but if all the tuples are fully resolved, then we don't need further resolution                                         _ -> throwError TupleExprsReferenceMultipleMarkersError   lift $ except $ validateAttributes tConsMap finalAttrs@@ -1164,12 +1212,14 @@         [] -> A.emptyAttributes         x : _ -> tupleAttributes x   lift $ except $ mkRelation attrs (RelationTupleSet tuples)-evalGraphRefRelationalExpr (MakeStaticRelation attributeSet tupleSet) = -  lift $ except $ mkRelation attributeSet tupleSet+evalGraphRefRelationalExpr (MakeStaticRelation attributeSet tupleSet') = +  lift $ except $ mkRelation attributeSet tupleSet' evalGraphRefRelationalExpr (ExistingRelation rel) = pure rel evalGraphRefRelationalExpr (RelationVariable name tid) = do   ctx <- gfDatabaseContextForMarker tid-  case M.lookup name (relationVariables ctx) of+  graph <- gfGraph+  rvs <- lift $ except $ resolveDBC' graph ctx relationVariables+  case M.lookup name rvs of     Nothing -> throwError (RelVarNotDefinedError name)     Just rv -> evalGraphRefRelationalExpr rv evalGraphRefRelationalExpr (RelationValuedAttribute attrName) = do@@ -1222,8 +1272,10 @@   pure $ if relA == relB then relationFalse else relationTrue evalGraphRefRelationalExpr (Extend extendTupleExpr expr) = do   rel <- evalGraphRefRelationalExpr expr-  (newAttrs, tupProc) <- extendGraphRefTupleExpressionProcessor rel extendTupleExpr-  lift $ except $ relMogrify tupProc newAttrs rel+  (newAttrs, tupProc) <- extendGraphRefTupleExpressionProcessor (attributes rel) extendTupleExpr+  extraTup <- asks envTuple+  let ctx = singletonContextTuple extraTup+  lift $ except $ relMogrify (`tupProc` ctx) newAttrs rel evalGraphRefRelationalExpr expr@With{} =   --strategy A: add relation variables to the contexts in the graph   --strategy B: drop in macros in place (easier programmatically)@@ -1247,7 +1299,7 @@         x : _ -> Right x  typeForGraphRefRelationalExpr :: GraphRefRelationalExpr -> GraphRefRelationalExprM Relation-typeForGraphRefRelationalExpr (MakeStaticRelation attrs _) = lift $ except $ mkRelation attrs emptyTupleSet+typeForGraphRefRelationalExpr (MakeStaticRelation attrs _) = lift $ except $ mkRelation attrs TS.empty typeForGraphRefRelationalExpr (ExistingRelation rel) = pure (emptyRelationWithAttrs (attributes rel)) typeForGraphRefRelationalExpr (MakeRelationFromExprs mAttrExprs tupleExprs) = do   mAttrs <- case mAttrExprs of@@ -1255,18 +1307,26 @@                 attrs <- mapM evalGraphRefAttributeExpr attrExprs                 pure (Just (attributesFromList attrs))               Nothing -> pure Nothing-  tuples <- evalGraphRefTupleExprs mAttrs tupleExprs-  let retAttrs = case tuples of-                (tup:_) -> tupleAttributes tup-                [] -> fromMaybe A.emptyAttributes mAttrs-  pure $ emptyRelationWithAttrs retAttrs+  retAttrs <- typeForGraphRefTupleExprs mAttrs tupleExprs+  case mAttrs of+    Nothing ->+      pure $ emptyRelationWithAttrs retAttrs+    Just attrs ->+      case A.reorderAttributes attrs retAttrs of+        Left err -> throwError err+        Right retAttrs' -> +          pure $ emptyRelationWithAttrs retAttrs'    typeForGraphRefRelationalExpr (RelationVariable rvName tid) = do-  relVars <- relationVariables <$> gfDatabaseContextForMarker tid-  case M.lookup rvName relVars of-    Nothing -> throwError (RelVarNotDefinedError rvName)-    Just rvExpr -> -      typeForGraphRefRelationalExpr rvExpr+  ctx <- gfDatabaseContextForMarker tid+  graph <- gfGraph+  case resolveDBC' graph ctx relationVariables of+    Left err -> throwError err+    Right relVars -> do+      case M.lookup rvName relVars of+        Nothing -> throwError (RelVarNotDefinedError rvName)+        Just rvExpr -> +          typeForGraphRefRelationalExpr rvExpr typeForGraphRefRelationalExpr (RelationValuedAttribute attrName) = do   env <- askEnv   case gre_extra env of@@ -1311,6 +1371,8 @@   lift $ except $ ungroup groupAttrName expr' typeForGraphRefRelationalExpr (Restrict pred' expr) = do   expr' <- typeForGraphRefRelationalExpr expr+  let mergedAttrsEnv = mergeAttributesIntoGraphRefRelationalExprEnv (attributes expr')+  R.local mergedAttrsEnv (typeForGraphRefRestrictionPredicateExpr pred')   filt <- predicateRestrictionFilter (attributes expr') pred'   lift $ except $ restrict filt expr' typeForGraphRefRelationalExpr Equals{} = @@ -1323,12 +1385,43 @@ typeForGraphRefRelationalExpr expr@(With withs _) = do   let expr' = substituteWithNameMacros [] expr       checkMacroName (WithNameExpr macroName tid) = do-        rvs <- relationVariables <$> gfDatabaseContextForMarker tid-        case M.lookup macroName rvs of-          Just _ -> lift $ except $ Left (RelVarAlreadyDefinedError macroName) --this error does not include the transaction marker, but should be good enough to identify the cause-          Nothing -> pure ()+        ctx <- gfDatabaseContextForMarker tid+        graph <- gfGraph+        case resolveDBC' graph ctx relationVariables of+          Left err -> throwError err+          Right rvs ->+            case M.lookup macroName rvs of+              Just _ -> lift $ except $ Left (RelVarAlreadyDefinedError macroName) --this error does not include the transaction marker, but should be good enough to identify the cause+              Nothing -> pure ()   mapM_ (checkMacroName . fst) withs   typeForGraphRefRelationalExpr expr'++-- | Typecheck for restriction predicate- predicates always return Bool, so the return value is nominal.+typeForGraphRefRestrictionPredicateExpr :: GraphRefRestrictionPredicateExpr -> GraphRefRelationalExprM ()+typeForGraphRefRestrictionPredicateExpr expr = do+  extra <- asks gre_extra+  let attrs = case extra of+                Nothing -> mempty+                Just (Right attrs') -> attrs'+                Just (Left _) -> error "never" -- mempty -- should never happen+      self = typeForGraphRefRestrictionPredicateExpr                +  case expr of+    TruePredicate -> pure ()+    AndPredicate a b -> do+      self a+      self b+    OrPredicate a b -> do+      self a+      self b+    NotPredicate a -> self a+    RelationalExprPredicate relExpr -> do+      rType <- typeForGraphRefRelationalExpr relExpr+      unless (A.null (attributes rType)) $ throwError (PredicateExpressionError "Relational restriction filter must evaluate to 'true' or 'false'")+    AtomExprPredicate atomExpr -> do+      void $ typeForGraphRefAtomExpr attrs atomExpr+    AttributeEqualityPredicate attrName atomExpr -> do+      void $ typeForGraphRefAtomExpr attrs atomExpr+      unless (A.isAttributeNameContained attrName attrs) $ throwError (NoSuchAttributeNamesError (S.singleton attrName))    evalGraphRefAttributeNames :: GraphRefAttributeNames -> GraphRefRelationalExpr -> GraphRefRelationalExprM (S.Set AttributeName) evalGraphRefAttributeNames attrNames expr = do@@ -1363,20 +1456,29 @@  evalGraphRefAttributeExpr :: GraphRefAttributeExpr -> GraphRefRelationalExprM Attribute evalGraphRefAttributeExpr (AttributeAndTypeNameExpr attrName tCons tid) = do-  tConsMap <- typeConstructorMapping <$> gfDatabaseContextForMarker tid-  case atomTypeForTypeConstructorValidate True tCons tConsMap M.empty of+  ctx <- gfDatabaseContextForMarker tid+  graph <- gfGraph+  case resolveDBC' graph ctx typeConstructorMapping of     Left err -> throwError err-    Right aType -> do-      case validateAtomType aType tConsMap of+    Right tConsMap -> +      case atomTypeForTypeConstructorValidate True tCons tConsMap M.empty of         Left err -> throwError err-        Right _ -> pure (Attribute attrName aType)+        Right aType -> do+          case validateAtomType aType tConsMap of+            Left err -> throwError err+            Right _ -> pure (Attribute attrName aType) evalGraphRefAttributeExpr (NakedAttributeExpr attr) = pure attr         +mkEmptyRelVars' :: TransactionGraph -> ValueMarker RelationVariables -> Either RelationalError (ValueMarker RelationVariables)+mkEmptyRelVars' graph v_rvs = do+  rvs <- resolveValueMarker graph relationVariables v_rvs+  pure (ValueMarker (mkEmptyRelVars rvs))+ mkEmptyRelVars :: RelationVariables -> RelationVariables mkEmptyRelVars = M.map mkEmptyRelVar   where     mkEmptyRelVar expr@MakeRelationFromExprs{} = expr --do not truncate here because we might lose essential type information in emptying the tuples-    mkEmptyRelVar (MakeStaticRelation attrs _) = MakeStaticRelation attrs emptyTupleSet+    mkEmptyRelVar (MakeStaticRelation attrs _) = MakeStaticRelation attrs TS.empty     mkEmptyRelVar (ExistingRelation rel) = ExistingRelation (emptyRelationWithAttrs (attributes rel))     mkEmptyRelVar x@RelationValuedAttribute{} = x     mkEmptyRelVar rv@RelationVariable{} = Restrict (NotPredicate TruePredicate) rv@@ -1400,10 +1502,10 @@ -- | Return a Relation describing the relation variables. relationVariablesAsRelation :: DatabaseContext -> TransactionGraph -> Either RelationalError Relation relationVariablesAsRelation ctx graph = do+  relVars <- resolveDBC' graph ctx relationVariables   let subrelAttrs = A.attributesFromList [Attribute "attribute" TextAtomType, Attribute "type" TextAtomType]       attrs = A.attributesFromList [Attribute "name" TextAtomType,                                   Attribute "attributes" (RelationAtomType subrelAttrs)]-      relVars = relationVariables ctx       mkRvDesc (rvName, gfExpr) = do         let gfEnv = freshGraphRefRelationalExprEnv (Just ctx) graph         gfType <- runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr gfExpr)@@ -1430,19 +1532,26 @@  class (MonadError RelationalError m, Monad m) => DatabaseContextM m where   getContext :: m DatabaseContext+  getGraph :: m TransactionGraph    instance DatabaseContextM (ReaderT GraphRefRelationalExprEnv (ExceptT RelationalError Identity)) where   getContext = gfDatabaseContextForMarker UncommittedContextMarker+  getGraph = gfGraph  instance DatabaseContextM (RWST DatabaseContextEvalEnv () DatabaseContextEvalState (ExceptT RelationalError Identity)) where   getContext = getStateContext+  getGraph = dbcGraph      relVarByName :: DatabaseContextM m => RelVarName -> m GraphRefRelationalExpr relVarByName rvName = do-  relvars <- relationVariables <$> getContext  -  case M.lookup rvName relvars of-    Nothing -> throwError (RelVarNotDefinedError rvName)-    Just gfexpr -> pure gfexpr+  ctx <- getContext  +  graph <- getGraph+  case resolveDBC' graph ctx relationVariables of+    Left err -> throwError err+    Right relvars -> +      case M.lookup rvName relvars of+        Nothing -> throwError (RelVarNotDefinedError rvName)+        Just gfexpr -> pure gfexpr    -- | resolve UncommittedTransactionMarker whenever possible- this is important in the DatabaseContext in order to mitigate self-referencing loops for updates class ResolveGraphRefTransactionMarker a where@@ -1456,10 +1565,14 @@   resolve orig@ExistingRelation{} = pure orig   resolve orig@RelationValuedAttribute{} = pure orig   resolve orig@(RelationVariable rvName UncommittedContextMarker) = do-    rvMap <- relationVariables <$> getStateContext-    case M.lookup rvName rvMap of-      Nothing -> pure orig-      Just resolvedRv -> resolve resolvedRv+    ctx <- getStateContext+    graph <- dbcGraph+    case resolveDBC' graph ctx relationVariables of+      Left err -> throwError err+      Right relVars ->+        case M.lookup rvName relVars of+          Nothing -> pure orig+          Just resolvedRv -> resolve resolvedRv   resolve orig@RelationVariable{} = pure orig   resolve (Project attrNames relExpr) = Project <$> resolve attrNames <*> resolve relExpr   resolve (Union exprA exprB) = Union <$> resolve exprA <*> resolve exprB@@ -1527,6 +1640,8 @@   where     opt :: RelationalExprBaseF GraphRefTransactionMarker GraphRefRelationalExpr -> GraphRefRelationalExpr     opt (UnionF exprA exprB) | exprA == exprB = exprA+    opt (UnionF exprA exprB) | isEmptyRelationExpr exprA = exprB+    opt (UnionF exprA exprB) | isEmptyRelationExpr exprB = exprA     opt (UnionF          exprA@(MakeRelationFromExprs mAttrs1 tupExprs1)          exprB@(MakeRelationFromExprs mAttrs2 tupExprs2)) | tupExprs1 == tupExprs2 = MakeRelationFromExprs (mAttrs1 <|> mAttrs2) tupExprs1@@ -1555,6 +1670,13 @@     _ -> orig applyRestrictionCollapse expr = expr +-- determine if the created relation can statically be determined to be empty+isEmptyRelationExpr :: RelationalExprBase a -> Bool    +isEmptyRelationExpr (MakeRelationFromExprs _ (TupleExprs _ [])) = True+isEmptyRelationExpr (MakeStaticRelation _ tupSet) = null (asList tupSet)+isEmptyRelationExpr (ExistingRelation rel) = TS.null (tupleSet rel)+isEmptyRelationExpr _ = False+ firstAtomForAttributeName :: AttributeName -> [RelationTuple] -> GraphRefRelationalExprM Atom firstAtomForAttributeName attrName tuples = do   let folder tup acc =@@ -1601,6 +1723,53 @@     targetAttrExprs = map NakedAttributeExpr (A.toList targetAttrs)     hint = addTargetTypeHints targetAttrs +resolveDBC :: (DatabaseContext -> ValueMarker a) -> DatabaseContextEvalMonad a+resolveDBC f = do+  graph <- dbcGraph+  context <- getStateContext+  case resolveDBC' graph context f of+    Left err -> throwError err+    Right v -> pure v++resolveIODBC :: (DatabaseContext -> ValueMarker a) -> DatabaseContextIOEvalMonad a+resolveIODBC f = do+  env <- ask+  let graph = dbcio_graph env+  context <- getDBCIOContext+  case resolveDBC' graph context f of+    Left err -> throwError err+    Right v -> pure v++resolveGR :: (DatabaseContext -> ValueMarker a) ->+             GraphRefRelationalExprM a+resolveGR f = do+  graph <- gfGraph+  mcontext <- gre_context <$> askEnv+  case mcontext of+    Nothing -> throwError NoUncommittedContextInEvalError+    Just context ->+      case resolveDBC' graph context f of+        Left err -> throwError err+        Right v -> pure v+  +-- | Recursively resolve a property of a database context.+resolveDBC' :: TransactionGraph ->+              DatabaseContext ->+              (DatabaseContext -> ValueMarker a) ->+              Either RelationalError a+resolveDBC' graph ctx f =+  case f ctx of+    ValueMarker val -> pure val+    NotChangedSinceMarker tid -> do+      t <- transactionForId tid graph+      resolveDBC' graph (concreteDatabaseContext t) f++resolveValueMarker :: TransactionGraph -> (DatabaseContext -> ValueMarker a) -> ValueMarker a -> Either RelationalError a+resolveValueMarker _graph _f (ValueMarker a) = pure a+resolveValueMarker graph f (NotChangedSinceMarker tid) = do+  t <- transactionForId tid graph  +  resolveDBC' graph (concreteDatabaseContext t) f+ -- | Ensure that the notification contains valid, type-checkable relational expressions. These relational expressions therefore become registered queries: queries which must remain valid. validateNotification :: Notification -> DatabaseContext -> TransactionGraph -> Either RelationalError Notification validateNotification notif context graph = do@@ -1610,3 +1779,115 @@     _ <- typeForRelationalExpr (reportOldExpr notif)     _ <- typeForRelationalExpr (reportNewExpr notif)     pure notif++-- | Resolved all fields which link to previous transactions.+toResolvedDatabaseContext :: DatabaseContext -> TransactionGraph -> Either RelationalError ResolvedDatabaseContext+toResolvedDatabaseContext ctx graph = do+  incDeps <- resolveDBC' graph ctx inclusionDependencies+  relVars <- resolveDBC' graph ctx relationVariables+  aFuncs <- resolveDBC' graph ctx atomFunctions+  dbcFuncs <- resolveDBC' graph ctx dbcFunctions+  nots <- resolveDBC' graph ctx notifications+  tConsMap <- resolveDBC' graph ctx typeConstructorMapping+  regQs <- resolveDBC' graph ctx registeredQueries+  acls <- resolveDBC' graph ctx acl+  pure (DatabaseContext {+           inclusionDependencies = Identity incDeps,+           relationVariables = Identity relVars,+           atomFunctions = Identity aFuncs,+           dbcFunctions = Identity dbcFuncs,+           notifications = Identity nots,+           typeConstructorMapping = Identity tConsMap,+           registeredQueries = Identity regQs,+           acl = Identity acls+           })+    +-- | RoleIds is resolved from the RoleName presented in the DatabaseContextExpr.+evalAlterDBCACLRoleIdExpr :: AlterDBCACLRoleIdExpr -> DatabaseContextEvalMonad ()+evalAlterDBCACLRoleIdExpr expr = do+    acl' <- resolveDBC acl+    ctx <- getStateContext+    case expr of+      GrantAccessExpr roleId somePerm mgrant -> do+        let newAcl =+              case somePerm of+                SomeRelVarPermission perm ->+                  acl' { relvarsACL = addAccess roleId perm mgrant (relvarsACL acl') }+                SomeFunctionPermission perm ->+                  acl' { dbcFunctionsACL = addAccess roleId perm mgrant (dbcFunctionsACL acl') }+                SomeAlterSchemaPermission perm ->+                  acl' { schemaACL = addAccess roleId perm mgrant (schemaACL acl') }+                SomeAlterTransGraphPermission perm ->+                  acl' { transGraphACL = addAccess roleId perm mgrant (transGraphACL acl') }+                SomeACLPermission perm ->+                  acl' { aclACL = addAccess roleId perm mgrant (aclACL acl') }+                SomeDBCFunctionPermission{} -> acl'+        putStateContext (ctx {+                            acl = ValueMarker newAcl+                            })+      RevokeAccessExpr roleId somePerm -> do+        let newAcl =+              case somePerm of+                SomeRelVarPermission perm ->+                  acl' { relvarsACL = removeAccess roleId perm (relvarsACL acl') }+                SomeFunctionPermission perm ->+                    acl' { dbcFunctionsACL = removeAccess roleId perm (dbcFunctionsACL acl') }+                SomeAlterSchemaPermission perm ->+                  acl' { schemaACL = removeAccess roleId perm (schemaACL acl') }+                SomeAlterTransGraphPermission perm ->+                  acl' { transGraphACL = removeAccess roleId perm (transGraphACL acl') }+                SomeACLPermission perm ->+                  acl' { aclACL = removeAccess roleId perm (aclACL acl') }+                SomeDBCFunctionPermission{} -> acl'                  +        putStateContext (ctx {+                            acl = ValueMarker newAcl+                            })                            +      GrantDBCFunctionAccessExpr roleId funcName' perm mgrant -> do+        dbcFuncs <- resolveDBC dbcFunctions+        case functionForName funcName' dbcFuncs of+          Left err -> dbErr err+          Right func -> do+            let newCtx = ctx { dbcFunctions = ValueMarker (HS.insert newFunc (HS.delete func dbcFuncs)) }+                newFunc = func { funcACL = addAccess roleId perm mgrant (funcACL func) }+            putStateContext newCtx+      RevokeDBCFunctionAccessExpr roleId funcName' perm -> do+        dbcFuncs <- resolveDBC dbcFunctions+        case functionForName funcName' dbcFuncs of+          Left err -> dbErr err+          Right func -> do+              let newCtx = ctx { dbcFunctions = ValueMarker (HS.insert newFunc dbcFuncs) }          +                  newFunc = func { funcACL = removeAccess roleId perm (funcACL func) }+              putStateContext newCtx++typeForGraphRefTupleExprs :: Maybe Attributes -> GraphRefTupleExprs -> GraphRefRelationalExprM Attributes+typeForGraphRefTupleExprs Nothing (TupleExprs _ []) = pure A.emptyAttributes+typeForGraphRefTupleExprs (Just attrs) (TupleExprs _ []) = pure attrs+typeForGraphRefTupleExprs mAttrs (TupleExprs _ tupExprs) = do+  let folder acc tupExpr = do+        nextAttrs <- typeForGraphRefTupleExpr mAttrs tupExpr+        case acc of+          [] -> pure [nextAttrs]+          (prevAttrs:_) -> do+            let attrsDiff = A.attributesDifference nextAttrs prevAttrs+            if attrsDiff == A.emptyAttributes then+              pure (nextAttrs:acc)+            else+              throwError (TupleAttributeTypeMismatchError attrsDiff)+  attrsList <- foldM folder [] tupExprs+  pure (head attrsList)+  ++typeForGraphRefTupleExpr :: Maybe Attributes -> GraphRefTupleExpr -> GraphRefRelationalExprM Attributes+typeForGraphRefTupleExpr mAttrHints (TupleExpr tupMap) = do+  let attrs = fromMaybe A.emptyAttributes mAttrHints+      resolveOneAtomType (attrName, atomExpr) = do+        let eExpectedAtomType = A.atomTypeForAttributeName attrName attrs+        unresolvedType <- typeForGraphRefAtomExpr attrs atomExpr+        resolvedType <- case eExpectedAtomType of+                         Left _ -> pure unresolvedType+                         Right typeHint ->+                           lift $ except $ resolveAtomType typeHint unresolvedType+        pure (Attribute attrName resolvedType)+  attrList <- mapM resolveOneAtomType (M.toList tupMap)+  pure (A.attributesFromList attrList)+        
src/lib/ProjectM36/SQL/Convert.hs view
@@ -4,8 +4,10 @@ import ProjectM36.Base as B import ProjectM36.Error import ProjectM36.DataTypes.SQL.Null+import ProjectM36.DatabaseContext.Types+import ProjectM36.DatabaseContext+import ProjectM36.TransactionGraph.Types import ProjectM36.SQL.Select-import ProjectM36.DatabaseContext (someDatabaseContextExprs) import ProjectM36.SQL.Insert as Insert import ProjectM36.Key (databaseContextExprForUniqueKey, inclusionDependencyForKey) import ProjectM36.SQL.DBUpdate@@ -27,7 +29,6 @@ import qualified Data.Functor.Foldable as Fold import qualified Data.List.NonEmpty as NE import Data.Maybe (isJust)---import Control.Monad (void) import Control.Monad.Trans.State (StateT, get, put, runStateT, evalStateT) import Control.Monad.Trans.Except (ExceptT, throwE, runExceptT) import Control.Monad.Identity (Identity, runIdentity)@@ -1096,7 +1097,8 @@          mkTableContextFromDatabaseContext :: DatabaseContext -> TransactionGraph -> Either RelationalError TableContext mkTableContextFromDatabaseContext dbc tgraph = do-  TableContext . M.fromList <$> mapM rvMapper (M.toList (relationVariables dbc))+  rvs <- resolveDBC' tgraph dbc relationVariables+  TableContext . M.fromList <$> mapM rvMapper (M.toList rvs)   where     rvMapper (nam, rvexpr) = do       let gfEnv = freshGraphRefRelationalExprEnv (Just dbc) tgraph
src/lib/ProjectM36/SQLDatabaseContext.hs view
@@ -1,13 +1,16 @@ -- | Enables SQL-equivalent features such as NULL types in the database in addition to Project:M36 basic functions. module ProjectM36.SQLDatabaseContext where-import ProjectM36.Base-import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContext.Basic import ProjectM36.DataTypes.SQL.Null+import ProjectM36.DatabaseContext.Types+import Data.Functor.Identity -sqlDatabaseContext :: DatabaseContext-sqlDatabaseContext = basicDatabaseContext { atomFunctions =-                                            atomFunctions basicDatabaseContext <> nullAtomFunctions,-                                            typeConstructorMapping =-                                            typeConstructorMapping basicDatabaseContext <> nullTypeConstructorMapping-                                            }+sqlDatabaseContext :: ResolvedDatabaseContext+sqlDatabaseContext =+  basicDatabaseContext {+      atomFunctions =+      atomFunctions basicDatabaseContext <> Identity nullAtomFunctions,+      typeConstructorMapping =+      typeConstructorMapping basicDatabaseContext <> Identity nullTypeConstructorMapping+      } 
src/lib/ProjectM36/ScriptSession.hs view
@@ -122,12 +122,14 @@      sandboxPkgPaths <- liftIO $ concat <$> mapM glob [       --"./dist-newstyle/packagedb/ghc-" ++ ghcVersion, --rely on cabal 3's ghc.environment install: "cabal install --lib project-m36"+      homeDir </> ".local/state/cabal/store/ghc-" ++ ghcVersion ++ "/package.db",       ".cabal-sandbox/*ghc-" ++ ghcVersion ++ "-packages.conf.d",       ".stack-work/install/*/*/" ++ ghcVersion ++ "/pkgdb",       ".stack-work/install/*/pkgdb/", --windows stack build       "C:/sr/snapshots/b201cfe6/pkgdb", --windows stack build- ideally, we could run `stack path --snapshot-pkg-db, but this is sufficient to pass CI-      homeDir </> ".stack/snapshots/*/*/" ++ ghcVersion ++ "/pkgdb"-      --homeDir </> ".cabal/store/ghc-" ++ ghcVersion ++ "/package.db"+      homeDir </> ".stack/snapshots/*/*/" ++ ghcVersion ++ "/pkgdb",+      --github CI path+      homeDir </> (".cabal/store/ghc-" ++ ghcVersion) </> "package.db"       ] #if MIN_VERSION_ghc(9,0,0)     let pkgConf = PkgDbPath@@ -242,10 +244,11 @@           "Data.Time.Calendar",           "Control.Monad.State",           "ProjectM36.Base",+          "ProjectM36.Error",           "ProjectM36.Relation",+          "ProjectM36.DatabaseContext",+          "ProjectM36.DatabaseContext.Types",           "ProjectM36.AtomFunctionError",-          "ProjectM36.DatabaseContextFunctionError",-          "ProjectM36.DatabaseContextFunctionUtils",           "ProjectM36.RelationalExpression"]         qualifiedModules = map (\(modn, qualNam) -> IIDecl $ safeImportDecl modn (Just qualNam)) [           ("Data.Text", "T")
src/lib/ProjectM36/Serialise/Base.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE StandaloneDeriving, DerivingVia, TypeApplications, TypeSynonymInstances, ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving, DerivingVia, TypeApplications, ScopedTypeVariables, DeriveGeneric, FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} --Serialise instances for ProjectM36.Base data types- orphan instance city module ProjectM36.Serialise.Base where@@ -9,13 +9,19 @@ import ProjectM36.MerkleHash import ProjectM36.Relation import ProjectM36.Tuple-import ProjectM36.TupleSet+import ProjectM36.TupleSet as TS+import ProjectM36.DatabaseContext.Types+import ProjectM36.AccessControlList+import ProjectM36.DisconnectedTransaction (CurrentHead)+import ProjectM36.IsomorphicSchema.Types+import ProjectM36.Transaction.Types import Data.UUID import Data.Proxy import Data.Word import ProjectM36.Attribute as A import qualified Data.Vector as V import Data.Time.Calendar (Day,toGregorian,fromGregorian)+import GHC.Generics (Generic) #if MIN_VERSION_winery(1,4,0) #else import qualified Data.List.NonEmpty as NE@@ -36,7 +42,7 @@ deriving via WineryVariant GraphRefTransactionMarker instance Serialise GraphRefTransactionMarker deriving via WineryVariant SchemaIsomorph instance Serialise SchemaIsomorph deriving via WineryVariant InclusionDependency instance Serialise InclusionDependency-deriving via WineryVariant (DatabaseContextExprBase a) instance Serialise a => Serialise (DatabaseContextExprBase a)+deriving via WineryVariant (DatabaseContextExprBase a r) instance (Serialise a, Serialise r) => Serialise (DatabaseContextExprBase a r) deriving via WineryVariant (DatabaseContextIOExprBase a) instance Serialise a => Serialise (DatabaseContextIOExprBase a) deriving via WineryVariant (RestrictionPredicateExprBase a) instance Serialise a => Serialise (RestrictionPredicateExprBase a) deriving via WineryVariant TransactionInfo instance Serialise TransactionInfo@@ -50,7 +56,31 @@ deriving via WineryVariant Schema instance Serialise Schema deriving via WineryVariant MergeStrategy instance Serialise MergeStrategy deriving via WineryVariant NotificationExpression instance Serialise NotificationExpression+deriving via WineryVariant CurrentHead instance Serialise CurrentHead+-- ACL+deriving via WineryRecord DatabaseContextACL instance Serialise DatabaseContextACL+deriving via WineryRecord RelVarAccessControlList instance Serialise RelVarAccessControlList+deriving via WineryVariant RelVarPermission instance Serialise RelVarPermission +deriving via WineryRecord FunctionAccessControlList instance Serialise FunctionAccessControlList+deriving via WineryVariant FunctionPermission instance Serialise FunctionPermission++deriving via WineryVariant ACLAccessControlList instance Serialise ACLAccessControlList+deriving via WineryVariant ACLPermission instance Serialise ACLPermission++deriving via WineryRecord AlterTransGraphAccessControlList instance Serialise AlterTransGraphAccessControlList+deriving via WineryVariant AlterTransGraphPermission instance Serialise AlterTransGraphPermission++deriving via WineryRecord SchemaAccessControlList instance Serialise SchemaAccessControlList+deriving via WineryVariant AlterSchemaPermission instance Serialise AlterSchemaPermission++deriving via WineryRecord DBCFunctionAccessControlList instance Serialise DBCFunctionAccessControlList+deriving via WineryVariant DBCFunctionPermission instance Serialise DBCFunctionPermission++deriving via WineryVariant (AlterDBCACLExprBase r) instance Serialise r => Serialise (AlterDBCACLExprBase r)++deriving via WineryVariant SomePermission instance Serialise SomePermission+ fromWordsTup :: (Word32, Word32, Word32, Word32) -> TransactionId fromWordsTup (a,b,c,d) = fromWords a b c d @@ -113,7 +143,7 @@  -- | restore slimmed tuple set to include single shared attributes list fattenTupleSet :: SlimTupleSet -> RelationTupleSet-fattenTupleSet Left{} = emptyTupleSet+fattenTupleSet Left{} = TS.empty fattenTupleSet (Right (attrs, vtups)) = RelationTupleSet $ map (RelationTuple attrs) vtups  -- | A special instance of Serialise which cuts down on duplicate attributes- we should only serialise the attributes at the top-level and not duplicate them per tuple. If we have an empty tupleset, we lack all attributes which is fine in this case.@@ -122,3 +152,20 @@   toBuilder tupSet = toBuilder (slimTupleSet tupSet)   extractor = fattenTupleSet <$> extractor   decodeCurrent = fattenTupleSet <$> decodeCurrent++data GraphRefOrUpdate a =+  -- | Use the value from the same field at the transaction marker (no difference).+  GraphRefValue GraphRefTransactionMarker |+  UpdateValue a++-- | Used only for serialization.+data GraphRefDatabaseContext = GraphRefDatabaseContext+  {+    inclusionDependencies :: GraphRefOrUpdate InclusionDependencies,+    relationVariables :: GraphRefOrUpdate RelationVariables,+    atomFunctions :: GraphRefOrUpdate AtomFunctions,+    dbcFunctions :: GraphRefOrUpdate DatabaseContextFunctions,+    notifications :: GraphRefOrUpdate Notifications,+    typeConstructorMapping :: GraphRefOrUpdate TypeConstructorMapping,+    registeredQueries :: GraphRefOrUpdate RegisteredQueries+  } deriving (Generic)
− src/lib/ProjectM36/Serialise/DatabaseContextFunctionError.hs
@@ -1,7 +0,0 @@-{-# LANGUAGE DerivingVia, StandaloneDeriving #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module ProjectM36.Serialise.DatabaseContextFunctionError where-import Codec.Winery-import ProjectM36.DatabaseContextFunctionError--deriving via WineryVariant DatabaseContextFunctionError instance Serialise DatabaseContextFunctionError
src/lib/ProjectM36/Serialise/Error.hs view
@@ -5,12 +5,14 @@ import Codec.Winery import ProjectM36.Serialise.Base () import ProjectM36.Serialise.AtomFunctionError ()-import ProjectM36.Serialise.DatabaseContextFunctionError ()+import ProjectM36.DatabaseContext.Fields  deriving via WineryVariant RelationalError instance Serialise RelationalError deriving via WineryVariant MergeError instance Serialise MergeError+deriving via WineryVariant DatabaseContextField instance Serialise DatabaseContextField deriving via WineryVariant ScriptCompilationError instance Serialise ScriptCompilationError deriving via WineryVariant PersistenceError instance Serialise PersistenceError deriving via WineryVariant SchemaError instance Serialise SchemaError deriving via WineryVariant ImportError' instance Serialise ImportError' deriving via WineryVariant SQLError instance Serialise SQLError+
+ src/lib/ProjectM36/Serialise/LoginRoles.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE StandaloneDeriving, DerivingVia, ScopedTypeVariables, FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module ProjectM36.Serialise.LoginRoles where+import ProjectM36.LoginRoles+import Codec.Winery hiding (Schema)++deriving via WineryVariant AlterLoginRolesExpr instance Serialise AlterLoginRolesExpr+deriving via WineryVariant LoginRoleError instance Serialise LoginRoleError+deriving via WineryVariant SuccessResult instance Serialise SuccessResult
src/lib/ProjectM36/Server.hs view
@@ -15,25 +15,30 @@ import Network.Socket import qualified StmContainers.Map as StmMap import Control.Concurrent.STM+import qualified Data.Text as T+import System.Random (initStdGen)  type TestMode = Bool  requestHandlers :: TestMode -> Maybe Timeout -> RequestHandlers ServerState requestHandlers testFlag ti =   [-    RequestHandler (\sState (Login dbName) -> do+    RequestHandler (\sState (Login dbName roleName) -> do                        addClientLogin dbName sState                        conn <- getConn sState-                       handleLogin conn (connectionSocket sState)),+                       let roleNameFromMutualTLS = connectionRoleName sState+                           roleName' = maybe roleName T.pack roleNameFromMutualTLS+                           clientId = connectionClientId sState+                       handleLogin conn clientId (connectionSocketContext sState) roleName'),      RequestHandler (\sState Logout -> do                         conn <- getConn sState                                                 handleLogout ti conn),     RequestHandler $ \sState (ExecuteHeadName sessionId) -> do       --socket -> dbname --maybe create a socket->client state mapping in the server state, too       conn <- getConn sState-      handleExecuteHeadName ti sessionId conn,+      handleExecuteCurrentHead ti sessionId conn,     RequestHandler (\sState (ExecuteRelationalExpr sessionId expr) -> do-                       conn <- getConn sState                        +                       conn <- getConn sState                        handleExecuteRelationalExpr ti sessionId conn expr),      RequestHandler (\sState (ExecuteDataFrameExpr sessionId expr) -> do                         conn <- getConn sState@@ -46,7 +51,7 @@                         handleExecuteDatabaseContextIOExpr ti sessionId conn expr),      RequestHandler (\sState (ExecuteGraphExpr sessionId expr) -> do                         conn <- getConn sState-                        handleExecuteGraphExpr ti sessionId conn expr),+                        handleExecuteTransactionGraphExpr ti sessionId conn expr),      RequestHandler (\sState (ExecuteTransGraphRelationalExpr sessionId expr) -> do                        conn <- getConn sState                        handleExecuteTransGraphRelationalExpr ti sessionId conn expr),@@ -59,6 +64,9 @@      RequestHandler (\sState (RetrievePlanForDatabaseContextExpr sessionId dbExpr) -> do                        conn <- getConn sState                                                handleRetrievePlanForDatabaseContextExpr ti sessionId conn dbExpr),+     RequestHandler (\sState (RetrievePlanForRelationalExpr sessionId expr) -> do+                       conn <- getConn sState                        +                       handleRetrievePlanForRelationalExpr ti sessionId conn expr),      RequestHandler (\sState (RetrieveHeadTransactionId sessionId) -> do                        conn <- getConn sState                                                handleRetrieveHeadTransactionId ti sessionId conn),@@ -68,9 +76,9 @@      RequestHandler (\sState (CreateSessionAtHead headn) -> do                        conn <- getConn sState                                                handleCreateSessionAtHead ti conn headn),-     RequestHandler (\sState (CreateSessionAtCommit commitId) -> do+     RequestHandler (\sState (CreateSessionAtTransactionId commitId) -> do                         conn <- getConn sState-                        handleCreateSessionAtCommit ti conn commitId),+                        handleCreateSessionAtTransactionId ti conn commitId),      RequestHandler (\sState (CloseSession sessionId) -> do                         conn <- getConn sState                                          handleCloseSession sessionId conn),@@ -120,17 +128,33 @@                         handleConvertSQLUpdates ti sessionId conn updates),      RequestHandler (\sState (RetrieveNotificationsAsRelation sessionId) -> do                         conn <- getConn sState-                        handleRetrieveNotificationsAsRelation ti sessionId conn)+                        handleRetrieveNotificationsAsRelation ti sessionId conn),+     RequestHandler (\sState (ExecuteAlterTransactionGraphExpr sessionId expr) -> do+                        conn <- getConn sState+                        handleExecuteAlterTransactionGraphExpr ti sessionId conn expr)      ] ++ if testFlag then testModeHandlers ti else []  getConn :: ConnectionState ServerState -> IO Connection getConn connState = do-  let sock = lockless (connectionSocket connState)+  let sock = lockless (lockingSocket (connectionSocketContext connState))       sState = connectionServerState connState   mConn <- connectionForClient sock sState   case mConn of     Nothing -> error "failed to find socket in client map"-    Just conn -> pure conn+    Just conn@RemoteConnection{} -> pure conn+    Just conn@(InProcessConnection connInfo) -> do+      -- add role info+      let clientNodes = ipClientNodes connInfo+          clientId = connectionClientId connState+      mClientNode <- atomically $ StmMap.lookup clientId clientNodes+      let mRoleName = case mClientNode of+                        Nothing -> Nothing+                        Just InProcessClientInfo{} -> Nothing+                        Just (RemoteClientInfo _ roleName) -> Just roleName+      case mRoleName of+        Nothing -> pure conn+        Just roleName -> +          pure (InProcessConnection (connInfo { ipRoleName = roleName }))  testModeHandlers :: Maybe Timeout -> RequestHandlers ServerState testModeHandlers ti = [RequestHandler (\sState (TestTimeout sessionId) -> do@@ -177,7 +201,7 @@ addClientLogin :: DatabaseName -> ConnectionState ServerState -> IO () addClientLogin dbName cState = do   let clientMap = stateClientMap (connectionServerState cState)-      sock = lockless (connectionSocket cState)+      sock = lockless (lockingSocket (connectionSocketContext cState))   atomically $ do     mVal <- StmMap.lookup (show sock) clientMap     case mVal of@@ -209,7 +233,8 @@     hPutStrLn stderr checkFSErrorMsg     pure False     else do-      econn <- connectProjectM36 (InProcessConnectionInfo (persistenceStrategy daemonConfig) loggingNotificationCallback (ghcPkgPaths daemonConfig) basicDatabaseContext)+      rando <- initStdGen+      econn <- connectProjectM36 (InProcessConnectionInfo (persistenceStrategy daemonConfig) loggingNotificationCallback (ghcPkgPaths daemonConfig) basicDatabaseContext rando "admin")       case econn of          Left err -> do                 hPutStrLn stderr ("Failed to create database connection: " ++ show err)@@ -221,6 +246,6 @@                   v -> Just v           (sockSpec, sockAddr) <- resolveRemoteServerAddress (bindAddress daemonConfig)           sState <- initialServerState (databaseName daemonConfig) conn-          serve (requestHandlers (testMode daemonConfig) mTimeout) sState sockSpec sockAddr mAddr+          serve (requestHandlers (testMode daemonConfig) mTimeout) sState (connConfig daemonConfig) sockSpec sockAddr mAddr  
src/lib/ProjectM36/Server/Config.hs view
@@ -1,5 +1,6 @@ module ProjectM36.Server.Config where import ProjectM36.Client+import Network.RPC.Curryer.Server  data ServerConfig = ServerConfig { persistenceStrategy :: PersistenceStrategy,                                    checkFS :: Bool,@@ -7,7 +8,8 @@                                    bindAddress :: RemoteServerAddress,                                    ghcPkgPaths :: [String], -- used for AtomFunction dynamic compilation                                    perRequestTimeout :: Int,-                                   testMode :: Bool -- used exclusively for automated testing of the server, thus not accessible from the command line+                                   testMode :: Bool, -- used exclusively for automated testing of the server, thus not accessible from the command line+                                   connConfig :: ServerConnectionConfig                                    }                     deriving (Show) @@ -25,5 +27,19 @@                  bindAddress = RemoteServerHostAddress "127.0.0.1" 6543,                  ghcPkgPaths = [],                  perRequestTimeout = 0,-                 testMode = False+                 testMode = False,+                 -- default to strongest security+                 connConfig = EncryptedConnectionConfig tlsConfig ClientAuthRequired                }+  where+    tlsConfig =+      ServerTLSConfig {+      tlsCertInfo = ServerTLSCertInfo {+          x509PublicFilePath = "server.cert.pem",+          x509PrivateFilePath = "server.key.pem",+          x509CertFilePath = Nothing+          },+      tlsServerHostName = "127.0.0.1",+      tlsServerServiceName = ""+                                }+
src/lib/ProjectM36/Server/EntryPoints.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE MonoLocalBinds #-} module ProjectM36.Server.EntryPoints where-import ProjectM36.Base hiding (inclusionDependencies)+import ProjectM36.Base import ProjectM36.IsomorphicSchema import ProjectM36.HashSecurely import ProjectM36.SQL.Select@@ -10,7 +10,6 @@ import Control.Concurrent (threadDelay) import Network.RPC.Curryer.Server  import System.Timeout hiding (Timeout)-import Network.Socket import Control.Exception  timeoutOrDie :: Maybe Timeout -> IO a -> IO (Maybe a)@@ -44,18 +43,22 @@ handleExecuteDatabaseContextIOExpr ti sessionId conn dbexpr =   timeoutRelErr ti (executeDatabaseContextIOExpr sessionId conn dbexpr)   -handleExecuteHeadName :: Maybe Timeout -> SessionId -> Connection -> IO (Either RelationalError HeadName)-handleExecuteHeadName ti sessionId conn =-  timeoutRelErr ti (headName sessionId conn)+handleExecuteCurrentHead :: Maybe Timeout -> SessionId -> Connection -> IO (Either RelationalError CurrentHead)+handleExecuteCurrentHead ti sessionId conn =+  timeoutRelErr ti (currentHead sessionId conn)   -handleLogin :: Connection -> Locking Socket -> IO Bool-handleLogin conn lockSock = do-  addClientNode conn lockSock+handleLogin :: Connection -> ClientConnectionId -> SocketContext -> RoleName -> IO Bool+handleLogin conn clientId sockCtx roleName = do+  addClientNode conn clientId sockCtx roleName   pure True   -handleExecuteGraphExpr :: Maybe Timeout -> SessionId -> Connection -> TransactionGraphOperator -> IO (Either RelationalError ())-handleExecuteGraphExpr ti sessionId conn graphExpr =-  timeoutRelErr ti (executeGraphExpr sessionId conn graphExpr)+handleExecuteTransactionGraphExpr :: Maybe Timeout -> SessionId -> Connection -> TransactionGraphExpr -> IO (Either RelationalError ())+handleExecuteTransactionGraphExpr ti sessionId conn graphExpr =+  timeoutRelErr ti (executeTransactionGraphExpr sessionId conn graphExpr)++handleExecuteAlterTransactionGraphExpr :: Maybe Timeout -> SessionId -> Connection -> AlterTransactionGraphExpr -> IO (Either RelationalError ())+handleExecuteAlterTransactionGraphExpr ti sessionId conn alterGraphExpr =+  timeoutRelErr ti (executeAlterTransactionGraphExpr sessionId conn alterGraphExpr)    handleExecuteTransGraphRelationalExpr :: Maybe Timeout -> SessionId -> Connection -> TransGraphRelationalExpr -> IO (Either RelationalError Relation) handleExecuteTransGraphRelationalExpr ti sessionId conn graphExpr =@@ -72,6 +75,10 @@ handleRetrievePlanForDatabaseContextExpr :: Maybe Timeout -> SessionId -> Connection -> DatabaseContextExpr -> IO (Either RelationalError GraphRefDatabaseContextExpr) handleRetrievePlanForDatabaseContextExpr ti sessionId conn dbExpr =   timeoutRelErr ti (planForDatabaseContextExpr sessionId conn dbExpr)++handleRetrievePlanForRelationalExpr :: Maybe Timeout -> SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError StringType)+handleRetrievePlanForRelationalExpr ti sessionId conn expr =+  timeoutRelErr ti (planForRelationalExpr sessionId conn expr)    handleRetrieveTransactionGraph :: Maybe Timeout -> SessionId -> Connection -> IO (Either RelationalError Relation)  handleRetrieveTransactionGraph ti sessionId conn =@@ -81,9 +88,9 @@ handleRetrieveHeadTransactionId ti sessionId conn =   timeoutRelErr ti (headTransactionId sessionId conn)   -handleCreateSessionAtCommit :: Maybe Timeout -> Connection -> TransactionId -> IO (Either RelationalError SessionId)-handleCreateSessionAtCommit ti conn commitId =-  timeoutRelErr ti (createSessionAtCommit conn commitId)+handleCreateSessionAtTransactionId :: Maybe Timeout -> Connection -> TransactionId -> IO (Either RelationalError SessionId)+handleCreateSessionAtTransactionId ti conn commitId =+  timeoutRelErr ti (createSessionAtTransactionId conn commitId)    handleCreateSessionAtHead :: Maybe Timeout -> Connection -> HeadName -> IO (Either RelationalError SessionId) handleCreateSessionAtHead ti conn headn = 
src/lib/ProjectM36/Server/ParseArgs.hs view
@@ -3,10 +3,11 @@ import ProjectM36.Client import Options.Applicative import ProjectM36.Server.Config-#if __GLASGOW_HASKELL__ < 804-import Data.Monoid-#endif+import Data.ByteString (ByteString)+import Network.RPC.Curryer.Server +type ForServer = Bool+ parseArgsWithDefaults :: ServerConfig -> Parser ServerConfig parseArgsWithDefaults defaults = ServerConfig <$>                                  parsePersistenceStrategy <*>@@ -15,7 +16,8 @@                                  parseServerAddress <*>                                  many parseGhcPkgPath <*>                                  parseTimeout (perRequestTimeout defaults) <*>-                                 parseTestMode+                                 parseTestMode <*>+                                 parseServerConnectionConfig  parsePersistenceStrategy :: Parser PersistenceStrategy parsePersistenceStrategy = CrashSafePersistence <$> (dbdirOpt <* fsyncOpt) <|>@@ -68,6 +70,13 @@                          metavar "PORT_NUMBER" <>                          value defPort) +parseServiceName :: ByteString -> Parser ByteString+parseServiceName defService = option auto (short 's' <>+                                          long "service" <>+                                          metavar "SERVICE_NAME" <>+                                          help "Used with TLS to distinguish between multiple databases on the same port via ALPN. Elide to use default service." <>+                                          value defService)+ parseGhcPkgPath :: Parser String parseGhcPkgPath = strOption (long "ghc-pkg-dir" <>                               metavar "GHC_PACKAGE_DIRECTORY")@@ -77,6 +86,35 @@                             metavar "MICROSECONDS" <>                             value defTimeout) +parseServerTLSConfig :: Parser ServerTLSConfig+parseServerTLSConfig =+  ServerTLSConfig <$>+  parseServerTLSCertInfo <*>+  parseHostname "localhost" <*>+  parseServiceName ""+  +parseServerTLSCertInfo :: Parser ServerTLSCertInfo+parseServerTLSCertInfo =+  ServerTLSCertInfo <$>+  strOption (long "public-x509-key" <>+             metavar "KEY_PATH" <>+             help "Enables TLS with path to public key.") <*>+  strOption (long "private-x509-key" <>+             metavar "KEY_PATH" <>+             help "Enables TLS with path to private key.") <*>+  optional (strOption (long "certificate-x509-path" <>+                        metavar "CERT_PATH" <>+                        help "Path to certificate for TLS. Elide to use system's certificate store."))+  +-- TLS configuration for native project-m36 socket communication+parseServerConnectionConfig :: Parser ServerConnectionConfig+parseServerConnectionConfig =+  flag' UnencryptedConnectionConfig (long "disable-tls" <>+                                    help "Disable encryption (not recommended in production).") <|>+  EncryptedConnectionConfig <$>+    parseServerTLSConfig <*>+    parseClientAuth+ parseConfig :: IO ServerConfig parseConfig = parseConfigWithDefaults defaultServerConfig @@ -89,18 +127,28 @@ parseWSArgsWithDefaults :: ServerConfig -> Parser WebsocketServerConfig parseWSArgsWithDefaults defaults = WebsocketServerConfig <$>                                  parseArgsWithDefaults defaults <*>-                                 parseTlsCertificatePath <*>-                                 parseTlsKeyPath-+                                 parseWSTlsCertificatePath <*>+                                 parseWSTlsKeyPath -parseTlsCertificatePath :: Parser (Maybe String)-parseTlsCertificatePath = optional $ strOption (long "tls-certificate-path" <>+parseWSTlsCertificatePath :: Parser (Maybe String)+parseWSTlsCertificatePath = optional $ strOption (long "tls-certificate-path" <>                               metavar "TLS_CERTIFICATE_PATH") -parseTlsKeyPath :: Parser (Maybe String)-parseTlsKeyPath = optional $ strOption (long "tls-key-path" <>+parseWSTlsKeyPath :: Parser (Maybe String)+parseWSTlsKeyPath = optional $ strOption (long "tls-key-path" <>                               metavar "TLS_KEY_PATH") +parseRoleName :: Parser RoleName+parseRoleName = strOption (long "login-role" <>+                           metavar "ROLE_NAME" <>+                           value "admin")++-- | Whether or not to require the server to require a client certificate for mutual TLS. By default, the server disables anonymous connections.+parseClientAuth :: Parser ClientAuth+parseClientAuth = flag ClientAuthRequired AcceptAnonymousClient+                  (long "allow-anonymous-clients" <>+                   help "Disable client certificate requirement to allow anonymous users to connect.")+                                                                 helpOption :: Parser (a -> a) helpOption = abortOption helpText $ mconcat   [ long "help"
src/lib/ProjectM36/Server/RemoteCallTypes.hs view
@@ -6,7 +6,10 @@ import ProjectM36.DataFrame import ProjectM36.TransGraphRelationalExpression import ProjectM36.Session+import ProjectM36.LoginRoles+import ProjectM36.IsomorphicSchema.Types import ProjectM36.Serialise.DataFrame ()+import ProjectM36.Serialise.LoginRoles () import ProjectM36.Serialise.IsomorphicSchema () import ProjectM36.SQL.Select import ProjectM36.SQL.DBUpdate@@ -18,7 +21,7 @@  {-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-} -- | The initial login message. The argument should be the process id of the initiating client. This ProcessId will receive notification callbacks.-data Login = Login DatabaseName+data Login = Login DatabaseName RoleName   RPCData(Login)                      data Logout = Logout@@ -36,8 +39,11 @@ data ExecuteDatabaseContextIOExpr = ExecuteDatabaseContextIOExpr SessionId DatabaseContextIOExpr   RPCData(ExecuteDatabaseContextIOExpr)   -data ExecuteGraphExpr = ExecuteGraphExpr SessionId TransactionGraphOperator +data ExecuteGraphExpr = ExecuteGraphExpr SessionId TransactionGraphExpr   RPCData(ExecuteGraphExpr)++data ExecuteAlterTransactionGraphExpr = ExecuteAlterTransactionGraphExpr SessionId AlterTransactionGraphExpr+  RPCData(ExecuteAlterTransactionGraphExpr)    data ExecuteTransGraphRelationalExpr = ExecuteTransGraphRelationalExpr SessionId TransGraphRelationalExpr                                  RPCData(ExecuteTransGraphRelationalExpr)@@ -58,6 +64,9 @@    data RetrievePlanForDatabaseContextExpr = RetrievePlanForDatabaseContextExpr SessionId DatabaseContextExpr   RPCData(RetrievePlanForDatabaseContextExpr)++data RetrievePlanForRelationalExpr = RetrievePlanForRelationalExpr SessionId RelationalExpr+  RPCData(RetrievePlanForRelationalExpr)    data RetrieveTransactionGraph = RetrieveTransactionGraph SessionId   RPCData(RetrieveTransactionGraph)@@ -65,8 +74,8 @@ data RetrieveHeadTransactionId = RetrieveHeadTransactionId SessionId   RPCData(RetrieveHeadTransactionId)   -data CreateSessionAtCommit = CreateSessionAtCommit TransactionId-  RPCData(CreateSessionAtCommit)+data CreateSessionAtTransactionId = CreateSessionAtTransactionId TransactionId+  RPCData(CreateSessionAtTransactionId)    data CreateSessionAtHead = CreateSessionAtHead HeadName   RPCData(CreateSessionAtHead)@@ -121,3 +130,6 @@  data ConvertSQLUpdates = ConvertSQLUpdates SessionId [DBUpdate]   RPCData(ConvertSQLUpdates)++data ExecuteAlterLoginRolesExpr = ExecuteAlterLoginRolesExpr SessionId AlterLoginRolesExpr+  RPCData(ExecuteAlterLoginRolesExpr)
src/lib/ProjectM36/Session.hs view
@@ -1,44 +1,63 @@ module ProjectM36.Session where import ProjectM36.Base+import ProjectM36.DatabaseContext.Types+import ProjectM36.TransactionGraph.Types+import qualified ProjectM36.Transaction.Types as T+import ProjectM36.RelationalExpression import Data.UUID import qualified Data.Map as M import ProjectM36.Error+import ProjectM36.ValueMarker import qualified ProjectM36.DisconnectedTransaction as Discon+import ProjectM36.DisconnectedTransaction (DisconnectedTransaction(..))+import qualified ProjectM36.IsomorphicSchema.Types as Schema+import ProjectM36.IsomorphicSchema.Types (SchemaName, Subschemas)  type SessionId = UUID  --the persistence of a session is as long as the life of the database (not serialized to disk) -- sessions are not associated with connections and have separate lifetimes -- | Represents a pointer into the database's transaction graph which the 'DatabaseContextExpr's can then modify subsequently be committed to extend the transaction graph. The session contains staged (uncommitted) database changes as well as the means to switch between isomorphic schemas.-data Session = Session DisconnectedTransaction SchemaName+data Session = Session {+  disconnectedTransaction :: DisconnectedTransaction,+  schemaName :: SchemaName+  }  defaultSchemaName :: SchemaName defaultSchemaName = "main" -disconnectedTransaction :: Session -> DisconnectedTransaction-disconnectedTransaction (Session discon _) = discon--isDirty :: Session -> DirtyFlag-isDirty (Session discon _) = Discon.isDirty discon+isUpdated :: Session -> DirtyFlag+isUpdated sess = Discon.isUpdated (disconnectedTransaction sess)  concreteDatabaseContext :: Session -> DatabaseContext-concreteDatabaseContext (Session (DisconnectedTransaction _ (Schemas context _) _) _) = context+concreteDatabaseContext sess = Discon.concreteDatabaseContext (disconnectedTransaction sess)  parentId :: Session -> TransactionId-parentId (Session (DisconnectedTransaction parentUUID _ _) _) = parentUUID+parentId sess = Discon.parentId (disconnectedTransaction sess) -subschemas :: Session -> Subschemas-subschemas (Session (DisconnectedTransaction _ (Schemas _ s) _) _) = s+subschemas :: Session -> ValueMarker Subschemas+subschemas sess = Schema.subschemas (disconSchemas (disconnectedTransaction sess)) -schemas :: Session -> Schemas-schemas (Session (DisconnectedTransaction _ s _) _) = s+resolveSubschemas :: Session -> TransactionGraph -> Either RelationalError Subschemas+resolveSubschemas session graph = do+  let resolveSubschemas' val = +        case val of+          ValueMarker v -> pure v+          NotChangedSinceMarker tid -> do+            t <- transactionForId tid graph+            resolveSubschemas' (T.subschemas t)+  resolveSubschemas' (subschemas session) -schemaName :: Session -> SchemaName-schemaName (Session _ s) = s+schemas :: Session -> Discon.TransactionRefSchemas+schemas sess = disconSchemas (disconnectedTransaction sess) -setSchemaName :: SchemaName -> Session -> Either RelationalError Session-setSchemaName sname session = if sname == defaultSchemaName || M.member sname (subschemas session) then-                                pure (Session (disconnectedTransaction session) sname)-                              else-                                Left (SubschemaNameNotInUseError sname)-  ++setSchemaName :: SchemaName -> Session -> TransactionGraph -> Either RelationalError Session+setSchemaName sname session graph = do+  sSchemas <- resolveSubschemas session graph+  if sname == defaultSchemaName || M.member sname sSchemas then+    pure (session { schemaName = sname })+  else+    Left (SubschemaNameNotInUseError sname)++
src/lib/ProjectM36/StaticOptimizer.hs view
@@ -1,17 +1,24 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances, TypeFamilies, MultiParamTypeClasses, RankNTypes #-}+ module ProjectM36.StaticOptimizer where import ProjectM36.Base import ProjectM36.GraphRefRelationalExpr import ProjectM36.Relation+import ProjectM36.Cache.RelationalExprCache as RelExprCache+import ProjectM36.SystemMemory+import qualified ProjectM36.TupleSet as TS import ProjectM36.RelationalExpression+import ProjectM36.PinnedRelationalExpr+import ProjectM36.TransactionGraph.Types+import ProjectM36.Transaction.Types+import ProjectM36.DatabaseContext.Types import ProjectM36.TransGraphRelationalExpression as TGRE hiding (askGraph) import ProjectM36.Error-import ProjectM36.Transaction import ProjectM36.NormalizeExpr import qualified ProjectM36.Attribute as A import qualified ProjectM36.AttributeNames as AS-import ProjectM36.TupleSet+import ProjectM36.Streaming.RelationalExpression+import Control.Monad (void) #if MIN_VERSION_base(4,18,0) import Control.Monad (foldM) #endif@@ -25,7 +32,11 @@ import qualified Data.Map as M import qualified Data.Set as S import Data.Functor.Foldable as Fold---import Debug.Trace+import Data.Time.Clock+import Control.Exception+import Control.DeepSeq+import System.Random+import Control.Monad.STM  -- the static optimizer performs optimizations which need not take any specific-relation statistics into account @@ -43,12 +54,47 @@   {     odce_graph :: TransactionGraph,     odce_context :: DatabaseContext, --not optional for DatabaseContextExpr evaluation-    odce_transId :: TransactionId -- parent if context is committed- needed because MultipleExpr optimization requires running the DatabaseContextExprs (with empty relvars)+    odce_transId :: TransactionId, -- parent if context is committed- needed because MultipleExpr optimization requires running the DatabaseContextExprs (with empty relvars)+    odce_dbcfuncutils :: DatabaseContextFunctionUtils     } +data GraphRefSOptDatabaseContextIOExprEnv =+  GraphRefSOptDatabaseContextIOExprEnv+  {+    odcioe_graph :: TransactionGraph,+    odcioe_context :: DatabaseContext, --not optional for DatabaseContextExpr evaluation+    odcioe_transId :: TransactionId -- parent if context is committed- needed bec+  }+   type GraphRefSOptDatabaseContextExprM a = ReaderT GraphRefSOptDatabaseContextExprEnv (ExceptT RelationalError Identity) a --- | A temporary function to be replaced by IO-based implementation.+type GraphRefSOptDatabaseContextIOExprM a = ReaderT GraphRefSOptDatabaseContextIOExprEnv (ExceptT RelationalError Identity) a++class Optimize expr optExpr where+  type OptimizeEnv expr optExpr+  +  optimize :: OptimizeEnv expr optExpr -> expr -> Either RelationalError optExpr+++instance Optimize RelationalExpr GraphRefRelationalExpr where+  type OptimizeEnv RelationalExpr GraphRefRelationalExpr = RelationalExprEnv+  +  optimize env expr = do+    let gfExpr = runProcessExprM UncommittedContextMarker (processRelationalExpr expr) -- references parent tid instead of context! options- I could add the context to the graph with a new transid or implement an evalRelationalExpr in RE.hs to use the context (which is what I had previously)+        ctx = re_context env+    runGraphRefSOptRelationalExprM (Just ctx) (re_graph env) (fullOptimizeGraphRefRelationalExpr gfExpr)++instance Optimize DatabaseContextExpr' GraphRefDatabaseContextExpr' where+  type OptimizeEnv DatabaseContextExpr' GraphRefDatabaseContextExpr' = GraphRefSOptDatabaseContextExprEnv+  optimize env expr = do+    let gfExpr = runProcessExprM UncommittedContextMarker (processDatabaseContextExpr expr)+        graph = odce_graph env+        transId = odce_transId env+        ctx = odce_context env+        dbcfuncutils = odce_dbcfuncutils env+    runGraphRefSOptDatabaseContextExprM transId ctx graph dbcfuncutils (optimizeGraphRefDatabaseContextExpr gfExpr)++-- | Apply pure optimizations. optimizeAndEvalRelationalExpr :: RelationalExprEnv -> RelationalExpr -> Either RelationalError Relation optimizeAndEvalRelationalExpr env expr = do   let graph = re_graph env@@ -57,6 +103,56 @@   optExpr <- optimizeRelationalExpr env expr   runGraphRefRelationalExprM gfEnv (evalGraphRefRelationalExpr optExpr) +-- | Uses streamly interface for parallel execution.+optimizeAndEvalRelationalExpr' :: RelationalExprEnv -> RelationalExpr -> RelExprCache -> IO (Either RelationalError Relation)+optimizeAndEvalRelationalExpr' env expr cache = do+  let gfExpr = runProcessExprM UncommittedContextMarker (processRelationalExpr expr) -- references parent tid instead of context! options- I could add the context to the graph with a new transid or implement an evalRelationalExpr in RE.hs to use the context (which is what I had previously)+      graph = re_graph env+      ctx = re_context env+      gfEnv = freshGraphRefRelationalExprEnv (Just ctx) graph+  --first, type check+  case runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr gfExpr) of+    Left err -> pure (Left err)+    Right _ -> do+      --then, optimize+      case runGraphRefSOptRelationalExprM (Just ctx) (re_graph env) (fullOptimizeGraphRefRelationalExpr gfExpr) of+        Left err -> pure (Left err)+        Right optGfExpr -> +          evalGraphRefRelationalExprWithCache (mkStdGen 36) gfEnv optGfExpr cache+          +-- | For internal use- expression argument should pass through static optimizer beforehand.+evalGraphRefRelationalExprWithCache :: RandomGen r => r -> GraphRefRelationalExprEnv -> GraphRefRelationalExpr -> RelExprCache -> IO (Either RelationalError Relation)+evalGraphRefRelationalExprWithCache rando gfEnv gfExpr cache =+  case planGraphRefRelationalExpr gfExpr gfEnv of+    Left err -> pure (Left err)+    Right plan -> do+      startExecTime <- getCurrentTime+      exec <- executePlan plan mempty gfEnv mempty cache -- try/catch to handle exceptions+      case exec of+        Left err -> pure (Left err)+        Right resultStream -> do+          --convert tuple stream into relation- we could push the results to the socket directly without materializing the entire relation+          relationResult <- streamRelationAsRelation resultStream+          relationResult' <- evaluate (force relationResult)+          endExecTime <- getCurrentTime+          let execDiffTime = endExecTime `diffUTCTime` startExecTime+          --add to the cache- we cannot add uncommitted data to the cache since uncommitted data does not have a unique key (transaction id) (should uncommitted data be able to be cached with a transaction id that has not been committed?)+              mCacheKey :: Maybe (RelationalExprBase TransactionId)+              mCacheKey = originalRelExpr plan >>= toPinnedRelationalExpr+              cacheValue = UnsortedTupleSetRep (attributes relationResult') (tupleSet relationResult')+              --cacheValue = PinnedExpressionRep (ExistingRelation relationResult') -- ideally, we would cache the expensive parts of the plan, not just the top-level result+          case mCacheKey of+            Nothing -> pure (Right relationResult')+            Just cacheKey -> do+              eMemStats <- getMemoryStats -- consider running mem stats less often if it's a bottleneck+              case eMemStats of+                Left err -> pure (Left (SystemError err))+                Right memStats -> do+                  void $ atomically $+                    RelExprCache.add rando cacheKey cacheValue execDiffTime False memStats cache +                  pure (Right relationResult')+  + optimizeRelationalExpr :: RelationalExprEnv -> RelationalExpr -> Either RelationalError GraphRefRelationalExpr optimizeRelationalExpr env expr = do   let gfExpr = runProcessExprM UncommittedContextMarker (processRelationalExpr expr) -- references parent tid instead of context! options- I could add the context to the graph with a new transid or implement an evalRelationalExpr in RE.hs to use the context (which is what I had previously)@@ -85,19 +181,20 @@ askMaybeContext :: GraphRefSOptRelationalExprM (Maybe DatabaseContext) askMaybeContext = asks ore_mcontext -optimizeDatabaseContextExpr :: DatabaseContextExpr -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr+optimizeDatabaseContextExpr :: DatabaseContextExpr' -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr' optimizeDatabaseContextExpr expr = do   let gfExpr = runProcessExprM UncommittedContextMarker (processDatabaseContextExpr expr)   optimizeGraphRefDatabaseContextExpr gfExpr   -optimizeAndEvalDatabaseContextExpr :: Bool -> DatabaseContextExpr -> DatabaseContextEvalMonad ()-optimizeAndEvalDatabaseContextExpr optimize expr = do+optimizeAndEvalDatabaseContextExpr :: Bool -> DatabaseContextExpr' -> DatabaseContextEvalMonad ()+optimizeAndEvalDatabaseContextExpr runOpt expr = do   graph <- asks dce_graph   transId <- asks dce_transId   context <- getStateContext+  dbcfuncutils <- asks dce_dbcfuncutils   let gfExpr = runProcessExprM UncommittedContextMarker (processDatabaseContextExpr expr)-      eOptExpr = if optimize then-                   runGraphRefSOptDatabaseContextExprM transId context graph (optimizeGraphRefDatabaseContextExpr gfExpr)+      eOptExpr = if runOpt then+                   runGraphRefSOptDatabaseContextExprM transId context graph dbcfuncutils (optimizeGraphRefDatabaseContextExpr gfExpr)                    else                    pure gfExpr   case eOptExpr of@@ -112,15 +209,26 @@   let gfEnv = freshGraphRefRelationalExprEnv Nothing graph   runGraphRefRelationalExprM gfEnv (evalGraphRefRelationalExpr optExpr) -optimizeAndEvalDatabaseContextIOExpr :: DatabaseContextIOExpr -> DatabaseContextIOEvalMonad (Either RelationalError ())+optimizeAndEvalTransGraphRelationalExprWithCache :: RandomGen r => r -> TransactionGraph -> TransGraphRelationalExpr -> RelExprCache -> IO (Either RelationalError Relation)+optimizeAndEvalTransGraphRelationalExprWithCache rando graph tgExpr cache = do+  let gfEnv = freshGraphRefRelationalExprEnv Nothing graph+      res = do+        gfExpr <- TGRE.process (TransGraphEvalEnv graph) tgExpr+        runGraphRefSOptRelationalExprM Nothing graph (fullOptimizeGraphRefRelationalExpr gfExpr)+  case res of+    Left err -> pure (Left err)+    Right optExpr ->+      evalGraphRefRelationalExprWithCache rando gfEnv optExpr cache++optimizeAndEvalDatabaseContextIOExpr :: DatabaseContextIOExpr -> DatabaseContextIOEvalMonad () optimizeAndEvalDatabaseContextIOExpr expr = do   transId <- asks dbcio_transId   ctx <- getDBCIOContext   graph <- asks dbcio_graph   let gfExpr = runProcessExprM UncommittedContextMarker (processDatabaseContextIOExpr expr)-      eOptExpr = runGraphRefSOptDatabaseContextExprM transId ctx graph (optimizeDatabaseContextIOExpr gfExpr)+      eOptExpr = runGraphRefSOptDatabaseContextIOExprM transId ctx graph (optimizeDatabaseContextIOExpr gfExpr)   case eOptExpr of-    Left err -> pure (Left err)+    Left err -> throwError err     Right optExpr ->       evalGraphRefDatabaseContextIOExpr optExpr @@ -146,17 +254,34 @@   TransactionId ->   DatabaseContext ->   TransactionGraph ->+  DatabaseContextFunctionUtils ->   GraphRefSOptDatabaseContextExprM a ->   Either RelationalError a-runGraphRefSOptDatabaseContextExprM tid ctx graph m =+runGraphRefSOptDatabaseContextExprM tid ctx graph dbcfuncutils m =   runIdentity (runExceptT (runReaderT m env))   where     env = GraphRefSOptDatabaseContextExprEnv {       odce_graph = graph,       odce_context = ctx,-      odce_transId = tid+      odce_transId = tid,+      odce_dbcfuncutils = dbcfuncutils       } +runGraphRefSOptDatabaseContextIOExprM ::+  TransactionId ->+  DatabaseContext ->+  TransactionGraph ->+  GraphRefSOptDatabaseContextIOExprM a ->+  Either RelationalError a+runGraphRefSOptDatabaseContextIOExprM tid ctx graph m =+  runIdentity (runExceptT (runReaderT m env))+  where+    env = GraphRefSOptDatabaseContextIOExprEnv {+      odcioe_graph = graph,+      odcioe_context = ctx,+      odcioe_transId = tid+        }+ optimizeGraphRefRelationalExpr' ::   Maybe DatabaseContext ->   TransactionGraph ->@@ -274,7 +399,7 @@             gfEnv = freshGraphRefRelationalExprEnv mctx graph         case attributesRel of            Left err -> throwError err-          Right attributesRelA -> pure $ MakeStaticRelation (attributes attributesRelA) emptyTupleSet+          Right attributesRelA -> pure $ MakeStaticRelation (attributes attributesRelA) TS.empty       | otherwise -> do         optSubExpr <- optimizeGraphRefRelationalExpr expr         pure $ Restrict optimizedPredicate' optSubExpr@@ -287,8 +412,10 @@  optimizeGraphRefRelationalExpr e@(With _ _) = pure e   --- database context expr-optimizeGraphRefDatabaseContextExpr :: GraphRefDatabaseContextExpr -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr+-- database context expr, we can only optimize with fully-resolved RoleIds to be able to enforce security which could otherwise be used to leak schema details+optimizeGraphRefDatabaseContextExpr ::+  GraphRefDatabaseContextExpr' ->+  GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextExpr' optimizeGraphRefDatabaseContextExpr x@NoOperation = pure x optimizeGraphRefDatabaseContextExpr x@(Define _ _) = pure x @@ -332,6 +459,7 @@ optimizeGraphRefDatabaseContextExpr c@(ExecuteDatabaseContextFunction _ _) = pure c optimizeGraphRefDatabaseContextExpr c@AddRegisteredQuery{} = pure c optimizeGraphRefDatabaseContextExpr c@RemoveRegisteredQuery{} = pure c+optimizeGraphRefDatabaseContextExpr c@AlterACL{} = pure c --optimization: from pgsql lists- check for join condition referencing foreign key- if join projection project away the referenced table, then it does not need to be scanned  --applyStaticDatabaseOptimization (MultipleExpr exprs) = pure $ Right $ MultipleExpr exprs@@ -341,18 +469,22 @@   context <- askContext   graph <- askGraph   parentId <- askTransId+  dbcfuncutils <- asks odce_dbcfuncutils   -  let emptyRvs ctx = ctx { relationVariables = mkEmptyRelVars (relationVariables ctx) }-      dbcEnv = mkDatabaseContextEvalEnv parentId graph+  let emptyRvs ctx = do+        emptyRvs' <- mkEmptyRelVars' graph (relationVariables ctx)+        pure $ ctx { relationVariables = emptyRvs' }+      dbcEnv = mkDatabaseContextEvalEnv parentId graph dbcfuncutils       folder (ctx, expracc) expr = do-        --optimize the expr and run it against empty relvars to add it to the context-        case runGraphRefSOptDatabaseContextExprM parentId ctx graph (optimizeGraphRefDatabaseContextExpr expr) of+        --optimize the expr and run it against empty relvars to add it to the context, otherwise some relvars could be missing in subsequent optimizations+        case runGraphRefSOptDatabaseContextExprM parentId ctx graph dbcfuncutils (optimizeGraphRefDatabaseContextExpr expr) of           Left err -> throwError err           Right optExpr ->             case runDatabaseContextEvalMonad ctx dbcEnv (evalGraphRefDatabaseContextExpr optExpr) of               Left err -> throwError err-              Right dbcState ->-                pure (emptyRvs $ dbc_context dbcState, expracc ++ [optExpr])+              Right dbcState -> do+                emptyRelVars <- lift (except (emptyRvs (dbc_context dbcState)))+                pure (emptyRelVars, expracc ++ [optExpr])    (_, exprs') <- foldM folder (context,[]) exprs   pure (MultipleExpr exprs')@@ -402,13 +534,6 @@ isFalseExpr (NotPredicate expr) = isTrueExpr expr isFalseExpr (AtomExprPredicate (NakedAtomExpr (BoolAtom False))) = True isFalseExpr _ = False---- determine if the created relation can statically be determined to be empty-isEmptyRelationExpr :: RelationalExprBase a -> Bool    -isEmptyRelationExpr (MakeRelationFromExprs _ (TupleExprs _ [])) = True-isEmptyRelationExpr (MakeStaticRelation _ tupSet) = null (asList tupSet)-isEmptyRelationExpr (ExistingRelation rel) = rel == emptyRelationWithAttrs (attributes rel)-isEmptyRelationExpr _ = False      --transitive static variable optimization                         replaceStaticAtomExprs :: GraphRefRestrictionPredicateExpr -> M.Map AttributeName GraphRefAtomExpr -> GraphRefRestrictionPredicateExpr@@ -480,13 +605,14 @@         Just ((joinedExpr, joinedType), (unjoinedExpr, _)) -> do         --lookup transaction         --scan inclusion dependencies for a foreign key relationship-         let incDeps = inclusionDependencies commonContext-             fkConstraint = foldM isFkConstraint False incDeps+         incDeps <- lift (except (resolveDBC' graph commonContext inclusionDependencies))+         let fkConstraint = foldM isFkConstraint False incDeps             --search for matching fk constraint              isFkConstraint acc (InclusionDependency (Project subAttrNames subrv) (Project _ superrv)) = do                let gfSubAttrNames = processM (processAttributeNames subAttrNames)                    gfSubRv = processM (processRelationalExpr subrv)                    gfSuperRv = processM (processRelationalExpr superrv)+                   processM :: forall a. ProcessExprM a -> a                    processM = runProcessExprM marker                case runGraphRefRelationalExprM gfEnv (evalGraphRefAttributeNames gfSubAttrNames expr) of                  Left _ -> pure acc@@ -619,6 +745,7 @@     folder e = traceShow ("para2", Fold.embed $ fst <$> e) $ Fold.embed $ fst <$> e -}     -- no optimizations available  -optimizeDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> GraphRefSOptDatabaseContextExprM GraphRefDatabaseContextIOExpr+optimizeDatabaseContextIOExpr :: GraphRefDatabaseContextIOExpr -> GraphRefSOptDatabaseContextIOExprM GraphRefDatabaseContextIOExpr optimizeDatabaseContextIOExpr = pure+ 
+ src/lib/ProjectM36/Streaming/RelationalExpression.hs view
@@ -0,0 +1,709 @@+{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, BangPatterns, FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module ProjectM36.Streaming.RelationalExpression where+import ProjectM36.Base+import ProjectM36.Error+import ProjectM36.RelationalExpression+import ProjectM36.Attribute as A+import ProjectM36.Tuple+import ProjectM36.TransactionGraph+import ProjectM36.PinnedRelationalExpr+import ProjectM36.Cache.RelationalExprCache as RECache+import ProjectM36.DatabaseContext.Types+import ProjectM36.Relation (RestrictionFilter, ContextTuples, attributes, contextTupleAtomForAttributeName, tupleSet)+import ProjectM36.WithNameExpr+import Streamly.Data.Stream (Stream)+import qualified Streamly.Data.Stream as Stream+import qualified Streamly.Data.Stream.Prelude as Stream+import qualified Streamly.Internal.Data.Stream as SD+import qualified Streamly.Data.StreamK as StreamK+import Prettyprinter+import Prettyprinter.Render.Text++import qualified Data.Map as M+import Control.Monad.IO.Class+import qualified Data.Vector as V+import qualified Data.HashSet as HS+import qualified Data.Set as S+import Control.DeepSeq (force)+import qualified Data.Text as T+import Data.Time.Clock (DiffTime)+--import qualified Data.List.NonEmpty as NE+import Control.Exception+import Control.Concurrent.STM+import qualified Data.UUID as U++newtype CacheMissException = CacheMissException FilePath+  deriving Show++instance Exception CacheMissException++type RelExprExecPlan = RelExprExecPlanBase () ()++type GraphRefRelExprExecPlan = RelExprExecPlanBase GraphRefTransactionMarker ()++type PostExecutionGraphRefRelExprExecPlan = RelExprExecPlanBase GraphRefTransactionMarker PlanNodeExecutionInfo++newtype PlanNodeExecutionInfo = PlanNodeExecutionInfo { duration :: DiffTime }+  +--this will become more useful once we have multiple join strategies, etc.+-- a: transaction marker+-- t: collected execution information as the graph is evaluated+data RelExprExecPlanBase marker execInfo =+  -- | Read relvar expr from transaction graph to generate tuple stream.+  -- Instead of locking cache entries during plans so that the entry is not ejected before the plan can run (race condition), the plan can point to a cache entry, but if the entry no longer exists, then the plan must provide an alternative solution.++--  ReadExprFromTransGraph RelVarName a |+  -- | Read tuples from a tuple set cache.+--  StreamTuplesFromCacheFilePlan Attributes FilePath execInfo (RelationalExprBase marker) |+  -- | Read tuples from memory. -- TODO: do we need this even if we have caching?+--  ReadTuplesFromMemoryPlan Attributes RelationTupleSet execInfo |+  -- | Alternative plans in case of failure. For example, the first node in the list may be to read from a cache file but the cache has since deleted the entry, so we proceed with an alternative. This is not a node to use for otherwise fatal errors.+--  AlternativePlan (RelExprExecPlanBase a t) (NE.NonEmpty (RelExprExecPlanBase a t)) t |+  -- | Run all the nodes simultaneously and return the results from the node that returns results first.+  -- RacePlan (RelExprExecPlanBase a t) (NE.NonEmpty (RelExprExecPlanBase a t)) t |+                       +                       RestrictTupleStreamPlan RestrictionFilter (RestrictionPredicateExprBase marker) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) | -- include compiled mode for stream execution and ADT version for planning printer+                       ProjectTupleStreamPlan Attributes (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       RenameTupleStreamPlan (S.Set (AttributeName, AttributeName)) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       GroupTupleStreamPlan Attributes AttributeName (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       UngroupTupleStreamPlan AttributeName (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       ExtendTupleStreamPlan ExtendTupleProcessor (ExtendTupleExprBase marker) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       RelationValuedAttributeStreamPlan Attribute execInfo (RelationalExprBase marker) | +                       +                       UnionTupleStreamsPlan (RelExprExecPlanBase marker execInfo) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) | -- ^ no uniquification implied with Union, if it's needed, planner needs to add it+                       NaiveJoinTupleStreamsPlan (RelExprExecPlanBase marker execInfo) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       DifferenceTupleStreamsPlan (RelExprExecPlanBase marker execInfo) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       EqualTupleStreamsPlan (RelExprExecPlanBase marker execInfo) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       NotEqualTupleStreamsPlan (RelExprExecPlanBase marker execInfo) (RelExprExecPlanBase marker execInfo) execInfo (RelationalExprBase marker) |+                       +                       MakeStaticRelationPlan Attributes RelationTupleSet execInfo |+                       -- TODO: ideally, the planner would create a new structure which extracts all necessary context so that we don't need to pass the entire transaction graph to execute MakeRelationFromExprsPlan+                       MakeRelationFromExprsPlan (Maybe [AttributeExprBase marker]) (TupleExprsBase marker) execInfo |+                       ExistingRelationPlan Relation execInfo |+                       UniqueifyTupleStreamPlan (RelExprExecPlanBase marker execInfo) execInfo -- ^ handy uniqueifier for cases when we know the sub-plan could include duplicates++-- | Get the original relational expression linked to the node. It can be used to generate the cache key, if relevant.+originalRelExpr :: RelExprExecPlanBase a t -> Maybe (RelationalExprBase a)+--originalRelExpr StreamTuplesFromCacheFilePlan{} = Nothing+--originalRelExpr ReadTuplesFromMemoryPlan{} = Nothing+originalRelExpr (RestrictTupleStreamPlan _ _ _ _ e) = Just e+originalRelExpr (ProjectTupleStreamPlan _ _ _ e) = Just e+originalRelExpr (RenameTupleStreamPlan _ _ _ e) = Just e+originalRelExpr (GroupTupleStreamPlan _ _ _ _ e) = Just e+originalRelExpr (UngroupTupleStreamPlan _ _ _ e) = Just e+originalRelExpr (ExtendTupleStreamPlan _ _ _ _ e) = Just e+originalRelExpr (RelationValuedAttributeStreamPlan _ _ e) = Just e+originalRelExpr (UnionTupleStreamsPlan _ _ _ e) = Just e+originalRelExpr (NaiveJoinTupleStreamsPlan _ _ _ e) = Just e+originalRelExpr (DifferenceTupleStreamsPlan _ _ _ e) = Just e+originalRelExpr (EqualTupleStreamsPlan _ _ _ e) = Just e+originalRelExpr (NotEqualTupleStreamsPlan _ _ _ e) = Just e+originalRelExpr MakeStaticRelationPlan{} = Nothing+originalRelExpr (MakeRelationFromExprsPlan mAttrExprs tupleExprs _) = Just (MakeRelationFromExprs mAttrExprs tupleExprs)+originalRelExpr (ExistingRelationPlan rel _) = Just (ExistingRelation rel)+originalRelExpr UniqueifyTupleStreamPlan{} = Nothing++{-+junction :: T.Text+junction = "\x251c\x2500"++horizontalLine :: T.Text+horizontalLine = "\x2502"+-}++-- | Create a plan Doc to show the user.+instance (Pretty t, Pretty a, Show a) => Pretty (RelExprExecPlanBase a t) where+  pretty expr =+    case expr of+{-      StreamTuplesFromCacheFilePlan attrs path t ->+        prettyNode "StreamTuplesFromCacheFilePlan" [pretty attrs, pretty path] [] t+      ReadTuplesFromMemoryPlan attrs _tupSet t ->+        prettyNode "ReadTuplesFromMemoryPlan" [pretty attrs, "<tuples elided"] [] t-}+      RestrictTupleStreamPlan _ predExpr expr' t _origExpr ->+        prettyNode "RestrictTupleStreamPlan" [pretty predExpr] [pretty expr'] t+      ProjectTupleStreamPlan attrs expr' t _ ->+        prettyNode "ProjectTupleStreamPlan " [pretty attrs] [pretty expr'] t+      RenameTupleStreamPlan attrs expr' t _ ->+        prettyNode "RenameTupleStreamPlan" [pretty attrs] [pretty expr'] t+      GroupTupleStreamPlan attrs nam expr' t _ ->+        prettyNode "GroupTupleStreamPlan" [pretty attrs <+> "as" <+> pretty nam] [pretty expr'] t+      UngroupTupleStreamPlan nam expr' t _ ->+        prettyNode "UngroupTupleStreamPlan" [pretty nam] [pretty expr'] t+      ExtendTupleStreamPlan _ extender expr' t _ ->+        prettyNode "ExtendTupleStreamPlan" [pretty extender] [pretty expr'] t+      RelationValuedAttributeStreamPlan relAttr t _ ->+        prettyNode "RelationValuedAttributeStreamPlan" [pretty relAttr] [] t+      UnionTupleStreamsPlan e1 e2 t _ ->+        prettyNode "UnionTupleStreamsPlan" [] [pretty e1, pretty e2] t+      NaiveJoinTupleStreamsPlan e1 e2 t _ ->+        prettyNode "NaiveJoinTupleStreamsPlan" [] [pretty e1, pretty e2] t+      DifferenceTupleStreamsPlan e1 e2 t _ ->+        prettyNode "DifferenceTupleStreamsPlan" [] [pretty e1, pretty e2] t+      EqualTupleStreamsPlan e1 e2 t _->+        prettyNode "EqualTupleStreamsPlan" [] [pretty e1, pretty e2] t+      NotEqualTupleStreamsPlan e1 e2 t _ ->+        prettyNode "NotEqualTupleStreamsPlan" [] [pretty e1, pretty e2] t+      MakeStaticRelationPlan attrs _tupSet t ->+        prettyNode "MakeStaticRelationPlan" [pretty attrs] ["<tuples elided>"] t+      MakeRelationFromExprsPlan mAttrs tupExprs t ->+        prettyNode "MakeRelationFromExprsPlan" [pretty mAttrs] [pretty tupExprs] t+      ExistingRelationPlan rel t ->+        prettyRelation "ExistingRelationPlan" rel t+      UniqueifyTupleStreamPlan e t ->+        prettyNode "UniqueifyTupleStreamPlan" [] [pretty e] t+{-      AlternativePlan one remainder t ->+        prettyNode "AlternativePlan" [] (pretty one : (map pretty (NE.toList remainder))) t-}+{-      RacePlan one remainder t ->+        prettyNode "RacePlan" [] (pretty one : (map pretty (NE.toList remainder))) t -}+    where+      prettyNode :: forall b. T.Text -> [Doc b] -> [Doc b] -> t -> Doc b+      prettyNode nodeName details subexprs execInfo =+        vsep ([pretty nodeName] <>+          map (indent 2) details <>+          [indent 3 (pretty execInfo)] <>+          map (indent 4) subexprs)+      prettyRelation :: forall b. T.Text -> Relation -> t -> Doc b+      prettyRelation nodeName rel execInfo =+        vsep (pretty nodeName :+         [indent 2 (pretty (attributes rel)),+          indent 4 (pretty (tupleSet rel)),+          indent 3 (pretty execInfo)]+             )++++parensList :: [Doc ann] -> Doc ann+parensList = group . encloseSep (flatAlt "( " "(") (flatAlt " )" ")") ", "+      +--todo- identify nodes which need uniqueifying+planGraphRefRelationalExpr :: GraphRefRelationalExpr -> +                              GraphRefRelationalExprEnv -> +                              Either RelationalError GraphRefRelExprExecPlan+planGraphRefRelationalExpr (RelationVariable name tid) gfEnv = do+  ctx <- runGraphRefRelationalExprM gfEnv (gfDatabaseContextForMarker tid)+  let graph = gre_graph gfEnv+  rvMap <- resolveDBC' graph ctx relationVariables+  case M.lookup name rvMap of+    Nothing -> Left (RelVarNotDefinedError name)+    Just rvExpr -> planGraphRefRelationalExpr rvExpr gfEnv+planGraphRefRelationalExpr orig@(RelationValuedAttribute relAttrName) gfEnv = do+  case A.attributeForName relAttrName (envAttributes gfEnv) of+    Left err -> throw err+    Right relAttr -> +      pure (RelationValuedAttributeStreamPlan relAttr () orig)+  +planGraphRefRelationalExpr orig@(Project attrNames expr) gfEnv = do+  exprT <- runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr expr)+  projectionAttrNames <- runGraphRefRelationalExprM gfEnv (evalGraphRefAttributeNames attrNames expr)+  case projectionAttributesForNames projectionAttrNames (attributes exprT) of+    Left err -> Left err+    Right attrs' -> do+      subExpr <- planGraphRefRelationalExpr expr gfEnv+      --if we know that the projection attributes represent a key, then we don't need to run the uniquification+      pure (UniqueifyTupleStreamPlan (ProjectTupleStreamPlan attrs' subExpr () orig) ())+  +planGraphRefRelationalExpr orig@(Union exprA exprB) state = do  +  planA <- planGraphRefRelationalExpr exprA state+  planB <- planGraphRefRelationalExpr exprB state+  pure (UnionTupleStreamsPlan planA planB () orig)+  +planGraphRefRelationalExpr orig@(Join exprA exprB) state = do  +  planA <- planGraphRefRelationalExpr exprA state+  planB <- planGraphRefRelationalExpr exprB state+  pure (NaiveJoinTupleStreamsPlan planA planB () orig)+  +planGraphRefRelationalExpr orig@(Difference exprA exprB) state = do+  planA <- planGraphRefRelationalExpr exprA state+  planB <- planGraphRefRelationalExpr exprB state+  pure (DifferenceTupleStreamsPlan planA planB () orig)++planGraphRefRelationalExpr (MakeStaticRelation attributeSet tupSet) _ = pure (MakeStaticRelationPlan attributeSet tupSet ())++-- MakeRelationFromExprs could include expensive atom functions+planGraphRefRelationalExpr (MakeRelationFromExprs mAttrExprs tupExprs) _gfEnv =+  pure (MakeRelationFromExprsPlan mAttrExprs tupExprs ())+  +planGraphRefRelationalExpr (ExistingRelation rel) _ = pure (ExistingRelationPlan rel ())  ++planGraphRefRelationalExpr orig@(Rename renameAssoc relExpr) state = +  RenameTupleStreamPlan renameAssoc <$> planGraphRefRelationalExpr relExpr state <*> pure () <*> pure orig+  +planGraphRefRelationalExpr orig@(Group groupAttrNames newAttrName relExpr) gfEnv = do+  groupAttrs <- runGraphRefRelationalExprM gfEnv $ do+    groupTypes <- typeForGraphRefRelationalExpr (Project groupAttrNames relExpr)+    pure (attributes groupTypes)+  GroupTupleStreamPlan groupAttrs newAttrName <$> planGraphRefRelationalExpr relExpr gfEnv <*> pure () <*> pure orig+  +planGraphRefRelationalExpr orig@(Ungroup attrName relExpr) state = +  UngroupTupleStreamPlan attrName <$> planGraphRefRelationalExpr relExpr state <*> pure () <*> pure orig+  +planGraphRefRelationalExpr origExpr@(Restrict predExpr relExpr) gfEnv = do+  rfilt <- runGraphRefRelationalExprM gfEnv $ do+    exprT <- typeForGraphRefRelationalExpr relExpr    +    predicateRestrictionFilter (attributes exprT) predExpr+  RestrictTupleStreamPlan rfilt predExpr <$> planGraphRefRelationalExpr relExpr gfEnv <*> pure () <*> pure origExpr+  +planGraphRefRelationalExpr orig@(Equals relExprA relExprB) state =   +  EqualTupleStreamsPlan <$> planGraphRefRelationalExpr relExprA state <*> planGraphRefRelationalExpr relExprB state <*> pure () <*> pure orig+  +planGraphRefRelationalExpr orig@(NotEquals relExprA relExprB) state =+  NotEqualTupleStreamsPlan <$> planGraphRefRelationalExpr relExprA state <*> planGraphRefRelationalExpr relExprB state <*> pure () <*> pure orig+  +planGraphRefRelationalExpr orig@(Extend extendTupleExpr relExpr) gfEnv = do+  subExprT <- runGraphRefRelationalExprM gfEnv (typeForGraphRefRelationalExpr relExpr)+  extendProc <- runGraphRefRelationalExprM gfEnv (extendGraphRefTupleExpressionProcessor (attributes subExprT) extendTupleExpr)+  ExtendTupleStreamPlan extendProc extendTupleExpr <$> planGraphRefRelationalExpr relExpr gfEnv <*> pure () <*> pure orig++planGraphRefRelationalExpr (With macros expr) gfEnv = +  --TODO: determine if macros should be expanded or executed separately- perhaps calculate how many times a macro appears and it's calculation and size cost to determine if it should be precalculated.+  planGraphRefRelationalExpr (substituteWithNameMacros macros expr) gfEnv++data StreamRelation m = StreamRelation {+  sRelAttributes :: Attributes,+  sRelTupStream :: Stream m RelationTuple+  }+++-- | Process a tuple stream into a Relation. This function presumes all validation has already been completed and will not remove duplicate tuples or tuples which do not match the attributes.+streamRelationAsRelation :: MonadIO m => StreamRelation m -> m Relation+streamRelationAsRelation (StreamRelation attrs tupS) = do+  tupSet <- Stream.toList tupS+  pure (Relation attrs (RelationTupleSet tupSet))+++-- a local variant of parallel stream map which includes deepseq+streamRelationMap :: MonadIO m =>+          Attributes ->+          (RelationTuple -> m RelationTuple) -> StreamRelation m -> StreamRelation m+streamRelationMap newAttrs fun (StreamRelation _ tupSIn) =+  StreamRelation newAttrs (Stream.mapM eval tupSIn)+ where+   eval tup = do+        tup' <- fun tup+        liftIO $ evaluate (force tup')++--parFilter ++type CacheKeyBlackList = HS.HashSet PinnedRelationalExpr+--until we can stream results to disk or socket, we return a lazy-list-based Relation+-- | Build a tuple stream from an execution plan to enable parallel execution.+executePlan :: GraphRefRelExprExecPlan -> ContextTuples -> GraphRefRelationalExprEnv -> CacheKeyBlackList -> RelExprCache -> IO (Either RelationalError (StreamRelation IO))+{-executePlan (ReadTuplesFromMemoryPlan attrs tupSet ()) _ _ _cache =+  pure $ StreamRelation attrs (Stream.fromList (asList tupSet))+executePlan (StreamTuplesFromCacheFilePlan{}) _ _ _cache =+  --todo: enable streaming tuples from file+  undefined+-}+executePlan plan ctxTuples gfEnv cacheKeyBlackList cache = do  +  -- check if plan is available in cache+  let checkCacheOr noCacheExec =+        case originalRelExpr plan of+          Nothing -> noCacheExec+          Just origRelExpr ->+            case toPinnedRelationalExpr origRelExpr of+              Nothing ->  -- no cache key available for plan+                noCacheExec+              Just cacheKey -> do+                mCachedVal <-+                  if cacheKey `HS.member` cacheKeyBlackList then -- the blacklist prevents infinite recursion into the cache+                    pure Nothing+                  else do+                    liftIO $ atomically $ RECache.lookup cacheKey cache+                case mCachedVal of -- check if the key is in the cache+                  Nothing -> noCacheExec+                  Just cacheInfo ->+                    case result cacheInfo of+                      PinnedExpressionRep pinnedRelExpr -> do+                        -- plan and execute alternative, cached expression which is equivalent to the results of the origPlan+                        let eNewPlan = planGraphRefRelationalExpr (toGraphRefRelationalExpr pinnedRelExpr) (freshGraphRefRelationalExprEnv Nothing emptyTransactionGraph)+                        case eNewPlan of+                          Left err -> pure (Left err)+                          Right newPlan -> do+                            executePlan newPlan ctxTuples gfEnv (HS.insert cacheKey cacheKeyBlackList) cache --prevent infinite loop using key blacklist+                      UnsortedTupleSetRep attrs tupSet -> do+                        pure (Right (StreamRelation attrs (Stream.fromList (asList tupSet))))+                      SortedTuplesRep _tupList _sortInfo -> error "sortedtupsrep unimplemented"+  case plan of+    RenameTupleStreamPlan attrsAssoc expr () _rexpr -> do+      checkCacheOr $ do+        eRelS <- executePlan expr ctxTuples gfEnv cacheKeyBlackList cache+        case eRelS of+          Left err -> pure (Left err)+          Right relS -> do+            let newAttrs = renameAttributes' attrsAssoc (sRelAttributes relS)+            --potential optimization- lookup attrs in advance to rename the correct vector index+            pure (Right (streamRelationMap newAttrs (pure . tupleRenameAttributes attrsAssoc) relS))+            +    RestrictTupleStreamPlan restrictionFilter _predExpr expr () _origExpr ->+      checkCacheOr $ do+        eStream <- executePlan expr ctxTuples gfEnv cacheKeyBlackList cache+        case eStream of+          Left err -> pure (Left err)+          Right (StreamRelation attrs tupS) -> do+            let tupS' = Stream.filterM filt tupS+      -- since we are building up a stream data structure, we can represent in-stream failure using exceptions- we won't be able to execute the stream here to extract errors+                filt t =+                  pure $ case restrictionFilter t ctxTuples of+                           Left err -> throw err -- this will blow up in a separate thread but streamly should shuttle it to the caller (I hope)+                           Right !t' -> t'+            pure (Right (StreamRelation attrs tupS'))+    ProjectTupleStreamPlan attrs expr () _ -> do+      checkCacheOr $ do+        eS <- executePlan expr ctxTuples gfEnv cacheKeyBlackList cache+        case eS of+          Left err -> pure (Left err)+          Right (StreamRelation _ tupS) -> do+            let tupS' = fmap projector tupS+            --optimize by projecting on vector indexes instead+                projector t = case tupleProject attrs t of+                                Left err -> throw err+                                Right t' -> t'+            pure (Right (StreamRelation attrs tupS'))+    UnionTupleStreamsPlan exprA exprB () _ -> do+      checkCacheOr $ do+        --ideally, the streams would have pre-ordered tuples and we could zip them together right away- if we have an ordered representation, we can drop the sorting here+  -- glue two streams together, then uniqueify+        ePlanA <- executePlan exprA ctxTuples gfEnv cacheKeyBlackList cache+        ePlanB <- executePlan exprB ctxTuples gfEnv cacheKeyBlackList cache+        case ePlanA of+          Left err -> pure (Left err)+          Right (StreamRelation attrsA tupSa) ->+            case ePlanB of+              Left err -> pure (Left err)+              Right (StreamRelation _ tupSb) -> do+                let tupS' = Stream.parList id [tupSa, tupSb]+                pure (Right (StreamRelation attrsA tupS'))+    EqualTupleStreamsPlan exprA exprB () _ -> do+      checkCacheOr $ do+        ePlanA <- executePlan exprA ctxTuples gfEnv cacheKeyBlackList cache+        ePlanB <- executePlan exprB ctxTuples gfEnv cacheKeyBlackList cache+        case ePlanA of+          Left err -> pure (Left err)+          Right (StreamRelation _ tupSa) ->+            case ePlanB of+              Left err -> pure (Left err)+              Right (StreamRelation _ tupSb) -> do+                let hsA = tuplesHashSet tupSa+                    hscmp = tuplesHashSet (Stream.parList id [tupSa, tupSb])+                    tupS' = SD.unCross $ do+                      tA <- liftIO hsA+                      tcmp <- liftIO hscmp+                      SD.mkCross $ Stream.fromList $ +                        [RelationTuple mempty mempty | HS.size tA == HS.size tcmp]+                pure (Right (StreamRelation mempty tupS'))+    RelationValuedAttributeStreamPlan relAttr () _ -> checkCacheOr $ do+      case contextTupleAtomForAttributeName ctxTuples (A.attributeName relAttr) of+        Left err -> pure (Left err)+        Right relAtom@(RelationAtom{}) -> do+          let newTup = RelationTuple (A.singleton relAttr) (V.singleton relAtom)+              tupS = Stream.fromList [newTup]+          pure (Right (StreamRelation (A.singleton relAttr) tupS))+        Right _ -> pure (Left (AttributeIsNotRelationValuedError (A.attributeName relAttr)))+    NotEqualTupleStreamsPlan exprA exprB () orig -> checkCacheOr $ do+      eS <- executePlan (EqualTupleStreamsPlan exprA exprB () orig) ctxTuples gfEnv cacheKeyBlackList cache+      case eS of+        Left err -> pure (Left err)+        Right (StreamRelation _ tupS) -> do+          let tupS' = SD.unCross $ do+                el <- liftIO $ SD.head tupS+                SD.mkCross $ Stream.fromList $ case el of+                                                 Nothing -> [RelationTuple mempty mempty]+                                                 Just _ -> []+          pure (Right (StreamRelation mempty tupS'))+    MakeStaticRelationPlan attrs tupSet () -> checkCacheOr $ do+      pure (Right (StreamRelation attrs (Stream.fromList (asList tupSet))))+    MakeRelationFromExprsPlan mAttrExprs tupExprs _ -> checkCacheOr $ do+      let expr = MakeRelationFromExprs mAttrExprs tupExprs+      case runGraphRefRelationalExprM gfEnv (evalGraphRefRelationalExpr expr) of+        Left err -> pure (Left err)+        Right rel ->+          pure (Right (StreamRelation (attributes rel) (Stream.fromList (asList (tupleSet rel)))))+    ExistingRelationPlan rel () -> do+      pure (Right (StreamRelation (attributes rel) (Stream.fromList (asList (tupleSet rel)))))+    ExtendTupleStreamPlan (newAttrs, extendProcessor) _extendExpr expr () _ -> checkCacheOr $ do+      eRelS <- executePlan expr ctxTuples gfEnv cacheKeyBlackList cache+      case eRelS of+        Left err -> pure (Left err)+        Right relS -> do+          let extender tup =+                case extendProcessor tup ctxTuples of+                  Left err -> throw err+                  Right t' -> pure t'+          pure (Right (streamRelationMap newAttrs extender relS))+    NaiveJoinTupleStreamsPlan exprA exprB () _ -> checkCacheOr $ do+      --naive join by scanning both exprB into a list for repeated O(n^2) scans which is fine for "small" tables+      eSA <- executePlan exprA ctxTuples gfEnv cacheKeyBlackList cache+      eSB <- executePlan exprB ctxTuples gfEnv cacheKeyBlackList cache+      case eSA of+        Left err -> pure (Left err)+        Right (StreamRelation attrsA tupSa) ->+          case eSB of+            Left err -> pure (Left err)+            Right (StreamRelation attrsB tupSb) -> do+              case joinAttributes attrsA attrsB of+                Left err -> pure (Left err)+                Right attrsOut -> do+                  let tupS' = SD.unCross $ do+                        bTupleList <- liftIO $ Stream.toList tupSb        +                        let tupleJoiner :: RelationTuple -> [RelationTuple]+                            tupleJoiner tupleFromA =+                              concatMap (\tupleFromB ->+                                        case singleTupleJoin attrsOut tupleFromA tupleFromB of+                                          Left err -> throw err+                                          Right Nothing -> []+                                          Right (Just joinedTuple) -> [joinedTuple]+                                    ) bTupleList+                        SD.mkCross $ Stream.concatMap (Stream.fromList . tupleJoiner) tupSa+                  pure (Right (StreamRelation attrsOut tupS'))+                  +    DifferenceTupleStreamsPlan exprA exprB () _ -> checkCacheOr $ do+      eSA <- executePlan exprA ctxTuples gfEnv cacheKeyBlackList cache+      eSB <- executePlan exprB ctxTuples gfEnv cacheKeyBlackList cache+      case eSA of+        Left err -> pure (Left err)+        Right (StreamRelation attrsA tupSa) ->+          case eSB of+            Left err -> pure (Left err)+            Right (StreamRelation _ tupSb) -> do+              let tupS' = SD.unCross $ do+                    bTupleList <- liftIO $ Stream.toList tupSb        +                    SD.mkCross $ Stream.filter (`notElem` bTupleList) tupSa+              pure (Right (StreamRelation attrsA tupS'))+    GroupTupleStreamPlan groupAttrs newAttrName expr () orig -> checkCacheOr $ do+      --naive implementation scans for image relation for each grouped value+      eS <- executePlan expr ctxTuples gfEnv cacheKeyBlackList cache+      case eS of+        Left err -> pure (Left err)+        Right (StreamRelation attrsIn tupS) -> do+          let nonGroupAttrNames = nonMatchingAttributeNameSet groupAttrNames (A.attributeNameSet attrsIn)+              groupAttrNames = A.attributeNameSet groupAttrs+          case projectionAttributesForNames nonGroupAttrNames attrsIn of+            Left err -> pure (Left err)+            Right nonGroupProjectionAttributes -> do+              case projectionAttributesForNames groupAttrNames attrsIn of+                Left err -> pure (Left err)+                Right groupProjectionAttributes -> do+                  eGroupS <- executePlan (ProjectTupleStreamPlan nonGroupProjectionAttributes expr () orig) ctxTuples gfEnv cacheKeyBlackList cache+                  case eGroupS of+                    Left err -> pure (Left err)+                    Right (StreamRelation _ nonGroupProjectionTupS) -> do+                      let outAttrs = addAttribute newAttr nonGroupProjectionAttributes+                          matchAttrs = V.fromList $ S.toList $ attributeNameSet nonGroupProjectionAttributes+                          newAttr = Attribute newAttrName (RelationAtomType groupProjectionAttributes)+                          tupS' = SD.unCross $ do+                            origTupList <- liftIO $ Stream.toList tupS+                            -- find matching tuples from list+                            let singleTupleGroupMatcher tup =+                                  let matchingTuples =+                                        filter (\groupTup ->+                                              atomsForAttributeNames matchAttrs tup == atomsForAttributeNames matchAttrs groupTup) origTupList+                                      newtups = RelationTupleSet $ map (ehandler . tupleProject groupProjectionAttributes) matchingTuples +                                      ehandler (Left err) = throw err+                                      ehandler (Right t) = t+                                      groupedRel = Relation groupProjectionAttributes newtups+                                  in+                                    tupleExtend tup (RelationTuple (A.singleton newAttr) (V.singleton (RelationAtom groupedRel)))+                            SD.mkCross $ fmap singleTupleGroupMatcher nonGroupProjectionTupS+                      pure (Right (StreamRelation outAttrs tupS'))+    UngroupTupleStreamPlan groupAttrName expr () _ -> checkCacheOr $ do+      eS <- executePlan expr ctxTuples gfEnv cacheKeyBlackList cache+      case eS of+        Left err -> pure (Left err)+        Right (StreamRelation attrsIn tupS) -> do+          case atomTypeForAttributeName groupAttrName attrsIn of+            Right (RelationAtomType subRelAttrs) -> do+              let outAttrs = attrsIn <> subRelAttrs+                  ungroup' tup =+                    --unwrap subrelation+                    let subrel = case atomForAttributeName groupAttrName tup of+                          Left err -> throw err+                          Right (RelationAtom r) -> r+                          Right _ -> throw (AttributeIsNotRelationValuedError groupAttrName) --typechecker should ensure that this never happens+                        flattenedTuples = map (tupleExtend tup) (asList (tupleSet subrel))+                    in+                      Stream.fromList flattenedTuples+              pure (Right (StreamRelation outAttrs (Stream.concatMap ungroup' tupS)))+            _ -> pure (Left (AttributeIsNotRelationValuedError groupAttrName))+    UniqueifyTupleStreamPlan e () -> checkCacheOr $ do+      eS <- executePlan e ctxTuples gfEnv cacheKeyBlackList cache+      case eS of+        Left err -> pure (Left err)+        Right (StreamRelation attrs tupS) -> do+          let tupS' = SD.unCross $ do+                uniqTups <- liftIO $ tuplesHashSet tupS+                SD.mkCross $ StreamK.toStream (StreamK.fromFoldable uniqTups)+          pure (Right (StreamRelation attrs tupS'))+{-    AlternativePlan first remainder () -> do+      -- catch cache miss exception- the cache entry may have been deleted+      let initOptions = first : NE.init remainder+          lastOption = NE.last remainder -- if the last option throws an exception, just bubble it up+      --convert to streamly+          runOptions :: (MonadIO m, MonadAsync m) => [GraphRefRelExprExecPlan] -> GraphRefRelExprExecPlan -> StreamRelation m+          runOptions (fstStream:remStream) finalStream =+            Stream.handle (\(_e :: CacheMissException) -> runOptions remStream finalStream) fstStream -- we may have other alternative-triggering exceptions in the future+          runOptions [] finalStream = finalStream+      initStreams <- mapM ((flip executePlan) ctx) initOptions+      finalStream <- executePlan lastOption ctx+      pure $ runOptions initStreams finalStream+  -}                         ++relationTrue :: (MonadIO m, Stream.MonadAsync m) => StreamRelation m+relationTrue = StreamRelation mempty (Stream.fromList [RelationTuple mempty mempty])++relationFalse :: (MonadIO m, Stream.MonadAsync m) => StreamRelation m+relationFalse = StreamRelation mempty (Stream.fromList [])++tuplesHashSet :: MonadIO m => Stream m RelationTuple -> m (HS.HashSet RelationTuple)+tuplesHashSet =+  Stream.foldr HS.insert mempty++instance Pretty Attributes where+  pretty attrs = encloseSep "{" "}" "," (map pretty (V.toList (attributesVec attrs)))++instance Pretty RelationTupleSet where+  pretty tupSet = vsep (map pretty (asList tupSet))++instance Pretty RenameAssocs where+  pretty renameSet = hsep (map pretty (S.toList renameSet))++instance Pretty Attribute where+  pretty attr = pretty (attributeName attr) <+> pretty (atomType attr)++instance Pretty RelationTuple where+  pretty tup = pretty (tupleAttributes tup) <+> vsep (map (indent 4 . pretty) (V.toList (tupleAtoms tup)))++instance Pretty a => Pretty (AttributeExprBase a) where+  pretty (AttributeAndTypeNameExpr attrName tCons a) =+    pretty attrName <+> pretty tCons <+> pretty a+  pretty (NakedAttributeExpr attrName) = pretty attrName++instance Pretty a => Pretty (TupleExprsBase a) where+  pretty (TupleExprs a tupleExprs) =+    pretty a <+> vsep (map pretty tupleExprs)++instance Pretty a => Pretty (TupleExprBase a) where+  pretty (TupleExpr attrAtomMap) =+    "TupleExpr" <+> indent 4 (hsep (map prettyMap (M.toList attrAtomMap)))+    where+      prettyMap (attrName, atomExpr) =+        pretty attrName <> ": " <> pretty atomExpr++instance Pretty a => Pretty (AtomExprBase a) where+  pretty atomExpr =+    case atomExpr of+      AttributeAtomExpr attrName ->+        "AttributeAtomExpr" <+> pretty attrName+      SubrelationAttributeAtomExpr attrName subAttrName ->+        "SubrelationAttributeAtomExpr" <+> pretty attrName <+> pretty subAttrName+      NakedAtomExpr atom ->+        "NakedAtomExpr" <+> pretty atom+      FunctionAtomExpr fname fargs marker ->+        "FunctionAtomExpr" <+> pretty fname <+> parens (pretty fargs) <+> pretty marker+      RelationAtomExpr relExpr ->+        "RelationAtomExpr" <+> pretty relExpr+      IfThenAtomExpr if' then' else' ->+        "IfThenAtomExpr" <+> pretty if' <+> pretty then' <+> pretty else'+      ConstructedAtomExpr dConsName atomExprs marker ->+        "ConstructedAtomExpr" <+> pretty dConsName <+> pretty atomExprs <+> pretty marker++instance Pretty a => Pretty (RelationalExprBase a) where+  pretty relExpr =+    case relExpr of+      MakeRelationFromExprs mAttrExprs tupExprs ->+        "MakeRelationFromExprs" <+> pretty mAttrExprs <+> pretty tupExprs+      MakeStaticRelation attrs tupSet ->+        "MakeStaticRelation" <+> pretty attrs <+> pretty tupSet+      ExistingRelation _rel ->+        "ExistingRelation"+      RelationVariable rvName marker ->+        "RelationVariable" <+> pretty rvName <+> pretty marker+      RelationValuedAttribute attrName ->+        "RelationValuedAttribute" <+> pretty attrName+      Project attrNames expr ->+        "Project" <+> pretty attrNames <+> pretty expr+      Union exprA exprB ->+        "Union" <+> pretty exprA <+> pretty exprB+      Join exprA exprB ->+        "Join" <+> pretty exprA <+> pretty exprB+      Rename renames expr ->+        "Rename" <+> pretty renames <+> pretty expr+      Difference exprA exprB ->+        "Difference" <+> pretty exprA <+> pretty exprB+      Group attrNames groupAttr expr ->+        "Group" <+> pretty attrNames <+> pretty groupAttr <+> pretty expr+      Ungroup attrName expr ->+        "Ungroup" <+> pretty attrName <+> pretty expr+      Restrict predExpr expr ->+        "Restrict" <+> pretty predExpr <+> pretty expr+      Equals exprA exprB ->+        "Equals" <+> pretty exprA <+> pretty exprB+      NotEquals exprA exprB ->+        "NotEquals" <+> pretty exprA <+> pretty exprB+      Extend extendExpr expr ->+        "Extend" <+> pretty extendExpr <+> pretty expr+      With withs expr ->+        "With" <+> pretty withs <+> pretty expr++instance Pretty a => Pretty (ExtendTupleExprBase a) where+  pretty (AttributeExtendTupleExpr attrName atomExpr) =+    pretty attrName <+> pretty atomExpr+  +instance Pretty a => Pretty (WithNameExprBase a) where+  pretty (WithNameExpr rvName marker) = "WithNameExpr" <+> pretty rvName <+> pretty marker++instance Pretty a => Pretty (AttributeNamesBase a) where+  pretty (AttributeNames attrNameSet) = "AttributeNames" <+> vsep (map pretty (S.toList attrNameSet))+  pretty (InvertedAttributeNames attrNameSet) = "InvertedAttributeNames" <+> vsep (map pretty (S.toList attrNameSet))+  pretty (UnionAttributeNames namesA namesB) =+    "UnionAttributeNames" <+> parens (pretty namesA) <+> parens (pretty namesB)+  pretty (IntersectAttributeNames namesA namesB) =+    "IntersectAttributeNames" <+> parens (pretty namesA) <+> parens (pretty namesB)+  pretty (RelationalExprAttributeNames expr) =+    "RelationalExprAttributeNames" <+> pretty expr+                                                                                  ++instance Pretty TypeConstructor where+  pretty (ADTypeConstructor tConsName []) =+    pretty tConsName+  pretty (ADTypeConstructor tConsName tArgs) =+    parens (pretty tConsName <+> vsep (map pretty tArgs))+  pretty (PrimitiveTypeConstructor tConsName _) =+    pretty tConsName+  pretty (RelationAtomTypeConstructor attrExprs) =+    parens (vsep (map pretty attrExprs))+  pretty (TypeVariable tVarName) =+    pretty tVarName+  +instance Pretty AtomType where+  pretty aType = pretty (show aType)++instance Pretty Atom where+  pretty atom = pretty (show atom)++instance Pretty a => Pretty (RestrictionPredicateExprBase a) where+  pretty x =+    case x of+      TruePredicate -> "True"+      AndPredicate exprA exprB ->+        "And" <+> parens (pretty exprA) <+> parens (pretty exprB)+      OrPredicate exprA exprB ->+        "Or" <+> parens (pretty exprA) <+> parens (pretty exprB)+      NotPredicate expr ->+        "Not" <+> parens (pretty expr)+      RelationalExprPredicate expr ->+        "RelationalExpr" <+> pretty expr+      AtomExprPredicate atomExpr ->+        "AtomExpr" <+> pretty atomExpr+      AttributeEqualityPredicate attrName atomExpr ->+        "AttributeEquality" <+> pretty attrName <+> pretty atomExpr++--instance Pretty a => Pretty (ExtendTupleExprBase a) where+  ++instance Pretty GraphRefTransactionMarker where+  pretty (TransactionMarker tid) = "TransactionId" <+> pretty (U.toText tid)+  pretty UncommittedContextMarker = "Uncomitted"++renderPretty :: (Show a, Pretty t, Pretty a) => RelExprExecPlanBase a t -> T.Text+renderPretty = renderStrict . layoutPretty defaultLayoutOptions . pretty
+ src/lib/ProjectM36/Streaming/Tuple.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}+module ProjectM36.Streaming.Tuple where+import ProjectM36.Base+import ProjectM36.Error+import qualified ProjectM36.Relation as Rel+import qualified ProjectM36.Attribute as A+import Control.Monad.Catch (throwM, MonadThrow)+import Control.Exception (throw)+import Streamly.Data.Stream (Stream)+import Streamly.Data.Stream.Prelude (MonadAsync)+import qualified Streamly.Data.Fold as FL+import qualified Streamly.Data.Stream as S+import qualified Streamly.Data.Stream.Prelude as SP+import qualified Streamly.Internal.Data.Stream as SD+import ProjectM36.Tuple+import qualified Data.Set as Set+import qualified Data.Vector as V+import qualified Data.HashMap.Lazy as HM+import Control.Monad.Trans.Class++type RestrictionFilter = RelationTuple -> Either RelationalError Bool++restrict :: MonadAsync m => RestrictionFilter -> Stream m RelationTuple -> Stream m RelationTuple+restrict func = S.filterM filt+  where+    filt tupIn =+      case func tupIn of+        Left err -> throwM err+        Right filt' -> pure filt'+  +cardinality :: MonadAsync m => Stream m RelationTuple -> m RelationCardinality+cardinality s = Finite <$> S.fold FL.length s++-- error handling needs to happen at type-checking time+rename :: MonadAsync m => AttributeName -> AttributeName -> Stream m RelationTuple -> Stream m RelationTuple+rename attrA attrB = S.mapM (pure . tupleRenameAttribute attrA attrB)++project :: MonadAsync m => Attributes -> Stream m RelationTuple -> Stream m RelationTuple+project attrs = S.mapM (\tup ->+                           case tupleProject attrs tup of+                             Left err -> throwM err+                             Right tup' -> pure tup')++union :: MonadAsync m => Stream m RelationTuple -> Stream m RelationTuple -> Stream m RelationTuple+union s1 s2 = unionMulti [s1,s2]++unionMulti :: MonadAsync m => [Stream m RelationTuple] -> Stream m RelationTuple+unionMulti = SP.parList id++--nested loop join requires traversing one of the stream n times+--call S.toList on first tuple stream+--for error handling, use MonadThrow?+join :: forall m.(MonadAsync m, MonadThrow m, Applicative m) => Attributes -> [RelationTuple] -> Stream m RelationTuple -> Stream m RelationTuple+join joinCondition tup1List =+  SP.parConcatMap id naiveJoiner+  where+    -- O(n^2) scanning join function+    naiveJoiner :: RelationTuple -> Stream m RelationTuple+    naiveJoiner tup2In =+      case singleTupleSetJoin joinCondition tup2In (RelationTupleSet tup1List) of+        Left err -> throw err+        Right joined -> SP.fromList joined++difference :: (MonadAsync m) => [RelationTuple] -> Stream m RelationTuple -> Stream m RelationTuple+difference filterTuples = S.filter filt+  where+    filt tup = tup `notElem` filterTuples++{-+group :: MonadAsync m => Attributes -> AttributeName -> Attributes -> Stream m RelationTuple -> Stream m RelationTuple+group groupAttrs newAttrName attrs tupSIn =+  let nonGroupAttrNames = A.nonMatchingAttributeNameSet groupAttrNames (Set.fromList (V.toList (A.attributeNames attrs)))+      groupAttrNames = A.attributeNameSet groupAttrs+      nonGroupAttrs = A.attributesForNames nonGroupAttrNames attrs+      handleError val = case val of +        Left err -> throwM err+        Right val' -> pure val'+  in+    unCrossStream $ do+      nonGroupProjectionAttributes <- handleError $ A.projectionAttributesForNames nonGroupAttrNames attrs+      groupProjectionAttributes <- handleError $ A.projectionAttributesForNames groupAttrNames attrs+      let newAttrs = A.addAttribute groupAttr nonGroupProjectionAttributes+          groupAttr = Attribute newAttrName (RelationAtomType groupProjectionAttributes)+          tupleGenerator nongrouped groups' accum = do+            subRel <- Rel.mkRelationFromTuples newAttrs groups'+            let subTup = RelationTuple (A.singleton groupAttr) (V.singleton (RelationAtom subRel))+            let newTup = tupleExtend nongrouped subTup+--        (:) <$> pure newTup <*> accum+        (newTup:) <$> accum+      +  groupedMap <- lift $ S.foldr groupFolder HM.empty tupsIn+  case HM.foldrWithKey tupleGenerator (Right []) groupedMap of+    Left err -> throwM err+    Right tuples -> SP.fromList tuples++    +    case A.projectionAttributesForNames nonGroupAttrNames attrs of+      Left err -> throwM err+      Right nonGroupProjectionAttributes ->+      case A.projectionAttributesForNames groupAttrNames attrs of+        Left err -> throw err+        Right groupProjectionAttributes ->+          let grouper tupIn =+                --naive algorithm: find matching attributes in ungrouped tuples+                +          in+            SP.parConcatMap id groupFolder tupSIn+-}+  +--perhaps the relation atom should also contain a stream of tuples+group' :: MonadAsync m => Attributes -> AttributeName -> Attributes -> Stream m RelationTuple -> Stream m RelationTuple+group' groupAttrs newAttrName attrs tupsIn = SD.unCross $ do+  let groupAttrNames = A.attributeNameSet groupAttrs+      nonGroupAttrNames = A.nonMatchingAttributeNameSet groupAttrNames (Set.fromList (V.toList (A.attributeNames attrs)))+      nonGroupAttrs = A.attributesForNames nonGroupAttrNames attrs+      handleError val = case val of +        Left err -> throwM err+        Right val' -> pure val'+      groupFolder oneTup accum =+        case tupleProject nonGroupAttrs oneTup of+          Right ungroupedProjection -> do+            let subTuples = case HM.lookup ungroupedProjection accum of+                              Nothing -> [oneTup]+                              Just l -> oneTup : l+            HM.insert ungroupedProjection subTuples accum                    +          Left err -> error $ "group failed" <> show err++  nonGroupProjectionAttributes <- handleError $ A.projectionAttributesForNames nonGroupAttrNames attrs+  groupProjectionAttributes <- handleError $ A.projectionAttributesForNames groupAttrNames attrs+  let newAttrs = A.addAttribute groupAttr nonGroupProjectionAttributes+      groupAttr = Attribute newAttrName (RelationAtomType groupProjectionAttributes)+      tupleGenerator nongrouped groups' accum = do+        subRel <- Rel.mkRelationFromTuples newAttrs groups'+        let subTup = RelationTuple (A.singleton groupAttr) (V.singleton (RelationAtom subRel))+        let newTup = tupleExtend nongrouped subTup+--        (:) <$> pure newTup <*> accum+        (newTup:) <$> accum+      +  groupedMap <- lift $ S.foldr groupFolder HM.empty tupsIn+  case HM.foldrWithKey tupleGenerator (Right []) groupedMap of+    Left err -> throwM err+    Right tuples -> SD.mkCross (SP.fromList tuples)+        +attributesForRelval :: AttributeName -> Attributes -> Either RelationalError Attributes+attributesForRelval relvalAttrName attrs = do+  atomType <- A.atomTypeForAttributeName relvalAttrName attrs+  case atomType of+    (RelationAtomType relAttrs) -> Right relAttrs+    _ -> Left $ AttributeIsNotRelationValuedError relvalAttrName+    +ungroup :: MonadAsync m => AttributeName -> Attributes -> Stream m RelationTuple -> Stream m RelationTuple+ungroup ungroupName attrs tupStream = do +  let newAttrs = A.addAttributes attrs nonGroupAttrs+      nonGroupAttrs = A.deleteAttributeName ungroupName attrs+      mapper =+        tupleUngroup ungroupName newAttrs+  S.concatMap mapper tupStream+        +--take an relval attribute name and a tuple and ungroup the relval+tupleUngroup :: MonadAsync m => AttributeName -> Attributes -> RelationTuple -> Stream m RelationTuple+tupleUngroup relvalAttrName newAttrs tuple = +  case relationForAttributeName relvalAttrName tuple of+    Left err -> throw err+    Right relvalRelation -> +      S.mapM mapper (tupleStream relvalRelation)+ where+   mapper rvTup = case tupleProject newAttrs tuple of+                    Left err -> throwM err+                    Right nonGroupTupleProjection ->+                      pure $ tupleExtend nonGroupTupleProjection rvTup++tupleStream :: MonadAsync m => Relation -> Stream m RelationTuple+tupleStream (Relation _ tupSet) = SP.fromList (asList tupSet)++
+ src/lib/ProjectM36/SystemMemory.hs view
@@ -0,0 +1,129 @@+#if defined(mingw32_HOST_OS)+{-# LANGUAGE ForeignFunctionInterface #-}+#endif+module ProjectM36.SystemMemory where+import Data.Int (Int64)+#if defined(darwin_HOST_OS)  +import System.Process (readProcess)+import qualified Control.Exception as Exc+import Text.Megaparsec+import Text.Megaparsec.Char+import qualified Text.Megaparsec.Char.Lexer as L+import Data.Void+#endif+#if defined(linux_HOST_OS)+import System.Linux.Proc.MemInfo+import System.Linux.Proc.Errors+import qualified Data.Text as T+import Text.Megaparsec+import Text.Megaparsec.Char+import qualified Text.Megaparsec.Char.Lexer as L+import Data.Void+#endif++-- keep the parsers separate and compiling on all platforms so that we can test the parsers without waiting on macos CI runs+-- | Parse memory_pressure output to extract free pages and total memory.+parseMemoryPressureFreeMem :: Parsec Void String MemoryStats+parseMemoryPressureFreeMem = do+  _ <- manyTill anySingle (try (string "The system has "))+  totalMemBytes <- L.decimal+  _ <- manyTill anySingle (try (string "Pages free:"))+  space1+  -- parse integer pages+  freePages <- L.decimal+  pure (freePages * 4096, totalMemBytes) -- 4096 default page size on macOS++-- | Parse memory_pressure output to extract memory pressure percentage.+parseMemoryPressureValue :: Parsec Void String Double+parseMemoryPressureValue = do+  _ <- manyTill anySingle (try (string "System-wide memory free percentage:"))+  space1+  -- parse integer percentage+  n <- L.decimal+  _ <- char '%'+  pure n+  +#if defined(mingw32_HOST_OS)+import Foreign+import Foreign.C.Types+import Foreign.Marshal.Alloc+import Foreign.Marshal.Array+import Foreign.Storable+import System.Win32.Types+import System.Win32.Memory++-- Define the MEMORYSTATUSEX structure+data MEMORYSTATUSEX = MEMORYSTATUSEX+    { dwLength       :: DWORD+    , dwMemoryLoad   :: DWORD+    , ullTotalPhys   :: DWORD64+    , ullAvailPhys   :: DWORD64+    , ullTotalPageFile :: DWORD64+    , ullAvailPageFile :: DWORD64+    , ullTotalVirtual :: DWORD64+    , ullAvailVirtual :: DWORD64+    , ullExtendedVirtual :: DWORD64+    }++-- Foreign function interface to call GlobalMemoryStatusEx+foreign import ccall "GlobalMemoryStatusEx" c_GlobalMemoryStatusEx :: Ptr MEMORYSTATUSEX -> IO ()+#endif++type FreeMemBytes = Int64+type TotalMemBytes = Int64+type MemoryStats = (FreeMemBytes, TotalMemBytes)++-- | Get total physical memory in a platform-specific way.+getMemoryStats :: IO (Either String MemoryStats)+getMemoryStats = do+#if defined(mingw32_HOST_OS)+    allocaBytes (sizeOf (undefined :: MEMORYSTATUSEX)) $ \ptr -> do+        let memStatus = MEMORYSTATUSEX { dwLength = fromIntegral (sizeOf (undefined :: MEMORYSTATUSEX)), .. }+        poke ptr memStatus+        c_GlobalMemoryStatusEx ptr+        memStatus' <- peek ptr+        return $ Right (fromIntegral (ullAvailPhys memStatus'), fromIntegral (ullTotalPhys memStatus'))+#elif defined(darwin_HOST_OS)+  eres <- Exc.try (readProcess "memory_pressure" [] "") :: IO (Either IOError String)+  case eres of+    Left err -> pure (Left (Exc.displayException err))+    Right memPressureText -> do+      case parse parseMemoryPressureFreeMem "" memPressureText of+        Left err -> pure (Left (errorBundlePretty err))+        Right memvals -> pure (Right memvals)+#elif defined(linux_HOST_OS)+  eMemInfo <- readProcMemInfo+  pure $ case eMemInfo of+    Left err -> Left (T.unpack (renderProcError err))+    Right memInfo -> Right (fromIntegral (memFree memInfo), fromIntegral (memTotal memInfo))+#endif++-- | Estimate the memory pressure as a number between 0 and 1 which is a ratio of free to actively used memory.+getMemoryPressure :: IO (Maybe Double)+getMemoryPressure = do+#if defined(mingw32_HOST_OS)+  allocaBytes (sizeOf (undefined :: MEMORYSTATUSEX)) $ \ptr -> do+    let memStatus = MEMORYSTATUSEX { dwLength = fromIntegral (sizeOf (undefined :: MEMORYSTATUSEX)), .. }+    poke ptr memStatus+    c_GlobalMemoryStatusEx ptr+    memStatus' <- peek ptr+    return $ Just (fromIntegral (ullAvailPhys memStatus') / fromIntegral (ullTotalPhys memStatus'))+#elif defined(darwin_HOST_OS)+  eres <- Exc.try (readProcess "memory_pressure" [] "") :: IO (Either IOError String)+  case eres of+    Left _err -> pure Nothing+    Right memPressureText -> do+      case parseMaybe parseMemoryPressureValue memPressureText of+        Nothing -> pure Nothing+        Just percentage -> pure (Just (percentage / 100.0))+#elif defined(linux_HOST_OS)+  eMemInfo <- readProcMemInfo+  pure $ case eMemInfo of+    Left _err -> Nothing+    Right memInfo -> Just (fromIntegral (memFree memInfo))+#else+#error Failed to determine matching OS.+#endif++-- on linux, /proc/pressure/(memory/cpu/io) track metrics for determining if useful work is being delayed due to pressure on those subsystems- however we want to prevent getting to this state at all, so it's not that useful+
src/lib/ProjectM36/TransGraphRelationalExpression.hs view
@@ -3,7 +3,9 @@ --really, a better name for this module could be "TransTransactionGraphRelationalExpr", but that name is too long module ProjectM36.TransGraphRelationalExpression where import ProjectM36.Base+import ProjectM36.TransactionGraph.Types import ProjectM36.TransactionGraph+import ProjectM36.Transaction.Types import ProjectM36.Error import qualified Data.Map as M import Control.Monad.Trans.Reader
src/lib/ProjectM36/Transaction.hs view
@@ -1,19 +1,13 @@ module ProjectM36.Transaction where import ProjectM36.Base+import ProjectM36.IsomorphicSchema.Types+import ProjectM36.DatabaseContext.Types+import ProjectM36.Transaction.Types import qualified Data.Set as S import qualified Data.UUID as U import Data.Time.Clock import qualified Data.List.NonEmpty as NE -parentIds :: Transaction -> S.Set TransactionId-parentIds (Transaction _ tinfo _) = S.fromList (NE.toList (parents tinfo))--rootParent :: TransactionParents-rootParent = singleParent U.nil--singleParent :: TransactionId -> TransactionParents-singleParent tid = tid NE.:| []- -- | Return the same transaction but referencing only the specific child transactions. This is useful when traversing a graph and returning a subgraph. This doesn't filter parent transactions because it assumes a head-to-root traversal. filterTransactionInfoTransactions :: S.Set TransactionId -> TransactionInfo -> TransactionInfo filterTransactionInfoTransactions filterIds tinfo =@@ -29,19 +23,8 @@ filterTransaction :: S.Set TransactionId -> Transaction -> Transaction filterTransaction filterIds (Transaction selfId tInfo context) = Transaction selfId (filterTransactionInfoTransactions filterIds tInfo) context --- | Return the singular context which is not virtual.-concreteDatabaseContext :: Transaction -> DatabaseContext-concreteDatabaseContext (Transaction _ _ (Schemas context _)) = context --- | Returns all schemas including the concrete schema.-schemas :: Transaction -> Schemas-schemas (Transaction _ _ schemas') = schemas'-    --- | Returns all subschemas which are isomorphic or sub-isomorphic to the concrete schema.-subschemas :: Transaction -> Subschemas-subschemas (Transaction _ _ (Schemas _ sschemas)) = sschemas--fresh :: TransactionId -> UTCTime -> Schemas -> Transaction+fresh :: TransactionId -> UTCTime -> Schemas DatabaseContext -> Transaction fresh freshId stamp' = Transaction freshId tinfo   where     tinfo = TransactionInfo {parents = rootParent,
src/lib/ProjectM36/Transaction/Persist.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE CPP #-} #ifdef PM36_HASKELL_SCRIPTING@@ -9,11 +11,15 @@ import ProjectM36.Trace import ProjectM36.Base import ProjectM36.Error-import ProjectM36.Transaction-import ProjectM36.DatabaseContextFunction+import ProjectM36.Transaction.Types+import ProjectM36.ValueMarker+import ProjectM36.DatabaseContext.Types+import ProjectM36.IsomorphicSchema.Types hiding (concreteDatabaseContext, subschemas)+import ProjectM36.DatabaseContextFunctions.Basic import ProjectM36.AtomFunction-import ProjectM36.Persist (DiskSync, renameSync, writeSerialiseSync)+import ProjectM36.Persist (DiskSync, renameSync, writeSerialiseSync, readDeserialise) import ProjectM36.Function+import ProjectM36.AccessControlList import qualified Data.Map as M import qualified Data.HashSet as HS import System.FilePath@@ -24,13 +30,29 @@ import ProjectM36.ScriptSession import ProjectM36.AtomFunctions.Basic (precompiledAtomFunctions) import Codec.Winery+import Control.Concurrent.Async+import GHC.Generics+import qualified Data.Set as S+import Data.Default +#if defined(__APPLE__) || defined(linux_HOST_OS)+#define USE_LINUX_XATTRS 1+#endif++#ifdef USE_LINUX_XATTRS+import System.Linux.XAttr+import qualified Data.Text.Encoding as TE+#endif+ #ifdef PM36_HASKELL_SCRIPTING import GHC import Control.Exception import GHC.Paths #endif +xattrName :: String+xattrName = "project-m36.relvarName"+ getDirectoryNames :: FilePath -> IO [FilePath] getDirectoryNames path =   filter (\ n -> n `notElem` ["..", "."]) <$> getDirectoryContents path@@ -45,12 +67,18 @@ transactionInfoPath :: FilePath -> FilePath transactionInfoPath transdir = transdir </> "info" +notChangedSinceDatabaseContextPath :: FilePath -> FilePath+notChangedSinceDatabaseContextPath transdir = transdir </> "ncs"++relvarsDir :: FilePath -> FilePath        +relvarsDir transdir = transdir </> "relvars"++relvarsSimplePath :: FilePath -> FilePath+relvarsSimplePath transdir = relvarsDir transdir </> "index"+ notificationsPath :: FilePath -> FilePath notificationsPath transdir = transdir </> "notifs" -relvarsPath :: FilePath -> FilePath        -relvarsPath transdir = transdir </> "relvars"- incDepsDir :: FilePath -> FilePath incDepsDir transdir = transdir </> "incdeps" @@ -76,6 +104,28 @@ objectFilesPath :: FilePath -> FilePath objectFilesPath transdir = transdir </> ".." </> "compiled_modules" +-- note that some database context function elements don't change between transactions+unchangedElementsPath :: FilePath -> FilePath+unchangedElementsPath transdir = transdir </> "unchanged"++aclPath :: FilePath -> FilePath+aclPath transdir = transdir </> "acl"++data NotChangedSinceDatabaseContext =+  NotChangedSinceDatabaseContext {+  ncsInclusionDependencies :: Maybe TransactionId,+  ncsRelationVariables :: Maybe TransactionId,+  ncsAtomFunctions :: Maybe TransactionId,+  ncsDbcFunctions :: Maybe TransactionId,+  ncsNotifications :: Maybe TransactionId,+  ncsTypeConstructorMapping :: Maybe TransactionId,+  ncsRegisteredQueries :: Maybe TransactionId,+  ncsSchemas :: Maybe TransactionId,+  ncsAcl :: Maybe TransactionId+  }+  deriving (Show, Generic)+  deriving Serialise via WineryRecord NotChangedSinceDatabaseContext+ readTransaction :: FilePath -> TransactionId -> Maybe ScriptSession -> IO (Either PersistenceError Transaction) readTransaction dbdir transId mScriptSession = do   let transDir = transactionDir dbdir transId@@ -83,56 +133,223 @@   if not transDirExists then         return $ Left $ MissingTransactionError transId     else do-    relvars <- readRelVars transDir-    transInfo <- readFileDeserialise (transactionInfoPath transDir)-    incDeps <- readIncDeps transDir-    typeCons <- readTypeConstructorMapping transDir-    sschemas <- readSubschemas transDir-    notifs <- readNotifications transDir-    dbcFuncs <- readFuncs transDir (dbcFuncsPath transDir) basicDatabaseContextFunctions mScriptSession-    atomFuncs <- readFuncs transDir (atomFuncsPath transDir) precompiledAtomFunctions mScriptSession-    registeredQs <- readRegisteredQueries transDir+    ncs <- readNotChangedSinceDatabaseContext transDir+    let ncsRead :: forall a. (NotChangedSinceDatabaseContext -> Maybe TransactionId) ->+                   (FilePath -> IO a) -> IO (ValueMarker a)+        ncsRead f readIO' = do+          case f ncs of+            Nothing -> ValueMarker <$> readIO' transDir+            Just tid -> pure (NotChangedSinceMarker tid)++    transInfo <- readDeserialise (transactionInfoPath transDir)+    relvars <- ncsRead ncsRelationVariables readRelVars+    incDeps <- ncsRead ncsInclusionDependencies readIncDeps+    typeCons <- ncsRead ncsTypeConstructorMapping readTypeConstructorMapping+    acl' <- ncsRead ncsAcl readAcl+    sschemas <- case ncsSchemas ncs of+                  Nothing -> ValueMarker <$> readSubschemas transDir+                  Just tid -> pure $ NotChangedSinceMarker tid+    notifs <- ncsRead ncsNotifications readNotifications+    dbcFuncs <- ncsRead ncsDbcFunctions (\d -> readFuncs d (dbcFuncsPath transDir) basicDatabaseContextFunctions mScriptSession)+    atomFuncs <- ncsRead ncsAtomFunctions (\d -> readFuncs d (atomFuncsPath transDir) precompiledAtomFunctions mScriptSession)+    registeredQs <- ncsRead ncsRegisteredQueries readRegisteredQueries     let newContext = DatabaseContext { inclusionDependencies = incDeps,                                        relationVariables = relvars,                                        typeConstructorMapping = typeCons,                                        notifications = notifs,                                        atomFunctions = atomFuncs,                                        dbcFunctions = dbcFuncs,-                                       registeredQueries = registeredQs }+                                       registeredQueries = registeredQs,+                                       acl = acl' }         newSchemas = Schemas newContext sschemas     return $ Right $ Transaction transId transInfo newSchemas-        -writeTransaction :: DiskSync -> FilePath -> Transaction -> IO ()-writeTransaction sync dbdir trans = do++-- | Transactions are always written write-once-read-many+writeTransaction :: DiskSync -> FilePath -> UncommittedTransaction -> IO ()+writeTransaction sync dbdir (UncommittedTransaction trans) = do   let tempTransDir = tempTransactionDir dbdir (transactionId trans)       finalTransDir = transactionDir dbdir (transactionId trans)       context = concreteDatabaseContext trans   transDirExists <- doesDirectoryExist finalTransDir   unless transDirExists $ do-    --create sub directories-    mapM_ createDirectory [tempTransDir, incDepsDir tempTransDir]-    writeRelVars sync tempTransDir (relationVariables context)-    writeIncDeps sync tempTransDir (inclusionDependencies context)-    writeFuncs sync (atomFuncsPath tempTransDir) (HS.toList (atomFunctions context))-    writeFuncs sync (dbcFuncsPath tempTransDir) (HS.toList (dbcFunctions context))-    writeNotifications sync tempTransDir (notifications context)-    writeTypeConstructorMapping sync tempTransDir (typeConstructorMapping context)-    writeSubschemas sync tempTransDir (subschemas trans)-    writeRegisteredQueries sync tempTransDir (registeredQueries context)-    writeFileSerialise (transactionInfoPath tempTransDir) (transactionInfo trans)+    createDirectory tempTransDir+    let ncs = mkNotChangedSinceDatabaseContext context (subschemas trans)+    writeNotChangedSinceDatabaseContext sync tempTransDir ncs+    +    let ncsWrite :: forall a. (DatabaseContext -> ValueMarker a) -> FilePath -> (DiskSync -> FilePath -> a -> IO ()) -> IO ()+        ncsWrite f path writeIO =+          case f context of+            ValueMarker val -> do+              writeIO sync path val+            NotChangedSinceMarker{} -> pure ()+            +    ncsWrite relationVariables tempTransDir writeRelVars+    ncsWrite inclusionDependencies tempTransDir writeIncDeps+    ncsWrite atomFunctions (atomFuncsPath tempTransDir) (\ds fp v -> writeFuncs ds fp (HS.toList v))+    ncsWrite dbcFunctions (dbcFuncsPath tempTransDir) (\ds fp v -> writeFuncs ds fp (HS.toList v))+    ncsWrite notifications tempTransDir writeNotifications+    ncsWrite typeConstructorMapping tempTransDir writeTypeConstructorMapping+    ncsWrite registeredQueries tempTransDir writeRegisteredQueries+    ncsWrite acl tempTransDir writeAcl++    case subschemas trans of+      NotChangedSinceMarker{} -> pure ()+      ValueMarker sschemas -> +        writeSubschemas sync tempTransDir sschemas+    +    writeSerialiseSync sync (transactionInfoPath tempTransDir) (transactionInfo trans)     --move the temp directory to final location     renameSync sync tempTransDir finalTransDir ++-- local data structure for serialization, simple relvars (those which merely reference other relvars) are written to one file, wherease everything else (altered relvars) are written to individual files, potentially in parallel+data SingleFileRelationVariables = SingleFileRelationVariables+  {+    simpleRelVars :: RelationVariables,+    complexRelVarNameMap :: M.Map RelVarName FilePath+  }+  deriving (Show, Generic, Eq)+  deriving Serialise via WineryRecord SingleFileRelationVariables++-- | ADT used to serialize fast-path, "unchanged" database context value relative to parent transaction.+data UnchangedDatabaseContextValues =+  UnchangedDatabaseContextValues {+     unchangedRelationVariables :: Maybe TransactionId,+     unchangedInclusionDependencies :: Maybe TransactionId,+     unchangedAtomFunctions :: Maybe TransactionId,+     unchangedDBCFunctions :: Maybe TransactionId,+     unchangedNotifications :: Maybe TransactionId,+     unchangedTypeConstructorMapping :: Maybe TransactionId,+     unchangedRegisteredQueries :: Maybe TransactionId+  }+  deriving (Generic, Show)+  deriving Serialise via WineryRecord UnchangedDatabaseContextValues++unwrittenTransactionCanUseDatabaseContextValuesFastPath :: UncommittedTransaction -> Bool+unwrittenTransactionCanUseDatabaseContextValuesFastPath (UncommittedTransaction trans) =+  S.size (parentIds trans) == 1++-- | For database context values which have *not* changed since the parent, write into a single file.+{-+writeUnchangedTransactionMarkers :: DiskSync -> FilePath -> DatabaseContext -> IO ()+writeUnchangedTransactionMarkers sync transDir ctdbc = do+  let wUnchanged = UnchangedDatabaseContextValues {+        unchangedRelationVariables = mTrans (_ctrelationVariables ctdbc),+        unchangedInclusionDependencies = mTrans (_ctinclusionDependencies ctdbc),+        unchangedAtomFunctions = mTrans (_ctatomFunctions ctdbc),+        unchangedDBCFunctions = mTrans (_ctdbcFunctions ctdbc),+        unchangedNotifications = mTrans (_ctnotifications ctdbc),+        unchangedTypeConstructorMapping = mTrans (_cttypeConstructorMapping ctdbc),+        unchangedRegisteredQueries = mTrans (_ctregisteredQueries ctdbc)+        }+      mTrans :: forall a. ChangedMarker a -> Maybe TransactionId+      mTrans (NotChangedMarker tid _) = Just tid+      mTrans ChangedMarker{} = Nothing+      unchangedPath = unchangedElementsPath transDir+  writeSerialiseSync sync unchangedPath wUnchanged+-}+ writeRelVars :: DiskSync -> FilePath -> RelationVariables -> IO () writeRelVars sync transDir relvars = do-  let path = relvarsPath transDir-  traceBlock "write relvars" $ writeSerialiseSync sync path relvars+  let relvarsPath = relvarsDir transDir+      simpleInfoPath = relvarsSimplePath transDir+  --write unchanged relvars and file name mapping to "relvars" file+      (simplervs, complexrvs) = M.partition isSmallRelExpr relvars+      -- with the winery schema, one relational expression takes 28 KB to store, so we should do our best to compact expressions into one file+      isSmallRelExpr _ = True+--      isSmallRelExpr _ = False+      --add incrementing integer to use as file name+      writeRvMapExprs = snd $ M.mapAccum (\acc rexpr -> (acc + 1, (acc, rexpr))) (0 :: Int) complexrvs+      writeRvMap = M.map (show . fst) writeRvMapExprs+      simpleFileInfo = SingleFileRelationVariables {+        simpleRelVars = simplervs,+        complexRelVarNameMap = writeRvMap+        }+  --parallelization opportunity+  traceBlock "write relvars" $ do+    createDirectory relvarsPath+    let writeSimple = do+          writeSerialiseSync sync simpleInfoPath simpleFileInfo+        writeComplex = do+          forConcurrently_ (M.toList writeRvMapExprs) $ \rvInfo -> do+            let rvpath = relvarsPath </> show rvnum+                rvExpr = snd (snd rvInfo)+                rvnum = fst (snd rvInfo)+            writeSerialiseSync sync rvpath rvExpr+-- Project:M36 does not read these extended attributes, but they might be useful for debugging or database restoration            +#ifdef USE_LINUX_XATTRS+            let rvname = fst rvInfo+            createUserXAttr rvpath xattrName (TE.encodeUtf8 rvname)+#endif            +    concurrently_ writeSimple writeComplex +{-+-- | Optimized code path to read one relvar expression from disk instead of all of them for a full transaction- useful for streaming results. Throws exception if the relvar name cannot be found since this function expects the database files to be coherent.+readOneRelVar :: FilePath -> RelVarName -> IO GraphRefRelationalExpr+readOneRelVar transDir rvName = do+  let relvarsIndex = relvarsSimplePath transDir+  rvindex <- readDeserialise relvarsIndex+  case M.lookup rvName (simpleRelVars rvindex) of+    Just rvexpr -> pure rvexpr+    Nothing -> do --look in complex rvs+      case M.lookup rvName (complexRelVarNameMap rvindex) of+        Nothing -> error $ "failed to find " <> T.unpack rvName <> " in filesystem."+        Just rvnum ->+          readDeserialise (relvarsDir transDir </> show rvnum)+-}++readNotChangedSinceDatabaseContext :: FilePath -> IO NotChangedSinceDatabaseContext+readNotChangedSinceDatabaseContext transDir = do+  let nscPath = notChangedSinceDatabaseContextPath transDir+  fExists <- doesFileExist nscPath+  if fExists then+    readDeserialise nscPath+  else+    pure $ NotChangedSinceDatabaseContext {+            ncsInclusionDependencies = Nothing,+            ncsRelationVariables = Nothing,+            ncsAtomFunctions = Nothing,+            ncsDbcFunctions = Nothing,+            ncsNotifications = Nothing,+            ncsTypeConstructorMapping = Nothing,+            ncsRegisteredQueries = Nothing,+            ncsSchemas = Nothing,+            ncsAcl = Nothing+    }++mkNotChangedSinceDatabaseContext :: DatabaseContext -> ValueMarker Subschemas -> NotChangedSinceDatabaseContext+mkNotChangedSinceDatabaseContext ctx mSubschemas =+  NotChangedSinceDatabaseContext {+  ncsInclusionDependencies = mkVal (inclusionDependencies ctx),+  ncsRelationVariables = mkVal (relationVariables ctx),+  ncsAtomFunctions = mkVal (atomFunctions ctx),+  ncsDbcFunctions = mkVal (dbcFunctions ctx),+  ncsNotifications = mkVal (notifications ctx),+  ncsTypeConstructorMapping = mkVal (typeConstructorMapping ctx),+  ncsRegisteredQueries = mkVal (registeredQueries ctx),+  ncsSchemas = mkVal mSubschemas,+  ncsAcl = mkVal (acl ctx)+  }+  where+    mkVal :: forall a. ValueMarker a -> Maybe TransactionId+    mkVal ValueMarker{} = Nothing+    mkVal (NotChangedSinceMarker tid) = Just tid+  +writeNotChangedSinceDatabaseContext :: DiskSync -> FilePath -> NotChangedSinceDatabaseContext -> IO ()+writeNotChangedSinceDatabaseContext diskSync transDir ncs = do+  let ncsPath = notChangedSinceDatabaseContextPath transDir+  writeSerialiseSync diskSync ncsPath ncs+ readRelVars :: FilePath -> IO RelationVariables-readRelVars transDir = -  readFileDeserialise (relvarsPath transDir)+readRelVars transDir = do+  let relvarsIndex = relvarsSimplePath transDir+  rvindex <- readDeserialise relvarsIndex+  complexRvAssocs <- forConcurrently (M.toList (complexRelVarNameMap rvindex)) $ \(rvname, rvpath) -> do+    rvExpr <- readDeserialise (relvarsDir transDir </> rvpath)+    pure (rvname, rvExpr)+  pure (simpleRelVars rvindex <> M.fromList complexRvAssocs) -writeFuncs :: Traversable t => DiskSync -> FilePath -> t (Function a) -> IO ()+writeFuncs :: Traversable t => DiskSync -> FilePath -> t (Function a acl) -> IO () writeFuncs sync funcWritePath funcs = traceBlock "write functions" $ do   funcs' <- forM funcs $ \fun -> do     case funcBody fun of@@ -144,7 +361,7 @@   --write additional data for object-loaded functions (which are not built-in or scripted)   let functionData f =           (funcType f, funcName f, functionScript f, objInfo f)-      objInfo :: Function a -> Maybe ObjectFileInfo+      objInfo :: Function a acl -> Maybe ObjectFileInfo       objInfo f =         case funcBody f of           FunctionObjectLoadedBody objPath modName entryFunc _ ->@@ -153,9 +370,9 @@           FunctionBuiltInBody{} -> Nothing   writeSerialiseSync sync funcWritePath (fmap functionData (toList funcs')) -readFuncs :: FilePath -> FilePath -> HS.HashSet (Function a) -> Maybe ScriptSession -> IO (HS.HashSet (Function a))+readFuncs :: Default acl => FilePath -> FilePath -> HS.HashSet (Function a acl) -> Maybe ScriptSession -> IO (HS.HashSet (Function a acl)) readFuncs transDir funcPath precompiledFunctions mScriptSession = do-  funcsList <- readFileDeserialise funcPath+  funcsList <- readDeserialise funcPath   --we always return the pre-compiled functions   --load object files and functions in objects (shared libraries or flat object files)   let objFilesDir = objectFilesPath transDir@@ -167,7 +384,7 @@  deriving (Show, Serialise) -- deriving Serialise via WineryVariant ObjectFileInfo -loadFunc :: FilePath -> HS.HashSet (Function a) -> Maybe ScriptSession -> FunctionName -> [AtomType] -> Maybe FunctionBodyScript -> Maybe ObjectFileInfo -> IO (Function a)+loadFunc :: Default acl => FilePath -> HS.HashSet (Function a acl) -> Maybe ScriptSession -> FunctionName -> [AtomType] -> Maybe FunctionBodyScript -> Maybe ObjectFileInfo -> IO (Function a acl) loadFunc objFilesDir precompiledFuncs _mScriptSession funcName' _funcType mFuncScript mObjInfo = do   case mObjInfo of     --load from shared or static object library@@ -202,7 +419,8 @@                 Left err -> throwIO err                 Right compiledScript -> pure Function { funcName = funcName',                                                         funcType = _funcType,-                                                        funcBody = FunctionScriptBody _funcScript compiledScript }+                                                        funcBody = FunctionScriptBody _funcScript compiledScript,+                                                        funcACL = def} #else          error "Haskell scripting is disabled" #endif                                    @@ -215,7 +433,7 @@ #else readAtomFunc transDir funcName' mScriptSession precompiledFuncs = do   let atomFuncPath = atomFuncsPath transDir-  (funcType', mFuncScript) <- readFileDeserialise @([AtomType],Maybe T.Text) atomFuncPath+  (funcType', mFuncScript) <- readDeserialise @([AtomType],Maybe T.Text) atomFuncPath   case mFuncScript of     --handle pre-compiled case- pull it from the precompiled list     Nothing -> case atomFunctionForName funcName' precompiledFuncs of@@ -236,33 +454,23 @@             Left err -> throwIO err             Right compiledScript -> pure Function { funcName = funcName',                                                     funcType = funcType',-                                                    funcBody = FunctionScriptBody funcScript compiledScript }+                                                    funcBody = FunctionScriptBody funcScript compiledScript,+                                                    funcACL = def } #endif -writeIncDep :: DiskSync -> FilePath -> (IncDepName, InclusionDependency) -> IO ()  -writeIncDep sync transDir (incDepName, incDep) = do-  writeSerialiseSync sync (incDepsDir transDir </> T.unpack incDepName) incDep-   writeIncDeps :: DiskSync -> FilePath -> M.Map IncDepName InclusionDependency -> IO ()  -writeIncDeps sync transDir incdeps = -  traceBlock "write incdeps" $ mapM_ (writeIncDep sync transDir) $ M.toList incdeps -  -readIncDep :: FilePath -> IncDepName -> IO (IncDepName, InclusionDependency)-readIncDep transDir incdepName = do-  let incDepPath = incDepsDir transDir </> T.unpack incdepName-  incDepData <- readFileDeserialise incDepPath-  pure (incdepName, incDepData)+writeIncDeps sync transDir incdeps = do+  traceBlock "write incdeps" $ writeSerialiseSync sync (incDepsDir transDir) incdeps   -readIncDeps :: FilePath -> IO (M.Map IncDepName InclusionDependency)  +readIncDeps :: FilePath -> IO (M.Map IncDepName InclusionDependency) readIncDeps transDir = do   let incDepsPath = incDepsDir transDir-  incDepNames <- getDirectoryNames incDepsPath-  M.fromList <$> mapM (readIncDep transDir . T.pack) incDepNames+  readDeserialise incDepsPath   -readSubschemas :: FilePath -> IO Subschemas  +readSubschemas :: FilePath -> IO Subschemas readSubschemas transDir = do   let sschemasPath = subschemasPath transDir-  readFileDeserialise sschemasPath+  readDeserialise sschemasPath  writeSubschemas :: DiskSync -> FilePath -> Subschemas -> IO ()   writeSubschemas sync transDir sschemas = do@@ -277,12 +485,12 @@ readTypeConstructorMapping :: FilePath -> IO TypeConstructorMapping readTypeConstructorMapping path = do   let atPath = typeConsPath path-  readFileDeserialise atPath+  readDeserialise atPath    readRegisteredQueries :: FilePath -> IO RegisteredQueries readRegisteredQueries transDir = do   let regQsPath = registeredQueriesPath transDir-  readFileDeserialise regQsPath+  readDeserialise regQsPath  writeRegisteredQueries :: DiskSync -> FilePath -> RegisteredQueries -> IO () writeRegisteredQueries sync transDir regQs = do@@ -292,12 +500,21 @@ readNotifications :: FilePath -> IO Notifications readNotifications transDir = do   let notifsPath = notificationsPath transDir-  readFileDeserialise notifsPath+  readDeserialise notifsPath  writeNotifications :: DiskSync -> FilePath -> Notifications -> IO () writeNotifications sync transDir notifs = do   let notifsPath = notificationsPath transDir   traceBlock "write notifications" $ writeSerialiseSync sync notifsPath notifs +readAcl :: FilePath -> IO DatabaseContextACL+readAcl transDir = do+  let aclPath' = aclPath transDir+  readDeserialise aclPath'++writeAcl :: DiskSync -> FilePath -> DatabaseContextACL -> IO ()+writeAcl sync transDir acl' = do+  let aclPath' = aclPath transDir+  traceBlock "write acl" $ writeSerialiseSync sync aclPath' acl'  
+ src/lib/ProjectM36/Transaction/Types.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE DeriveGeneric #-}+module ProjectM36.Transaction.Types where+import ProjectM36.Base+import ProjectM36.ValueMarker+import ProjectM36.IsomorphicSchema.Types+import ProjectM36.DatabaseContext.Types+import ProjectM36.MerkleHash+import GHC.Generics+import Data.Time.Clock+import qualified Data.List.NonEmpty as NE+import qualified Data.UUID as U+import qualified Data.Set as S++data TransactionBase ctx = Transaction TransactionId TransactionInfo (Schemas ctx)+  deriving Generic++type Transaction = TransactionBase DatabaseContext++-- | Data needed to write a transaction to permanent storage. This is different from a DisconnectedTransaction in that it's not clear if the DisconnectedTransaction will be written to disk (or rolled back). This transaction will be written.+newtype UncommittedTransaction = UncommittedTransaction {+  _uncommittedTransaction :: TransactionBase DatabaseContext+  }+  deriving (Ord, Eq)++--- | Every transaction has context-specific information attached to it.+--- The `TransactionDiff`s represent child/edge relationships to previous transactions (branches or continuations of the same branch).+data TransactionInfo = TransactionInfo {+  parents :: TransactionParents,+  stamp :: UTCTime,+  merkleHash :: MerkleHash+  } deriving (Show, Generic)++type TransactionParents = NE.NonEmpty TransactionId++rootParent :: TransactionParents+rootParent = singleParent U.nil++singleParent :: TransactionId -> TransactionParents+singleParent tid = tid NE.:| []++info :: TransactionBase a -> TransactionInfo+info (Transaction _ info' _) = info'++transactionId :: TransactionBase a -> TransactionId+transactionId (Transaction tid _ _) = tid++transactionInfo :: TransactionBase a -> TransactionInfo+transactionInfo (Transaction _ info' _) = info'++instance Eq (TransactionBase a) where                            +  (Transaction uuidA _ _) == (Transaction uuidB _ _) = uuidA == uuidB+                   +instance Ord (TransactionBase a) where                            +  compare (Transaction uuidA _ _) (Transaction uuidB _ _) = compare uuidA uuidB++-- | Return the singular context which is not virtual.+concreteDatabaseContext :: TransactionBase a -> a+concreteDatabaseContext (Transaction _ _ (Schemas context _)) = context++-- | Returns all schemas including the concrete schema.+schemas :: TransactionBase a -> Schemas a+schemas (Transaction _ _ schemas') = schemas'+    +-- | Returns all subschemas which are isomorphic or sub-isomorphic to the concrete schema.+subschemas :: TransactionBase a -> ValueMarker Subschemas+subschemas (Transaction _ _ (Schemas _ sschemas)) = sschemas++parentIds :: TransactionBase a -> S.Set TransactionId+parentIds (Transaction _ tinfo _) = S.fromList (NE.toList (parents tinfo))
src/lib/ProjectM36/TransactionGraph.hs view
@@ -5,15 +5,24 @@ import ProjectM36.Transaction import ProjectM36.TransactionInfo as TI import ProjectM36.Relation-import ProjectM36.TupleSet+import ProjectM36.ValueMarker+import ProjectM36.DatabaseContext.Types+import ProjectM36.DatabaseContext+import ProjectM36.TransactionGraph.Types+import ProjectM36.Transaction.Types+import ProjectM36.IsomorphicSchema.Types hiding (concreteDatabaseContext, subschemas)+import qualified ProjectM36.IsomorphicSchema.Types as Schema+import qualified ProjectM36.TupleSet as TS import ProjectM36.Tuple import ProjectM36.RelationalExpression import ProjectM36.TransactionGraph.Merge import ProjectM36.MerkleHash+import ProjectM36.DisconnectedTransaction (DisconnectedTransaction(..), CurrentHead(..)) import qualified ProjectM36.DisconnectedTransaction as Discon import qualified ProjectM36.Attribute as A import ProjectM36.HashSecurely import ProjectM36.ReferencedTransactionIds+import ProjectM36.DatabaseContext.Fields  import Codec.Winery #if MIN_VERSION_base(4,18,0)@@ -51,19 +60,23 @@                                   deriving Serialise via WineryVariant TransactionIdHeadBacktrack  +data TransactionGraphExpr = JumpToHead HeadName  |+                            JumpToTransaction TransactionId |+                            WalkBackToTime UTCTime+                          deriving (Eq, Show, Generic)+  deriving Serialise via WineryVariant TransactionGraphExpr+                                 -- | Operators which manipulate a transaction graph and which transaction the current 'Session' is based upon.-data TransactionGraphOperator = JumpToHead HeadName  |-                                JumpToTransaction TransactionId |-                                WalkBackToTime UTCTime |+data AlterTransactionGraphExpr =                                  Branch HeadName |                                 DeleteBranch HeadName |                                 MergeTransactions MergeStrategy HeadName HeadName |                                 Commit |                                 Rollback                               deriving (Eq, Show, Generic)-                              deriving Serialise via WineryVariant TransactionGraphOperator+                              deriving Serialise via WineryVariant AlterTransactionGraphExpr -isCommit :: TransactionGraphOperator -> Bool+isCommit :: AlterTransactionGraphExpr -> Bool isCommit Commit = True isCommit _ = False @@ -74,11 +87,14 @@ bootstrapTransactionGraph stamp' freshId context = TransactionGraph bootstrapHeads bootstrapTransactions   where     bootstrapHeads = M.singleton "master" freshTransaction-    newSchemas = Schemas context M.empty+    newSchemas = Schemas context emptyValue     freshTransaction = fresh freshId stamp' newSchemas     hashedTransaction = Transaction freshId ((transactionInfo freshTransaction) { merkleHash = calculateMerkleHash freshTransaction emptyTransactionGraph }) newSchemas     bootstrapTransactions = S.singleton hashedTransaction +freshTransactionGraph' :: ResolvedDatabaseContext -> IO (TransactionGraph, TransactionId)+freshTransactionGraph' = freshTransactionGraph . toDatabaseContext+   -- | Create a transaction graph from a context. freshTransactionGraph :: DatabaseContext -> IO (TransactionGraph, TransactionId) freshTransactionGraph ctx = do@@ -96,11 +112,9 @@ headList :: TransactionGraph -> [(HeadName, TransactionId)] headList graph = map (second transactionId) (M.assocs (transactionHeadsForGraph graph)) -headNameForTransaction :: Transaction -> TransactionGraph -> Maybe HeadName-headNameForTransaction transaction (TransactionGraph heads _) =-  case M.keys $ M.filter (transaction ==) heads of-    [] -> Nothing-    name : _ -> Just name+headNamesForTransaction :: Transaction -> TransactionGraph -> [HeadName]+headNamesForTransaction transaction (TransactionGraph heads _) =+  M.keys $ M.filter (transaction ==) heads  transactionsForIds :: S.Set TransactionId -> TransactionGraph -> Either RelationalError (S.Set Transaction) transactionsForIds idSet graph =@@ -123,23 +137,19 @@     childIds = S.map transactionId (S.filter filt (transactionsForGraph graph))     filt trans' = S.member (transactionId trans) (parentIds trans') --- create a new commit and add it to the heads--- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)-addBranch :: UTCTime -> TransactionId -> HeadName -> TransactionId -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)-addBranch stamp' newId newBranchName branchPointId graph = do-  parentTrans <- transactionForId branchPointId graph-  let newTrans = Transaction newId (TI.singleParent branchPointId stamp') (schemas parentTrans)-  addTransactionToGraph newBranchName newTrans graph- --adds a disconnected transaction to a transaction graph at some head-addDisconnectedTransaction :: UTCTime -> TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)-addDisconnectedTransaction stamp' newId headName (DisconnectedTransaction parentId schemas' _) = addTransactionToGraph headName newTrans-  where-    newTrans = Transaction newId newTInfo schemas'-    newTInfo = TI.singleParent parentId stamp'+addDisconnectedTransaction :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (UncommittedTransaction, TransactionGraph)+addDisconnectedTransaction stamp' newId discon graph = do+  headName <- case disconCurrentHead discon of+                   CurrentHeadBranch hname -> pure hname+                   CurrentHeadTransactionId tid -> Left $ TransactionIsNotAHeadError tid+  let newTrans = UncommittedTransaction $ Transaction newId newTInfo schemas'+      schemas' = Schemas (Discon.concreteDatabaseContext discon) (Schema.subschemas (disconSchemas discon))+      newTInfo = TI.singleParent (Discon.parentId discon) stamp'+  addTransactionToGraph headName newTrans graph -addTransactionToGraph :: HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)-addTransactionToGraph headName newTrans graph = do+addTransactionToGraph :: HeadName -> UncommittedTransaction -> TransactionGraph -> Either RelationalError (UncommittedTransaction, TransactionGraph)+addTransactionToGraph headName (UncommittedTransaction newTrans) graph = do   let parentIds' = parentIds newTrans       newId = transactionId newTrans       validateIds ids = mapM (`transactionForId` graph) (S.toList ids)@@ -163,17 +173,22 @@       hashedTrans = Transaction (transactionId newTrans') hashedTransactionInfo (schemas newTrans')       updatedTransSet = S.insert hashedTrans (transactionsForGraph graph)       updatedHeads = M.insert headName hashedTrans (transactionHeadsForGraph graph)-  pure (hashedTrans, TransactionGraph updatedHeads updatedTransSet)+  pure (UncommittedTransaction hashedTrans, TransactionGraph updatedHeads updatedTransSet)  --replace all occurrences of the uncommitted context marker newTransUncommittedReplace :: Transaction -> Transaction newTransUncommittedReplace trans@(Transaction tid tinfo (Schemas ctx sschemas)) =   Transaction tid tinfo (Schemas fixedContext sschemas)   where-  uncommittedReplace UncommittedContextMarker = TransactionMarker tid-  uncommittedReplace marker = marker+  uncommittedReplaceRelVars (ValueMarker rvs) =+    ValueMarker $ M.map (fmap uncommittedReplaceMarker) rvs+  uncommittedReplaceRelVars e@NotChangedSinceMarker{} = e+  +  uncommittedReplaceMarker UncommittedContextMarker = TransactionMarker tid+  uncommittedReplaceMarker marker = marker+     relvars = relationVariables (concreteDatabaseContext trans)-  fixedRelvars = M.map (fmap uncommittedReplace) relvars+  fixedRelvars = uncommittedReplaceRelVars relvars   fixedContext = ctx { relationVariables = fixedRelvars }  @@ -225,85 +240,92 @@            err:_ -> Just err            _ -> Nothing --- returns the new "current" transaction, updated graph, and tutorial d result--- the current transaction is not part of the transaction graph until it is committed-evalGraphOp :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> TransactionGraphOperator -> Either RelationalError (DisconnectedTransaction, TransactionGraph)--evalGraphOp _ _ _ graph (JumpToTransaction jumpId) = case transactionForId jumpId graph of-  Left err -> Left err-  Right parentTrans -> Right (newTrans, graph)-    where-      newTrans = DisconnectedTransaction jumpId (schemas parentTrans) False---- switch from one head to another-evalGraphOp _ _ _ graph (JumpToHead headName) =-  case transactionForHead headName graph of-    Just newHeadTransaction -> let disconnectedTrans = DisconnectedTransaction (transactionId newHeadTransaction) (schemas newHeadTransaction) False in-      Right (disconnectedTrans, graph)-    Nothing -> Left $ NoSuchHeadNameError headName--evalGraphOp _ _ discon graph (WalkBackToTime backTime) = do+data ROTransactionGraphCreateDisconnectedTransaction =+  -- | Lookup disconnected transaction info from persistent storage or graph.+  MakeDisconnectedTransactionFromTransaction TransactionId |+  -- | No further lookup required.+  PureDisconnectedTransaction DisconnectedTransaction +  +-- | Evaluate the graph-changing operator, reading graph info from disk, if necessary. Returns the new disconnected transaction and new graph. When jumping to a new part of the graph, we may need to read from disk to update the diconnected transaction.+evalTransactionGraphExpr :: DisconnectedTransaction -> TransactionGraph -> TransactionGraphExpr -> Either RelationalError DisconnectedTransaction+evalTransactionGraphExpr _discon graph (JumpToHead headName) =+    case transactionForHead headName graph of+      Just newHeadTransaction -> do+        let discon' = DisconnectedTransaction {+              disconTransactionId = transactionId newHeadTransaction,+              disconSchemas = schemas newHeadTransaction,+              disconCurrentHead = Discon.CurrentHeadBranch headName+              }+        pure discon'+      Nothing -> Left $ NoSuchHeadNameError headName+evalTransactionGraphExpr _discon graph (JumpToTransaction jumpId) =+  case transactionForId jumpId graph of+    Left err -> Left err+    Right parentTrans -> do+      pure (DisconnectedTransaction {+               disconTransactionId = transactionId parentTrans,+               disconSchemas = schemas parentTrans,+               disconCurrentHead = CurrentHeadTransactionId jumpId+               })+evalTransactionGraphExpr discon graph (WalkBackToTime backTime) = do   let startTransId = Discon.parentId discon   jumpDest <- backtrackGraph graph startTransId (TransactionStampHeadBacktrack backTime)   case transactionForId jumpDest graph of     Left err -> Left err     Right trans -> do-      let disconnectedTrans = Discon.freshTransaction (transactionId trans) (schemas trans)-      Right (disconnectedTrans, graph)---- add new head pointing to branchPoint--- repoint the disconnected transaction to the new branch commit (with a potentially different disconnected context)--- affects transactiongraph and the disconnectedtransaction is recreated based off the branch-    {--evalGraphOp newId discon@(DisconnectedTransaction parentId disconContext) graph (Branch newBranchName) = case transactionForId parentId graph of-  Nothing -> (discon, graph, DisplayErrorResult "Failed to find parent transaction.")-  Just parentTrans -> case addBranch newBranchName parentTrans graph of-    Nothing -> (discon, graph, DisplayErrorResult "Failed to add branch.")-    Just newGraph -> (newDiscon, newGraph, DisplayResult "Branched.")-     where-       newDiscon = DisconnectedTransaction (transactionId parentTrans) disconContext--}---- create a new commit and add it to the heads--- technically, the new head could be added to an existing commit, but by adding a new commit, the new head is unambiguously linked to a new commit (with a context indentical to its parent)-evalGraphOp stamp' newId (DisconnectedTransaction parentId schemas' _) graph (Branch newBranchName) = do-  let newDiscon = Discon.freshTransaction newId schemas'-  case addBranch stamp' newId newBranchName parentId graph of-    Left err -> Left err-    Right (_, newGraph) -> Right (newDiscon, newGraph)+      pure (DisconnectedTransaction {+               disconTransactionId = transactionId trans,+               disconSchemas = schemas trans,+               disconCurrentHead = CurrentHeadTransactionId jumpDest+               }) --- add the disconnected transaction to the graph--- affects graph and disconnectedtransaction- the new disconnectedtransaction's parent is the freshly committed transaction-evalGraphOp stamp' newTransId discon@(DisconnectedTransaction parentId schemas' _) graph Commit = case transactionForId parentId graph of-  Left err -> Left err-  Right parentTransaction -> case headNameForTransaction parentTransaction graph of-    Nothing -> Left $ TransactionIsNotAHeadError parentId-    Just headName -> case maybeUpdatedGraph of-      Left err-> Left err-      Right (_, updatedGraph) -> Right (newDisconnectedTrans, updatedGraph)-      where-        newDisconnectedTrans = Discon.freshTransaction newTransId schemas'-        maybeUpdatedGraph = addDisconnectedTransaction stamp' newTransId headName discon graph+evalAlterTransactionGraphExpr :: UTCTime -> TransactionId -> DisconnectedTransaction -> TransactionGraph -> AlterTransactionGraphExpr -> Either RelationalError (DisconnectedTransaction, Maybe UncommittedTransaction, TransactionGraph)+evalAlterTransactionGraphExpr _stamp' _newId discon graph@(TransactionGraph heads transSet) (Branch newBranchName) =+  if M.member newBranchName heads then+    Left (HeadNameAlreadyInUseError newBranchName)+    else do+    trans <- transactionForId (Discon.parentId discon) graph+    let discon' = discon { disconTransactionId = transactionId trans,+                           disconCurrentHead = CurrentHeadBranch newBranchName+                         }+    pure (discon', Nothing, TransactionGraph (M.insert newBranchName trans heads) transSet)+evalAlterTransactionGraphExpr _stamp' _newId discon graph@(TransactionGraph graphHeads transSet) (DeleteBranch branchName) =+  case transactionForHead branchName graph of+    Nothing -> Left (NoSuchHeadNameError branchName)+    Just _ -> Right (discon, Nothing, TransactionGraph (M.delete branchName graphHeads) transSet)+evalAlterTransactionGraphExpr stamp' newId discon graph (MergeTransactions mergeStrategy headNameA headNameB) = do+  let env = freshGraphRefRelationalExprEnv Nothing graph+  (uTrans@(UncommittedTransaction trans), graph') <- runGraphRefRelationalExprM env $ mergeTransactions stamp' newId (Discon.parentId discon) mergeStrategy (MergeHeadNames { sourceHead = headNameA,+        targetHead = headNameB })+  let discon' = discon { disconSchemas = schemas trans,+                         disconCurrentHead = CurrentHeadBranch headNameB+                       }+  pure (discon', Just uTrans, graph') --- refresh the disconnected transaction, return the same graph-evalGraphOp _ _ (DisconnectedTransaction parentId _ _) graph Rollback = case transactionForId parentId graph of+evalAlterTransactionGraphExpr stamp' newTransId discon graph Commit =+  case transactionForId (Discon.parentId discon) graph of   Left err -> Left err-  Right parentTransaction -> Right (newDiscon, graph)-    where-      newDiscon = Discon.freshTransaction parentId (schemas parentTransaction)--evalGraphOp stamp' newId (DisconnectedTransaction parentId _ _) graph (MergeTransactions mergeStrategy headNameA headNameB) =-  runGraphRefRelationalExprM env $ mergeTransactions stamp' newId parentId mergeStrategy (headNameA, headNameB)-  where-    env = freshGraphRefRelationalExprEnv Nothing graph+  Right parentTransaction ->+    case headNamesForTransaction parentTransaction graph of+      [] -> Left $ TransactionIsNotAHeadError (Discon.parentId discon)+      _ -> do+        let maybeUpdatedGraph = addDisconnectedTransaction stamp' newTransId discon graph+        case maybeUpdatedGraph of+          Left err -> Left err+          Right (uncommittedTrans, updatedGraph) -> do+                let newDisconnectedTrans = Discon.freshTransaction (disconCurrentHead discon) newTransId (disconSchemas discon)+                Right (newDisconnectedTrans, Just uncommittedTrans, updatedGraph) -evalGraphOp _ _ discon graph@(TransactionGraph graphHeads transSet) (DeleteBranch branchName) = case transactionForHead branchName graph of-  Nothing -> Left (NoSuchHeadNameError branchName)-  Just _ -> Right (discon, TransactionGraph (M.delete branchName graphHeads) transSet)+evalAlterTransactionGraphExpr _stamp' _newId discon graph Rollback =+  case transactionForId (disconTransactionId discon) graph of+    Left err -> Left err+    Right parentTransaction -> Right (newDiscon, Nothing, graph)+      where+        newDiscon = Discon.freshTransaction (disconCurrentHead discon) (disconTransactionId discon) (schemas parentTransaction)  --present a transaction graph as a relation showing the uuids, parentuuids, and flag for the current location of the disconnected transaction graphAsRelation :: DisconnectedTransaction -> TransactionGraph -> Either RelationalError Relation-graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _ transSet) = do+graphAsRelation discon graph@(TransactionGraph _ transSet) = do   tupleMatrix <- mapM tupleGenerator (S.toList transSet)   mkRelationFromList attrs tupleMatrix   where@@ -312,23 +334,27 @@                                   Attribute "stamp" DateTimeAtomType,                                   Attribute "parents" (RelationAtomType parentAttributes),                                   Attribute "current" BoolAtomType,-                                  Attribute "head" TextAtomType+                                  Attribute "heads" (RelationAtomType headAttributes)                                  ]     parentAttributes = A.attributesFromList [Attribute "id" TextAtomType]+    headAttributes = A.attributesFromList [Attribute "name" TextAtomType]     tupleGenerator transaction = case transactionParentsRelation transaction graph of       Left err -> Left err-      Right parentTransRel -> Right [TextAtom $ T.pack $ show (transactionId transaction),+      Right parentTransRel -> do+        let headNames = headNamesForTransaction transaction graph+        headsRel <- mkRelationFromList headAttributes (map (\hname -> [TextAtom hname]) headNames)+        Right [TextAtom $ T.pack $ show (transactionId transaction),                                      ByteStringAtom $ _unMerkleHash (merkleHash (transactionInfo transaction)),                                      DateTimeAtom (timestamp transaction),                                      RelationAtom parentTransRel,-                                     BoolAtom $ parentId == transactionId transaction,-                                     TextAtom $ fromMaybe "" (headNameForTransaction transaction graph)-                                      ]+                                     BoolAtom $ Discon.parentId discon == transactionId transaction,+                                     RelationAtom headsRel+              ]  transactionParentsRelation :: Transaction -> TransactionGraph -> Either RelationalError Relation transactionParentsRelation trans graph =   if isRootTransaction trans then-    mkRelation attrs emptyTupleSet+    mkRelation attrs TS.empty     else do       parentTransSet <- parentTransactions trans graph       let tuples = map trans2tuple (S.toList parentTransSet)@@ -337,20 +363,12 @@     attrs = A.attributesFromList [Attribute "id" TextAtomType]     trans2tuple trans2 = mkRelationTuple attrs $ V.singleton (TextAtom (T.pack (show $ transactionId trans2))) -{----display transaction graph as relation-evalROGraphOp :: DisconnectedTransaction -> TransactionGraph -> ROTransactionGraphOperator -> Either RelationalError Relation-evalROGraphOp discon graph ShowGraph = do-  graphRel <- graphAsRelation discon graph-  return graphRel--}- -- | Execute the merge strategy against the transactions, returning a new transaction which can be then added to the transaction graph-createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction+createMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM UncommittedTransaction createMergeTransaction stamp' newId (SelectedBranchMergeStrategy selectedBranch) t2@(trans1, trans2) = do   graph <- gfGraph   selectedTrans <- validateHeadName selectedBranch graph t2-  pure $ addMerkleHash graph $+  pure $ UncommittedTransaction $ addMerkleHash graph $     Transaction newId (TransactionInfo {                           parents = NE.fromList [transactionId trans1,                                                  transactionId trans2],@@ -436,8 +454,8 @@           -- we have some paths!           _ -> Right $ S.unions paths -mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> (HeadName, HeadName) -> GraphRefRelationalExprM (DisconnectedTransaction, TransactionGraph)-mergeTransactions stamp' newId parentId mergeStrategy (headNameA, headNameB) = do+mergeTransactions :: UTCTime -> TransactionId -> TransactionId -> MergeStrategy -> MergeHeadNames -> GraphRefRelationalExprM (UncommittedTransaction, TransactionGraph)+mergeTransactions stamp' newId parentId mergeStrategy mergeHeadNames = do   graph <- gfGraph   let transactionForHeadErr name = case transactionForHead name graph of         Nothing -> throwError (NoSuchHeadNameError name)@@ -445,10 +463,10 @@       runE e = case e of         Left e' -> throwError e'         Right v -> pure v-  transA <- transactionForHeadErr headNameA-  transB <- transactionForHeadErr headNameB+  transA <- transactionForHeadErr (sourceHead mergeHeadNames)+  transB <- transactionForHeadErr (targetHead mergeHeadNames)   disconParent <- gfTransForId parentId-  let subHeads = M.filterWithKey (\k _ -> k `elem` [headNameA, headNameB]) (transactionHeadsForGraph graph)+  let subHeads = M.filterWithKey (\k _ -> k `elem` [sourceHead mergeHeadNames, targetHead mergeHeadNames]) (transactionHeadsForGraph graph)   -- is this an optimization???   subGraph <- runE $ subGraphOfFirstCommonAncestor graph subHeads transA transB S.empty   _ <- runE $ validateConnectivity subGraph@@ -457,24 +475,27 @@   -- we cannot cut the transaction graph away only to "relevant" transactions because transactions can reference other transactions via relvar expressions   mergedTrans <- local (const (freshGraphRefRelationalExprEnv Nothing subGraph')) $                  createMergeTransaction stamp' newId mergeStrategy (transA, transB)-  case headNameForTransaction disconParent graph of-        Nothing -> throwError (TransactionIsNotAHeadError parentId)-        Just headName -> do-          (newTrans, newGraph) <- runE $ addTransactionToGraph headName mergedTrans graph-          case checkConstraints (concreteDatabaseContext mergedTrans) newId graph of+  case headNamesForTransaction disconParent graph of+        [] -> throwError (TransactionIsNotAHeadError parentId)+        _ -> do+          (uTrans, newGraph') <- runE $ addTransactionToGraph (targetHead mergeHeadNames) mergedTrans graph+          case checkConstraints (concreteDatabaseContext (_uncommittedTransaction mergedTrans)) newId graph of             Left err -> throwError err             Right _ -> do-              let newGraph' = TransactionGraph (transactionHeadsForGraph newGraph) (transactionsForGraph newGraph)-                  newDiscon = Discon.freshTransaction newId (schemas newTrans)-              pure (newDiscon, newGraph')+              let newGraph'' = TransactionGraph (transactionHeadsForGraph newGraph') (transactionsForGraph newGraph')+              pure (uTrans, newGraph'')  --TEMPORARY COPY/PASTE showTransactionStructureX :: Bool -> Transaction -> TransactionGraph -> String showTransactionStructureX showRelVars trans graph = headInfo ++ " " ++ show (transactionId trans) ++ " " ++ parentTransactionsInfo ++ relVarsInfo   where+    relVars = relationVariables (concreteDatabaseContext trans)+    relVarsAsString = case relVars of+                    NotChangedSinceMarker tid -> "TransactionId " <> show tid+                    ValueMarker m -> concatMap show (M.toList m)     relVarsInfo | not showRelVars = ""-                | otherwise = "\n" <> concatMap show (M.toList (relationVariables (concreteDatabaseContext trans)))-    headInfo = maybe "" show (headNameForTransaction trans graph)+                | otherwise = "\n" <> relVarsAsString+    headInfo = show (headNamesForTransaction trans graph)     parentTransactionsInfo = if isRootTransaction trans then "root" else case parentTransactions trans graph of       Left err -> show err       Right parentTransSet -> concat $ S.toList $ S.map (show . transactionId) parentTransSet@@ -494,12 +515,12 @@     newHeads = M.map (filterTransaction validIds) heads  --helper function for commonalities in union merge-createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM Transaction+createUnionMergeTransaction :: UTCTime -> TransactionId -> MergeStrategy -> (Transaction, Transaction) -> GraphRefRelationalExprM UncommittedTransaction createUnionMergeTransaction stamp' newId strategy (t1,t2) = do   let contextA = concreteDatabaseContext t1       contextB = concreteDatabaseContext t2-      liftMergeE x = case x of-        Left e -> throwError (MergeTransactionError e)+      liftE x = case x of+        Left e -> throwError e         Right t -> pure t    graph <- gfGraph@@ -511,13 +532,14 @@         Just preferredTrans -> pure $ if t1 == preferredTrans then PreferFirst else PreferSecond     badStrat -> throwError (MergeTransactionError (InvalidMergeStrategyError badStrat)) -  incDeps <- liftMergeE $ unionMergeMaps preference (inclusionDependencies contextA) (inclusionDependencies contextB)-  relVars <- unionMergeRelVars preference (relationVariables contextA) (relationVariables contextB)-  atomFuncs <- liftMergeE $ unionMergeAtomFunctions preference (atomFunctions contextA) (atomFunctions contextB)-  notifs <- liftMergeE $ unionMergeMaps preference (notifications contextA) (notifications contextB)-  types <- liftMergeE $ unionMergeTypeConstructorMapping preference (typeConstructorMapping contextA) (typeConstructorMapping contextB)-  dbcFuncs <- liftMergeE $ unionMergeDatabaseContextFunctions preference (dbcFunctions contextA) (dbcFunctions contextB)-  registeredQs <- liftMergeE $ unionMergeRegisteredQueries preference (registeredQueries contextA) (registeredQueries contextB)+  incDeps <- liftE $ unionMergeMaps InclusionDependenciesField preference graph inclusionDependencies (inclusionDependencies contextA) (inclusionDependencies contextB)+  relVars <- unionMergeRelVars preference graph (relationVariables contextA) (relationVariables contextB)+  atomFuncs <- liftE $ unionMergeAtomFunctions preference graph (atomFunctions contextA) (atomFunctions contextB)+  notifs <- liftE $ unionMergeMaps NotificationsField preference graph notifications (notifications contextA) (notifications contextB)+  types <- liftE $ unionMergeTypeConstructorMapping preference graph (typeConstructorMapping contextA) (typeConstructorMapping contextB)+  dbcFuncs <- liftE $ unionMergeDatabaseContextFunctions preference graph (dbcFunctions contextA) (dbcFunctions contextB)+  registeredQs <- liftE $ unionMergeRegisteredQueries preference graph (registeredQueries contextA) (registeredQueries contextB)+  acls <- liftE $ unionMergeACLs preference graph (acl contextA) (acl contextB)   -- TODO: add merge of subschemas   let newContext = DatabaseContext {         inclusionDependencies = incDeps,@@ -526,16 +548,16 @@         dbcFunctions = dbcFuncs,         notifications = notifs,         typeConstructorMapping = types,-        registeredQueries = registeredQs+        registeredQueries = registeredQs,+        acl = acls         }       newSchemas = Schemas newContext (subschemas t1)-  pure $ addMerkleHash graph $+  pure $ UncommittedTransaction $ addMerkleHash graph $     Transaction newId (TransactionInfo {                           parents = NE.fromList [transactionId t1,                                                   transactionId t2],                             stamp = stamp',                             merkleHash = mempty }) newSchemas- lookupTransaction :: TransactionGraph -> TransactionIdLookup -> Either RelationalError Transaction lookupTransaction graph (TransactionIdLookup tid) = transactionForId tid graph lookupTransaction graph (TransactionIdHeadNameLookup headName backtracks) = case transactionForHead headName graph of@@ -583,30 +605,37 @@  -- | Create a temporary branch for commit, merge the result to head, delete the temporary branch. This is useful to atomically commit a transaction, avoiding a TransactionIsNotHeadError but trading it for a potential MergeError. --this is not a GraphOp because it combines multiple graph operations-autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraph)+autoMergeToHead :: UTCTime -> (TransactionId, TransactionId, TransactionId) -> DisconnectedTransaction -> HeadName -> MergeStrategy -> TransactionGraph -> Either RelationalError (DisconnectedTransaction, TransactionGraphIncrementalWriteInfo) autoMergeToHead stamp' (tempBranchTransId, tempCommitTransId, mergeTransId) discon mergeToHeadName strat graph = do   let tempBranchName = "mergebranch_" <> U.toText tempBranchTransId-  --create the temp branch-  (discon', graph') <- evalGraphOp stamp' tempBranchTransId discon graph (Branch tempBranchName)+  --create the temp branch, branching does not create a new transaction+  (discon', mtrans', graph') <- evalAlterTransactionGraphExpr stamp' tempBranchTransId discon graph (Branch tempBranchName)    --commit to the new branch- possible future optimization: don't require fsync for this- create a temp commit type-  (discon'', graph'') <- evalGraphOp stamp' tempCommitTransId discon' graph' Commit+  (discon'', mtrans'', graph'') <- evalAlterTransactionGraphExpr stamp' tempCommitTransId discon' graph' Commit    --jump to merge head-  (discon''', graph''') <- evalGraphOp stamp' tempBranchTransId discon'' graph'' (JumpToHead mergeToHeadName)+  discon''' <- evalTransactionGraphExpr discon'' graph'' (JumpToHead mergeToHeadName)    --create the merge-  (discon'''', graph'''') <- evalGraphOp stamp' mergeTransId discon''' graph''' (MergeTransactions strat tempBranchName mergeToHeadName)+  (discon'''', mtrans'''', graph'''') <- evalAlterTransactionGraphExpr stamp' mergeTransId discon''' graph'' (MergeTransactions strat tempBranchName mergeToHeadName) +   --delete the temp branch-  (discon''''', graph''''') <- evalGraphOp stamp' tempBranchTransId discon'''' graph'''' (DeleteBranch tempBranchName)+  (discon''''', mtrans''''', graph''''') <- evalAlterTransactionGraphExpr stamp' tempBranchTransId discon'''' graph'''' (DeleteBranch tempBranchName)   {-   let rel = runReader (evalRelationalExpr (RelationVariable "s" ())) (mkRelationalExprState $ D.concreteDatabaseContext discon'''')   traceShowM rel -}--  pure (discon''''', graph''''')-+  pure (discon''''',+        TransactionGraphIncrementalWriteInfo {+           uncommittedTransactions = S.fromList (catMaybes [mtrans',+                                                            mtrans'',+                                                            mtrans'''',+                                                            mtrans'''''+                                                           ]),+           newGraph = graph'''''+           })  addMerkleHash :: TransactionGraph -> Transaction -> Transaction addMerkleHash graph trans = Transaction (transactionId trans) newInfo (schemas trans)@@ -650,3 +679,4 @@         mapM_ (`transactionForId` graph) (referencedTransactionIds (concreteDatabaseContext trans))   mapM_ validateTrans (transactionsForGraph graph)   pure graph+
src/lib/ProjectM36/TransactionGraph/Merge.hs view
@@ -3,7 +3,12 @@ module ProjectM36.TransactionGraph.Merge where import ProjectM36.Base import ProjectM36.Error+import ProjectM36.ValueMarker+import ProjectM36.TransactionGraph.Types import ProjectM36.RelationalExpression+import ProjectM36.DatabaseContext.Types+import ProjectM36.AccessControlList+import ProjectM36.DatabaseContext.Fields #if MIN_VERSION_base(4,18,0) import Control.Monad (foldM) import Control.Monad.Except@@ -19,14 +24,28 @@ data MergePreference = PreferFirst | PreferSecond | PreferNeither  -- Check for overlapping keys. If the values differ, try a preference resolution-unionMergeMaps :: (Ord k, Eq a) => MergePreference -> M.Map k a -> M.Map k a -> Either MergeError (M.Map k a)-unionMergeMaps prefer mapA mapB = case prefer of-  PreferFirst -> pure $ M.union mapA mapB-  PreferSecond -> pure $ M.union mapB mapA-  PreferNeither -> if M.intersection mapA mapB == M.intersection mapA mapB then-                     pure $ M.union mapA mapB-                   else-                     Left StrategyViolatesComponentMergeError+unionMergeMaps :: Eq a => DatabaseContextField -> MergePreference -> TransactionGraph -> (DatabaseContext -> ValueMarker (M.Map StringType a)) -> ValueMarker (M.Map StringType a) -> ValueMarker (M.Map StringType a) -> Either RelationalError (ValueMarker (M.Map StringType a))+unionMergeMaps _field _prefer _graph _f a@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB =+      pure a+unionMergeMaps field prefer graph f v_mapA v_mapB = do+  mapA <- resolveValueMarker graph f v_mapA+  mapB <- resolveValueMarker graph f v_mapB+  let diff = foldr folder Nothing overlappingKeys+      overlappingKeys = S.intersection (M.keysSet mapA) (M.keysSet mapB)+      folder _key (Just ret) = Just ret+      folder key acc =+        if mapA M.! key /= mapB M.! key then+          Just key+          else+          acc+  case prefer of+    PreferFirst -> pure (ValueMarker $ M.union mapA mapB)+    PreferSecond -> pure $ ValueMarker $ M.union mapB mapA+    PreferNeither -> case diff of+                       Nothing -> pure $ ValueMarker $ M.union mapA mapB+                       Just violatingKey ->+                         Left (MergeTransactionError (StrategyViolatesComponentMergeError field violatingKey))                       -- perform the merge even if the attributes are different- is this what we want? Obviously, we need finer-grained merge options. unionMergeRelation :: MergePreference -> GraphRefRelationalExpr -> GraphRefRelationalExpr -> GraphRefRelationalExprM GraphRefRelationalExpr@@ -44,10 +63,24 @@   (evalGraphRefRelationalExpr unioned >> pure (Union relA relB)) `catchError` handler  --try to execute unions against the relvars contents -- if a relvar only appears in one context, include it-unionMergeRelVars :: MergePreference -> RelationVariables -> RelationVariables -> GraphRefRelationalExprM RelationVariables-unionMergeRelVars prefer relvarsA relvarsB = do+unionMergeRelVars ::+  MergePreference ->+  TransactionGraph ->+  ValueMarker RelationVariables ->+  ValueMarker RelationVariables ->+  GraphRefRelationalExprM (ValueMarker RelationVariables)+unionMergeRelVars _prefer _graph v@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB =+      pure v+unionMergeRelVars prefer graph v_relvarsA v_relvarsB = do+  relvarsA <- case resolveValueMarker graph relationVariables v_relvarsA of+       Left err -> throwError err+       Right val -> pure val+  relvarsB <- case resolveValueMarker graph relationVariables v_relvarsB of+       Left err -> throwError err+       Right val -> pure val   let allNames = S.toList (S.union (M.keysSet relvarsA) (M.keysSet relvarsB))-  foldM (\acc name -> do+  ValueMarker <$> foldM (\acc name -> do             mergedRel <- do               let findRel = M.lookup name                   lookupA = findRel relvarsA@@ -63,16 +96,29 @@  -- if two functions have the same name, ensure that the functions are identical, otherwise, conflict or prefer --because we don't have a bytecode, there is no way to verify that function bodies are equal, so if the types match up, just choose the first function. This is a serious bug, but intractable until we have a function bytecode.-unionMergeAtomFunctions :: MergePreference -> AtomFunctions -> AtomFunctions -> Either MergeError AtomFunctions  -unionMergeAtomFunctions prefer funcsA funcsB = case prefer of-  PreferFirst -> pure $ HS.union funcsA funcsB-  PreferSecond -> pure $ HS.union funcsB funcsA-  PreferNeither -> pure $ HS.union funcsA funcsB+unionMergeAtomFunctions :: MergePreference -> TransactionGraph -> ValueMarker AtomFunctions -> ValueMarker AtomFunctions -> Either RelationalError (ValueMarker AtomFunctions)+unionMergeAtomFunctions _prefer _g v@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB =+    pure v+unionMergeAtomFunctions prefer graph v_funcsA v_funcsB = do+  funcsA <- resolveValueMarker graph atomFunctions v_funcsA+  funcsB <- resolveValueMarker graph atomFunctions v_funcsB  +  pure $ ValueMarker $+    case prefer of+      PreferFirst -> HS.union funcsA funcsB+      PreferSecond -> HS.union funcsB funcsA+      PreferNeither -> HS.union funcsA funcsB -unionMergeTypeConstructorMapping :: MergePreference -> TypeConstructorMapping -> TypeConstructorMapping -> Either MergeError TypeConstructorMapping  -unionMergeTypeConstructorMapping prefer typesA typesB = do+unionMergeTypeConstructorMapping :: MergePreference -> TransactionGraph -> ValueMarker TypeConstructorMapping -> ValueMarker TypeConstructorMapping -> Either RelationalError (ValueMarker TypeConstructorMapping)+unionMergeTypeConstructorMapping _prefer _graph v@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB =+      pure v+unionMergeTypeConstructorMapping prefer graph v_typesA v_typesB = do+  typesA <- resolveValueMarker graph typeConstructorMapping v_typesA+  typesB <- resolveValueMarker graph typeConstructorMapping v_typesB  +     let allFuncNames = S.fromList $ map (\(tc,_) -> TCD.name tc) (typesA ++ typesB)-  foldM (\acc name -> do+  val <- foldM (\acc name -> do             let findType tcm = case filter (\(t,_) -> TCD.name t == name) tcm of                   [] -> Nothing                   [x] -> Just x@@ -90,25 +136,48 @@                                              case prefer of                                                 PreferFirst -> cat typeA                                                PreferSecond -> cat typeB-                                               PreferNeither -> Left StrategyViolatesTypeConstructorMergeError+                                               PreferNeither -> Left (MergeTransactionError StrategyViolatesTypeConstructorMergeError)             ) [] (S.toList allFuncNames)--unionMergeDatabaseContextFunctions :: MergePreference -> DatabaseContextFunctions -> DatabaseContextFunctions -> Either MergeError DatabaseContextFunctions-unionMergeDatabaseContextFunctions prefer funcsA funcsB = case prefer of-  PreferFirst -> pure $ HS.union funcsA funcsB-  PreferSecond -> pure $ HS.union funcsB funcsA-  PreferNeither -> pure $ HS.union funcsA funcsB+  pure (ValueMarker val) -unionMergeRegisteredQueries :: MergePreference -> RegisteredQueries -> RegisteredQueries -> Either MergeError RegisteredQueries-unionMergeRegisteredQueries prefer regQsA regQsB =-  case prefer of-    PreferFirst -> pure (M.union regQsA regQsB)-    PreferSecond -> pure (M.union regQsB regQsA)-    PreferNeither -> do-      let isect = M.filter id $ M.mapWithKey (\qname val -> M.lookup qname regQsB /= Just val) (M.intersection regQsA regQsB)-      --if the values in the intersection are the same, we can merge them      -      if M.null isect then-        pure (M.union regQsA regQsB)-        else-        Left (StrategyViolatesRegisteredQueryMergeError (M.keys isect))+unionMergeDatabaseContextFunctions :: MergePreference -> TransactionGraph -> ValueMarker DatabaseContextFunctions -> ValueMarker DatabaseContextFunctions -> Either RelationalError (ValueMarker DatabaseContextFunctions)+unionMergeDatabaseContextFunctions _prefer _graph v@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB =+    pure v+unionMergeDatabaseContextFunctions prefer graph v_funcsA v_funcsB = do+  funcsA <- resolveValueMarker graph dbcFunctions v_funcsA+  funcsB <- resolveValueMarker graph dbcFunctions v_funcsB+  pure $ ValueMarker $+    case prefer of+      PreferFirst -> HS.union funcsA funcsB+      PreferSecond -> HS.union funcsB funcsA+      PreferNeither -> HS.union funcsA funcsB+  +unionMergeRegisteredQueries :: MergePreference -> TransactionGraph -> ValueMarker RegisteredQueries -> ValueMarker RegisteredQueries -> Either RelationalError (ValueMarker RegisteredQueries)+unionMergeRegisteredQueries _prefer _g v@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB = pure v+unionMergeRegisteredQueries prefer graph v_regQsA v_regQsB = do+  regQsA <- resolveValueMarker graph registeredQueries v_regQsA+  regQsB <- resolveValueMarker graph registeredQueries v_regQsB+  +  v <- case prefer of+         PreferFirst -> pure (M.union regQsA regQsB)+         PreferSecond -> pure (M.union regQsB regQsA)+         PreferNeither -> do+           let isect = M.filter id $ M.mapWithKey (\qname val -> M.lookup qname regQsB /= Just val) (M.intersection regQsA regQsB)+           --if the values in the intersection are the same, we can merge them      +           if M.null isect then+             pure (M.union regQsA regQsB)+             else+             Left (MergeTransactionError (StrategyViolatesRegisteredQueryMergeError (M.keys isect)))+  pure (ValueMarker v)         +unionMergeACLs :: MergePreference -> TransactionGraph -> ValueMarker DatabaseContextACL -> ValueMarker DatabaseContextACL -> Either RelationalError (ValueMarker DatabaseContextACL)+unionMergeACLs _prefer _g v@(NotChangedSinceMarker tidA) (NotChangedSinceMarker tidB)+  | tidA == tidB = pure v+unionMergeACLs _prefer graph v_aclA v_aclB = do+  aclA <- resolveValueMarker graph acl v_aclA+  aclB <- resolveValueMarker graph acl v_aclB++  pure $ ValueMarker $ aclA <> aclB+
src/lib/ProjectM36/TransactionGraph/Persist.hs view
@@ -2,6 +2,8 @@ import ProjectM36.Error import ProjectM36.Transaction import ProjectM36.Transaction.Persist+import ProjectM36.TransactionGraph.Types+import ProjectM36.Transaction.Types import ProjectM36.RelationalExpression import ProjectM36.Base import ProjectM36.ScriptSession@@ -40,11 +42,11 @@ The "m36vX" file at the top-level of the destination directory contains the the transaction graph as a set of transaction ids referencing their parents (1 or more) Each Transaction is written to it own directory named by its transaction id. Partially written transactions ids are prefixed with a "." to indicate incompleteness in the graph. -Persistence requires a POSIX-compliant, journaled-metadata filesystem.+Persistence requires a POSIX-compliant, journaled-metadata filesystem. Extended attributes (when supported by the filesystem are used to mark which relation variable name maps to which file. Relation variable names may contain arbitrary UTF-8 (such as colons, newlines, etc.) which may not be suitable for direct use in file names. -}  expectedVersion :: Int-expectedVersion = 7+expectedVersion = 8  transactionLogFileName :: FilePath  transactionLogFileName = "m36v" ++ show expectedVersion@@ -96,39 +98,45 @@ bootstrapDatabaseDir sync dbdir bootstrapGraph = do   createDirectory dbdir   locker <- openLockFile (lockFilePath dbdir)-  let allTransIds = map transactionId (S.toList (transactionsForGraph bootstrapGraph))   createDirectoryIfMissing False (ProjectM36.TransactionGraph.Persist.objectFilesPath dbdir)-  digest  <- bracket_ (lockFile locker WriteLock) (unlockFile locker) (transactionGraphPersist sync dbdir allTransIds bootstrapGraph)+  digest  <- bracket_ (lockFile locker WriteLock) (unlockFile locker) (transactionGraphPersist sync dbdir bootstrapGraph)   pure (locker, digest)  objectFilesPath :: FilePath -> FilePath objectFilesPath dbdir = dbdir </> "compiled_modules"-{- -incrementally updates an existing database directory---algorithm: ---assume that all transaction data has already been written--assume that all non-head transactions have already been written because this is an incremental (and concurrent!) write method---store the head names with a symlink to the transaction under "heads"--}-transactionGraphPersist :: DiskSync -> FilePath -> [TransactionId] -> TransactionGraph -> IO LockFileHash-transactionGraphPersist sync destDirectory transIds graph = do-  transactionsPersist sync transIds destDirectory graph++-- | Write an entire transaction graph to storage.+transactionGraphPersist :: DiskSync -> FilePath -> TransactionGraph -> IO LockFileHash+transactionGraphPersist sync destDirectory graph = do+  let tgWriteInfo =+        TransactionGraphIncrementalWriteInfo+        {+          uncommittedTransactions = newTransactions,+          newGraph = graph+        }+      newTransactions = S.map UncommittedTransaction (transactionsForGraph graph)+  transactionGraphPersistIncremental sync destDirectory tgWriteInfo +++-- | The incremental writer writes the transactions ids specified by the second argument.+transactionGraphPersistIncremental :: DiskSync -> FilePath -> TransactionGraphIncrementalWriteInfo -> IO LockFileHash+transactionGraphPersistIncremental sync destDirectory tgWriteInfo = do+  mapM_ (writeTransaction sync destDirectory) (S.toList (uncommittedTransactions tgWriteInfo))   --write graph file-  newDigest <- writeGraphTransactionIdFile sync destDirectory graph+  newDigest <- writeGraphTransactionIdFile sync destDirectory (newGraph tgWriteInfo)   --write heads file-  transactionGraphHeadsPersist sync destDirectory graph+  transactionGraphHeadsPersist sync destDirectory (newGraph tgWriteInfo)   pure newDigest-  --- | The incremental writer writes the transactions ids specified by the second argument.  -- There was a bug here via #128 because automerge added multiple transactions to the graph but this function used to only write the head transactions from the graph. Automerge creates multiple transactions, so these are now passed in as the second argument.+{- transactionsPersist :: DiskSync -> [TransactionId] -> FilePath -> TransactionGraph -> IO () transactionsPersist sync transIds destDirectory graphIn = mapM_ writeTrans transIds   where writeTrans tid =           case transactionForId tid graphIn of              Left err -> error ("writeTransaction: " ++ show err)             Right trans -> writeTransaction sync destDirectory trans-+-} {-  write graph heads to a file which can be atomically swapped -}@@ -170,11 +178,11 @@   freshHeadsAssoc <- transactionGraphHeadsLoad dbdir   case uuidInfo of     Left err -> return $ Left err-    Right info -> do  +    Right info' -> do         let folder eitherGraph transId = case eitherGraph of             Left err -> return $ Left err             Right graph -> readTransactionIfNecessary dbdir transId mScriptSession graph-      loadedGraph <- foldM folder (Right graphIn) (map (\(tid,_,_) -> tid) info)+      loadedGraph <- foldM folder (Right graphIn) (map (\(tid,_,_) -> tid) info')       case loadedGraph of          Left err -> return $ Left err         Right freshGraph -> do
src/lib/ProjectM36/TransactionGraph/Show.hs view
@@ -1,12 +1,13 @@ module ProjectM36.TransactionGraph.Show where-import ProjectM36.Base import ProjectM36.TransactionGraph+import ProjectM36.TransactionGraph.Types+import ProjectM36.Transaction.Types import qualified Data.Set as S  showTransactionStructure :: Transaction -> TransactionGraph -> String showTransactionStructure trans graph = headInfo ++ " " ++ show (transactionId trans) ++ " p" ++ parentTransactionsInfo   where-    headInfo = maybe "" show (headNameForTransaction trans graph)+    headInfo = show (headNamesForTransaction trans graph)     parentTransactionsInfo = if isRootTransaction trans then "root" else case parentTransactions trans graph of       Left err -> show err       Right parentTransSet -> concat $ S.toList $ S.map (show . transactionId) parentTransSet
+ src/lib/ProjectM36/TransactionGraph/Types.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE DeriveGeneric #-}+module ProjectM36.TransactionGraph.Types where+import ProjectM36.Base+--import ProjectM36.DisconnectedTransaction+import ProjectM36.Transaction.Types+import qualified Data.Set as S+import qualified Data.Map as M+import GHC.Generics+import qualified Data.List.NonEmpty as NE++transactions :: TransactionGraph -> S.Set Transaction+transactions (TransactionGraph _ t) = t++transactionHeads :: TransactionGraph -> TransactionHeads+transactionHeads (TransactionGraph heads _) = heads+++type TransactionHeads = M.Map HeadName Transaction++-- | The transaction graph is the global database's state which references every committed transaction.++data TransactionGraph = TransactionGraph TransactionHeads (S.Set Transaction)+  deriving Generic++type TransactionParents = NE.NonEmpty TransactionId++transactionHeadsForGraph :: TransactionGraph -> TransactionHeads+transactionHeadsForGraph (TransactionGraph hs _) = hs++transactionsForGraph :: TransactionGraph -> S.Set Transaction+transactionsForGraph (TransactionGraph _ ts) = ts++transactionIdsForGraph :: TransactionGraph -> S.Set TransactionId+transactionIdsForGraph = S.map transactionId . transactionsForGraph++-- | Used to track changes to child transactions relative to parent transactions as a write optimization. We can refer to the parent transaction for the data which has already been written, but we need to track then what actually changed.+data TransactionGraphIncrementalWriteInfo =+  TransactionGraphIncrementalWriteInfo {+  uncommittedTransactions :: S.Set UncommittedTransaction, -- the disconnected transaction holds the parent id, but the fst transaction id is the transaction id of the transaction in the "newGraph" which has not yet been flushed to permanent storage+  newGraph :: TransactionGraph+      }++affectedTransactionIds :: TransactionGraphIncrementalWriteInfo -> S.Set TransactionId+affectedTransactionIds winfo = S.map (\(UncommittedTransaction t) -> transactionId t) (uncommittedTransactions winfo)++data MergeHeadNames = MergeHeadNames { sourceHead :: HeadName,+                                       targetHead :: HeadName }+                      deriving (Eq, Show)
src/lib/ProjectM36/TransactionInfo.hs view
@@ -1,5 +1,6 @@ module ProjectM36.TransactionInfo where import ProjectM36.Base+import ProjectM36.Transaction.Types import Data.Time.Clock import qualified Data.List.NonEmpty as NE 
src/lib/ProjectM36/Tuple.hs view
@@ -61,6 +61,13 @@     unknownAttrNames = V.filter (`V.notElem` attributeNames attrs) attrNameVec     mapper attrName = fromMaybe (error "logic failure in vectorIndicesForAttributeNames") (V.elemIndex attrName (attributeNames attrs)) +indicesForAttributeNames :: V.Vector AttributeName -> Attributes -> V.Vector (AttributeName, Maybe Int)+indicesForAttributeNames attrNamessToFind attrs =+  V.map finder attrNamessToFind+  where+    finder nam =+      (nam, V.findIndex (\x -> attributeName x == nam) (attributesVec attrs))+ relationForAttributeName :: AttributeName -> RelationTuple -> Either RelationalError Relation relationForAttributeName attrName tuple = do   aType <- atomTypeForAttributeName attrName (tupleAttributes tuple)@@ -76,6 +83,9 @@   where     newAttrs = renameAttributes oldattr newattr tupAttrs +tupleRenameAttributes :: S.Set (AttributeName, AttributeName) -> RelationTuple -> RelationTuple+tupleRenameAttributes attrsAssoc tup = foldr (\(a,b) tup' -> tupleRenameAttribute a b tup') tup (S.toList attrsAssoc)+ mkRelationTuple :: Attributes -> V.Vector Atom -> RelationTuple mkRelationTuple = RelationTuple  @@ -100,6 +110,7 @@         Right Nothing -> Right accumulator         Right (Just relTuple) -> Right $ relTuple : accumulator         Left err -> Left err+              {-             singleTupleSetJoin :: RelationTuple -> RelationTupleSet -> RelationTupleSet@@ -152,17 +163,8 @@   where     newTup = RelationTuple (A.singleton $ Attribute newAttrName (atomTypeForAtom atom)) (V.singleton atom) -{-tupleProject :: S.Set AttributeName -> RelationTuple -> RelationTuple-tupleProject projectAttrs (RelationTuple attrs tupVec) = RelationTuple newAttrs newTupVec-  where-    deleteIndices = V.findIndices (\attr -> S.notMember (attributeName attr) projectAttrs) (attributesVec attrs)-    indexDeleter = V.ifilter (\index _ -> V.notElem index deleteIndices)-    newAttrs = case A.projectionAttributesForNames projectAttrs attrs of-                 Left err -> error (show (err, projectAttrs, attrs))-                 Right attrs' ->  attrs'-    newTupVec = indexDeleter tupVec--} -- remember that the attributes order matters+-- we prefer to pass attributes so that we don't have look up attribute ordering for every tuple (was a bottleneck) tupleProject :: Attributes -> RelationTuple -> Either RelationalError RelationTuple tupleProject projectAttrs tup = do   newTupVec <- foldM (\acc attr ->@@ -228,6 +230,32 @@   RelationTuple newAttrs (V.drop index vals)   where     newAttrs = A.drop index attrs-  -  -  ++-- | Used in tuple context evaluation- the first tuple can replace atoms of the second if the attribute names overlap.+{-+mergeTuples :: RelationTuple -> RelationTuple -> RelationTuple+mergeTuples tup1 tup2 =+  V.foldr folder tup2 attrsToAdd+  where+  folder attrAdd acc =+      case V.find (\x -> fst x == attributeName attrAdd) tup2NameMatches of+        Nothing -> -- just extend the tuple+          extendTupleAtom attrAdd (tup1AtomForAttrName (attributeName attrAdd)) acc+        Just (aName, Nothing) ->+          extendTupleAtom attrAdd (tup1AtomForAttrName aName) acc+        Just (aName, Just idx) -> +          -- replace existing atom+          let atom = tup1AtomForAttrName aName in+              RelationTuple (tupleAttributes acc) (V.update (tupleAtoms acc) (V.singleton (idx, atom)))+  attrsToAdd = attributesVec (tupleAttributes tup1)+  tup2NameMatches = indicesForAttributeNames (V.fromList (S.toList (attributeNameSet (tupleAttributes tup2)))) (tupleAttributes tup1)+  tup1AtomForAttrName nam =+    case atomForAttributeName nam tup1 of+      Left err -> error $ "impossible missing attribute in tuple: " <> show err+      Right atom -> atom+++extendTupleAtom :: Attribute -> Atom -> RelationTuple -> RelationTuple+extendTupleAtom attrAdd atomAdd tupIn =+  RelationTuple (addAttribute attrAdd (tupleAttributes tupIn)) (V.snoc (tupleAtoms tupIn) atomAdd)+-}
src/lib/ProjectM36/TupleSet.hs view
@@ -7,11 +7,11 @@ import qualified Control.Parallel.Strategies as P import Data.Either -emptyTupleSet :: RelationTupleSet-emptyTupleSet = RelationTupleSet []+empty :: RelationTupleSet+empty = RelationTupleSet [] -singletonTupleSet :: RelationTupleSet-singletonTupleSet = RelationTupleSet [emptyTuple]+emptySingleTuple :: RelationTupleSet+emptySingleTuple = RelationTupleSet [emptyTuple]  --ensure that all maps have the same keys and key count @@ -37,3 +37,6 @@ tupleSetUnion targetAttrs tupSet1 tupSet2 = RelationTupleSet $ HS.toList . HS.fromList $ reorder (asList tupSet1) ++ reorder (asList tupSet2)   where     reorder = map (reorderTuple targetAttrs)++null :: RelationTupleSet -> Bool+null (RelationTupleSet tups) = Prelude.null tups
src/lib/ProjectM36/Tupleable.hs view
@@ -43,7 +43,7 @@ import           ProjectM36.DataTypes.Primitive import           ProjectM36.Error import           ProjectM36.Tuple-import           ProjectM36.TupleSet+import qualified ProjectM36.TupleSet as TS import qualified Data.Set as S  {-import Data.Binary@@ -83,7 +83,7 @@ toInsertExpr :: forall a t. (Tupleable a, Traversable t) => t a -> RelVarName -> Either RelationalError DatabaseContextExpr toInsertExpr vals rvName = do   let attrs = toAttributes (Proxy :: Proxy a)-  tuples <- mkTupleSet attrs $ toList (fmap toTuple vals)+  tuples <- TS.mkTupleSet attrs $ toList (fmap toTuple vals)   let rel = MakeStaticRelation attrs tuples   pure (Insert rvName rel) 
src/lib/ProjectM36/Tupleable/Deriving.hs view
@@ -55,7 +55,7 @@ import           Data.Maybe           (fromMaybe) import           Data.Proxy import qualified Data.Text            as T-import           Data.Text.Manipulate+import Data.Text.Manipulate import           GHC.TypeLits import           GHC.Generics         (Generic, Rep) import           ProjectM36.Tupleable
+ src/lib/ProjectM36/ValueMarker.hs view
@@ -0,0 +1,16 @@+-- | Mark fields as not updated since a specific transaction. Useful for not writing redundant data to disk- the data was written by an earlier transaction.+{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}+module ProjectM36.ValueMarker where+import ProjectM36.Base+import GHC.Generics+import Control.DeepSeq (NFData)+  +data ValueMarker a = ValueMarker a | NotChangedSinceMarker TransactionId+  deriving (NFData, Generic, Show)++valueIsUpdated :: ValueMarker a -> Bool+valueIsUpdated ValueMarker{} = True+valueIsUpdated NotChangedSinceMarker{} = False++emptyValue :: Monoid a => ValueMarker a+emptyValue = ValueMarker mempty
src/lib/ProjectM36/Win32Handle.hs view
@@ -44,7 +44,7 @@         fd <- haFD <$> readMVar write_handle_mvar #else         --readMVar write_handle_mvar >>= \(Handle__ { haDevice = dev }) -> print (typeOf dev)-        Just fd <- fmap (\(Handle__ { haDevice = dev }) -> fmap fdFD (cast dev)) $ readMVar write_handle_mvar+        Just fd <- (\(Handle__ { haDevice = dev }) -> fmap fdFD (cast dev)) <$> readMVar write_handle_mvar #endif          -- Finally, turn that (C-land) FD into a HANDLE using msvcrt
src/lib/ProjectM36/WithNameExpr.hs view
@@ -1,15 +1,20 @@+{-# LANGUAGE FlexibleInstances #-} module ProjectM36.WithNameExpr where import ProjectM36.Base import Data.List (find)---- substitute all instances of With-based macros to remove macro context--- ideally, we would use a different relational expr type to "prove" that the with macros can no longer exist+import qualified Data.Set as S --- |  lookup :: RelVarName -> WithNamesAssocsBase a -> Maybe (RelationalExprBase a) lookup matchrv assocs =   snd <$> find (\(WithNameExpr rv _, _) -> rv == matchrv) assocs +macroNames :: WithNamesAssocsBase a -> S.Set RelVarName+macroNames assocs = S.fromList (map getMacroName assocs)+  where+    getMacroName (WithNameExpr rvName _, _expand) = rvName+  +-- substitute all instances of With-based macros to remove macro context+-- ideally, we would use a different relational expr type to "prove" that the with macros can no longer exist -- | Drop macros into the relational expression wherever they are referenced. substituteWithNameMacros ::   GraphRefWithNameAssocs ->@@ -95,6 +100,62 @@     ConstructedAtomExpr dconsName atomExprs tid ->       ConstructedAtomExpr dconsName (map (substituteWithNameMacrosAtomExpr macros) atomExprs) tid ++{-+-- | Return error if with clause name is shadowed. We're not sure if name shadowing is a useful feature or a footgun, so disable it for now.+class ValidateWith a where+  validate :: S.Set RelVarName -> WithNameAssocs -> a -> Either RelationalError a+  ++instance ValidateWith GraphRefRelationalExpr where+  validate inUseSet withAssocs expr =+    case expr of+      MakeRelationFromExprs mAttrs tupleExprs ->+        MakeRelationFromExprs mAttrs <$> val tupleExprs+      rel@MakeStaticRelation{} ->+        pure expr+      rel@ExistingRelation{} ->+        pure expr+      RelationVariable rvName marker -> do+        checkName rvName+      Project attrs exprA ->+        Project attrs <$> val exprA+      Union exprA exprB ->+        Union <$> val exprA <*> val exprB+      Join exprA exprB ->+        Join <$> val exprA <*> val exprB+      Rename nam1 nam2 exprA ->+        Rename nam1 nam2 <$> val exprA+      Difference exprA exprB ->+        Difference <$> val exprA <*> val exprB+      Group attrs nam exprA ->+        Group attrs nam <$> val exprA+      Ungroup nam expr' ->+        Ungroup nam <$> val expr'+      Restrict pred exprA ->+        Restrict <$> val pred <*> val exprA+      Equals exprA exprB ->+        Equals <$> val exprA <*> val exprB+      NotEquals exprA exprB ->+        NotEquals <$> val exprA <*> val exprB+      Extend extExpr exprA ->+        Extend <$> val extExpr <*> val exprA+      With withAssocs exprA -> do+        mapM_ checkName (map fst withAssocs)+        With withAssocs <$> val exprA+   where+     val :: forall a. (ValidateWith a) => a -> Either RelationalError a+     val x = validate inUseSet withAssocs x+     checkName :: RelVarName -> Either RelationalError GraphRefRelationalExpr+     checkName nam =+       if nam S.member inUseSet then+         Left (RelVarNameShadowingForbiddenError nam)+         else+         pure expr+         +      +-}+ substituteWithNameMacrosAttributeNames :: GraphRefWithNameAssocs -> GraphRefAttributeNames -> GraphRefAttributeNames substituteWithNameMacrosAttributeNames macros attrNames =   case attrNames of@@ -108,3 +169,4 @@       RelationalExprAttributeNames (substituteWithNameMacros macros relExpr)        +
+ test/AccessControl.hs view
@@ -0,0 +1,104 @@+import TutorialD.Interpreter.TestBase+import ProjectM36.Client+import ProjectM36.Relation+import qualified ProjectM36.Attribute as A+import Test.HUnit+import System.Exit++main :: IO ()+main = do+  tcounts <- runTestTT (TestList [testDBCFunctionACL,+                                 testRelVarAccess,+                                 testFunctionAccess])+  if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess++testDBCFunctionACL :: Test+testDBCFunctionACL = TestCase $ do+  (sessionId, conn) <- dateExamplesConnection emptyNotificationCallback+  -- add a less-privileged role+  let user1 = "user1"+  res <- executeAlterLoginRolesExpr sessionId conn (AddLoginRoleExpr user1 False)+  assertEqual "add role" (Right QuietSuccessResult) res+  let user1conn = setRoleName user1 conn+  -- test that the role does *not* have access to a dbc function+  res' <- executeDatabaseContextExpr sessionId user1conn (ExecuteDatabaseContextFunction "deleteAll" [])+  assertEqual "failure to run dbc function" (Left (AccessDeniedError (SomeFunctionPermission ExecuteFunctionPermission))) res'+  -- test that the role cannot create a dbc function+  res'' <- executeDatabaseContextIOExpr sessionId user1conn (AddDatabaseContextFunction "failure" [] "")+  assertEqual "failure to add dbc function" (Left (AccessDeniedError (SomeFunctionPermission AlterFunctionPermission))) res''+  -- grant permission to the role to execute the function+  res''' <- executeDatabaseContextExpr sessionId conn (AlterACL (GrantDBCFunctionAccessExpr user1 "deleteAll" ExecuteDBCFunctionPermission False))+  assertEqual "success adding function permission" (Right ()) res'''+  +  -- fail again because of two-tiered ACLs necessary to execute the dbc function+  res'''' <- executeDatabaseContextExpr sessionId user1conn (ExecuteDatabaseContextFunction "deleteAll" [])+  assertEqual "failure of calling deleteAll" (Left (AccessDeniedError (SomeFunctionPermission ExecuteFunctionPermission))) res''''++  -- grant the remaining, necessary permission+  res'''''' <- executeDatabaseContextExpr sessionId conn (AlterACL (GrantAccessExpr user1 (SomeFunctionPermission ExecuteFunctionPermission) False))+  assertEqual "success adding function permission" (Right ()) res''''''++  -- successfully call the dbc function+  res''''''' <- executeDatabaseContextExpr sessionId user1conn (ExecuteDatabaseContextFunction "deleteAll" [])+  assertEqual "success calling deleteAll" (Right ()) res'''''''+++testRelVarAccess :: Test+testRelVarAccess = TestCase $ do+  (sessionId, conn) <- dateExamplesConnection emptyNotificationCallback+  -- add a less-privileged role+  let user1 = "user1"+  res <- executeAlterLoginRolesExpr sessionId conn (AddLoginRoleExpr user1 False)+  assertEqual "add role" (Right QuietSuccessResult) res+  let user1conn = setRoleName user1 conn++  -- check that the user cannot view the relvars+  res' <- executeRelationalExpr sessionId user1conn (RelationVariable "x" ())++  assertEqual "reject relvar access" (Left (AccessDeniedError (SomeRelVarPermission AccessRelVarsPermission))) res'++  -- grant relvars access+  res'' <- executeDatabaseContextExpr sessionId conn (AlterACL (GrantAccessExpr user1 (SomeRelVarPermission AccessRelVarsPermission) False))++  assertEqual "grant rv access" (Right ()) res''++  -- check that the user can view the relvars+  res''' <- executeRelationalExpr sessionId user1conn (RelationVariable "true" ())++  assertEqual "accept relvar access" (Right relationTrue) res'''+  +testFunctionAccess :: Test+testFunctionAccess = TestCase $ do+  (sessionId, conn) <- dateExamplesConnection emptyNotificationCallback+  -- add a less-privileged role+  let user1 = "user1"+  res <- executeAlterLoginRolesExpr sessionId conn (AddLoginRoleExpr user1 False)+  assertEqual "add role" (Right QuietSuccessResult) res+  let user1conn = setRoleName user1 conn++  -- check that function view access is denied+  res' <- databaseContextFunctionsAsRelation sessionId user1conn +  assertEqual "rejected dbc function view" (Left (AccessDeniedError (SomeFunctionPermission ViewFunctionPermission))) res'+  +  -- grant function view+  res'' <- executeDatabaseContextExpr sessionId conn (AlterACL (GrantAccessExpr user1 (SomeFunctionPermission ViewFunctionPermission) False))+  assertEqual "grant function access" (Right ()) res''++  -- check that function view works+  res''' <- databaseContextFunctionsAsRelation sessionId user1conn +  assertEqual "dbc function view" (mkRelationFromList (A.attributesFromList [Attribute "name" TextAtomType, Attribute "arguments" TextAtomType]) []) res'''++  -- check that function execute is denied+  res'''' <- executeDatabaseContextExpr sessionId user1conn (ExecuteDatabaseContextFunction "deleteAll" [])+  assertEqual "failure to run dbc function" (Left (AccessDeniedError (SomeFunctionPermission ExecuteFunctionPermission))) res''''++  -- grant permission to the role to execute the function+  res''''' <- executeDatabaseContextExpr sessionId conn (AlterACL (GrantDBCFunctionAccessExpr user1 "deleteAll" ExecuteDBCFunctionPermission False))+  assertEqual "success adding dbcfunction permission" (Right ()) res'''''++  res'''''' <- executeDatabaseContextExpr sessionId conn (AlterACL (GrantAccessExpr user1 (SomeFunctionPermission ExecuteFunctionPermission) False))+  assertEqual "success adding function permission" (Right ()) res''''''++  -- check that function execute works+  res''''''' <- executeDatabaseContextExpr sessionId user1conn (ExecuteDatabaseContextFunction "deleteAll" [])+  assertEqual "success calling deleteAll" (Right ()) res'''''''
+ test/AccessControlList.hs view
@@ -0,0 +1,39 @@+import Test.HUnit+import ProjectM36.AccessControlList+import qualified Data.UUID as U+import qualified Data.ByteString.Lazy as BS+import Data.Word+import qualified Data.Map as M+import Data.Maybe+import System.Exit++main :: IO ()+main = do+  tcounts <- runTestTT (TestList [testBasic])+  if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess  ++fakeUUID :: Word8 -> U.UUID+fakeUUID x = fromMaybe (error "impossible uuid") (U.fromByteString (BS.concat (replicate 4 w32)))+  where w32 = BS.pack (replicate 4 x)++testBasic :: Test+testBasic = TestCase $ do+  let func_acl = AccessControlList (M.singleton role1+                                                 (grantable ViewFunctionPermission))+      role1 = fakeUUID 1+      role2 = fakeUUID 2++  assertBool "simple hasAccess" (hasAccess [role1] ViewFunctionPermission func_acl)++  let func_acl1 = addAccess role1 ExecuteFunctionPermission True func_acl+  assertBool "addAccess to existing role" (hasAccess [role1] ExecuteFunctionPermission func_acl1)++  let func_acl2 = addAccess role2 ViewFunctionPermission True func_acl+  assertBool "addAccess with new role" (hasAccess [role2] ViewFunctionPermission func_acl2)+  let func_acl3 = removeAccess role1 ViewFunctionPermission func_acl+  assertBool "remove access" (not (hasAccess [role1] ViewFunctionPermission func_acl3))++  let func_acl4 = addAccess role2 ExecuteFunctionPermission True func_acl+  assertBool "mixed perms" (hasAccess [role2] ExecuteFunctionPermission func_acl4)++  
+ test/Cache.hs view
@@ -0,0 +1,198 @@+import ProjectM36.Base+import ProjectM36.Tuple+import ProjectM36.DatabaseContext.SelfTest+import ProjectM36.Attribute+import ProjectM36.Session+import ProjectM36.Client+import ProjectM36.Relation+import ProjectM36.Cache.RelationalExprCache+import ProjectM36.Cache.Tuple+import ProjectM36.SystemMemory++import Test.HUnit+import Text.Megaparsec+import System.Exit+import Control.Concurrent.STM+import Data.UUID.V4+import qualified Streamly.Data.Stream.Prelude as S+import qualified Data.Vector as V+import System.IO.Temp+import System.IO+import qualified Data.Text as T+import System.FilePath+import qualified Data.Map as M+import Data.Time.Clock+import qualified Data.Set as Set+import System.Random++testList :: Test+testList = TestList [+                     testTupleCacheRoundtripv000,+                     testExpensiveExpr,+                     testCacheEviction,+                     testMemoryPressureParsers+                     ]++main :: IO ()+main = do+  tcounts <- runTestTT testList+  if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess  +++testConnection :: NotificationCallback -> IO (SessionId, Connection)+testConnection callback = do+  dbconn <- connectProjectM36 (InProcessConnectionInfo NoPersistence callback [] selfTestDatabaseContext (mkStdGen 36) adminRoleName)+  case dbconn of +    Left err -> error (show err)+    Right conn -> do+      eSessionId <- createSessionAtHead conn "master"+      case eSessionId of+        Left err -> error (show err)+        Right sessionId -> do+          Right _ <- commit sessionId conn +          pure (sessionId, conn)++{-+testCachePurgeProbability :: Test+testCachePurgeProbability = TestCase $ do+  ++testInMemoryCache :: Test+testInMemoryCache = TestCase $ do+  cache <- empty+  --test adding an entry+-}++testTupleCacheRoundtripv000 :: Test+testTupleCacheRoundtripv000 = TestCase $ do+  newTid <- nextRandom+  let tuples = mkRelationTuples attrs (map (\n -> V.fromList [IntegerAtom n, TextAtom (T.pack (show n))]) numbers)+      numbers = [1 .. 500]+      attrs = attributesFromList [Attribute "a" IntegerAtomType, Attribute "b" TextAtomType]+      expr = RelationVariable "x" newTid+  +  withSystemTempDirectory "pm36tuplecache" $ \tmpdir ->+    withFile (tmpdir </> "pm36tuplecache") ReadWriteMode $ \h -> do+      writeTupleStream h expr 100 tuples+      hSeek h AbsoluteSeek 0+      rrTuples <- S.toList (readTupleStream h)+      assertEqual "round-trip tuple cache" tuples rrTuples+    +testExpensiveExpr :: Test+testExpensiveExpr = TestCase $ do+  -- run expensive query twice, the second time the result should be cached since the cache is large enough and nothing else should be in the cache.+  (session, conn) <- testConnection emptyNotificationCallback+  cache <- case conn of+             RemoteConnection{} -> assertFailure "unexpected remote connection"+             InProcessConnection conf -> pure (ipRelExprCache conf)++  currentSize' <- readTVarIO (currentSize cache)+  assertEqual "cache size zero" 0 currentSize'+  +  Right headTransId <- headTransactionId session conn+  let expensiveExpr = MakeRelationFromExprs Nothing+                      (TupleExprs tmarker+                       [TupleExpr (M.fromList [("expensive",+                                                FunctionAtomExpr "test_expensive"+                                                  [NakedAtomExpr (TextAtom "test"),+                                                   NakedAtomExpr (IntegerAtom 1000000)] tmarker)+                                              ])+                       ])+      tmarker = TransactionIdLookup headTransId+      expensiveResult = mkRelationFromList (attributesFromList [Attribute "expensive" TextAtomType]) [[TextAtom "test"]]+  before <- getCurrentTime+  result1 <- executeTransGraphRelationalExpr session conn expensiveExpr+  +  assertEqual "first expensive run" expensiveResult result1+  after <- getCurrentTime  +  let firstDiff = diffUTCTime after before+--  print $ firstDiff+  assertBool "first expensive time" (firstDiff > 1.0)++  -- run the expensive expression again but we expect that it should be cached+  before' <- getCurrentTime+  result' <- executeTransGraphRelationalExpr session conn expensiveExpr+  +  assertEqual "second expensive run" expensiveResult result'+  after' <- getCurrentTime+  let secondDiff = diffUTCTime after' before'+--  print secondDiff+  assertBool ("second expensive time, actual: " <> show secondDiff) (secondDiff < 1.0)  ++  currentSize'' <- readTVarIO (currentSize cache)+  assertEqual "primed cache size" 80 currentSize''++  putStrLn "ext_expensive"+  -- project on the expensive attribute to check that the composed expression can still be serviced by the cache+  let expensiveExpr2 = Project (AttributeNames (Set.singleton "ext_expensive")) $ Extend (AttributeExtendTupleExpr "ext_expensive" (FunctionAtomExpr "text_length" [AttributeAtomExpr "expensive"] tmarker)) expensiveExpr+      expensiveResult2 = mkRelationFromList (attributesFromList [Attribute "ext_expensive" IntegerAtomType]) [[IntegerAtom 4]]+  before'' <- getCurrentTime++  result'' <- executeTransGraphRelationalExpr session conn expensiveExpr2++  assertEqual "extended expensive (cached)" expensiveResult2 result''++  after'' <- getCurrentTime++  let expensive2Diff = diffUTCTime after'' before''+  assertBool ("stacked expensive time, actual: " <> show expensive2Diff) (expensive2Diff < 1.0)    ++--test cache eviction when size boundaries are hit+testCacheEviction :: Test+testCacheEviction = TestCase $ do+  (session, conn) <- testConnection emptyNotificationCallback+  Right headTransId <- headTransactionId session conn  +  let maxCacheSize = 100+      -- change the attribute to create multiple cache entries+      tmarker = TransactionIdLookup headTransId      +      expensiveExpr attr = MakeRelationFromExprs Nothing+                      (TupleExprs tmarker+                       [TupleExpr (M.fromList [(attr,+                                                FunctionAtomExpr "test_expensive"+                                                  [NakedAtomExpr (TextAtom "test"),+                                                   NakedAtomExpr (IntegerAtom 100000)] tmarker)+                                              ])+                       ])+  +  cache <- case conn of+             RemoteConnection{} -> assertFailure "unexpected remote connection"+             InProcessConnection conf -> pure (ipRelExprCache conf)+  +  -- add something to the cache+  _result1 <- executeTransGraphRelationalExpr session conn (expensiveExpr "expensive1")+  +  -- check size+  currentSize' <- readTVarIO (currentSize cache)+  assertBool ("expensive1 cache size: " <> show currentSize') (currentSize' > 0)++  atomically $ writeTVar (upperBound cache) maxCacheSize++  -- add an item to go over the max cache size+  _result2 <- executeTransGraphRelationalExpr session conn (expensiveExpr "expensive2")  ++  -- check cache size to ensure that previous entry was evicted+  currentSize'' <- readTVarIO (currentSize cache)+  assertBool ("expensive2 cache size: " <> show currentSize'' <> " maxCacheSize: " <> show maxCacheSize) (currentSize'' < maxCacheSize)  -- fails because maxCacheSize is now ignored in preference to totalMem++  -- check that the correct entry was evicted+  before <- getCurrentTime+  _result2 <- executeTransGraphRelationalExpr session conn (expensiveExpr "expensive2")+  after <- getCurrentTime++  assertBool "expensive2 was not cached" (diffUTCTime after before < 1.0)+  +testMemoryPressureParsers :: Test+testMemoryPressureParsers = TestCase $ do+  let memPressureText = "The system has 17179869184 (4194304 pages with a page size of 4096).\n\nStats: \nPages free: 21520 \nPages purgeable: 35807 \nPages purged: 1955197 \n\nSwap I/O:\nSwapins: 3271498 \nSwapouts: 3621839 \n\nPage Q counts:\nPages active: 1384067 \nPages inactive: 1355579 \nPages speculative: 31236 \nPages throttled: 0 \nPages wired down: 785131 \n\nCompressor Stats:\nPages used by compressor: 616157 \nPages decompressed: 16701642 \nPages compressed: 26508007 \n\nFile I/O:\nPageins: 31529331 \nPageouts: 166002 \n\nSystem-wide memory free percentage: 66%"+  let res = parse parseMemoryPressureFreeMem "" memPressureText+  case res of+    Left err -> putStrLn (errorBundlePretty err)+    Right _ -> pure ()+  assertEqual "parse free mem memory_pressure" (Right (21520 * 4096, 17179869184)) res++  let res' = parse parseMemoryPressureValue "" memPressureText+  case res' of+    Left err -> putStrLn (errorBundlePretty err)+    Right _ -> pure ()+  assertEqual "parse memory_pressure percentage" (Right 66) res'+  
test/Client/Simple.hs view
@@ -4,12 +4,13 @@ import ProjectM36.Relation import qualified ProjectM36.Client as C import ProjectM36.DateExamples-import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContext.Basic import System.IO.Temp import System.FilePath-import ProjectM36.TupleSet+import ProjectM36.TupleSet as TS import ProjectM36.Attribute import qualified Data.Map as M+import System.Random  main :: IO ()            main = do @@ -29,7 +30,8 @@ testSimpleCommitSuccess :: Test testSimpleCommitSuccess = TestCase $   withSystemTempDirectory "m36tempdb" $ \tempdir -> do-    let connInfo = InProcessConnectionInfo (MinimalPersistence (tempdir </> "db")) emptyNotificationCallback [] basicDatabaseContext+    rando <- initStdGen+    let connInfo = InProcessConnectionInfo (MinimalPersistence (tempdir </> "db")) emptyNotificationCallback [] basicDatabaseContext rando C.adminRoleName         relExpr = Union (RelationVariable "x" ()) (RelationVariable "y" ())          dbconn <- assertEither (simpleConnectProjectM36 connInfo)@@ -52,21 +54,24 @@ testSimpleCommitFailure = TestCase $ do   let failAttrs = attributesFromList [Attribute "fail" IntAtomType]   err <- withSystemTempDirectory "m36tempdb" $ \tempdir -> do-    let connInfo = InProcessConnectionInfo (MinimalPersistence (tempdir </> "db")) emptyNotificationCallback [] basicDatabaseContext+    rando <- initStdGen+    let connInfo = InProcessConnectionInfo (MinimalPersistence (tempdir </> "db")) emptyNotificationCallback [] basicDatabaseContext rando C.adminRoleName     dbconn <- assertEither (simpleConnectProjectM36 connInfo)     withTransaction dbconn $ do       execute $ Assign "x" (ExistingRelation relationTrue)       --cause error-      execute $ Assign "x" (MakeStaticRelation failAttrs emptyTupleSet)+      execute $ Assign "x" (MakeStaticRelation failAttrs TS.empty)   let expectedErr = Left (RelError (RelationTypeMismatchError mempty failAttrs))   assertEqual "dbc error" expectedErr err  -- #176 default merge couldn't handle Update   testSimpleUpdate :: Test testSimpleUpdate = TestCase $ do-  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext+  rando <- initStdGen+  let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] basicDatabaseContext rando C.adminRoleName   dbconn <- assertEither (simpleConnectProjectM36 connInfo)+  Right dateExprs <- pure dateExamplesDatabaseContextExpr   assertEither $ withTransaction dbconn $ -    execute $ databaseContextAsDatabaseContextExpr dateExamples+    execute dateExprs   assertEither $ withTransaction dbconn $      execute $ Update "s" (M.singleton "sname" (C.NakedAtomExpr (C.TextAtom "Blakey"))) (C.AttributeEqualityPredicate "sname" (C.NakedAtomExpr (C.TextAtom "Blake")))
test/IsomorphicSchema.hs view
@@ -1,17 +1,23 @@ import Test.HUnit import ProjectM36.Base import ProjectM36.Error+import ProjectM36.ValueMarker+import ProjectM36.IsomorphicSchema.Types import ProjectM36.IsomorphicSchema import ProjectM36.Relation import ProjectM36.RelationalExpression import ProjectM36.TransactionGraph import ProjectM36.StaticOptimizer+import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContext.Types import qualified ProjectM36.DatabaseContext as DBC+import qualified ProjectM36.DatabaseContext.Basic as DBC import ProjectM36.Attribute (attributesFromList) import System.Exit import qualified Data.Map as M import qualified Data.Set as S import qualified Data.Vector as V+import Data.Functor.Identity  testList :: Test testList = TestList [testIsoRename, testIsoRestrict, testIsoUnion, testSchemaValidation]@@ -36,24 +42,23 @@ -- create some potential schemas which should not be accepted   testSchemaValidation :: Test testSchemaValidation = TestCase $ do  -  let potentialSchema = DBC.basicDatabaseContext {-        relationVariables = M.singleton "anotherRel" (ExistingRelation relationTrue)-        }+  let rvNames = S.singleton "anotherRel"   -- missing relvars failure       morphs = [IsoRename "true" "true", IsoRename "false" "false"]       missingRelVarError = Just (RelVarReferencesMissing (S.singleton "anotherRel"))-  assertEqual "missing relvar validation" missingRelVarError (validateSchema (Schema morphs) potentialSchema)+  assertEqual "missing relvar validation" missingRelVarError (validateSchema (Schema morphs) rvNames)   -- duplicate relvar mention   let morphs' = [IsoRename "true" "true", IsoRename "false" "true", IsoRename "true" "anotherRel"]       duplicateRelVarError = Just (RelVarOutReferencedMoreThanOnce "true")-  assertEqual "duplicate relvars in morphs" duplicateRelVarError (validateSchema (Schema morphs') potentialSchema)+  assertEqual "duplicate relvars in morphs" duplicateRelVarError (validateSchema (Schema morphs') rvNames)    testIsoRename :: Test testIsoRename = TestCase $ do   -- create a schema with two relvars and rename one while the other remains the same in the isomorphic schema   let ctx = DBC.empty { -        relationVariables = M.fromList [("employee", ExistingRelation relationTrue), -                                        ("department", ExistingRelation relationFalse)]+        relationVariables = ValueMarker $+          M.fromList [("employee", ExistingRelation relationTrue), +                      ("department", ExistingRelation relationFalse)]         }   (graph, _) <- freshTransactionGraph ctx   let isomorphsAtoB = [IsoRename "emp" "employee", @@ -82,7 +87,7 @@                let schemaA = mkRelationalExprEnv baseContext graph       baseContext = DBC.empty {-        relationVariables = M.fromList [("nonboss", ExistingRelation nonBossRel),+        relationVariables = ValueMarker $ M.fromList [("nonboss", ExistingRelation nonBossRel),                                         ("boss", ExistingRelation bossRel)]         }       isomorphsAtoB = [IsoRestrict "employee" predicate ("boss", "nonboss")]@@ -100,13 +105,17 @@   bobRel <- assertEither (mkRelationFromList empattrs [[TextAtom "Bob", TextAtom ""]])   let schemaBInsertExpr = Insert "employee" (ExistingRelation bobRel)   schemaBInsertExpr' <- assertEither (processDatabaseContextExprInSchema (Schema isomorphsAtoB) schemaBInsertExpr)-  let Right dbcState = evalDBCExpr schemaBInsertExpr'-      postInsertContext = dbc_context dbcState-      evalDBCExpr expr' = runDatabaseContextEvalMonad baseContext dbcenv (optimizeAndEvalDatabaseContextExpr False expr')-      dbcenv = mkDatabaseContextEvalEnv transId graph+  let evalDBCExpr expr' = runDatabaseContextEvalMonad baseContext dbcenv (optimizeAndEvalDatabaseContextExpr False expr')+      dbcenv = mkDatabaseContextEvalEnv transId graph dbcfuncutils+      dbcfuncutils = DatabaseContextFunctionUtils {+        executeDatabaseContextExpr = error "test dbc expr",+        executeRelationalExpr = error "test relexpr"+        }+  --execute the expression against the schema and compare against the base context+  dbcState <- assertEither $ evalDBCExpr schemaBInsertExpr'+  let postInsertContext = dbc_context dbcState       expectedRel = runRelationalExprM postInsertEnv (evalRelationalExpr (Union (RelationVariable "boss" ()) (RelationVariable "nonboss" ())))       postInsertEnv = mkRelationalExprEnv postInsertContext graph-  --execute the expression against the schema and compare against the base context   processedExpr <- assertEither (processRelationalExprInSchema (Schema isomorphsAtoB) (RelationVariable "employee" ()))   let processedRel = runRelationalExprM postInsertEnv (evalRelationalExpr processedExpr)   assertEqual "insert bob boss" expectedRel processedRel@@ -133,9 +142,10 @@                 [TextAtom "Scooter", IntegerAtom 49],                 [TextAtom "Auto", IntegerAtom 200],                 [TextAtom "Tractor", IntegerAtom 500]]-  let env = mkRelationalExprEnv DBC.basicDatabaseContext {-        relationVariables = M.singleton "motor" (ExistingRelation motorsRel)-        } graph+  let env = mkRelationalExprEnv (toDatabaseContext $ DBC.basicDatabaseContext {+        relationVariables = Identity $ M.singleton "motor" (ExistingRelation motorsRel)+        }+        ) graph       splitPredicate = AtomExprPredicate (FunctionAtomExpr "lt" [AttributeAtomExpr "power", NakedAtomExpr (IntegerAtom 50)] ())       splitIsomorphs = [IsoUnion ("lowpower", "highpower") splitPredicate "motor",                         IsoRename "true" "true",
+ test/LoginRoles.hs view
@@ -0,0 +1,92 @@+-- | Test correctness of the role-based authorization from LoginRoles.import Test.HUnit+import ProjectM36.LoginRoles as LR+import Test.HUnit+import System.Exit+import Data.UUID as UUID++main :: IO ()+main = do+  let tests = [testRoleMayGrant,+              testAlterLoginRolePrivilege,+              testGrantPermission,+              testAdminRole]+  tcounts <- runTestTT (TestList tests)+  if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess  ++-- test that role with maygrant can share his role and that a role may not granted a role when unprivileged+testRoleMayGrant :: Test+testRoleMayGrant = TestCase $ do+  db <- LR.openNoPersistence+  let run roleName expr = do+        res <- executeAlterLoginRolesExpr roleName db expr+        case res of+          Left err -> assertFailure (show (expr, err))+          Right _ -> pure ()+      runAdmin = run adminRoleName+  --create some roles++  runAdmin (AddLoginRoleExpr "programmer1" True)+  runAdmin (AddLoginRoleExpr "programmer2" True)+  runAdmin (AddLoginRoleExpr "programmer" False) -- make programmer a non-login role++  --grant programmer to programmer1 and may grant privilege+  runAdmin (AddRoleToRoleExpr "programmer1" "programmer" True)++  -- programmer1 can grant programmer to programmer2+  run "programmer1" (AddRoleToRoleExpr "programmer2" "programmer" False)++  -- however, programmer2 may not grant the role+  res <- executeAlterLoginRolesExpr "programmer2" db (AddRoleToRoleExpr "admin" "programmer" False)+  assertEqual "reject grant" (Left PermissionDeniedError) res++    +--test that role without alter_login_roles cannot make changes+testAlterLoginRolePrivilege :: Test+testAlterLoginRolePrivilege = TestCase $ do+  db <- LR.openNoPersistence+  let run roleName expr = do+        res <- executeAlterLoginRolesExpr roleName db expr+        case res of+          Left err -> assertFailure (show (expr, err))+          Right _ -> pure ()+      runAdmin = run adminRoleName++  runAdmin (AddLoginRoleExpr "programmer1" True)+  --programmer1 can see his own roles+  run "programmer1" (ShowRolesForRoleExpr "programmer1")+  +  -- programmer1 cannot view all the roles+  res <- executeAlterLoginRolesExpr "programmer1" db ShowAllRolesExpr+  assertEqual "reject show all roles" (Left PermissionDeniedError) res  ++  --programmer1 cannot alter the roles+  res' <- executeAlterLoginRolesExpr "programmer1" db (AddLoginRoleExpr "evilprogrammer" True)+  assertEqual "reject role add" (Left PermissionDeniedError) res'+  +--test permission addition and removal+testGrantPermission :: Test+testGrantPermission = TestCase $ do+    db <- LR.openNoPersistence+    let run roleName expr = do+          res <- executeAlterLoginRolesExpr roleName db expr+          case res of+            Left err -> assertFailure (show (expr, err))+            Right _ -> pure ()+        runAdmin = run adminRoleName+    runAdmin (AddLoginRoleExpr "programmer1" True)+    runAdmin (AddLoginRoleExpr "programmer2" True)+    runAdmin (AddPermissionToRoleExpr "programmer1" viewLoginRolesPerm False)++    res' <- executeAlterLoginRolesExpr "programmer1" db (AddPermissionToRoleExpr "programmer2" viewLoginRolesPerm False)+    assertEqual "reject perm grant" (Left PermissionDeniedError) res'++-- admin role has a special nil UUID+testAdminRole :: Test+testAdminRole = TestCase $ do+  -- db <- LR.openNoPersistence+  db <- LR.open "loginroles.db"+  LR.setupDatabaseIfNecessary db+  eRes <- roleIdsForRoleName LR.adminRoleName db+  assertEqual "roleIds for admin" (Right [UUID.nil]) eRes+  +
test/MultiProcessDatabaseAccess.hs view
@@ -5,6 +5,7 @@ import System.IO.Temp import System.Exit import System.FilePath+import System.Random  main :: IO () main = do@@ -24,7 +25,8 @@ testMultipleProcessAccess :: Test testMultipleProcessAccess = TestCase $    withSystemTempDirectory "pm36" $ \tmpdir -> do-    let connInfo = InProcessConnectionInfo (MinimalPersistence dbdir) emptyNotificationCallback [] basicDatabaseContext+    rando <- initStdGen+    let connInfo = InProcessConnectionInfo (MinimalPersistence dbdir) emptyNotificationCallback [] basicDatabaseContext rando adminRoleName         master = "master"         dudExpr = Assign "x" (RelationVariable "true" ())         dbdir = tmpdir </> "db"
test/Relation/Basic.hs view
@@ -3,6 +3,7 @@ import ProjectM36.Relation import ProjectM36.Error import ProjectM36.DateExamples+import ProjectM36.DatabaseContext import ProjectM36.DataTypes.Primitive import ProjectM36.RelationalExpression import ProjectM36.TransactionGraph@@ -69,12 +70,12 @@                                   A.atomTypeForAttributeName (A.attributeName attr) attrs) (attributesVec (attributes rel))      simpleRel :: Relation    -simpleRel = case mkRelation attrs tupleSet of+simpleRel = case mkRelation attrs tupleSet' of   Right rel -> rel   Left _ -> undefined   where     attrs = A.attributesFromList [Attribute "a" TextAtomType, Attribute "b" TextAtomType]-    tupleSet = RelationTupleSet [mkRelationTuple attrs (V.fromList [TextAtom "spam", TextAtom "spam2"])]+    tupleSet' = RelationTupleSet [mkRelationTuple attrs (V.fromList [TextAtom "spam", TextAtom "spam2"])]      --rename tests testRename1 :: Test@@ -106,9 +107,9 @@  testExistingRelationType :: Test testExistingRelationType = TestCase $ do-  (graph, _) <- freshTransactionGraph dateExamples+  (graph, _) <- freshTransactionGraph' dateExamples   let typeResult = runRelationalExprM reenv (typeForRelationalExpr (ExistingRelation relationTrue))-      reenv = mkRelationalExprEnv dateExamples graph+      reenv = mkRelationalExprEnv (toDatabaseContext dateExamples) graph   assertEqual "ExistingRelation with tuples type" (Right relationFalse) typeResult  -- | Ensure that tuple reordering honors the 
test/Relation/StaticOptimizer.hs view
@@ -2,9 +2,10 @@ import ProjectM36.Relation import ProjectM36.TransactionGraph import ProjectM36.DateExamples-import ProjectM36.TupleSet+import ProjectM36.TupleSet as TS import ProjectM36.StaticOptimizer-import ProjectM36.DatabaseContext as DBC+import qualified ProjectM36.DatabaseContext as DBC+import ProjectM36.DatabaseContext.Types import System.Exit import Test.HUnit import qualified Data.Set as S@@ -40,12 +41,18 @@                        Assign "z" (Restrict TruePredicate (relvar "s"))])         ]   forM_ testInfos $ \(name, expectedExpr, unoptExpr) -> do-    let optExpr = runGraphRefSOptDatabaseContextExprM transId dateExamples graph (optimizeDatabaseContextExpr unoptExpr)+    let optExpr = runGraphRefSOptDatabaseContextExprM transId (DBC.toDatabaseContext dateExamples) graph dudFunctionUtils (optimizeDatabaseContextExpr unoptExpr)     assertEqual name expectedExpr optExpr +dudFunctionUtils :: DatabaseContextFunctionUtils+dudFunctionUtils = DatabaseContextFunctionUtils {+  executeDatabaseContextExpr = error "test executeDatabaseContextExpr",+  executeRelationalExpr = error "test executeRelationalExpr"+  }+ testTransitiveOptimization :: Test testTransitiveOptimization = TestCase $ do-  (graph, _) <- freshTransactionGraph dateExamples+  (graph, _) <- freshTransactionGraph' dateExamples   let expr atomexpr = AndPredicate (AttributeEqualityPredicate "a" (NakedAtomExpr (IntegerAtom 300))) (AttributeEqualityPredicate "b" atomexpr)       exprIn = expr (AttributeAtomExpr "a")       exprOut = expr (NakedAtomExpr (IntegerAtom 300))@@ -55,7 +62,7 @@  testOptimizeRelationalExpr :: Test testOptimizeRelationalExpr = TestCase $ do-  (graph, _) <- freshTransactionGraph dateExamples+  (graph, _) <- freshTransactionGraph' dateExamples   let relvarId nam = RelationVariable nam UncommittedContextMarker       relationOptTests = [         ("StaticProject",@@ -69,19 +76,19 @@          Join (relvarId "s") (relvarId "s"))         ]   forM_ relationOptTests $ \(name, expr, unoptExpr) -> do-    let optExpr = runGraphRefSOptRelationalExprM (Just dateExamples) graph (optimizeGraphRefRelationalExpr unoptExpr)+    let optExpr = runGraphRefSOptRelationalExprM (Just (DBC.toDatabaseContext dateExamples)) graph (optimizeGraphRefRelationalExpr unoptExpr)     assertEqual name expr optExpr  testImpossiblePredicates :: Test   testImpossiblePredicates = TestCase $ do-  (graph, _) <- freshTransactionGraph dateExamples  +  (graph, _) <- freshTransactionGraph' dateExamples   let tautTrue = Restrict (AtomExprPredicate (NakedAtomExpr (BoolAtom True))) rel       tautTrue2 = Restrict TruePredicate rel       tautFalse = Restrict (AtomExprPredicate (NakedAtomExpr (BoolAtom False))) rel       tautFalse2 = Restrict (NotPredicate TruePredicate) rel-      emptyRel = MakeStaticRelation (attributes suppliersRel) emptyTupleSet+      emptyRel = MakeStaticRelation (attributes suppliersRel) TS.empty       rel = RelationVariable "s" UncommittedContextMarker-      optRelExpr = optimizeGraphRefRelationalExpr' (Just dateExamples) graph+      optRelExpr = optimizeGraphRefRelationalExpr' (Just (DBC.toDatabaseContext dateExamples)) graph   assertEqual "remove tautology where true" (Right rel) (optRelExpr tautTrue)   assertEqual "remove tautology where true2" (Right rel) (optRelExpr tautTrue2)   assertEqual "remove tautology where false" (Right emptyRel) (optRelExpr tautFalse)@@ -89,9 +96,9 @@  testJoinElimination :: Test   testJoinElimination = TestCase $ do-  (graph, _) <- freshTransactionGraph dateExamples  +  (graph, _) <- freshTransactionGraph' dateExamples   let marker = UncommittedContextMarker-      optJoinExpr expr = runGraphRefSOptRelationalExprM (Just dateExamples) graph (applyStaticJoinElimination expr)+      optJoinExpr expr = runGraphRefSOptRelationalExprM (Just (DBC.toDatabaseContext dateExamples)) graph (applyStaticJoinElimination expr)       relvar rv = RelationVariable rv marker    -- (s join sp){s#,qty,p#} == sp
test/SQL/InterpreterTest.hs view
@@ -5,7 +5,7 @@ import TutorialD.Interpreter.RODatabaseContextOperator import TutorialD.Interpreter.DatabaseContextExpr import ProjectM36.RelationalExpression---import ProjectM36.StaticOptimizer+import ProjectM36.DatabaseContext.Types hiding (DatabaseContextFunctionUtils(..)) import SQL.Interpreter.DBUpdate import SQL.Interpreter.CreateTable import SQL.Interpreter.Base (semi)@@ -16,6 +16,7 @@ import ProjectM36.NormalizeExpr import ProjectM36.Client hiding (typeConstructorMapping) import ProjectM36.SQLDatabaseContext+import ProjectM36.DatabaseContextExpr import ProjectM36.Base import ProjectM36.Relation import qualified ProjectM36.Attribute as A@@ -25,6 +26,8 @@ import qualified Data.Text as T import qualified Data.Map as M import TutorialD.Printer+import Data.Functor.Identity+import System.Random  main :: IO () main = do@@ -56,8 +59,8 @@ testSelect :: Test testSelect = TestCase $ do   -- check that SQL and tutd compile to same thing-  let sqlDBContext = dateExamples { relationVariables = M.insert "snull" (ExistingRelation sNullRelVar) (relationVariables dateExamples) }-  (tgraph,transId) <- freshTransactionGraph sqlDBContext+  let sqlDBContext = dateExamples { relationVariables = Identity (M.singleton "snull" (ExistingRelation sNullRelVar)) <> relationVariables dateExamples }+  (tgraph,transId) <- freshTransactionGraph (toDatabaseContext sqlDBContext)   (sess, conn) <- dateExamplesConnection emptyNotificationCallback      let readTests = [{-@@ -286,7 +289,7 @@          "(relation{attr_1 SQLNullable Bool}{tuple{attr_1 SQLJust True}})")-}         ]       gfEnv = GraphRefRelationalExprEnv {-        gre_context = Just sqlDBContext,+        gre_context = Just (toDatabaseContext sqlDBContext),         gre_graph = tgraph,         gre_extra = mempty }       typeF expr = do@@ -333,8 +336,8 @@  testCreateTable :: Test testCreateTable = TestCase $ do-  let sqlDBContext = dateExamples { relationVariables = M.insert "snull" (ExistingRelation sNullRelVar) (relationVariables dateExamples) }-  (tgraph,transId) <- freshTransactionGraph sqlDBContext+  let sqlDBContext = dateExamples { relationVariables = Identity (M.singleton "snull" (ExistingRelation sNullRelVar)) <> relationVariables dateExamples }+  (tgraph,transId) <- freshTransactionGraph (toDatabaseContext sqlDBContext)    let createTableTests = [         --no columns@@ -368,7 +371,7 @@           Right x -> do             pure x       gfEnv = GraphRefRelationalExprEnv {-        gre_context = Just sqlDBContext,+        gre_context = Just (toDatabaseContext sqlDBContext),         gre_graph = tgraph,         gre_extra = mempty }       typeF expr = do@@ -397,10 +400,10 @@ testDBUpdate :: Test testDBUpdate = TestCase $ do   let sqlDBContext = dateExamples { relationVariables =-                                      M.insert "snull" (ExistingRelation sNullRelVar) (relationVariables dateExamples),-                                    typeConstructorMapping = typeConstructorMapping dateExamples <> nullTypeConstructorMapping+                                      Identity (M.singleton "snull" (ExistingRelation sNullRelVar)) <> relationVariables dateExamples,+                                    typeConstructorMapping = typeConstructorMapping dateExamples <> Identity nullTypeConstructorMapping                                   }-  (tgraph,transId) <- freshTransactionGraph sqlDBContext+  (tgraph,transId) <- freshTransactionGraph (toDatabaseContext sqlDBContext)    let updateTests = [         -- simple insert with no nulls@@ -431,7 +434,7 @@           Right x -> do             pure x       gfEnv = GraphRefRelationalExprEnv {-        gre_context = Just sqlDBContext,+        gre_context = Just (toDatabaseContext sqlDBContext),         gre_graph = tgraph,         gre_extra = mempty } {-      typeF = @@ -477,7 +480,8 @@ --  assertEqual "SELECT * FROM test"  (Right (Select {distinctness = Nothing, projectionClause = [(Identifier (QualifiedProjectionName [Asterisk]),Nothing)], tableExpr = Just (TableExpr {fromClause = [SimpleTableRef (QualifiedName ["test"])], whereClause = Nothing, groupByClause = [], havingClause = Nothing, orderByClause = [], limitClause = Nothing, offsetClause = Nothing})})) (p "SELECT * FROM test") dateExamplesConnection :: NotificationCallback -> IO (SessionId, Connection) dateExamplesConnection callback = do-  dbconn <- connectProjectM36 (InProcessConnectionInfo NoPersistence callback [] sqlDatabaseContext)+  rando <- initStdGen+  dbconn <- connectProjectM36 (InProcessConnectionInfo NoPersistence callback [] sqlDatabaseContext rando adminRoleName)   case dbconn of      Left err -> error (show err)     Right conn -> do@@ -485,11 +489,14 @@       case eSessionId of         Left err -> error (show err)         Right sessionId -> do-          executeDatabaseContextExpr sessionId conn (databaseContextAsDatabaseContextExpr dateExamples) >>= eitherFail-          --add a relvar with some nulls-          executeDatabaseContextExpr sessionId conn addNullTable >>= eitherFail-          commit sessionId conn >>= eitherFail-          pure (sessionId, conn)+          case databaseContextAsDatabaseContextExpr (toDatabaseContext dateExamples) emptyTransactionGraph of+            Left err -> assertFailure (show err)+            Right dbexpr -> do+              executeDatabaseContextExpr sessionId conn dbexpr >>= eitherFail+              --add a relvar with some nulls+              executeDatabaseContextExpr sessionId conn addNullTable >>= eitherFail+              commit sessionId conn >>= eitherFail+              pure (sessionId, conn)  eitherFail :: Either RelationalError a -> IO () eitherFail (Left err) = assertFailure (show err)
test/Server/Main.hs view
@@ -1,14 +1,15 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP, LambdaCase #-} {- test client/server interaction -} import Test.HUnit import ProjectM36.Client import qualified ProjectM36.Client as C+import ProjectM36.IsomorphicSchema.Types import ProjectM36.Server import ProjectM36.Server.Config import ProjectM36.Relation-import ProjectM36.TupleSet+import ProjectM36.TupleSet as TS import ProjectM36.IsomorphicSchema import ProjectM36.Base @@ -20,6 +21,8 @@ import System.IO.Temp import System.FilePath import Control.Monad (void)+import Network.RPC.Curryer.Client (ClientConnectionConfig(..))+import qualified Network.RPC.Curryer.Server as S #if defined(linux_HOST_OS) import System.Directory #endif@@ -38,7 +41,7 @@       testPlanForDatabaseContextExpr,       testTransactionGraphAsRelation,       testHeadTransactionId,-      testHeadName,+      testCurrentHead,       testSession,       testRelationVariableSummary,       testNotification notificationTestMVar@@ -56,17 +59,12 @@       tcounts <- runTestTT (testList session testConn notificationTestMVar)       if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess -{-main = do-    tcounts <- runTestTT (TestList [testRequestTimeout])-    if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess-}-                                                                      testDatabaseName :: DatabaseName testDatabaseName = "test"  testConnection :: Port -> MVar () -> IO (Either ConnectionError (SessionId, Connection)) testConnection serverPort mvar = do-  let connInfo = RemoteConnectionInfo testDatabaseName (RemoteServerHostAddress "127.0.0.1" serverPort) (testNotificationCallback mvar)-  --putStrLn ("testConnection: " ++ show serverAddress)+  let connInfo = RemoteConnectionInfo testDatabaseName (RemoteServerHostAddress "127.0.0.1" serverPort) UnencryptedConnectionConfig (testNotificationCallback mvar) "admin"   eConn <- connectProjectM36 connInfo   case eConn of      Left err -> pure $ Left err@@ -87,22 +85,23 @@                                          perRequestTimeout = ti,                                          testMode = True,                                          bindAddress = RemoteServerHostAddress "127.0.0.1" 0,-                                         checkFS = False --not stricly needed for these tests+                                         checkFS = False, --not strictly needed for these tests+                                         connConfig = S.UnencryptedConnectionConfig                                        }            void $ launchServer config (Just addressMVar)   (SockAddrInet port _) <- takeMVar addressMVar-  --liftIO $ putStrLn ("launched server on " ++ show endPointAddress)   pure (fromIntegral port, tid)    testRelationalExpr :: SessionId -> Connection -> Test   testRelationalExpr sessionId conn = TestCase $ do   relResult <- executeRelationalExpr sessionId conn (RelationVariable "true" ())   assertEqual "invalid relation result" (Right relationTrue) relResult-  -eitherFail :: (Show e) => Either e a -> IO ()++#define eitherFail (\case { Left err -> assertFailure (show err); Right _ -> pure () })+{-eitherFail :: (Show e) => Either e a -> IO () eitherFail (Left err) = assertFailure (show err)-eitherFail (Right _) = pure ()+eitherFail (Right _) = pure ()-}    -- test adding an removing a schema against true/false relations   testSchemaExpr :: SessionId -> Connection -> Test@@ -119,13 +118,13 @@       testrv = "testrv"   executeDatabaseContextExpr sessionId conn (Define testrv attrExprs) >>= eitherFail   eRel <- executeRelationalExpr sessionId conn (RelationVariable testrv ())-  let expected = mkRelation attrs emptyTupleSet+  let expected = mkRelation attrs TS.empty   case eRel of     Left err -> assertFailure (show err)     Right rel -> assertEqual "dbcontext definition failed" expected (Right rel)          testGraphExpr :: SessionId -> Connection -> Test        -testGraphExpr sessionId conn = TestCase (executeGraphExpr sessionId conn (JumpToHead "master") >>= eitherFail)+testGraphExpr sessionId conn = TestCase (executeTransactionGraphExpr sessionId conn (JumpToHead "master") >>= eitherFail)      testTypeForRelationalExpr :: SessionId -> Connection -> Test testTypeForRelationalExpr sessionId conn = TestCase $ do@@ -158,10 +157,10 @@   assertBool "invalid head transaction uuid" (isRight uuid)   pure ()   -testHeadName :: SessionId -> Connection -> Test-testHeadName sessionId conn = TestCase $ do-  eHeadName <- headName sessionId conn-  assertEqual "headName failure" (Right "master") eHeadName+testCurrentHead :: SessionId -> Connection -> Test+testCurrentHead sessionId conn = TestCase $ do+  eCurrentHead <- currentHead sessionId conn+  assertEqual "headName failure" (Right (CurrentHeadBranch "master")) eCurrentHead    testRelationVariableSummary :: SessionId -> Connection -> Test   testRelationVariableSummary sessionId conn = TestCase $ do@@ -181,7 +180,7 @@       case eHeadId of         Left err -> assertFailure ("invalid head id: " ++ show err)         Right headId -> do-          eSessionId2 <- createSessionAtCommit conn headId+          eSessionId2 <- createSessionAtTransactionId conn headId           assertBool ("invalid session: " ++ show eSessionId2) (isRight eSessionId2)           closeSession sessionId1 conn @@ -242,7 +241,7 @@  testClientConnectFail :: Test testClientConnectFail = TestCase $ do-  let connInfo = RemoteConnectionInfo "nonexistentdb" (RemoteServerHostAddress "127.0.0.1" 7777) emptyNotificationCallback+  let connInfo = RemoteConnectionInfo "nonexistentdb" (RemoteServerHostAddress "127.0.0.1" 7777) UnencryptedConnectionConfig emptyNotificationCallback "admin"   eConn <- connectProjectM36 connInfo   case eConn of     Left (IOExceptionError _) -> pure ()
test/Server/WebSocket.hs view
@@ -6,6 +6,7 @@ import ProjectM36.Server.Config import ProjectM36.Server import ProjectM36.Client+import qualified Network.RPC.Curryer.Server as S  import Network.Socket import Control.Exception@@ -27,7 +28,9 @@   addressMVar <- newEmptyMVar   let config = defaultServerConfig { databaseName = testDatabaseName,                                       bindAddress = RemoteServerHostAddress "127.0.0.1" 0,-                                     checkFS = False}+                                     checkFS = False,+                                     connConfig = S.UnencryptedConnectionConfig+                                     }       testDatabaseName = "test"   -- start normal server   void $ forkIO (void $ launchServer config (Just addressMVar))
test/TransactionGraph/Automerge.hs view
@@ -8,6 +8,7 @@ import System.IO.Temp import System.FilePath import Control.Exception.Base+import System.Random  {-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} main :: IO ()           @@ -18,7 +19,8 @@ testList :: Test testList = TestList [testAutomergeSuccess,                      testAutomergeFailure,-                     testAutomergeReconnect]+                     testAutomergeReconnect+                     ]             checkEither :: IO (Either RelationalError a) -> IO a checkEither io = do@@ -51,8 +53,8 @@      assertEqual "new S7" relationTrue result'   -  eHeadName <- headName sessionPastId conn-  assertEqual "back on master" (Right "master") eHeadName+  eCurrentHead <- currentHead sessionPastId conn+  assertEqual "back on master" (Right (CurrentHeadBranch "master")) eCurrentHead    testAutomergeFailure :: Test   testAutomergeFailure = TestCase $ do@@ -78,7 +80,8 @@ testAutomergeReconnect :: Test testAutomergeReconnect = TestCase $ withSystemTempDirectory "m36testdb" $ \tempdir -> do   let repro = do-          conn <- unsafeLeftCrash =<< connectProjectM36 (InProcessConnectionInfo (CrashSafePersistence (tempdir </> "test.db")) emptyNotificationCallback [] basicDatabaseContext)+          rando <- initStdGen+          conn <- unsafeLeftCrash =<< connectProjectM36 (InProcessConnectionInfo (CrashSafePersistence (tempdir </> "test.db")) emptyNotificationCallback [] basicDatabaseContext rando adminRoleName)           sess <- unsafeLeftCrash =<< createSessionAtHead conn "master"           autoMergeToHead sess conn UnionMergeStrategy "master"         -- commit sess conn
test/TransactionGraph/Merge.hs view
@@ -3,15 +3,20 @@ import ProjectM36.Base import ProjectM36.Attribute import ProjectM36.Relation-import ProjectM36.Transaction+import ProjectM36.ValueMarker import ProjectM36.TransactionInfo as TI+import ProjectM36.DatabaseContext.Types import ProjectM36.TransactionGraph+import ProjectM36.IsomorphicSchema.Types hiding (concreteDatabaseContext)+import ProjectM36.TransactionGraph.Types import ProjectM36.Error import ProjectM36.Key-import qualified ProjectM36.DisconnectedTransaction as Discon-import qualified ProjectM36.DatabaseContext as DBC+import ProjectM36.DatabaseContext (toDatabaseContext)+import qualified ProjectM36.DatabaseContext.Basic as DBC import ProjectM36.RelationalExpression import ProjectM36.StaticOptimizer+import ProjectM36.Transaction.Types+import ProjectM36.DatabaseContext.Fields  import qualified Data.ByteString.Lazy as BS import System.Exit@@ -22,6 +27,7 @@ import Data.Maybe import Data.Time.Clock import Data.Time.Calendar+import Data.Either  {-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} @@ -39,7 +45,9 @@   testSubGraphToFirstAncestorSnipBranch,   testSubGraphToFirstAncestorMoreTransactions,   testSelectedBranchMerge,-  testUnionMergeStrategy,+  testUnionMergeStrategyOnRelVars,+  testUnionMergeStrategyOnInclusionDependencies,+  testUnionMergeStrategyOnInclusionDependenciesConflict,   testUnionPreferMergeStrategy,   testUnionMergeIncDepViolation   ]@@ -51,33 +59,45 @@ |--- branchB bbbb -} -createTrans :: TransactionId -> TransactionInfo -> DatabaseContext -> Transaction-createTrans tid info ctx = Transaction tid info (Schemas ctx M.empty)+createTrans :: TransactionId -> TransactionInfo -> DatabaseContext -> UncommittedTransaction+createTrans tid info' ctx = UncommittedTransaction $ Transaction tid info' (Schemas ctx emptyValue)  basicTransactionGraph :: IO TransactionGraph basicTransactionGraph = do-  let bsGraph = bootstrapTransactionGraph testTime uuidRoot DBC.basicDatabaseContext+  let bsGraph = bootstrapTransactionGraph testTime uuidRoot (toDatabaseContext DBC.basicDatabaseContext)       rootTrans = fromMaybe (error "bonk") (transactionForHead "master" bsGraph)-      uuidA = fakeUUID 10-      uuidB = fakeUUID 11-      uuidRoot = fakeUUID 1+      uuidA = fakeUUID [10]+      uuidB = fakeUUID [11]+      uuidRoot = fakeUUID [1]       rootContext = concreteDatabaseContext rootTrans-  (_, bsGraph') <- addTransaction "branchA" (createTrans uuidA (TI.singleParent uuidRoot testTime) rootContext) bsGraph-  (_, bsGraph'') <- addTransaction "branchB" (createTrans uuidB (TI.singleParent uuidRoot testTime) rootContext) bsGraph'+  (_, bsGraph') <- addTransaction "basicTransactionGraph" "branchA" (createTrans uuidA (TI.singleParent uuidRoot testTime) rootContext) bsGraph+  (_, bsGraph'') <- addTransaction "basicTransactionGraph2" "branchB" (createTrans uuidB (TI.singleParent uuidRoot testTime) rootContext) bsGraph'   pure bsGraph''   -addTransaction :: HeadName -> Transaction -> TransactionGraph -> IO (Transaction, TransactionGraph)-addTransaction headName transaction graph = case addTransactionToGraph headName transaction graph of-  Left err -> assertFailure (show err) >> error ""+addTransaction :: String -> HeadName -> UncommittedTransaction -> TransactionGraph -> IO (UncommittedTransaction, TransactionGraph)+addTransaction ctx headName transaction graph = case addTransactionToGraph headName transaction graph of+  Left err -> assertFailure (ctx <> ": " <> show err) >> error ""   Right (t,g) -> pure (t,g)               -fakeUUID :: Word8 -> TransactionId-fakeUUID x = fromMaybe (error "impossible uuid") (U.fromByteString (BS.concat (replicate 4 w32)))-  where w32 = BS.pack (replicate 4 x)+fakeUUID :: [Word8] -> TransactionId+fakeUUID x = fromMaybe (error "impossible uuid") (U.fromByteString repbs)+  where repbs = BS.pack (take 16 (cycle x))++data TestBranchName = BranchA | BranchB+  deriving (Show, Eq)++-- makes a fake uuid based on the branch name Char and increment Word8+fakeUUID' :: TestBranchName -> Word8 -> TransactionId+fakeUUID' branchName version =+  fakeUUID [intBranch, version]+  where+    intBranch = case branchName of+                  BranchA -> 10+                  BranchB -> 11   -assertEither :: (Show a) => Either a b -> IO b-assertEither x = case x of-  Left err -> assertFailure (show err) >> undefined+assertEither :: (Show a) => String -> Either a b -> IO b+assertEither ctx x = case x of+  Left err -> assertFailure (ctx ++ ": " ++ show err) >> undefined   Right val -> pure val    assertGraph :: TransactionGraph -> IO ()  @@ -97,7 +117,7 @@   assertGraph graph   transA <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA"   transB <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB"-  subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph graph) transA transB S.empty+  subgraph <- assertEither "testSubGraphToFirstAncestorBasic" $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph graph) transA transB S.empty   let graphEq graphArg = S.map transactionId (transactionsForGraph graphArg)     assertEqual "no graph changes" (graphEq graph) (graphEq subgraph)    @@ -107,8 +127,8 @@   baseGraph <- basicTransactionGraph     transA <- assertMaybe (transactionForHead "branchA" baseGraph) "failed to get branchA"   transB <- assertMaybe (transactionForHead "branchB" baseGraph) "failed to get branchB"-  (_, graph) <- addTransaction "branchC" (createTrans (fakeUUID 12) (TI.singleParent (fakeUUID 1) testTime) (concreteDatabaseContext transA)) baseGraph-  subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph baseGraph) transA transB S.empty+  (_, graph) <- addTransaction "testSubGraphToFirstAncestorSnipBranch" "branchC" (createTrans (fakeUUID [12]) (TI.singleParent (fakeUUID [1]) testTime) (concreteDatabaseContext transA)) baseGraph+  subgraph <- assertEither "testSubGraphToFirstAncestorSnipBranch" $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph baseGraph) transA transB S.empty   assertGraph subgraph   let graphEq graphArg = S.map transactionId (transactionsForGraph graphArg)     assertEqual "failed to snip branch" (graphEq baseGraph) (graphEq subgraph)@@ -121,55 +141,65 @@    -- add another relvar to branchB   branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"-  let env = mkDatabaseContextEvalEnv (transactionId branchBTrans) graph+  let env = mkDatabaseContextEvalEnv (transactionId branchBTrans) graph dudFunctionUtils       branchBContext = concreteDatabaseContext branchBTrans   updatedBranchBContext <- case runDatabaseContextEvalMonad branchBContext env (optimizeAndEvalDatabaseContextExpr True (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) of     Left err -> assertFailure (show err) >> undefined     Right st -> pure $ dbc_context st-  (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph+  (_, graph') <- addTransaction "testSubGraphToFirstAncestorMoreTransactions" "branchB" (createTrans (fakeUUID [3]) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph   branchBTrans' <- assertMaybe (transactionForHead "branchB" graph') "failed to get branchB head"  -  assertEqual "branchB id 3" (fakeUUID 3) (transactionId branchBTrans')  +  assertEqual "branchB id 3" (fakeUUID [3]) (transactionId branchBTrans')        -- add another transaction to branchA   branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"  -  (_, graph'') <- addTransaction "branchA" (createTrans (fakeUUID 4) (TI.singleParent (transactionId branchATrans) testTime) (concreteDatabaseContext branchATrans)) graph'+  (_, graph'') <- addTransaction "testSubGraphToFirstAncestorMoreTransactions2" "branchA" (createTrans (fakeUUID [4]) (TI.singleParent (transactionId branchATrans) testTime) (concreteDatabaseContext branchATrans)) graph'   branchATrans' <- assertMaybe (transactionForHead "branchA" graph'') "failed to get branchA head"-  assertEqual "branchA id 4" (fakeUUID 4) (transactionId branchATrans')+  assertEqual "branchA id 4" (fakeUUID [4]) (transactionId branchATrans')                                                  --retrieve subgraph                                            -  let subGraphHeads = M.filter (\t -> transactionId t `elem` [fakeUUID 3, fakeUUID 4]) (transactionHeadsForGraph graph'')-  subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph'' subGraphHeads branchATrans' branchBTrans' S.empty+  let subGraphHeads = M.filter (\t -> transactionId t `elem` [fakeUUID [3], fakeUUID [4]]) (transactionHeadsForGraph graph'')+  subgraph <- assertEither "testSubGraphToFirstAncestorMoreTransactions" $ subGraphOfFirstCommonAncestor graph'' subGraphHeads branchATrans' branchBTrans' S.empty   --verify that the subgraph includes both the heads and the common ancestor-  let expectedTransSet = S.fromList (map fakeUUID [1,3,4])+  let expectedTransSet = S.fromList (map fakeUUID [[1],[3],[4]])   assertBool "validate transactions in subgraph" (S.isProperSubsetOf expectedTransSet (S.map transactionId (transactionsForGraph subgraph)))++dudFunctionUtils :: DatabaseContextFunctionUtils+dudFunctionUtils = DatabaseContextFunctionUtils {+  executeDatabaseContextExpr = error "test executeDatabaseContextExpr",+  executeRelationalExpr = error "test executeRelationalExpr"+  }    testSelectedBranchMerge :: Test testSelectedBranchMerge = TestCase $ do   graph <- basicTransactionGraph   assertGraph graph   -  -- add another relvar to branchB+  -- add a relvar "branchBOnlyRelvar" to branchB only   branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"-  let env = mkDatabaseContextEvalEnv (transactionId branchBTrans) graph+  let env = mkDatabaseContextEvalEnv (transactionId branchBTrans) graph dudFunctionUtils       branchBContext = concreteDatabaseContext branchBTrans     updatedBranchBContext <- case runDatabaseContextEvalMonad branchBContext env (optimizeAndEvalDatabaseContextExpr True (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) of     Left err -> assertFailure (show err) >> undefined     Right st -> pure (dbc_context st)-  (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph+    -- add the transaction with the "branchBOnlyRelvar" to the graph at transaction ID 3+  (_, graph') <- addTransaction "testSelectedBranchMerge" "branchB" (createTrans (fakeUUID' BranchB 2) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph   --create the merge transaction in the graph-  let eGraph' = runGraphRefRelationalExprM gfEnv $ mergeTransactions testTime (fakeUUID 4) (fakeUUID 10) (SelectedBranchMergeStrategy "branchA") ("branchA", "branchB")+  let eGraph' = runGraphRefRelationalExprM gfEnv $ mergeTransactions testTime (fakeUUID' BranchB 3 {-new trans ID-}) (fakeUUID' BranchB 2 {-add transaction to parent ID-}) (SelectedBranchMergeStrategy "branchA") (MergeHeadNames { sourceHead = "branchA", targetHead = "branchB" })       gfEnv = freshGraphRefRelationalExprEnv Nothing graph'       -  (_, graph'') <- assertEither eGraph'+  (_, graph'') <- assertEither "testSelectedBranchMerge" eGraph' +--  putStrLn $ showGraphStructureX True graph''+   assertGraph graph''   --validate that the branchB remains   branchBTrans' <- assertMaybe (transactionForHead "branchB" graph'') "failed to find branchB head"-  assertEqual "head of merged transaction was removed" (transactionId branchBTrans') (fakeUUID 3)+  assertEqual "head of merged transaction was removed" (fakeUUID' BranchB 3) (transactionId branchBTrans')    --validate that the branchB relvar does *not* appear in the merge because branchA was selected-  mergeTrans <- assertEither (transactionForId (fakeUUID 4) graph'')-  assertBool "branchOnlyRelvar is present in merge" (M.notMember "branchBOnlyRelvar" (relationVariables (concreteDatabaseContext mergeTrans)))+  mergeTrans <- assertEither "testSelectedBranchMerge2" (transactionForId (fakeUUID' BranchB 3) graph'')+  mergedRVs <- assertEither "testSelectedBranchMerge3" $ resolveDBC' graph'' (concreteDatabaseContext mergeTrans) relationVariables+  assertBool "branchOnlyRelvar is present in merge" (M.notMember "branchBOnlyRelvar" mergedRVs)  -- try various individual component conflicts and check for resolution testUnionPreferMergeStrategy :: Test@@ -178,77 +208,137 @@   graph <- basicTransactionGraph   branchATrans <- assertMaybe (transactionForHead "branchA" graph) "branchATrans"   branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "branchBTrans"-  branchBRelVar <- assertEither $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []  -  let branchAContext = (concreteDatabaseContext branchATrans) {relationVariables = M.insert conflictRelVarName branchARelVar (relationVariables (concreteDatabaseContext branchATrans))}+  branchBRelVar <- assertEither "testUnionPreferMergeStrategy" $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []+  branchARVs <- assertEither "testUnionPreferMergeStrategy2" $ resolveDBC' graph (concreteDatabaseContext branchATrans) relationVariables+  branchBRVs <- assertEither "testUnionPreferMergeStrategy3" $ resolveDBC' graph (concreteDatabaseContext branchBTrans) relationVariables+  let branchAContext = (concreteDatabaseContext branchATrans) {+        relationVariables = ValueMarker $ M.insert conflictRelVarName branchARelVar branchARVs+        }       branchARelVar = ExistingRelation relationTrue -      branchBContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.insert conflictRelVarName (ExistingRelation branchBRelVar) (relationVariables (concreteDatabaseContext branchBTrans))}+      branchBContext = (concreteDatabaseContext branchBTrans) {+            relationVariables = ValueMarker $  M.insert conflictRelVarName (ExistingRelation branchBRelVar) branchBRVs+            }       conflictRelVarName = "conflictRelVar"  -  (_, graph') <- addTransaction "branchA" (createTrans (fakeUUID 3) (TI.singleParent (transactionId branchATrans) testTime) branchAContext) graph-  (_, graph'') <- addTransaction "branchB" (createTrans (fakeUUID 4) (TI.singleParent (transactionId branchBTrans) testTime) branchBContext) graph'+  (_, graph') <- addTransaction "testUnionPreferMergeStrategy" "branchA" (createTrans (fakeUUID [3]) (TI.singleParent (transactionId branchATrans) testTime) branchAContext) graph+  (_, graph'') <- addTransaction "testUnionPreferMergeStrategy2" "branchB" (createTrans (fakeUUID [4]) (TI.singleParent (transactionId branchBTrans) testTime) branchBContext) graph'   -- validate that the conflict is hidden because we preferred a branch-  let merged = runGraphRefRelationalExprM env $ mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) (UnionPreferMergeStrategy "branchB") ("branchA", "branchB")+  let merged = runGraphRefRelationalExprM env $ mergeTransactions testTime (fakeUUID [5]) (fakeUUID [3]) (UnionPreferMergeStrategy "branchB") (MergeHeadNames { sourceHead = "branchA", targetHead = "branchB" })       env = freshGraphRefRelationalExprEnv Nothing graph''   case merged of     Left err -> assertFailure ("expected merge success: " ++ show err)-    Right (discon, _) -> do-      let Just rvExpr = M.lookup conflictRelVarName (relationVariables (Discon.concreteDatabaseContext discon))-          reEnv = freshGraphRefRelationalExprEnv (Just (Discon.concreteDatabaseContext discon)) graph-      let eRvRel = runGraphRefRelationalExprM reEnv (evalGraphRefRelationalExpr rvExpr)+    Right (UncommittedTransaction trans, _) -> do+      let dbc = concreteDatabaseContext trans+      mergedRVs <- assertEither "testUnionPreferMergeStrategy" $ resolveDBC' graph'' dbc relationVariables+      let rvExpr = fromMaybe (error "conflictRelVarName") $ M.lookup conflictRelVarName mergedRVs+          reEnv = freshGraphRefRelationalExprEnv (Just dbc) graph+          eRvRel = runGraphRefRelationalExprM reEnv (evalGraphRefRelationalExpr rvExpr)       assertEqual "branchB relvar preferred in conflict" (Right branchBRelVar) eRvRel    -- try various individual component conflicts and check for merge failure-testUnionMergeStrategy :: Test-testUnionMergeStrategy = TestCase $ do+testUnionMergeStrategyOnRelVars :: Test+testUnionMergeStrategyOnRelVars = TestCase $ do   graph <- basicTransactionGraph   assertGraph graph      branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"-  branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"   -- add another relvar to branchB - branchBOnlyRelvar should appear in the merge  -  -- add inclusion dependency in branchA -  let updatedBranchBContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.insert branchBOnlyRelVarName branchBOnlyRelVar (relationVariables (concreteDatabaseContext branchBTrans)) }-      updatedBranchAContext = (concreteDatabaseContext branchATrans) {inclusionDependencies = M.insert branchAOnlyIncDepName branchAOnlyIncDep (inclusionDependencies (concreteDatabaseContext branchATrans)) }+  branchBRVs <- assertEither "testUnionMergeStrategy" $ resolveDBC' graph (concreteDatabaseContext branchBTrans) relationVariables+  let updatedBranchBContext = (concreteDatabaseContext branchBTrans) {+        relationVariables = ValueMarker $ M.insert branchBOnlyRelVarName branchBOnlyRelVar branchBRVs+        }       branchBOnlyRelVar = ExistingRelation relationTrue-      branchAOnlyIncDepName = "branchAOnlyIncDep"       branchBOnlyRelVarName = "branchBOnlyRelVar"-      branchAOnlyIncDep = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationTrue)-  (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph+  (_, graph') <- addTransaction "testUnionMergeStrategy" "branchB" (createTrans (fakeUUID [3]) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph   let env = freshGraphRefRelationalExprEnv Nothing graph'-  (discon, _) <- assertEither $ runGraphRefRelationalExprM env $ mergeTransactions testTime (fakeUUID 5) (fakeUUID 10) UnionMergeStrategy ("branchA", "branchB")-  let Just rvExpr = M.lookup branchBOnlyRelVarName (relationVariables (Discon.concreteDatabaseContext discon))-      Right rvRel = runGraphRefRelationalExprM reEnv (evalGraphRefRelationalExpr rvExpr)-      reEnv = freshGraphRefRelationalExprEnv (Just (Discon.concreteDatabaseContext discon)) graph+  (UncommittedTransaction trans, graph'') <- assertEither "testUnionMergeStrategy" $ runGraphRefRelationalExprM env $ mergeTransactions testTime (fakeUUID [5]) (fakeUUID [10]) UnionMergeStrategy (MergeHeadNames {sourceHead = "branchA", targetHead = "branchB" })+  branchBRVs' <- assertEither "testUnionMergeStrategy3" $ resolveDBC' graph'' (concreteDatabaseContext trans) relationVariables+  let rvExpr = fromMaybe (error "branchOnlyBRelVarName") $ M.lookup branchBOnlyRelVarName branchBRVs'+      rvRel = fromRight (error "rvRel") $ runGraphRefRelationalExprM reEnv (evalGraphRefRelationalExpr rvExpr)+      reEnv = freshGraphRefRelationalExprEnv (Just (concreteDatabaseContext trans)) graph'          assertEqual "branchBOnlyRelVar should appear in the merge" relationTrue rvRel-  (_, graph'') <- addTransaction "branchA" (createTrans (fakeUUID 4) (TI.singleParent (transactionId branchATrans) testTime) updatedBranchAContext) graph'-  let eMergeGraph = runGraphRefRelationalExprM gfEnv $ mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB")++testUnionMergeStrategyOnInclusionDependencies :: Test+testUnionMergeStrategyOnInclusionDependencies = TestCase $ do+  graph <- basicTransactionGraph+  assertGraph graph++  branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"+  branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"+  branchAIncDeps <- assertEither "testUnionMergeStrategy2" $ resolveDBC' graph (concreteDatabaseContext branchATrans) inclusionDependencies+  branchBIncDeps <- assertEither "testUnionMergeStrategy2" $ resolveDBC' graph (concreteDatabaseContext branchBTrans) inclusionDependencies  ++  -- update branchA with exclusive incdep+  let updatedBranchAContext = (concreteDatabaseContext branchATrans) {+        inclusionDependencies = ValueMarker $ M.insert branchAOnlyIncDepName branchAOnlyIncDep branchAIncDeps+        }+      updatedBranchBContext = (concreteDatabaseContext branchBTrans) {+        inclusionDependencies = ValueMarker $ M.insert branchBOnlyIncDepName branchBOnlyIncDep branchBIncDeps+        }        +      branchAOnlyIncDep = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationTrue)+      branchAOnlyIncDepName = "branchAOnlyIncDep"+      branchBOnlyIncDep = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationTrue)+      branchBOnlyIncDepName = "branchBOnlyIncDep"++  (_, graph') <- addTransaction "testUnionMergeStrategy4" "branchA" (createTrans (fakeUUID' BranchA 2) (TI.singleParent (transactionId branchATrans) testTime) updatedBranchAContext) graph+  (_, graph'') <- addTransaction "testUnionMergeStrategy4" "branchB" (createTrans (fakeUUID' BranchB 2) (TI.singleParent (transactionId branchBTrans) testTime) updatedBranchBContext) graph'++  -- merge branchA to branchB+  let eMergeGraph = runGraphRefRelationalExprM gfEnv $ mergeTransactions testTime (fakeUUID' BranchB 3{- newid -}) (fakeUUID' BranchB 2{-parentid-}) UnionMergeStrategy (MergeHeadNames { sourceHead = "branchA", targetHead = "branchB" })       gfEnv = freshGraphRefRelationalExprEnv Nothing graph''++--  putStrLn $ showGraphStructureX False graph'+         case eMergeGraph of     Left err -> assertFailure ("expected merge success: " ++ show err)     Right (_, mergeGraph) -> do       -- check that the new merge transaction has the correct parents-      mergeTrans <- assertEither $ transactionForId (fakeUUID 5) mergeGraph+      mergeTrans <- assertEither "testUnionMergeStrategy5" $ transactionForId (fakeUUID' BranchB 3) mergeGraph       let mergeContext = concreteDatabaseContext mergeTrans-      assertEqual "merge transaction parents" (parentIds mergeTrans) (S.fromList [fakeUUID 3, fakeUUID 4])+      --check merge transaction parent ids+      assertEqual "merge transaction parents" (parentIds mergeTrans) (S.fromList [fakeUUID' BranchA 2, fakeUUID' BranchB 2])       -- check that the new merge tranasction has elements from both A and B branches-      let Just rvExpr' = M.lookup branchBOnlyRelVarName (relationVariables mergeContext)-          Right rvRel' = runGraphRefRelationalExprM reEnv' (evalGraphRefRelationalExpr rvExpr')-          reEnv' = freshGraphRefRelationalExprEnv (Just mergeContext) graph-      assertEqual "merge transaction relvars" relationTrue rvRel'-      assertEqual "merge transaction incdeps" (Just branchAOnlyIncDep) (M.lookup branchAOnlyIncDepName (inclusionDependencies mergeContext)) -      -- test an expected conflict- add branchBOnlyRelVar with same name but different attributes-      conflictRelVar <- assertEither $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []-      let conflictContextA = updatedBranchAContext {relationVariables = M.insert branchBOnlyRelVarName (ExistingRelation conflictRelVar) (relationVariables updatedBranchAContext) }-      conflictBranchATrans <- assertMaybe (transactionForHead "branchA" graph'') "retrieving head transaction for expected conflict"-      (_, graph''') <- addTransaction "branchA" (createTrans (fakeUUID 6) (TI.singleParent (transactionId conflictBranchATrans) testTime) conflictContextA) graph''-      let failingMerge = runGraphRefRelationalExprM gfEnv' $ mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB")-          gfEnv' = freshGraphRefRelationalExprEnv Nothing graph'''-      case failingMerge of-        Right _ -> assertFailure "expected merge failure"-        Left err -> assertEqual "merge failure" err (MergeTransactionError StrategyWithoutPreferredBranchResolutionMergeError)+      mergeIncDeps <- assertEither "testUnionMergeStrategy7" $ resolveDBC' mergeGraph mergeContext inclusionDependencies+      -- check that inc deps from branchA and branchB are present (union)+      assertEqual "inclusion dependencies from branchA and branchB" (S.fromList [branchAOnlyIncDepName, branchBOnlyIncDepName]) (M.keysSet mergeIncDeps) +--          rvRel' = fromRight (error "rvRel'") $ runGraphRefRelationalExprM reEnv' (evalGraphRefRelationalExpr rvExpr')+--          reEnv' = freshGraphRefRelationalExprEnv (Just mergeContext) graph+--      assertEqual "merge transaction relvars" relationTrue rvRel'+      assertEqual "merge transaction incdeps" (Just branchAOnlyIncDep) (M.lookup branchAOnlyIncDepName mergeIncDeps)++-- test an expected conflict- add incdeps with same name but different attributes+testUnionMergeStrategyOnInclusionDependenciesConflict :: Test+testUnionMergeStrategyOnInclusionDependenciesConflict = TestCase $ do+  graph <- basicTransactionGraph+  assertGraph graph++  branchATrans <- assertMaybe (transactionForHead "branchA" graph) "retrieving head transaction for expected conflict"+  branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "retrieving head transaction for expected conflict"+  let incDepName = "conflictIncDep"+      incDepA = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationTrue)+      incDepB = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationFalse)+      conflictContextA = (concreteDatabaseContext branchATrans) {+        inclusionDependencies = ValueMarker $ M.singleton incDepName incDepA+        }+      conflictContextB = (concreteDatabaseContext branchBTrans) {+        inclusionDependencies = ValueMarker $ M.singleton incDepName incDepB+        }+  -- add to branchA+  (_, graph') <- addTransaction "testUnionMergeStrategy10" "branchA" (createTrans (fakeUUID' BranchA 2) (TI.singleParent (transactionId branchATrans) testTime) conflictContextA) graph+  -- add to branchB+  (_, graph'') <- addTransaction "testUnionMergeStrategy10" "branchB" (createTrans (fakeUUID' BranchB 2) (TI.singleParent (transactionId branchBTrans) testTime) conflictContextB) graph'+  -- perform merge, expect conflict+  let failingMerge = runGraphRefRelationalExprM gfEnv $ mergeTransactions testTime (fakeUUID' BranchB 3) (fakeUUID' BranchA 2) UnionMergeStrategy (MergeHeadNames { sourceHead = "branchA", targetHead = "branchB" })+      gfEnv = freshGraphRefRelationalExprEnv Nothing graph''++  putStrLn $ showGraphStructureX False graph''+  case failingMerge of+    Right _ -> assertFailure "expected merge failure"+    Left err -> assertEqual "merge failure" err (MergeTransactionError (StrategyViolatesComponentMergeError InclusionDependenciesField incDepName))+     -- test that a merge will fail if a constraint is violated testUnionMergeIncDepViolation :: Test testUnionMergeIncDepViolation = TestCase $ do@@ -261,22 +351,22 @@   --add relvar and key constraint to both branches   let eRel val = mkRelationFromList (attributesFromList [Attribute "x" IntAtomType, Attribute "y" IntAtomType]) [[IntAtom 1, IntAtom val]]        rvName = "x"-      Right branchArv = eRel 2-      Right branchBrv = eRel 3-      branchAContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.singleton rvName (ExistingRelation branchArv)}-      branchBContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.singleton rvName (ExistingRelation branchBrv), -                                                               inclusionDependencies = M.singleton incDepName incDep}      +      branchArv = fromRight (error "branchArv") $ eRel 2+      branchBrv = fromRight (error "branchBrv") $ eRel 3+      branchAContext = (concreteDatabaseContext branchBTrans) {relationVariables = ValueMarker $ M.singleton rvName (ExistingRelation branchArv)}+      branchBContext = (concreteDatabaseContext branchBTrans) { relationVariables = ValueMarker $ M.singleton rvName (ExistingRelation branchBrv), +                                                                inclusionDependencies = ValueMarker $ M.singleton incDepName incDep}             incDepName = "x_key"       incDep = inclusionDependencyForKey (AttributeNames (S.singleton "x")) (RelationVariable "x" ())      --add the rv in new commits to both branches-  (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TI.singleParent (transactionId branchBTrans) testTime) branchBContext) graph+  (_, graph') <- addTransaction "testUnionMergeIncDepViolation" "branchB" (createTrans (fakeUUID [3]) (TI.singleParent (transactionId branchBTrans) testTime) branchBContext) graph                   -  (_, graph'') <- addTransaction "branchA" (createTrans (fakeUUID 4) (TI.singleParent (transactionId branchATrans) testTime) branchAContext) graph'+  (_, graph'') <- addTransaction "testUnionMergeIncDepViolation2" "branchA" (createTrans (fakeUUID [4]) (TI.singleParent (transactionId branchATrans) testTime) branchAContext) graph'      --check that the union merge fails due to a violated constraint-  let eMerge = runGraphRefRelationalExprM env $ mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB")+  let eMerge = runGraphRefRelationalExprM env $ mergeTransactions testTime (fakeUUID [5]) (fakeUUID [3]) UnionMergeStrategy (MergeHeadNames { sourceHead = "branchA", targetHead = "branchB" })       env = freshGraphRefRelationalExprEnv Nothing graph''   case eMerge of     Left (InclusionDependencyCheckError incDepName' Nothing) -> assertEqual "incdep violation name" incDepName incDepName'
test/TransactionGraph/Persist.hs view
@@ -2,10 +2,16 @@ import Test.HUnit import ProjectM36.Base import ProjectM36.Persist (DiskSync(NoDiskSync))+import ProjectM36.RelationalExpression (resolveDBC') import ProjectM36.TransactionGraph.Persist+import ProjectM36.IsomorphicSchema.Types+import ProjectM36.Transaction.Types as T import ProjectM36.TransactionGraph as TG-import ProjectM36.Transaction+import ProjectM36.TransactionGraph.Types+import ProjectM36.DatabaseContext+import ProjectM36.DisconnectedTransaction import ProjectM36.DateExamples+import ProjectM36.ValueMarker import System.IO.Temp import System.Exit import System.Directory@@ -20,6 +26,9 @@ import ProjectM36.Client as C import ProjectM36.Relation import ProjectM36.Transaction.Persist+import ProjectM36.DatabaseContext.Types hiding (DatabaseContextFunctionUtils(..))+import Control.Monad (forM)+import System.Random  main :: IO ()            main = do @@ -30,7 +39,9 @@ testList = TestList [testBootstrapDB,                       testDBSimplePersistence,                      testFunctionPersistence,-                     testMerkleHashValidation]+                     testMerkleHashValidation,+                     testTransactionDirectorySizeGrowth+                     ]  stamp' :: UTCTime stamp' = UTCTime (fromGregorian 1980 01 01) (secondsToDiffTime 1000)@@ -41,7 +52,7 @@   let dbdir = tempdir </> "dbdir"   freshId <- nextRandom -  _ <- bootstrapDatabaseDir NoDiskSync dbdir (bootstrapTransactionGraph stamp' freshId dateExamples)+  _ <- bootstrapDatabaseDir NoDiskSync dbdir (bootstrapTransactionGraph stamp' freshId (toDatabaseContext dateExamples))   loadedGraph <- transactionGraphLoad dbdir emptyTransactionGraph Nothing   assertBool "transactionGraphLoad" $ isRight loadedGraph @@ -51,23 +62,23 @@   let dbdir = tempdir </> "dbdir"   freshId <- nextRandom -  let graph = bootstrapTransactionGraph stamp' freshId dateExamples+  let graph = bootstrapTransactionGraph stamp' freshId (toDatabaseContext dateExamples)   _ <- bootstrapDatabaseDir NoDiskSync dbdir graph   case transactionForHead "master" graph of     Nothing -> assertFailure "Failed to retrieve head transaction for master branch."     Just headTrans -> -          case interpretDatabaseContextExpr (concreteDatabaseContext headTrans) (transactionId headTrans) graph "x:=s" of+          case interpretDatabaseContextExpr (T.concreteDatabaseContext headTrans) (transactionId headTrans) graph "x:=s" of             Left err -> assertFailure (show err)             Right context' -> do               freshId' <- nextRandom-              let newdiscon = DisconnectedTransaction (transactionId headTrans) (Schemas context' M.empty) False-                  addTrans = addDisconnectedTransaction stamp' freshId' "master" newdiscon graph+              let newdiscon = DisconnectedTransaction (transactionId headTrans) (Schemas context' emptyValue) (CurrentHeadBranch "master")+                  addTrans = addDisconnectedTransaction stamp' freshId' newdiscon graph               --add a transaction to the graph               case addTrans of                 Left err -> assertFailure (show err)                 Right (_, graph') -> do                   --persist the new graph-                  _ <- transactionGraphPersist NoDiskSync dbdir [freshId'] graph'+                  _ <- transactionGraphPersist NoDiskSync dbdir graph'                   --reload the graph from the filesystem and confirm that the transaction is present                   graphErr <- transactionGraphLoad dbdir emptyTransactionGraph Nothing                   let mapEq graphArg = S.map transactionId (transactionsForGraph graphArg)@@ -79,8 +90,9 @@ testMerkleHashValidation = TestCase $   -- add a commit and validate the hashes successfully   withSystemTempDirectory "m36testdb" $ \tempdir -> do+  rando <- initStdGen   let dbdir = tempdir </> "dbdir"-      connInfo = InProcessConnectionInfo (MinimalPersistence dbdir) emptyNotificationCallback [] basicDatabaseContext+      connInfo = InProcessConnectionInfo (MinimalPersistence dbdir) emptyNotificationCallback [] basicDatabaseContext rando adminRoleName   conn <- assertIOEither $ connectProjectM36 connInfo   sess <- assertIOEither $ createSessionAtHead conn "master"   Right _ <- executeDatabaseContextExpr sess conn (Assign "x" (ExistingRelation relationTrue))@@ -107,11 +119,14 @@           updatedTrans = Transaction (transactionId trans) (transactionInfo trans) updatedSchemas           transactionDir' = dbdir </> show (transactionId trans)           updatedSchemas =-            case schemas trans of+            case T.schemas trans of               Schemas ctx sschemas ->-                let updatedContext = ctx {-                      relationVariables = M.insert "malicious" (ExistingRelation relationFalse) (relationVariables ctx)-                      } in+                let relVars = case resolveDBC' graph ctx relationVariables of+                                Left err -> error ("resolveDBC' failure: " <> show err)+                                Right rv -> rv+                    updatedContext = ctx {+                      relationVariables = ValueMarker (M.insert "malicious" (ExistingRelation relationFalse) relVars) }+                      in                 Schemas updatedContext sschemas           maliciousGraph = TransactionGraph malHeads malTransSet           malHeads = M.insert "master" updatedTrans (transactionHeadsForGraph graph)@@ -123,7 +138,7 @@       assertEqual "loaded graph merkle hashes" (Left [MerkleValidationError (transactionId trans) regMerkleHash malMerkleHash]) val''       --delete existing transaction directory       removeDirectoryRecursive transactionDir'-      writeTransaction NoDiskSync dbdir updatedTrans+      writeTransaction NoDiskSync dbdir (UncommittedTransaction updatedTrans)        --read graph from disk       eConnFail <- connectProjectM36 connInfo@@ -167,3 +182,38 @@   case ret of     Left err -> assertFailure (show err) >> undefined     Right val -> pure val++-- test that the transaction graph sizes on disk are within expected bounds+-- create an empty database, check its size, add a commit, check the difference in size+testTransactionDirectorySizeGrowth :: Test+testTransactionDirectorySizeGrowth = TestCase $ do+  withSystemTempDirectory "m36testdb" $ \tempdir -> do+    rando <- initStdGen+    Right db <- C.connectProjectM36 (InProcessConnectionInfo (MinimalPersistence (tempdir </> "db")) emptyNotificationCallback [] basicDatabaseContext rando adminRoleName)+    Right sessionId <- C.createSessionAtHead db "master"++    originalDBSize <- getDirectorySize tempdir+    let expectedOriginalSize = 40960 {- sqlite3 loginroles.db -} + 1415+    +    assertBool ("graph with one transaction size- expected: " <> show expectedOriginalSize <> ", actual: " <> show originalDBSize) (originalDBSize <= expectedOriginalSize)++    Right () <- C.executeDatabaseContextExpr sessionId db (Assign "x" (RelationVariable "true" ()))+    Right () <- C.commit sessionId db++    postUpdateDBSize <- getDirectorySize tempdir+    let postUpdateExpectedDiffSize = 1591+        sizeDiff = postUpdateDBSize - originalDBSize +    assertBool ("graph with two transaction size: " <> show postUpdateDBSize) (sizeDiff <= postUpdateExpectedDiffSize)+        ++getDirectorySize :: FilePath -> IO Integer+getDirectorySize dir = do+    contents <- getDirectoryContents dir+    let filteredContents = filter (`notElem` [".", ".."]) contents+    sizes <- forM filteredContents $ \name -> do+        let path' = dir </> name+        isDir <- doesDirectoryExist path'+        if isDir+            then getDirectorySize path'+            else getFileSize path' +    pure $ sum sizes        
test/TutorialD/Interpreter/DatabaseContextFunctionScript.hs view
@@ -24,7 +24,7 @@ testBasicDBCFunction :: Test testBasicDBCFunction = TestCase $ do     (sess, conn) <- dateExamplesConnection emptyNotificationCallback-  let addfunc = "adddatabasecontextfunction \"addTrue2\" DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext  \"\"\"(\\[] ctx -> executeDatabaseContextExpr (Assign \"true2\" (ExistingRelation relationTrue)) ctx) :: [Atom] -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext\"\"\""+  let addfunc = "adddatabasecontextfunction \"addTrue2\" DatabaseContext -> Either RelationalError DatabaseContext  \"\"\"(\\util [] ctx -> (executeDatabaseContextExpr util) ctx (Assign \"true2\" (ExistingRelation relationTrue))) :: DatabaseContextFunctionBodyType\"\"\""   executeTutorialD sess conn addfunc   executeTutorialD sess conn "execute addTrue2()"   {-@@ -35,15 +35,15 @@ testErrorDBCFunction :: Test testErrorDBCFunction = TestCase $ do   (sess, conn) <- dateExamplesConnection emptyNotificationCallback  -  let addfunc = "adddatabasecontextfunction \"retErr\" DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext \"\"\"(\\[] ctx -> Left (DatabaseContextFunctionUserError \"err\")) :: [Atom] -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext\"\"\""+  let addfunc = "adddatabasecontextfunction \"retErr\" DatabaseContext -> Either RelationalError DatabaseContext \"\"\"(\\util [] ctx -> Left (DatabaseContextFunctionUserError \"err\")) :: DatabaseContextFunctionBodyType\"\"\""   executeTutorialD sess conn addfunc-  expectTutorialDErr sess conn (T.isPrefixOf "DatabaseContextFunctionUserError (DatabaseContextFunctionUserError \"err\")") "execute retErr()"+  expectTutorialDErr sess conn (T.isPrefixOf "DatabaseContextFunctionUserError \"err\"") "execute retErr()"    testExceptionDBCFunction :: Test testExceptionDBCFunction = TestCase $ do   -- throw an error, make sure it is caught- it might not be caught, for example, if the function is not forced   (sess, conn) <- dateExamplesConnection emptyNotificationCallback-  let addfunc = "adddatabasecontextfunction \"bomb\" DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext \"\"\"(\\[] _ -> error \"boom\") :: [Atom] -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext\"\"\""+  let addfunc = "adddatabasecontextfunction \"bomb\" DatabaseContext -> Either RelationalError DatabaseContext \"\"\"(\\util [] _ -> error \"boom\") :: DatabaseContextFunctionBodyType\"\"\""   executeTutorialD sess conn addfunc   expectTutorialDErr sess conn ("UnhandledExceptionError" `T.isPrefixOf`) "execute bomb()"   @@ -52,7 +52,7 @@ testDBCFunctionWithAtomArguments = TestCase $ do   --test function with creation of a relvar with some arguments   (sess, conn) <- dateExamplesConnection emptyNotificationCallback-  let addfunc = "adddatabasecontextfunction \"multiArgFunc\" Integer -> Text -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext \"\"\"(\\(age:name:_) ctx -> executeDatabaseContextExpr (Assign \"person\" (MakeRelationFromExprs Nothing [TupleExpr (fromList [(\"name\", NakedAtomExpr name), (\"age\", NakedAtomExpr age)])])) ctx) :: [Atom] -> DatabaseContext -> Either DatabaseContextFunctionError DatabaseContext\"\"\""+  let addfunc = "adddatabasecontextfunction \"multiArgFunc\" Integer -> Text -> DatabaseContext -> Either RelationalError DatabaseContext \"\"\"(\\util (age:name:_) ctx -> (executeDatabaseContextExpr util) ctx (Assign \"person\" (MakeRelationFromExprs Nothing (TupleExprs () [TupleExpr (fromList [(\"name\", NakedAtomExpr name), (\"age\", NakedAtomExpr age)])])))) :: DatabaseContextFunctionBodyType\"\"\""   executeTutorialD sess conn addfunc   executeTutorialD sess conn "execute multiArgFunc(30,\"Steve\")"   result <- executeRelationalExpr sess conn (RelationVariable "person" ())
test/TutorialD/Interpreter/Import/ImportTest.hs view
@@ -1,10 +1,10 @@+{-# LANGUAGE CPP #-} import Test.HUnit import ProjectM36.Base import TutorialD.Interpreter.Import.TutorialD import System.Exit import qualified Data.Text as T import System.IO.Temp-import System.FilePath import qualified Data.Map as M import System.IO import qualified Data.ByteString as BS@@ -36,7 +36,11 @@           Assign "y" (MakeRelationFromExprs Nothing                        $ TupleExprs () [TupleExpr (M.fromList [("b", NakedAtomExpr (TextAtom "漢字"))])])]     --on Windows, the file URI should not include the drive letter "/c/Users..." -> "/Users"-    let uri = "file://" <> map (\c -> if c == '\\' then '/' else c) ( joinDrive "/" (dropDrive tempPath))+#if defined(mingw32_HOST_OS)+    let uri = "file:" <> map (\c -> if c == '\\' then '/' else c) tempPath+#else+    let uri = "file:" <> tempPath   +#endif     fileURI <- mkURI (T.pack uri)     imported <- importTutorialDFromFile fileURI Nothing     assertEqual "import tutd" (Right expectedExpr) imported
test/TutorialD/Interpreter/TestBase.hs view
@@ -1,15 +1,16 @@ module TutorialD.Interpreter.TestBase where import ProjectM36.Client-import ProjectM36.Interpreter+import ProjectM36.Interpreter as I import TutorialD.Interpreter import ProjectM36.DateExamples-import ProjectM36.DatabaseContext+import ProjectM36.DatabaseContextExpr import Test.HUnit import Data.Text+import System.Random (mkStdGen)  dateExamplesConnection :: NotificationCallback -> IO (SessionId, Connection) dateExamplesConnection callback = do-  dbconn <- connectProjectM36 (InProcessConnectionInfo NoPersistence callback [] basicDatabaseContext)+  dbconn <- connectProjectM36 (InProcessConnectionInfo NoPersistence callback [] basicDatabaseContext (mkStdGen 36) adminRoleName)   case dbconn of      Left err -> error (show err)     Right conn -> do@@ -17,7 +18,7 @@       case eSessionId of         Left err -> error (show err)         Right sessionId -> do-          executeDatabaseContextExpr sessionId conn (databaseContextAsDatabaseContextExpr dateExamples) >>= eitherFail+          executeDatabaseContextExpr sessionId conn (resolvedDatabaseContextAsDatabaseContextExpr dateExamples) >>= eitherFail       --skipping atom functions for now- there are no atom function manipulation operators yet           commit sessionId conn >>= eitherFail           pure (sessionId, conn)@@ -29,13 +30,13 @@       result <- evalTutorialD sessionId conn UnsafeEvaluation parsed       case result of         QuitResult -> assertFailure "quit?"-        DisplayResult _ -> assertFailure "display?"+        DisplayResult x -> assertFailure ("display: " <> show x)         DisplayIOResult _ -> assertFailure "displayIO?"         DisplayRelationResult _ -> assertFailure "displayrelation?"         DisplayDataFrameResult _ -> assertFailure "displaydataframe?"         DisplayParseErrorResult _ _ -> assertFailure "displayparseerrorresult?"         DisplayErrorResult err -> assertFailure (show tutd ++ ": " ++ show err)        -        QuietSuccessResult -> pure ()+        I.QuietSuccessResult -> pure ()         DisplayRelationalErrorResult err -> assertFailure ("DisplayRelationalErrorResult: " <> show err)         DisplayHintWith _ _ -> pure ()         @@ -52,7 +53,7 @@         DisplayDataFrameResult _ -> assertFailure "displaydataframe?"         DisplayParseErrorResult _ _ -> assertFailure "displayparseerrorresult?"         DisplayErrorResult err -> assertBool (unpack tutd ++ " match error on: " ++ unpack err) (matchFunc err)-        QuietSuccessResult -> pure ()+        I.QuietSuccessResult -> pure ()         DisplayRelationalErrorResult err -> assertFailure ("DisplayRelationalErrorResult: " <> show err)         DisplayHintWith{} -> pure () @@ -69,7 +70,7 @@         DisplayDataFrameResult _ -> assertFailure "displaydataframe?"         DisplayParseErrorResult _ _ -> assertFailure "displayparseerrorresult?"         DisplayErrorResult err -> assertFailure (unpack err)-        QuietSuccessResult -> assertFailure "quietsuccess?"+        I.QuietSuccessResult -> assertFailure "quietsuccess?"         DisplayRelationalErrorResult err -> assertEqual "relational error" matchErr err         DisplayHintWith{} -> assertFailure "displayhintwith?" 
test/TutorialD/InterpreterTest.hs view
@@ -5,8 +5,9 @@ import Test.HUnit import ProjectM36.Relation as R import ProjectM36.Tuple-import ProjectM36.TupleSet+import ProjectM36.TupleSet as TS import ProjectM36.Error+import ProjectM36.DatabaseContext.Types hiding (DatabaseContextFunctionUtils(..)) import ProjectM36.DatabaseContext import ProjectM36.AtomFunctions.Primitive import ProjectM36.RelationalExpression@@ -17,9 +18,11 @@ import ProjectM36.DateExamples import ProjectM36.Base hiding (Finite) import ProjectM36.TransactionGraph+import ProjectM36.TransactionGraph.Types+import ProjectM36.Transaction.Types import ProjectM36.Client import ProjectM36.HashSecurely-import ProjectM36.Interpreter+import ProjectM36.Interpreter as I import qualified ProjectM36.DisconnectedTransaction as Discon import qualified ProjectM36.AttributeNames as AN import qualified ProjectM36.Session as Sess@@ -56,12 +59,12 @@       simpleJoinTest,        testTypeConstructors,        testMergeTransactions, -      testComments, +      testComments,       testTransGraphRelationalExpr,        failJoinTest,        testMultiAttributeRename,        testSchemaExpr, -      testRelationalExprStateTupleElems, +      testContextTuples,       testFunctionalDependencies,        testEmptyCommits,       testIntervalAtom,@@ -97,12 +100,13 @@       testIfThenExpr,       testSubrelationAttributeAtomExpr,       testComplexTypeVarResolution,-      testNotifications+      testNotifications,+      testDBCFunctionAccessControl       ]  simpleRelTests :: Test simpleRelTests = TestCase $ do-  (graph, transId) <- freshTransactionGraph dateExamples  +  (graph, transId) <- freshTransactionGraph' dateExamples   mapM_ (\(tutd, expected) ->             assertTutdEqual basicDatabaseContext transId graph expected tutd) testTups   where@@ -112,9 +116,9 @@                       ("x:=true minus false", Right relationTrue),                       ("x:=false minus true", Right relationFalse),                                             ("x:=true; x:=false", Right relationFalse),-                      ("x:=relation{a Integer}{}", mkRelation simpleAAttributes emptyTupleSet),-                      ("x:=relation{c Integer}{} rename {c as d}", mkRelation simpleBAttributes emptyTupleSet),-                      ("y:=relation{b Integer, c Integer}{}; x:=y{c}", mkRelation simpleProjectionAttributes emptyTupleSet),+                      ("x:=relation{a Integer}{}", mkRelation simpleAAttributes TS.empty),+                      ("x:=relation{c Integer}{} rename {c as d}", mkRelation simpleBAttributes TS.empty),+                      ("y:=relation{b Integer, c Integer}{}; x:=y{c}", mkRelation simpleProjectionAttributes TS.empty),                       ("x:=relation{tuple{a \"spam\", b 5}}", mkRelation simpleCAttributes $ RelationTupleSet [RelationTuple simpleCAttributes (V.fromList [TextAtom "spam", IntegerAtom 5])]),                       ("constraint failc true in false; x:=true", Left $ InclusionDependencyCheckError "failc" Nothing),                       ("x:=y; x:=true", Left $ RelVarNotDefinedError "y"),@@ -153,7 +157,7 @@    dateExampleRelTests :: Test dateExampleRelTests = TestCase $ do-  (graph, transId) <- freshTransactionGraph dateExamples+  (graph, transId) <- freshTransactionGraph' dateExamples   mapM_ (\(tutd, expected) ->             assertTutdEqual dateExamples transId graph expected tutd) testTups   where@@ -173,8 +177,8 @@                                      else Right tuple) suppliersRel     extendTestAttributes = A.attributesFromList [Attribute "a" IntegerAtomType, Attribute "b" $ RelationAtomType (R.attributes suppliersRel)]     testTups = [("x:=s where true", Right suppliersRel),-                           ("x:=s where city = \"London\"", restrict (\tuple -> pure $ atomForAttributeName "city" tuple == Right (TextAtom "London")) suppliersRel),-                           ("x:=s where false", Right $ Relation (R.attributes suppliersRel) emptyTupleSet),+                           ("x:=s where city = \"London\"", restrict (\tuple _ -> pure $ atomForAttributeName "city" tuple == Right (TextAtom "London")) suppliersRel),+                           ("x:=s where false", Right $ Relation (R.attributes suppliersRel) TS.empty),                            ("x:=p where color=\"Blue\" and city=\"Paris\"", mkRelationFromList (R.attributes productsRel) [[TextAtom "P5", TextAtom "Cam", TextAtom "Blue", IntegerAtom 12, TextAtom "Paris"]]),                            ("a:=s; update a (status:=50); x:=a{status}", mkRelation (A.attributesFromList [Attribute "status" IntegerAtomType]) (RelationTupleSet [mkRelationTuple (A.attributesFromList [Attribute "status" IntegerAtomType]) (V.fromList [IntegerAtom 50])])),                            ("x:=s minus (s where status=20)", mkRelationFromList (R.attributes suppliersRel) [[TextAtom "S2", TextAtom "Jones", IntegerAtom 10, TextAtom "Paris"], [TextAtom "S3", TextAtom "Blake", IntegerAtom 30, TextAtom "Paris"], [TextAtom "S5", TextAtom "Adams", IntegerAtom 30, TextAtom "Athens"]]),@@ -215,16 +219,20 @@                            ("x:=relation{tuple{a fromGregorian(2017,05,30)}}", mkRelationFromList (A.attributesFromList [Attribute "a" DayAtomType]) [[DayAtom (fromGregorian 2017 05 30)]])                           ] -assertTutdEqual :: DatabaseContext -> TransactionId -> TransactionGraph -> Either RelationalError Relation -> Text -> Assertion-assertTutdEqual databaseContext transId graph expected tutd = assertEqual (unpack tutd) expected interpreted+assertTutdEqual :: ResolvedDatabaseContext -> TransactionId -> TransactionGraph -> Either RelationalError Relation -> Text -> Assertion+assertTutdEqual contextIn transId graph expected tutd = assertEqual (unpack tutd) expected interpreted   where-    interpreted = case interpretDatabaseContextExpr databaseContext transId graph tutd of+    interpreted = case interpretDatabaseContextExpr (toDatabaseContext contextIn) transId graph tutd of       Left err -> Left err-      Right context -> case M.lookup "x" (relationVariables context) of-        Nothing -> Left $ RelVarNotDefinedError "x"-        Just relExpr -> do-          let env = freshGraphRefRelationalExprEnv (Just context) graph-          runGraphRefRelationalExprM env (evalGraphRefRelationalExpr relExpr)+      Right context -> do+        case resolveDBC' graph context relationVariables of+          Left err -> Left err+          Right relVars ->+            case M.lookup "x" relVars of+              Nothing -> Left $ RelVarNotDefinedError "x"+              Just relExpr -> do+                let env = freshGraphRefRelationalExprEnv (Just context) graph+                runGraphRefRelationalExprM env (evalGraphRefRelationalExpr relExpr)   transactionGraphBasicTest :: Test@@ -249,11 +257,10 @@         DisplayDataFrameResult _ -> assertFailure "displaydataframe?"         DisplayParseErrorResult _ _ -> assertFailure "displayparseerror?"         DisplayErrorResult err -> assertFailure (show err)   -        QuietSuccessResult -> do+        I.QuietSuccessResult -> do           commit sessionId dbconn >>= eitherFail-          discon <- disconnectedTransaction_ sessionId dbconn-          let context = Discon.concreteDatabaseContext discon-          assertEqual "ensure x was added" (M.lookup "x" (relationVariables context)) (Just (ExistingRelation suppliersRel))+          eX <- executeRelationalExpr sessionId dbconn (RelationVariable "x" ())+          assertEqual "ensure x was added" (Right suppliersRel) eX         DisplayRelationalErrorResult err -> assertFailure (show err)         DisplayHintWith{} -> assertFailure "displayhintwith?" @@ -263,9 +270,10 @@   graph <- transactionGraph_ dbconn   executeDatabaseContextExpr sessionId dbconn (Assign "x" (RelationVariable "s" ())) >>= eitherFail   rollback sessionId dbconn >>= eitherFail-  discon <- disconnectedTransaction_ sessionId dbconn   graph' <- transactionGraph_ dbconn-  assertEqual "validate context" Nothing (M.lookup "x" (relationVariables (Discon.concreteDatabaseContext discon)))+  eX <- executeRelationalExpr sessionId dbconn (RelationVariable "x" ())+  +  assertEqual "validate context" (Left (RelVarNotDefinedError "x")) eX   let graphEq graphArg = S.map transactionId (transactionsForGraph graphArg)   assertEqual "validate graph" (graphEq graph) (graphEq graph') @@ -273,22 +281,22 @@ transactionJumpTest :: Test transactionJumpTest = TestCase $ do   (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback-  (DisconnectedTransaction firstUUID _ _) <- disconnectedTransaction_ sessionId dbconn+  firstUUID <- Discon.disconTransactionId <$> disconnectedTransaction_ sessionId dbconn   executeDatabaseContextExpr sessionId dbconn (Assign "x" (RelationVariable "s" ())) >>= eitherFail   commit sessionId dbconn >>= eitherFail   --perform the jump-  executeGraphExpr sessionId dbconn (JumpToTransaction firstUUID) >>= eitherFail+  executeTransactionGraphExpr sessionId dbconn (JumpToTransaction firstUUID) >>= eitherFail   --check that the disconnected transaction does not include "x"-  discon <- disconnectedTransaction_ sessionId dbconn-  assertEqual "ensure x is not present" Nothing (M.lookup "x" (relationVariables (Discon.concreteDatabaseContext discon)))          +  eX <- executeRelationalExpr sessionId dbconn (RelationVariable "x" ())+  assertEqual "ensure x is not present" (Left (RelVarNotDefinedError "x")) eX --branch from the first transaction and verify that there are two heads transactionBranchTest :: Test transactionBranchTest = TestCase $ do   (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback-  mapM_ (>>= eitherFail) [executeGraphExpr sessionId dbconn (Branch "test"),+  mapM_ (>>= eitherFail) [executeAlterTransactionGraphExpr sessionId dbconn (Branch "test"),                                   executeDatabaseContextExpr sessionId dbconn (Assign "x" (RelationVariable "s" ())),                                   commit sessionId dbconn,-                                  executeGraphExpr sessionId dbconn (JumpToHead "master"),+                                  executeTransactionGraphExpr sessionId dbconn (JumpToHead "master"),                                   executeDatabaseContextExpr sessionId dbconn (Assign "y" (RelationVariable "s" ()))                   ]   graph <- transactionGraph_ dbconn@@ -298,14 +306,14 @@ -- test that overlapping attribute names with different types fail with an error failJoinTest :: Test failJoinTest = TestCase $ do-  (graph, transId) <- freshTransactionGraph dateExamples  +  (graph, transId) <- freshTransactionGraph' dateExamples     assertTutdEqual basicDatabaseContext transId graph err "x:=relation{tuple{test 4}} join relation{tuple{test \"test\"}}"   where     err = Left (TupleAttributeTypeMismatchError (A.attributesFromList [Attribute "test" IntegerAtomType]))  simpleJoinTest :: Test simpleJoinTest = TestCase $ do-  (graph, transId) <- freshTransactionGraph dateExamples    +  (graph, transId) <- freshTransactionGraph' dateExamples       assertTutdEqual dateExamples transId graph joinedRel "x:=s join sp"     where         attrs = A.attributesFromList [Attribute "city" TextAtomType,@@ -427,7 +435,7 @@   --test walkback to time (stay in current location)   now <- getCurrentTime   headId <- headTransactionId sessionId dbconn-  _ <- executeGraphExpr sessionId dbconn (WalkBackToTime now)+  _ <- executeTransactionGraphExpr sessionId dbconn (WalkBackToTime now)   headId' <- headTransactionId sessionId dbconn   assertEqual "transaction walk back stays in place" headId headId' @@ -444,7 +452,7 @@      testMultiAttributeRename :: Test testMultiAttributeRename = TestCase $ do-  (graph, transId) <- freshTransactionGraph dateExamples    +  (graph, transId) <- freshTransactionGraph' dateExamples       assertTutdEqual dateExamples transId graph renamedRel "x:=s rename {city as town, status as price} where false"   where     sattrs = attributesFromList [Attribute "town" TextAtomType,@@ -475,8 +483,8 @@   Right val -> pure val    -- | Validate that a tuple passed through the context correctly typechecks and propagates to the AttributeAtomExpr.-testRelationalExprStateTupleElems :: Test-testRelationalExprStateTupleElems = TestCase $ do+testContextTuples :: Test+testContextTuples = TestCase $ do   (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback   executeTutorialD sessionId dbconn "x := (s : { parts := p rename {city as pcity} where pcity=@city}) : {z:=count(@parts)}" @@ -489,20 +497,20 @@                      [TextAtom "Athens", IntegerAtom 0]]   assertEqual "validate parts count" expectedRel eRv   -  executeTutorialD sessionId dbconn "rv1:=relation{tuple{test 1}}"-  executeTutorialD sessionId dbconn "rv2:=relation{tuple{val 1},tuple{val 2}}"+  executeTutorialD sessionId dbconn "rv1:=relation{tuple{val1 1}}"+  executeTutorialD sessionId dbconn "rv2:=relation{tuple{val2 1},tuple{val2 2}}"   --check subexpression evaluation in restriction predicate   -- "rv1 where ((rv2 where val=@test) {})"-  let correctSubexpr = Restrict (AttributeEqualityPredicate "val" (AttributeAtomExpr "test")) (RelationVariable "rv2" ())+  let correctSubexpr = Restrict (AttributeEqualityPredicate "val2" (AttributeAtomExpr "val2")) (RelationVariable "rv2" ())       mainExpr subexpr = Restrict                           (RelationalExprPredicate                           (Project AN.empty subexpr)) (RelationVariable "rv1" ())   eRv2 <- executeRelationalExpr sessionId dbconn (mainExpr correctSubexpr)-  let expectedRel2 = mkRelationFromList (attributesFromList [Attribute "test" IntegerAtomType]) [[IntegerAtom 1]]+  let expectedRel2 = mkRelationFromList (attributesFromList [Attribute "val1" IntegerAtomType]) [[IntegerAtom 1]]   assertEqual "validate sub-expression attribute" expectedRel2 eRv2      --check error in subexpression-  let wrongSubexpr = Restrict (AttributeEqualityPredicate "nosuchattr" (AttributeAtomExpr "test")) (RelationVariable "rv2" ())+  let wrongSubexpr = Restrict (AttributeEqualityPredicate "nosuchattr" (AttributeAtomExpr "val1")) (RelationVariable "rv2" ())   eRv3 <- executeRelationalExpr sessionId dbconn (mainExpr wrongSubexpr)   assertEqual "validate missing attribute in subexpression" (Left (NoSuchAttributeNamesError (S.singleton "nosuchattr"))) eRv3   @@ -554,7 +562,7 @@   executeTutorialD sessionId dbconn "x:=relation{tuple{n 1, a interval(3,4,False,False), b interval(4,5,False,False)}, tuple{n 2,a interval(3,4,True,True), b interval(4,5,True,True)}}"   --test failed interval creation   let err1 = "AtomFunctionUserError InvalidIntervalOrderingError"-      err2 = "AtomFunctionUserError (AtomTypeDoesNotSupportIntervalError \"Text\")"+      err2 = "RelationTypeMismatchError"   expectTutorialDErr sessionId dbconn (T.isPrefixOf err1) "z:=relation{tuple{a interval(4,3,False,False)}}"   expectTutorialDErr sessionId dbconn (T.isPrefixOf err2) "z:=relation{tuple{a interval(\"s\",\"t\",False,False)}}"   @@ -585,7 +593,7 @@   -- or   executeTutorialD sessionId dbconn "x:=s where status=20 or status=10"   eRvOr <- executeRelationalExpr sessionId dbconn (RelationVariable "x" ())-  let expectedRelOr = restrict (\tuple -> +  let expectedRelOr = restrict (\tuple _ ->                                 pure (atomForAttributeName "status" tuple `elem` [Right (IntegerAtom 10), Right (IntegerAtom 20)])) suppliersRel   assertEqual "status 20 or 10" expectedRelOr eRvOr   -- and@@ -690,11 +698,12 @@   expectTutorialDErr sessionId dbconn (T.isPrefixOf err1) "x:=relation{tuple{a Empty}}"   executeTutorialD sessionId dbconn "x:=relation{a List Int}{tuple{a Empty}}" --- with (x as s) s    +-- with (x as s) s+-- TODO: add tests with names shadowing existing with clauses and rv names testWithClause :: Test testWithClause = TestCase $ do   (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback-  executeTutorialD sessionId dbconn "x:=with (x as s) x"+  executeTutorialD sessionId dbconn "x:=with (w as s) w"   eX <- executeRelationalExpr sessionId dbconn (RelationVariable "x" ())   assertEqual "with x as s" (Right suppliersRel) eX @@ -738,8 +747,8 @@ testInvalidTuples :: Test testInvalidTuples = TestCase $ do   (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback-  expectTutorialDErr sessionId dbconn (T.isPrefixOf "AttributeNamesMismatchError") ":showexpr relation{tuple{a 1},tuple{b 2}}"-  expectTutorialDErr sessionId dbconn (T.isPrefixOf "AttributeNamesMismatchError") ":showexpr relation{tuple{a 1},tuple{a 2, b 3}}"+  expectTutorialDErr sessionId dbconn (T.isPrefixOf "TupleAttributeTypeMismatchError") ":showexpr relation{tuple{a 1},tuple{b 2}}"+  expectTutorialDErr sessionId dbconn (T.isPrefixOf "TupleAttributeTypeMismatchError") ":showexpr relation{tuple{a 1},tuple{a 2, b 3}}" --  expectTutorialDErr sessionId dbconn (T.isPrefixOf "ParseErrorBundle") ":showexpr relation{tuple{a 2, a 3}}" --parse failure can't be validated with this function  testSelfReferencingUncommittedContext :: Test@@ -800,7 +809,7 @@   --test that context-based tuples are pushed through to the tuple extend processor used commonly in OUTER-JOIN-equivalent extensions   (sessionId, dbconn) <- dateExamplesConnection emptyNotificationCallback   executeTutorialD sessionId dbconn "x := (relation{tuple{p# \"P7\", city \"Reykjavik\", color \"Beige\", pname \"Widget\", weight 21}} union p : {suppliers := (sp rename {p# as pid} where p#=@pid) {s#}}) {p#,suppliers}"-  {-+  {-" :showexpr relation{tuple{p# "P5", suppliers relation{tuple{s# "S1"}, tuple{s# "S4"}}}, tuple{p# "P4", suppliers relation{tuple{s# "S1"}, tuple{s# "S4"}}}, tuple{p# "P2", suppliers relation{tuple{s# "S1"}, tuple{s# "S2"}, tuple{s# "S3"}, tuple{s# "S4"}}}, tuple{p# "P3", suppliers relation{tuple{s# "S1"}}}, tuple{p# "P6", suppliers relation{tuple{s# "S1"}}}, tuple{p# "P1", suppliers relation{tuple{s# "S1"},tuple{s# "S2"}}}, tuple{p# "P7", suppliers relation{s# Text}}}   -}@@ -817,7 +826,7 @@   Right hash2 <- getDDLHash sessionId dbconn     assertBool "add relvar" (hash1 /= hash2)   -- the test should break if the hash is calculated differently-  assertEqual "static hash check" "3aNi/azK9QNSXQQQ0QOuGcqAPlRh0d7zX0bNwjowPDA=" (B64.encode (_unSecureHash hash1))+  assertEqual "static hash check" "Aoyq+ARAHMyXGYkLewesLxkIEEgBtpAEaYtzPOpIXo4=" (B64.encode (_unSecureHash hash1))   -- remove an rv   executeTutorialD sessionId dbconn "undefine x"   Right hash3 <- getDDLHash sessionId dbconn@@ -926,3 +935,18 @@     assertEqual "type var resolution y" expectedY resY +testDBCFunctionAccessControl :: Test+testDBCFunctionAccessControl = TestCase $ do+  (session, conn) <- dateExamplesConnection emptyNotificationCallback+  -- add a non-privileged role+  let user1 = "user1"+  executeTutorialD session conn (":addloginrole " <> user1 <> " maynotlogin")+  let user1conn = setRoleName user1 conn  +  -- check that the non-privileged role cannot run the dbc function+  expectTutorialDErr session user1conn ("AccessDeniedError" `T.isInfixOf`) "execute deleteAll()"+  -- grant permission to a dbc function to the role+  executeTutorialD session conn $ "grant dbcfunction " <> user1 <> " deleteAll executefunction nogrant"+  executeTutorialD session conn $ "grant " <> user1 <> " executefunctions nogrant"+  -- check that the non-privileged role can run the dbc function+  executeTutorialD session user1conn "execute deleteAll()"+  
test/scripts.hs view
@@ -22,7 +22,7 @@    testScript :: FilePath -> Test testScript tutdFile = TestCase $ do-  fileURI <- mkURI (T.pack ("file://" <> tutdFile))+  fileURI <- mkURI (T.pack ("file:" <> tutdFile))   eImport <- importTutorialDFromFile fileURI Nothing   case eImport of     Left err -> assertFailure ("tutd import failure in " ++ tutdFile ++ ": " ++ show err)