packages feed

datalog-0.1.0.0: src/Database/Datalog.hs

{-# LANGUAGE FlexibleContexts #-}
module Database.Datalog (
  -- * Types
  Database,
  Relation,
  DatabaseBuilder,
  QueryBuilder,
  Term(LogicVar, BindVar, Anything, Atom),
  QueryPlan,
  DatalogError(..),
  Query,
  Failure,

  -- * Building the IDB
  makeDatabase,
  addRelation,
  assertFact,

  -- * Building Logic Programs
  (|-),
  assertRule,
  relationPredicateFromName,
  inferencePredicate,
  issueQuery,
  lit,
  negLit,
  cond1,
  cond2,
  cond3,
  cond4,
  cond5,

  -- * Evaluating Queries
  queryDatabase,
  buildQueryPlan,
  executeQueryPlan
  ) where

import Control.Failure
import Control.Monad ( foldM )
import Data.Hashable
import Data.Text ( Text )

import Database.Datalog.Database
import Database.Datalog.Errors
import Database.Datalog.Evaluate
import Database.Datalog.Rules
import Database.Datalog.MagicSets
import Database.Datalog.Stratification

import Debug.Trace
import Text.Printf
debug = flip trace

-- | A fully-stratified query plan that is ready to be executed.
data QueryPlan a = QueryPlan (Query a) [[Rule a]]

-- | This is a shortcut to build a query plan and execute in one step,
-- with no bindings provided.  It doesn't make sense to have bindings
-- in one-shot queries.
queryDatabase :: (Failure DatalogError m, Eq a, Hashable a, Show a)
                 => Database a -- ^ The intensional database of facts
                 -> QueryBuilder m a (Query a) -- ^ A monad building up a set of rules and returning a Query
                 -> m [[a]]
queryDatabase idb qm = do
  qp <- buildQueryPlan idb qm
  executeQueryPlan qp idb []

-- | Given a query description, build a query plan by stratifying the
-- rules and performing the magic sets transformation.  Throws an
-- error if the rules cannot be stratified.
buildQueryPlan :: (Failure DatalogError m, Eq a, Hashable a, Show a)
                  => Database a
                  -> QueryBuilder m a (Query a)
                  -> m (QueryPlan a)
buildQueryPlan idb qm = do
  (q, rs) <- runQuery qm idb
  rs' <- magicSetsRules q rs
  strata <- stratifyRules rs'
  return $! QueryPlan q strata

-- | Execute a query plan with an intensional database and a set of
-- bindings (substituted in for 'BindVar's).  Throw an error if:
--
--  * The rules and database define the same relation
executeQueryPlan :: (Failure DatalogError m, Eq a, Hashable a, Show a)
                    => QueryPlan a -> Database a -> [(Text, a)] -> m [[a]]
executeQueryPlan (QueryPlan q strata) idb bindings = do
  -- FIXME: Bindings is used to substitute in values for BoundVars in
  -- the query.  Those might actually affect the magic rules that are
  -- required...  This is the seed-rule and
  -- seed-predicate-for-insertion code in the clojure implementation
  sdb <- seedDatabase idb (concat strata) q bindings
  edb <- applyStrata strata sdb
  let q' = bindQuery q bindings
      pt = queryToPartialTuple q'
      p = queryPredicate q'
  return $! map unTuple $ select edb p pt -- `debug` show edb

-- Private helpers

-- | Apply the rules in each stratum bottom-up.  Compute a fixed-point
-- for each stratum
applyStrata :: (Failure DatalogError m, Eq a, Hashable a, Show a)
               => [[Rule a]] -> Database a -> m (Database a)
applyStrata [] db = return db
applyStrata ss@(s:strata) db = do
  -- Group the rules by their head relations.  The delta table has to
  -- be managed for all of the related rules at once.
  db' <- foldM applyRuleSet db (partitionRules s)
  case databaseHasDelta db' of
    True -> applyStrata ss db'
    False -> applyStrata strata db'