bitemyapp / bitemyapp/esqueleto

Database.Esqueleto.PostgreSQL.values does not perform necessary type coercion at runtime

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

Description

If I define a simple enum type in PostgreSQL like this:

```sql
CREATE TYPE behavior AS ENUM ('Good', 'Bad');
```

And then give it the appropriate matching Haskell implementation (plus all the other stuff which is less material):

```haskell
data Behavior = Good | Bad
deriving stock (Show, Eq)

instance PersistField Behavior where
toPersistValue = PersistText . pack . show
fromPersistValue = undefined -- Not important here

instance PersistFieldSql Behavior where
sqlType _ = SqlOther "behavior"
```

I can do something like join on values of that enum (like in this silly example):

```haskell
from $
table @Kids
`innerJoin` behaviorDinner
`on` (\(kid :& (bvr, _)) -> kid ^. KidBehavior ==. bvr)
where
behaviorDinner :: From (Behavior, Dinner)
behaviorDinner = values $ (Good, Pizza) :| [(Bad, Poison)]
```

When I run this though, I get an error message something like this (paraphrased from the real error):

```text
uncaught exception: SqlError
SqlError {sqlState = "42883", sqlExecStatus = FatalError, sqlErrorMsg = "operator does not exist: behavior = text", sqlErrorDetail = "", sqlErrorHint = "No operator matches the given name and argument types. You might need to add explicit type casts."}
```

Looking at the SQL Esqueleto generates for this, it looks something like this:

```sql
INNER JOIN (VALUES (?,?),(?,?)) AS "vq"("v","v2") ON "kids"."behavior" = "vq"."v"
```

So it seems like we're missing a type coercion (which `sqlType` makes available) to correctly distill the values. From [the PostgreSQL documentation](https://www.postgresql.org/docs/current/sql-values.html):

> When VALUES is used in INSERT, the values are all automatically coerced to the data type of the corresponding destination column. When it's used in other contexts, it might be necessary to specify the correct data type. If the entries are all quoted literal constants, coercing the first is sufficient to determine the assumed type for all

In the implementation itself, a quick read indicates that we might want to add that here:

https://github.com/bitemyapp/esqueleto/blob/754b0b5cccad3756e4f46e3c916a94e5eceb61a6/src/Database/Esqueleto/PostgreSQL.hs#L420

Not knowing much about Esqueleto, I'd guess we'd need to add some type constraints to extract the type underlying `a`.

Also I'd guess it would be more efficient to only add the coercion to the first row, but that would require breaking up the current mapping (where we currently have `NE.toList`, we'd want to actually process the head and tail differently).

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.