200 lines
6.3 KiB
Haskell
Raw Permalink Normal View History

{-# LANGUAGE BlockArguments #-}
2026-01-21 11:24:30 +00:00
{-# LANGUAGE DuplicateRecordFields #-}
2026-01-27 16:41:33 +00:00
{-# LANGUAGE ImportQualifiedPost #-}
2026-01-21 11:24:30 +00:00
{-# LANGUAGE OverloadedRecordDot #-}
{-# HLINT ignore "Use const" #-}
{-# HLINT ignore "Unused LANGUAGE pragma" #-}
{-# HLINT ignore "Avoid lambda" #-}
{-# LANGUAGE ScopedTypeVariables #-}
2026-01-27 16:41:33 +00:00
{-# LANGUAGE TypeApplications #-}
2026-01-21 11:24:30 +00:00
{-# LANGUAGE NoFieldSelectors #-}
2026-01-27 17:27:18 +00:00
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
2026-01-21 11:24:30 +00:00
2026-01-30 15:39:17 +00:00
module Test.Datalog.InMemoryDBSpec where
2026-01-21 11:24:30 +00:00
2026-01-27 16:41:33 +00:00
import Data.Map qualified as Map
2026-01-22 14:25:09 +00:00
import Data.Set qualified as Set
import Datalog.DatalogParser
2026-01-30 15:39:17 +00:00
import Datalog.InMemoryDB
import Datalog.InMemoryDB qualified as InMemoryDB
2026-01-27 16:41:33 +00:00
import Test.Hspec
2026-01-30 14:46:09 +00:00
import Datalog.DatalogDB
2026-01-30 16:07:23 +00:00
import Data.Text
2026-01-21 11:24:30 +00:00
spec :: Spec
spec = do
2026-01-30 15:39:17 +00:00
describe "InMemoryDB operations" do
2026-01-22 14:25:09 +00:00
it "can ingest facts into relations & a universe" $ do
let db =
2026-01-30 15:39:17 +00:00
InMemoryDB.withFacts
[ "parent(\"alice\", \"bob\")."
, "parent(\"bob\", \"carol\")."
]
2026-01-27 16:41:33 +00:00
constants db
`shouldBe` Set.fromList (Sym <$> ["alice", "bob", "carol"])
relations db
`shouldBe` Map.fromList
[
( "parent"
2026-01-27 17:27:18 +00:00
, Relation "parent" 2 (Set.fromList $ Sym <<$>> [["alice", "bob"], ["bob", "carol"]]) []
2026-01-27 16:41:33 +00:00
)
]
it "can ingest facts and rules" do
let db =
2026-01-30 15:39:17 +00:00
InMemoryDB.withFactsAndRules
[ "parent(\"alice\", \"bob\")."
, "parent(\"bob\", \"carol\")."
]
[ "ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y)."
, "ancestor(X,Y) :- parent(X,Y)."
]
2026-01-27 16:41:33 +00:00
parentRelation =
Relation
{ _name = "parent"
, _arity = 2
, _tuples =
Set.fromList $
2026-01-30 16:07:23 +00:00
Sym <<$>> [["alice", "bob"], ["bob", "carol"]]
2026-01-27 16:41:33 +00:00
, _rules = []
}
ancestorRule = RelationRule
2026-01-27 16:41:33 +00:00
{ headVariables = ["X", "Y", "Z"]
, bodyElements =
2026-01-30 16:07:23 +00:00
[ ruleBody "parent" [0, 2]
, ruleBody "ancestor" [2, 1]
2026-01-27 16:41:33 +00:00
]
2026-01-27 12:41:23 +00:00
}
ancestorRule2 = RelationRule
{ headVariables = ["X", "Y"]
, bodyElements =
2026-01-30 16:07:23 +00:00
[ ruleBody "parent" [0, 1] ]
}
2026-01-27 16:41:33 +00:00
ancestorRelation =
Relation
{ _arity = 2
, _name = "ancestor"
, _tuples = Set.empty
, _rules = [ancestorRule, ancestorRule2]
2026-01-27 16:41:33 +00:00
}
2026-01-27 16:41:33 +00:00
constants db
2026-01-27 17:27:18 +00:00
`shouldBe` Set.fromList (Sym <$> ["alice", "bob", "carol"])
2026-01-27 16:41:33 +00:00
relations db
`shouldBe` Map.fromList
[ ("ancestor", ancestorRelation)
, ("parent", parentRelation)
]
2026-01-22 14:25:09 +00:00
2026-01-27 16:41:33 +00:00
it "can ingest facts and rules with constants" do
let db =
2026-01-30 15:39:17 +00:00
InMemoryDB.withFactsAndRules
2026-01-27 16:41:33 +00:00
[]
["ancestor(X,\"patriarch\") :- ."]
ancestorRule =
RelationRule
{ headVariables = ["X"]
, bodyElements = []
}
ancestorRelation =
Relation
{ _arity = 2
, _name = "ancestor"
, _tuples = Set.empty
, _rules = [ancestorRule]
}
relations db
`shouldBe` Map.singleton "ancestor" ancestorRelation
2026-01-27 16:41:33 +00:00
constants db
2026-01-27 17:27:18 +00:00
`shouldBe` Set.fromList (Sym <$> ["patriarch"])
it "can ingest facts and rules with duplicate head entries" do
2026-01-27 16:41:33 +00:00
let db =
2026-01-30 15:39:17 +00:00
InMemoryDB.withFactsAndRules
[]
["equivalent(Q,Q) :- ."]
equivalentRule =
RelationRule
{ headVariables = ["Q"]
, bodyElements = []
}
equivalentRelation =
Relation
{ _arity = 2
, _name = "equivalent"
, _tuples = Set.empty
, _rules = [equivalentRule]
}
relations db
`shouldBe` Map.singleton "equivalent" equivalentRelation
constants db
`shouldBe` Set.empty
it "can ingest a theory of equivalence relations" do
let db =
2026-01-30 15:39:17 +00:00
InMemoryDB.withFactsAndRules
[]
[ "equivalent(Q,Q) :- ."
, "equivalent(R,Q) :- equivalent(Q,R)."
, "equivalent(Q,S) :- equivalent(Q,R), equivalent(R,S)."
]
rule1 =
RelationRule
{ headVariables = ["Q"]
, bodyElements = []
}
rule2 =
RelationRule
{ headVariables = ["R", "Q"]
, bodyElements =
2026-01-30 16:07:23 +00:00
[ ruleBody "equivalent" [1, 0] ]
}
rule3 =
RelationRule
{ headVariables = ["Q", "S", "R"]
, bodyElements =
2026-01-30 16:07:23 +00:00
[ ruleBody "equivalent" [0, 2]
, ruleBody "equivalent" [2, 1]
]
}
equivalentRelation =
Relation
{ _arity = 2
, _name = "equivalent"
, _tuples = Set.empty
, _rules = [rule1, rule2, rule3]
}
relations db
`shouldBe` Map.singleton "equivalent" equivalentRelation
constants db
`shouldBe` Set.empty
it "can do basic queries" do
2026-01-30 15:39:17 +00:00
let db :: InMemoryDB =
InMemoryDB.withFacts
[ "parent(\"alice\", \"bob\")."
, "parent(\"bob\", \"carol\")."
]
2026-01-22 14:25:09 +00:00
query db "?- parent(alice,X)." `shouldBe` "#NYI" -- ideally, 'bob'
2026-01-27 17:27:18 +00:00
(<<$>>) :: (Functor f1, Functor f2) => (a -> b) -> f1 (f2 a) -> f1 (f2 b)
(<<$>>) = fmap fmap fmap
-- (<<<$>>>) :: (Functor f1, Functor f2, Functor f3) => f1 (f2 (a -> b)) -> f1 (f2 (f3 a -> f3 b))
-- (<<<$>>>) :: Functor f => (a1 -> b) -> (a2 -> a1) -> f a2 -> f b
-- (<<<$>>>) :: (Functor f1, Functor f2) => (a1 -> a2 -> b) -> f1 a1 -> f1 (f2 a2 -> f2 b)
-- (<<<$>>>) :: (Functor f1, Functor f2, Functor f3) => f1 (a -> b) -> f1 (f2 (f3 a) -> f2 (f3 b))
(<<<$>>>) :: (Functor f1, Functor f2, Functor f3) => (a -> b) -> f1 (f2 (f3 a)) -> f1 (f2 (f3 b))
(<<<$>>>) = fmap fmap fmap fmap fmap fmap fmap fmap
2026-01-30 16:07:23 +00:00
ruleBody :: Text -> [Int] -> RuleBodyElement
ruleBody subRelationId indices =
RuleBodyElement
{ _subRelationId = subRelationId
, _ruleElements =
RuleElementVariable <$> indices
}