2026-01-30 10:27:26 +00:00
|
|
|
{-# HLINT ignore "Redundant flip" #-}
|
|
|
|
|
{-# LANGUAGE ImportQualifiedPost #-}
|
|
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
|
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
2026-01-30 16:16:06 +00:00
|
|
|
{-# LANGUAGE BlockArguments #-}
|
2026-01-30 10:27:26 +00:00
|
|
|
|
|
|
|
|
module Datalog.DatalogDB where
|
|
|
|
|
|
|
|
|
|
import Control.Exception.Base
|
|
|
|
|
import Data.Set (Set)
|
|
|
|
|
import Data.Set qualified as Set
|
|
|
|
|
import Data.Text (Text)
|
2026-01-30 14:46:09 +00:00
|
|
|
import Datalog.DatalogParser (Literal (..), Term (..), Statement)
|
2026-01-30 10:27:26 +00:00
|
|
|
import Text.Megaparsec (ParseErrorBundle)
|
2026-01-30 14:46:09 +00:00
|
|
|
import Data.Void
|
|
|
|
|
|
|
|
|
|
data Relation = Relation
|
|
|
|
|
{ _name :: RelationId
|
|
|
|
|
, _arity :: Int
|
|
|
|
|
, _tuples :: Set [Constant]
|
|
|
|
|
, _rules :: [RelationRule]
|
|
|
|
|
}
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
data RelationRule = RelationRule
|
|
|
|
|
{ headVariables :: [Text]
|
|
|
|
|
, bodyElements :: [RuleBodyElement]
|
|
|
|
|
}
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
data RuleBodyElement = RuleBodyElement
|
|
|
|
|
{ _subRelationId :: RelationId
|
|
|
|
|
, _ruleElements :: [RuleElement]
|
|
|
|
|
}
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
data RuleElement -- entry occurring in a head or body relation - constant or variable
|
|
|
|
|
= RuleElementConstant Constant
|
|
|
|
|
| RuleElementVariable Int
|
|
|
|
|
deriving (Show, Eq)
|
|
|
|
|
|
|
|
|
|
data DatalogDBException
|
|
|
|
|
= CannotParseStatementException Text (ParseErrorBundle Text Void)
|
|
|
|
|
| NonFactException Text Statement
|
|
|
|
|
| NonRuleException Text Statement
|
|
|
|
|
| NonQueryException Text Statement
|
|
|
|
|
| BadArityException Text Int
|
|
|
|
|
| VariableLookupException Text [Text]
|
|
|
|
|
| UnexpectedConstantException Constant
|
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
|
|
instance Exception DatalogDBException
|
|
|
|
|
|
|
|
|
|
-- Our constants will be the terms of the Datalog grammar - ints/variables/symbols
|
|
|
|
|
type Constant = Term
|
|
|
|
|
type RelationId = Text
|
2026-01-30 10:27:26 +00:00
|
|
|
|
|
|
|
|
class DatalogDB db where
|
|
|
|
|
emptyDB :: db
|
|
|
|
|
lookupRelation :: db -> Text -> Maybe Relation
|
|
|
|
|
insertRelation :: db -> Relation -> db
|
2026-01-30 12:53:40 +00:00
|
|
|
addConstants :: db -> Set Constant -> db
|
|
|
|
|
|
2026-01-30 14:37:45 +00:00
|
|
|
lookupRelationArity :: DatalogDB db => Text -> db -> Int -> Set [Constant] -> Relation
|
|
|
|
|
lookupRelationArity relationName db newArity tuples =
|
|
|
|
|
case lookupRelation db relationName of
|
|
|
|
|
Nothing -> Relation relationName newArity tuples []
|
|
|
|
|
Just relation ->
|
|
|
|
|
if _arity relation == newArity then
|
|
|
|
|
let newTuples = Set.union tuples $ _tuples relation
|
|
|
|
|
in relation { _tuples = newTuples }
|
|
|
|
|
else throw $ BadArityException relationName newArity
|
|
|
|
|
|
2026-01-30 16:16:06 +00:00
|
|
|
lookupRelationArity0 :: DatalogDB db => Text -> db -> Int -> (Relation -> Relation) -> db
|
|
|
|
|
lookupRelationArity0 relationName db newArity update =
|
|
|
|
|
insertRelation db (update newRelation)
|
|
|
|
|
where
|
|
|
|
|
newRelation = case lookupRelation db relationName of
|
|
|
|
|
Nothing -> Relation relationName newArity Set.empty []
|
|
|
|
|
Just relation ->
|
|
|
|
|
if _arity relation == newArity then relation
|
|
|
|
|
else throw $ BadArityException relationName newArity
|
|
|
|
|
|
2026-01-30 14:37:45 +00:00
|
|
|
addFact :: (DatalogDB db) => Literal -> db -> db
|
|
|
|
|
addFact (Literal neg relationName terms) db =
|
|
|
|
|
insertRelation (addConstants db extraConstants) newRelation
|
|
|
|
|
where
|
|
|
|
|
newArity = length terms
|
|
|
|
|
newRelation = lookupRelationArity relationName db newArity (Set.singleton terms)
|
|
|
|
|
extraConstants = Set.fromList terms
|
|
|
|
|
|
2026-01-30 16:16:06 +00:00
|
|
|
lookupRelation00 :: DatalogDB db =>
|
|
|
|
|
Text -> db -> Int -> (Relation -> Relation) -> db
|
|
|
|
|
lookupRelation00 relationName db newArity update =
|
|
|
|
|
insertRelation db (update newRelation)
|
|
|
|
|
where
|
|
|
|
|
newRelation = case lookupRelation db relationName of
|
|
|
|
|
Nothing -> Relation relationName newArity Set.empty []
|
|
|
|
|
Just relation ->
|
|
|
|
|
if _arity relation == newArity then
|
|
|
|
|
relation
|
|
|
|
|
else throw $ BadArityException relationName newArity
|
|
|
|
|
|
|
|
|
|
lookupRelation000 :: DatalogDB db =>
|
|
|
|
|
Text -> db -> Int -> Set [Constant] -> (Relation -> Relation) -> db
|
|
|
|
|
lookupRelation000 relationName db newArity tuples update =
|
|
|
|
|
lookupRelation00 relationName db newArity \relation ->
|
|
|
|
|
update relation {
|
|
|
|
|
_tuples = Set.union tuples $ _tuples relation
|
|
|
|
|
}
|