haskell-beam / haskell-beam/beam
How to update Entity ADT inside a migration that is foreign key of another entities
- Dominant language
- Haskell
- Stars
- 635
- Forks
- 193
- PR merge metrics
- No merged PRs in 30d
Description
Hello! Beam is awesome library, I'm so grateful for the elegant types you have implemented here and the powerful migration system. I was missing for a such type safe migrations like you have and when I found Beam about a week ago I dive immediately into developing the demo blog project.
And now I have a problem with this demo project where I have used migrations: I've added the column to the one table and all my relations that were defined before has a primary key to outdated version of such a column. And I have no idea even how to redefine all relations in new migration file (although it is very undesirable things, because it will be extremely boilerplaty). Here is the most short example I have prepared to illustrate the problem. The full version of files you can find in [the Gist](https://gist.github.com/Znack/92a802f17a5e2b445fe1fd924cd20470).
Consider the demo blog project where we have defined UserT and PostT (notice PostT has a field `_postUserId :: PrimaryKey UserT f`) in migration file `V0001CreateUserAndPostTables.hs`.
Example of a such migration file:
```haskell
type User = UserT Identity
data UserT f = User
{ _userId :: Columnar f (SqlSerial Int)
, _userName :: Columnar f Text
} deriving (Generic, Beamable)
instance Table UserT where
data PrimaryKey UserT f = UserId (Columnar f (SqlSerial Int))
deriving (Generic, Beamable)
primaryKey = UserId . _userId
type Post = PostT Identity
data PostT f = Post
{ _postId :: Columnar f (SqlSerial Int)
, _postContent :: Columnar f Text
, _postAuthor :: PrimaryKey UserT f
} deriving (Generic, Beamable)
instance Table PostT where
data PrimaryKey PostT f = PostId (Columnar f (SqlSerial Int))
deriving (Generic, Beamable)
primaryKey = PostId . _postId
migration ::
()
-> Migration PgCommandSyntax (CheckedDatabaseSettings Postgres DemoblogDb)
migration () =
DemoblogDb <$>
createTable
"user"
(User (field "user_id" serial) (field "name" (varchar (Just 255)) notNull)) <*>
createTable
"post"
(Post
(field "post_id" serial)
(field "content" text notNull)
(UserId (field "user_id" smallint)))
```
Then we have added the column `_userCreatedAt` for UserT in next migration `V0002AddCreatedAtColumnForUser.hs` and so we redefine UserT ADT like this:
```haskell
module Schema.Migrations.V0002AddCreatedAtColumnForUser
( module Schema.Migrations.V0001CreateUserAndPostTables
, module Schema.Migrations.V0002AddCreatedAtColumnForUser
) where
import qualified Schema.Migrations.V0001CreateUserAndPostTables as V0001 hiding
( PrimaryKey(UserId)
)
import Schema.Migrations.V0001CreateUserAndPostTables hiding
( DemoblogDb(..)
, PrimaryKey(UserId)
, User
, UserId
, UserT(..)
, migration
) -- to reexport the old ADT
-- ... boilerplate with Beam imports, Data.Text etc...
data UserT f = User
{ _userId :: Columnar f (SqlSerial Int)
, _userName :: Columnar f Text
, _userCreatedAt :: Columnar f LocalTime
} deriving (Generic, Beamable)
instance Table UserT where
data PrimaryKey UserT f = UserId (Columnar f (SqlSerial Int))
deriving (Generic, Beamable)
primaryKey = UserId . _userId
migration ::
CheckedDatabaseSettings Postgres V0001.DemoblogDb
-> Migration PgCommandSyntax (CheckedDatabaseSettings Postgres DemoblogDb)
migration oldDb = DemoblogDb <$> alterUserTable <*> preserve (V0001._post oldDb)
where
alterUserTable = alterTable (V0001._user oldDb) tableMigration
tableMigration oldTable =
User (V0001._userId oldTable) (V0001._userName oldTable) <$>
addColumn (field "created_at" timestamptz (defaultTo_ now_) notNull)
```
All this stuff well-typed and works correctly, I have tested the migration (although in database there is no foreign key constraint from post table to user, this is sad although I suppose it is not yet implemented in Beam entirely).
But the problem arises when I want to use PostT, for example when I try to create an entity like this:
```
createPost content userId =
BeamExtensions.runInsertReturningList (_post db) $
insertExpressions
[Post default_ (val_ content) (UserId $ fromIntegral userId)]
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ here we have next error:
-- Couldn't match type ‘UserT’
-- with ‘V0001.UserT’
-- NB: ‘V0001.UserT’ is defined at
-- V0001CreateUserAndPostTables.hs:(18,1)-(21,32)
-- ‘UserT’ is defined at
-- V0002AddCreatedAtColumnForUser.hs:(35,1)-(39,32)
-- Expected type: PrimaryKey
-- V0001.UserT
-- (QExpr Database.Beam.Postgres.Syntax.PgExpressionSyntax s')
-- Actual type: PrimaryKey
-- UserT (QExpr Database.Beam.Postgres.Syntax.PgExpressionSyntax s')
```
So we have to pass the old version of UserT to Post constructor. Although if I will redefine the entire Post ADT in the second migration too (which is extremely undesirable), I should somehow to preserve the table data in migration, but I cannot use the old good `preserve` (like I did above in second migration: `preserve (V0001._post oldDb)`) because oldDb is also have a version of old database from V0001 migration. So I'm stuck and I will be very happy to have a some help here :)
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.