bitemyapp / bitemyapp/esqueleto

`val` may not round trip for an obvious `PersistField` instance

Open
#314 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Haskell
Stars
399
Forks
107
Avg merge
1d 17h
Merged PRs (30d)
1

Description

We have this instance at work:

```haskell
instance PersistField UUID where
toPersistValue = PersistLiteralEscaped . uuidToBytes
fromPersistValue (PersistLiteralEscaped bytes)
| Just uuid <- fromAsciiBytes bytes = Right uuid
fromPersistValue other =
Left $ "Failed to make UUID from: " <> tshow other
```

This fails to round-trip if you do a `select $ pure $ val uuid` - it fails, because we were given a `PersistText` instead of a `PersistLiteralEscaped`.

The logic, as far as I can tell, is something like:

1. We splice in `val uuid`, which turns into `( "?", toPersistValue uuid)` to `("?", PersistLiteralEscaped uuid)`
2. After parameter substitution, `postgres` sees a `'1234-2345-3456-4567'` looking thing, which looks a *lot* like a `text` value. So that's the type it infers.
3. `postgresql-simple` asks Postgres what the type of the column is, and says `text`.
4. So `persistent` wraps the `text` in a `PersistText` constructor
5. And then `esqueleto` tries to parse a `UUID` from it, which fails, since the `PersistText` constructor isn't specified above.

One potential fix is to provide a type annotation to `val`. Something like,

```haskell
val x =
( mconcat ["(? :: ", showType (sqlType (Proxy :: Proxy a)), ")"]
, toPersistValue x
)
```

Another fix is on `persistent` side, where we encourage folks to use helpers instead of the `PersistValue` constructors directly. Eg, `withBytes :: PersistValue -> (ByteString -> Either Text a) -> Either Text a`, similar to the `withObject` etc functions in `aeson`. These would, behind the scenes, translate a `PersistText txt -> f (encodeUtf8 txt)`.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reproducing `select $ pure $ val uuid` with the shown `PersistField UUID` instance and trace the value through `val`, parameter substitution, PostgreSQL type inference, and the resulting `PersistValue`. Compare the proposed `val` type annotation with persistent-side `PersistValue` helpers; done means the UUID value round-trips without the `PersistText` versus `PersistLiteralEscaped` failure.

Written by the indexing model from the issue text.

Assessment

Tech stack
haskell, postgresql
Domain
databases
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.