lpsmith / postgresql-simple

Mid-level client library for accessing PostgreSQL from Haskell
Other
206 stars 71 forks source link

Can't FromField literal text/varchar without type cast? #199

Open tomjaguarpaw opened 7 years ago

tomjaguarpaw commented 7 years ago

I'm a bit confused why instance FromField String can't decode literal strings without a type cast. Is it not possible to make them work?

Example:

> query_ connection "select 'hello'" :: IO [Only String]
*** Exception: Incompatible {errSQLType = "unknown", errSQLTableOid = Nothing, errSQLField = "?column?", errHaskellType = "Text", errMessage = "types incompatible"}
> query_ connection "select 'hello' :: text" :: IO [Only String]
[Only {fromOnly = "hello"}]
> query_ connection "select 'hello' :: varchar" :: IO [Only String]
[Only {fromOnly = "hello"}]
tomjaguarpaw commented 7 years ago

I had an Opaleye bug filed about this issue:

https://github.com/tomjaguarpaw/haskell-opaleye/issues/250#issuecomment-265146334

I can add type casts to all literal strings but I'd rather not if at all possible.

cimmanon commented 7 years ago

It's worth noting that if you try to create a table from a query without casting the type directly in PostgreSQL, you get a warning:

guest=> create temporary table foo as select 'hello' as x;
WARNING:  column "x" has type "unknown"
DETAIL:  Proceeding with relation creation anyway.
SELECT 1
guest=> \d foo
    Table "pg_temp_2.foo"
 Column |  Type   | Modifiers
--------+---------+-----------
 x      | unknown |

It is also worth noting that PostgreSQL won't let you do comparisons when the types are not comparable:

guest=> create temporary table bar as select 'hello' :: text as x;
SELECT 1
guest=> select * from foo join bar using (x);
ERROR:  failed to find conversion function from unknown to text

So if PostgreSQL doesn't think they're the same, why should postgresql-simple?

tomjaguarpaw commented 7 years ago

I really don't have a clear idea what the correct design is.

If I were writing a language I wouldn't allow literals whose type was ambiguous or unknown. But we're working with the SQL we've got, not the one we deserve. The question is, would it be more helpful to users of postgresql-simple to guess that it's a UTF8 encoded string and try to decode it anyway, even if its type oid is "unknown"?

cimmanon commented 7 years ago

I should also point out that postgresql-simple does not support casting a field that has a known type to an incompatible Haskell type. So postgresql-simple's behavior regarding the PostgreSQL unknown type is consistent to me.

foo :: (HasPostgres m) => m [Only Text]
foo = query_ "SELECT now()"
A web handler threw an exception. Details:
Incompatible {errSQLType = "timestamptz", errSQLTableOid = Nothing, errSQLField = "now", errHaskellType = "Text", errMessage = "types incompatible"}

Text types aren't the only type that could potentially be unknown. Dates, intervals, and the number/date/time values of infinity and -infinity also look like text types on the surface because they require quotes (and by extension, require inference or casting in order to be treated correctly) in PostgreSQL.

lpsmith commented 7 years ago

Well, there is an argument for making all postgresql-simple FromField instances compatible with unknown, taking the optimistic instead of pessimistic route to type checking. An "actual" type error would be more likely to manifest as a parse error or (perhaps less likely) "strange" behavior, but this would also allow postgresql-simple to more easily support the record type, where we don't have access to type information for the individual record fields. (Note though, that composite types, i.e. explicitly declared record types, don't have this problem)

PostgreSQL's type system often does correspond reasonably well to Haskell's, but there's definitely some issues there as well.