bitemyapp / bitemyapp/esqueleto
`val` may not round trip for an obvious `PersistField` instance
- 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