-- a simple example of a blog schema {-# LANGUAGE DeriveAnyClass, DeriveGeneric, OverloadedStrings #-} import ProjectM36.Client import ProjectM36.Relation import ProjectM36.Tupleable import Data.Either import GHC.Generics import Data.Binary import qualified Data.Text as T import Data.Time.Clock import Data.Time.Calendar import Control.DeepSeq --define your data types data Blog = Blog { title :: T.Text, stamp :: UTCTime, category :: Category --note that this type is an algebraic data type } deriving (Generic, Show) --derive Generic so that Tupleable can use default instances --instantiate default Tupleable instances instance Tupleable Blog data Comment = Comment { blogTitle :: T.Text, commentTime :: UTCTime, contents :: T.Text } deriving (Generic, Show) instance Tupleable Comment data Category = Food | Cats | Photos | Other T.Text -- note that this data type could not be represented by an "enumeration" as found in SQL databases deriving (Atomable, Eq, Show, NFData, Binary, Generic) -- derive Atomable so that values of this type can be stored as a database value -- add some short-hand error handling- your application should have proper handling handleIOError :: Show e => IO (Either e a) -> IO a handleIOError m = do v <- m handleError v handleError :: Show e => Either e a -> IO a handleError eErr = case eErr of Left err -> print err >> error "Died due to errors." Right v -> pure v handleIOErrors :: Show e => IO [Either e a] -> IO [a] handleIOErrors m = do eErrs <- m case lefts eErrs of [] -> pure (rights eErrs) err:_ -> handleError (Left err) main :: IO () main = do --connect to the database let connInfo = InProcessConnectionInfo NoPersistence emptyNotificationCallback [] conn <- handleIOError $ connectProjectM36 connInfo sessionId <- handleIOError $ createSessionAtHead conn "master" createSchema sessionId conn insertSampleData sessionId conn executeSampleQueries sessionId conn --define the schema with the new Category atom (data) type, blog relvar, a comment relvar, and a foreign key relationship between them createSchema :: SessionId -> Connection -> IO () createSchema sessionId conn = do _ <- handleIOErrors $ mapM (executeDatabaseContextExpr sessionId conn) [ toAddTypeExpr (undefined :: Category), toDefineExpr (undefined :: Blog) "blog", toDefineExpr (undefined :: Comment) "comment", databaseContextExprForForeignKey "blog_comment" ("comment", ["blogTitle"]) ("blog", ["title"]) ] pure () --create some sample values and insert them into the database's relation variables insertSampleData :: SessionId -> Connection -> IO () insertSampleData sessionId conn = do let blogs = [Blog { title = "Eat More Tofu", stamp = UTCTime (fromGregorian 2017 5 8) (secondsToDiffTime 1000), category = Food }, Blog { title = "Cat Falls Off Table", stamp = UTCTime (fromGregorian 2017 6 10) (secondsToDiffTime 2000), category = Cats } ] comments = [Comment { blogTitle = "Cat Falls Off Table", commentTime = UTCTime (fromGregorian 2017 7 8) (secondsToDiffTime 3000), contents = "more cats please" }] insertBlogsExpr <- handleError $ toInsertExpr blogs "blog" handleIOError $ executeDatabaseContextExpr sessionId conn insertBlogsExpr insertCommentsExpr <- handleError $ toInsertExpr comments "comment" handleIOError $ executeDatabaseContextExpr sessionId conn insertCommentsExpr --issue a query and marshal the data back to the original data value executeSampleQueries :: SessionId -> Connection -> IO () executeSampleQueries sessionId conn = do commentsRelation <- handleIOError $ executeRelationalExpr sessionId conn (RelationVariable "comment" ()) comments <- toList commentsRelation >>= mapM (handleError . fromTuple) :: IO [Comment] print comments