project-m36-1.2.0: src/lib/ProjectM36/Streaming/Tuple.hs
{-# 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)