haskell-beam / beam

A type-safe, non-TH Haskell SQL library and ORM
https://haskell-beam.github.io/beam/
576 stars 170 forks source link

Compatibility with Postgres enumerations #393

Open 0xd34df00d opened 5 years ago

0xd34df00d commented 5 years ago

First of all, thanks for the great library! It has been a breeze so far, and I really enjoyed writing DB access layer with it — something that's not happening too often!

Right now I'm stuck trying to do something, though. I've created a custom Postgres enumeration type (as in CREATE TYPE post_type AS ENUM ('post', 'article', 'news');) and I'm trying to make my Haskell type (a very similar data PostType = TyPost | TyArticle | TyNews) compatible with it. So I have

instance (BeamBackend be, FromBackendRow be String) => FromBackendRow be PostType where
  fromBackendRow = do
    val <- fromBackendRow
    case lookup val tys of
      Just ty -> pure ty
      _ -> fail $ "invalid value for PostType: " <> val
    where tys = [ (postTypeName ty, ty) | ty <- [minBound .. maxBound] ]

but when I actually try to read something from the DB, I'm getting BeamRowReadError {brreColumn = Just 13, brreError = ColumnTypeMismatch {ctmHaskellType = "Text", ctmSQLType = "post_type", ctmMessage = "types incompatible"}}. I believe this is due to my implementation relying on fromBackendRow for strings and thus the Typeable magic derives that the column must have a textual type. Is there a good way for me to support Postgres-defined enum types?

I've read through https://github.com/tathougies/beam/blob/master/beam-postgres/examples/Pagila/Schema/CustomMigrateExample.hs , but it seems like it assumes the underlying representation is a textual one, so it does not apply directly.

archaeron commented 5 years ago

Hey

I got this to work (just the reading part for now)

CREATE TYPE ab_type as ENUM ('A', 'B');
data AB = A | B

instance FromBackendRow Postgres AB where

instance FromField AB where
  fromField f mbValue = do
    fieldType <- typename f
    case fieldType of
      "ab_type" -> do
        case mbValue of
          Nothing ->
            returnError UnexpectedNull f ""
          Just value ->
            case value of
              "A" ->
                pure A
              "B" ->
                pure B
              _ ->
                returnError ConversionFailed f "Could not 'read' value for 'AB'"
      _ ->
        returnError Incompatible f ""

I hope this helps :)

I used these two resources to figure it out: https://github.com/jacobono/beam-postgres-tutorial/blob/master/beam-postgres.org#shipping-information https://github.com/lpsmith/postgresql-simple/issues/110

archaeron commented 5 years ago

the writing part was even simpler:

instance HasSqlValueSyntax PgValueSyntax AB where
  sqlValueSyntax = pgEnumValueSyntax $ \case
    A ->
      "A"
    B ->
      "B"