packages feed

project-m36-0.9.5: test/Relation/Basic.hs

import Test.HUnit
import ProjectM36.Base
import ProjectM36.Relation
import ProjectM36.Error
import ProjectM36.DateExamples
import ProjectM36.DataTypes.Primitive
import ProjectM36.RelationalExpression
import ProjectM36.TransactionGraph
import ProjectM36.Tuple
import ProjectM36.Attribute (attributesFromList)
import qualified ProjectM36.DatabaseContext as DBC
import qualified ProjectM36.Attribute as A
import qualified Data.Map as M
import qualified Data.Vector as V
import qualified Data.Set as S
import System.Exit
import Data.Hashable (hashWithSalt)


testList :: Test
testList = TestList [testRelation "relationTrue" relationTrue, testRelation "relationFalse" relationFalse,
                     testMkRelation1,
                     testRename1, testRename2,
                     testRelation "suppliers" suppliersRel,
                     testRelation "products" productsRel,
                     testRelation "supplierProducts" supplierProductsRel,
                     testMkRelationFromExprsBadAttrs,
                     testExistingRelationType,
                     testReorderTuple,
                     testRelationEquality
                    ]

main :: IO ()           
main = do 
  tcounts <- runTestTT testList
  if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess

testRelation :: String -> Relation -> Test
testRelation testName rel = TestCase $ assertEqual testName relationValidation (Right rel)
  where
    relationValidation = validateRelation rel

-- run common relation checks
validateRelation :: Relation -> Either RelationalError Relation
validateRelation rel = do
  _ <- validateAttrNamesMatchTupleAttrNames rel
  validateAttrTypesMatchTupleAttrTypes rel
  
validateAttrNamesMatchTupleAttrNames :: Relation -> Either RelationalError Relation
validateAttrNamesMatchTupleAttrNames rel@(Relation _ tupSet) 
  | null invalidSet = Right rel
  | otherwise = Left $ AttributeNamesMismatchError S.empty
  where
    nameCheck tuple = attributeNames rel == tupleAttributeNameSet tuple
    invalidSet = filter (not . nameCheck) (asList tupSet)
    
validateAttrTypesMatchTupleAttrTypes :: Relation -> Either RelationalError Relation
validateAttrTypesMatchTupleAttrTypes rel@(Relation attrs tupSet) = foldr (\tuple acc -> 
                                                                              if tupleAttributes tuple == attrs && tupleAtomCheck tuple then 
                                                                                acc 
                                                                              else
                                                                                Left $ TupleAttributeTypeMismatchError A.emptyAttributes
                                                                            ) (Right rel) (asList tupSet)
  where
    tupleAtomCheck tuple = V.all (== True) (attrChecks tuple)
    attrChecks tuple = V.map (\attr -> case atomForAttributeName (A.attributeName attr) tuple of
                                 Left _ -> False
                                 Right atom -> Right (atomTypeForAtom atom) ==
                                  A.atomTypeForAttributeName (A.attributeName attr) attrs) (attributesVec (attributes rel))
    
simpleRel :: Relation    
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"])]
    
--rename tests
testRename1 :: Test
testRename1 = TestCase $ assertEqual "attribute invalid" (rename "a" "b" relationTrue) (Left $ AttributeNamesMismatchError (S.singleton "a"))

testRename2 :: Test
testRename2 = TestCase $ assertEqual "attribute in use" (rename "b" "a" simpleRel) (Left $ AttributeNameInUseError "a")

--mkRelation tests
--test tupleset key mismatch failure
testMkRelation1 :: Test
testMkRelation1 = TestCase $ assertEqual "key mismatch" expectedError (mkRelation testAttrs testTupSet) -- the error attribute set is empty due to an optimization- the tuple attrs do not match the atoms' types
  where
    expectedError = Left (AtomTypeMismatchError TextAtomType IntAtomType)
    testAttrs = A.attributesFromList [Attribute "a" TextAtomType]
    testTupSet = RelationTupleSet [mkRelationTuple testAttrs $ V.fromList [TextAtom "v"],
                                   mkRelationTuple testAttrs $ V.fromList [IntAtom 2]]

testMkRelationFromExprsBadAttrs :: Test
testMkRelationFromExprsBadAttrs = TestCase $ do
  let context = DBC.empty 
  (graph,_) <- freshTransactionGraph context  
  let reenv = mkRelationalExprEnv context graph
      reExpr = MakeRelationFromExprs (Just [AttributeAndTypeNameExpr "badAttr1" (PrimitiveTypeConstructor "Int" IntAtomType) ()]) (TupleExprs () [TupleExpr (M.singleton "badAttr2" (NakedAtomExpr (IntAtom 1)))])
      evald = runRelationalExprM reenv (evalRelationalExpr reExpr)
  case evald of
    Left err -> assertEqual "tuple type mismatch" (TupleAttributeTypeMismatchError (A.attributesFromList [Attribute "badAttr2" IntAtomType])) err
    Right _ -> assertFailure "expected tuple type mismatch"

testExistingRelationType :: Test
testExistingRelationType = TestCase $ do
  (graph, _) <- freshTransactionGraph dateExamples
  let typeResult = runRelationalExprM reenv (typeForRelationalExpr (ExistingRelation relationTrue))
      reenv = mkRelationalExprEnv dateExamples graph
  assertEqual "ExistingRelation with tuples type" (Right relationFalse) typeResult

-- | Ensure that tuple reordering honors the 
testReorderTuple :: Test
testReorderTuple = TestCase $ do
  let tup1 = mkRelationTuple attrs1 (V.fromList [IntAtom 4, TextAtom "test"])
      attrs1 = A.attributesFromList [Attribute "a" IntAtomType, Attribute "b" TextAtomType]
      attrs2 = A.attributesFromList [Attribute "b" TextAtomType, Attribute "a" IntAtomType]
      actual = reorderTuple attrs2 tup1
      expected = mkRelationTuple attrs2 (V.fromList [TextAtom "test", IntAtom 4])
  assertEqual "reorderTuple" expected actual

testRelationEquality :: Test
testRelationEquality = TestCase $ do
  -- relations with changed orders of tuples must hash to the same value and be equal, even in lieu of subrelations
  let r1 = Relation rattrs $ RelationTupleSet {asList = [RelationTuple subrelattrs
                                                        (V.fromList [RelationAtom subrel1])]
                                             }
      rattrs = attributesFromList [Attribute "x" (RelationAtomType subrelattrs)]

      r2 = Relation rattrs $ RelationTupleSet {asList = [RelationTuple subrelattrs
                                 (V.fromList [RelationAtom subrel2])]
                      }
      subrelattrs = attributesFromList [Attribute "y" IntAtomType]           
      subrel1 = Relation subrelattrs tupset1
      tupset1 = RelationTupleSet [RelationTuple subrelattrs (V.fromList [IntAtom 3]),
                                RelationTuple subrelattrs (V.fromList [IntAtom 4])]
      subrel2 = Relation subrelattrs tupset2
      tupset2 = RelationTupleSet [RelationTuple subrelattrs (V.fromList [IntAtom 4]),
                                  RelationTuple subrelattrs (V.fromList [IntAtom 3])]

  assertEqual "relation eq" r1 r2
  assertEqual "relation hash" (hashWithSalt 0 r1) (hashWithSalt 0 r2)
  assertEqual "tupset eq" tupset1 tupset2
  assertEqual "tupset hash" (hashWithSalt 0 tupset1) (hashWithSalt 0 tupset2)