{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Example where import Control.Exception import Data.Aeson.TH import Data.ByteString (ByteString) import Data.Int (Int32, Int64) import Data.Proxy import Data.Text (Text) import Data.Time (LocalTime) import Database.Beam.AutoMigrate ( DbEnum, Diff, HasColumnType, Migration, PgEnum, ReferenceAction (..), Schema, defaultAnnotatedDbSettings, diff, fromAnnotatedDbSettings, migrate, printMigration, unsafeRunMigration, ) import Database.Beam.AutoMigrate.Annotated import Database.Beam.AutoMigrate.Postgres (getSchema) import Database.Beam.Backend.SQL.Types (SqlSerial (..)) import Database.Beam.Postgres import qualified Database.Beam.Postgres as Pg import Database.Beam.Query (currentTimestamp_, val_) import Database.Beam.Schema ( Beamable, Columnar, Database, DatabaseSettings, PrimaryKey, TableEntity, dbModification, defaultDbSettings, fieldNamed, modifyTableFields, tableModification, withDbModification, ) import qualified Database.Beam.Schema as Beam import Database.Beam.Schema.Tables (primaryKey) import qualified Database.PostgreSQL.Simple as Pg import GHC.Generics -- -- Example -- data MyJson = MyJson { foo :: Int, bar :: Maybe Bool, quux :: Text } deriveJSON defaultOptions ''MyJson data FlowerType = Rose | Sunflower | Tulip deriving (Show, Enum, Bounded) data Foo = Bar | Baz | Quux deriving (Show, Enum, Bounded) deriving (HasColumnType) via (PgEnum Foo) -- A mixin embedded into another (i.e. 'Address'). data AddressRegion f = AddressRegion { addressState :: Columnar f (Maybe Text), addressCountry :: Columnar f (Maybe Text), addressPostalCode :: Columnar f (Maybe Text) } deriving (Generic, Beamable) data Address f = Address { address :: Columnar f (Maybe Text), addressCity :: Columnar f (Maybe Text), addressRegion :: AddressRegion f } deriving (Generic, Beamable) data FlowerT f = Flower { flowerID :: Columnar f Int32, flowerName :: Columnar f Text, flowerInternalID :: Columnar f (SqlSerial Int), flowerPrice :: Columnar (Beam.Nullable f) Double, flowerDiscounted :: Columnar f (Maybe Bool), flowerSchemaOne :: Columnar f (PgJSON MyJson), flowerSchemaTwo :: Columnar f (PgJSONB MyJson), flowerType :: Columnar f (DbEnum FlowerType), flowerPgType :: Columnar f Foo, flowerAddress :: Address f } deriving (Generic, Beamable) data OrderT f = Order { orderID :: Columnar f (SqlSerial Int64), orderTime :: Columnar f LocalTime, orderFlowerIdRef :: PrimaryKey FlowerT f, orderValidity :: Columnar f (Pg.PgRange Pg.PgInt8Range Int64), orderAddress :: Address f, orderLineItemRef :: PrimaryKey LineItemT f } deriving (Generic, Beamable) data LineItemT f = LineItem { lineItemOrderID :: PrimaryKey OrderT f, lineItemFlowerID :: PrimaryKey FlowerT f, lineItemQuantity :: Columnar f Int64, lineItemDiscount :: Columnar f (Maybe Bool), lineItemBytearray :: Columnar f ByteString, lineItemNullableRef :: PrimaryKey OrderT (Beam.Nullable f) } deriving (Generic, Beamable) data LineItemTwoT f = LineItemTwo { lineItemTwoID :: Columnar f Int64, lineItemTwoFk :: PrimaryKey LineItemT f } deriving (Generic, Beamable) data FlowerDB f = FlowerDB { dbFlowers :: f (TableEntity FlowerT), dbOrders :: f (TableEntity OrderT), dbOrders2 :: f (TableEntity OrderT), dbLineItems :: f (TableEntity LineItemT), dbLineItemsTwo :: f (TableEntity LineItemTwoT) } deriving (Generic, Database be) instance Beam.Table FlowerT where data PrimaryKey FlowerT f = FlowerID (Columnar f Int32) deriving (Generic, Beamable) primaryKey = FlowerID . flowerID instance Beam.Table OrderT where data PrimaryKey OrderT f = OrderID (Columnar f (SqlSerial Int64)) deriving (Generic, Beamable) primaryKey = OrderID . orderID instance Beam.Table LineItemT where data PrimaryKey LineItemT f = LineItemID (PrimaryKey OrderT f) (PrimaryKey FlowerT f) deriving (Generic, Beamable) primaryKey = LineItemID <$> lineItemOrderID <*> lineItemFlowerID instance Beam.Table LineItemTwoT where data PrimaryKey LineItemTwoT f = LineItemTwoID (Columnar f Int64) deriving (Generic, Beamable) primaryKey = LineItemTwoID <$> lineItemTwoID -- Modify the field names to be compliant with William Yao's format. flowerDB :: DatabaseSettings Postgres FlowerDB flowerDB = defaultDbSettings `withDbModification` dbModification { dbFlowers = modifyTableFields tableModification {flowerID = fieldNamed "id"}, dbOrders = modifyTableFields tableModification { orderID = fieldNamed "id", orderTime = fieldNamed "order_time" }, dbLineItems = modifyTableFields tableModification { lineItemFlowerID = FlowerID "flower_id", lineItemOrderID = OrderID "order_id", lineItemQuantity = fieldNamed "quantity", lineItemNullableRef = OrderID "external_nullable_ref" } } annotatedDB :: AnnotatedDatabaseSettings Postgres FlowerDB annotatedDB = defaultAnnotatedDbSettings flowerDB `withDbModification` dbModification { dbFlowers = annotateTableFields tableModification {flowerDiscounted = defaultsTo (val_ $ Just True)} <> annotateTableFields tableModification {flowerPrice = defaultsTo (val_ $ Just 10.0)} <> uniqueConstraintOn [U (addressPostalCode . addressRegion . flowerAddress)], dbLineItems = annotateTableFields tableModification {lineItemDiscount = defaultsTo (val_ $ Just False)} <> uniqueConstraintOn [U lineItemFlowerID, U lineItemOrderID, U lineItemQuantity], dbOrders = annotateTableFields tableModification { orderTime = defaultsTo currentTimestamp_, orderValidity = defaultsTo (range_ Inclusive Inclusive (val_ $ Just 10) (val_ $ Just 20)) } <> foreignKeyOnPk (dbFlowers flowerDB) orderFlowerIdRef Cascade Restrict <> uniqueConstraintOn [U (addressPostalCode . addressRegion . orderAddress)] --, dbLineItemsTwo = foreignKeyOn (dbLineItems flowerDB) [ -- lineItemTwoFk `References` LineItemID -- ] Cascade Restrict } hsSchema :: Schema hsSchema = fromAnnotatedDbSettings annotatedDB (Proxy @'[ 'UserDefinedFk LineItemT]) -- fromAnnotatedDbSettings annotatedDB (Proxy @'[]) getDbSchema :: String -> IO Schema getDbSchema dbName = bracket (connect defaultConnectInfo {connectUser = "adinapoli", connectDatabase = dbName}) close getSchema getFlowerDbSchema :: IO Schema getFlowerDbSchema = getDbSchema "beam-test-db" getSchemaDiff :: IO Diff getSchemaDiff = diff hsSchema <$> getFlowerDbSchema getGroundhogSchema :: IO Schema getGroundhogSchema = getDbSchema "groundhog-test-db" -- | Just a simple example demonstrating a possible workflow for a migration. example :: IO () example = do print hsSchema dbSchema <- getFlowerDbSchema print dbSchema print $ dbSchema == hsSchema let schemaDiff = diff hsSchema dbSchema print schemaDiff putStrLn "GROUNDHOG" getGroundhogSchema >>= print exampleShowMigration :: IO () exampleShowMigration = withBeamTestDb printMigration withBeamTestDb :: (Migration Pg -> Pg ()) -> IO () withBeamTestDb action = do let connInfo = "host=localhost port=5432 dbname=beam-test-db" bracket (Pg.connectPostgreSQL connInfo) Pg.close $ \conn -> Pg.withTransaction conn $ runBeamPostgresDebug putStrLn conn $ do let mig = migrate conn hsSchema action mig exampleAutoMigration :: IO () exampleAutoMigration = withBeamTestDb unsafeRunMigration