Adding a UUID Column to a Persistent Table
This is just a quick snippet I’ve been meaning to post for a few weeks. A few weeks ago I needed to add a Postgres UUID column to one of my tables using Persistent. I dug around and all I found were vague, closed tickets, and old irrelevant blog posts on the Yesod site that mentioned UUIDs but didn’t give any good examples. The solution ended up being simple but I hope it helps someone else who is having this problem, or more likely, future me when I forget how this is done.
Let’s say you’ve got some schema TH that looks like:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
deriving Show
|]We want to:
- Add a UUID column to Person.
- Give it a default value so any existing columns are backfilled.
- Give it a unique index for fast lookups and guaranteed uniqueness.
- Wrap it in a distinct type to avoid confusing it with other UUIDs, much like how persistent gives us distinct types for primary keys.
Our schema TH now looks like:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name String
age Int Maybe
uuid PersonUUID default=uuid_generate_v4()
UniquePersonUUID uuid
deriving Show
|]In a module accessible from your schema’s you’ll also add something like:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
module MyApp.Schema where
import Control.Error
import Control.Lens
import Data.ByteString (ByteString)
import Data.UUID
import Data.Text (Text)
import qualified Data.Text as T
import System.Random
newtype PersonUUID = PersonUUID {
_personUuid :: UUID
} deriving (Show, Eq, Ord, Random)
makeLenses ''PersonUUID
instance PersistFieldSql PersonUUID where
sqlType = const $ SqlOther "uuid"
instance PersistField PersonUUID where
toPersistValue = toPersistValueUUID personUuid
fromPersistValue = fromPersistValueUUID personUuid
_ASCIIBytes :: Prism' ByteString UUID
_ASCIIBytes = prism toASCIIBytes $ \bs -> note bs $ fromASCIIBytes bs
toPersistValueUUID :: Iso' a UUID -> a -> PersistValue
toPersistValueUUID i a = PersistDbSpecific $ a ^. i . re _ASCIIBytes
fromPersistValueUUID :: Iso' a UUID -> PersistValue -> Either Text a
fromPersistValueUUID i (PersistDbSpecific bs) =
note "Could not parse UUID" $ bs ^? _ASCIIBytes . from i
fromPersistValueUUID _ x = Left $ "Invalid value for UUID: " <> showT x
showT :: Show a => a -> Text
showT = T.pack . showLet’s break this down a bit. First, we create a newtype around UUID to distinguish the type and then derive an Iso that can get us to and from the UUID via makeLenses. We also create a Prism between ByteString and UUID. I like to read prisms as the left type variable (ByteString) is the “wider” type and the right type variable (UUID) is the “narrow” one. That is to say, you know you can always go from the narrow type to the wide one but not necessarily the other way. Conveniently, PersistDbSpecific expects a ByteString so this is exactly what we need to serialize our type to the database.
note is a great little function from the errors package of type e -> Maybe a -> Either e a that upgrades a Maybe into an Either.
Lastly, you’ll want to make sure that the uuid-ossp extension is enabled in your database. You can issue the command CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; to do so.