Open tysonzero opened 1 month ago
Yeah, the DefaultFromField
asymmetry comes specifically from the days before Field Nullable sqlType
/Field NonNullable sqlType
when the nullable/non-nullable distinction looked like Column (Maybe sqlType)
/Column sqlType
. We needed the instance
DefaultFromField a b => DefaultFromField (Nullable a) (Maybe b)
to automatically generate the code for decoding nullable field types. Since moving from Column
to Field
we don't actually need DefaultFromField
or FromField
any more. However, it's very difficult to smoothly transition between type class hierarchies in Haskell, so I think we stuck with it, short of coming up with a whole parallel hierarchy of functions Opaleye.RunSelect2
.
ToFields
It seems like without it I have to reach for
unsafeCast
orInternal
modules to serialize a custom type (in my case for PostGIS types).
Have you found a way of doing this at all? As far as I know the only way that would work currently would be to serialise to a SqlText
and then use unsafeCast
on the value (and unsafeCoerceField
on the type). Is that what you're doing?
I agree that it would be good to have fromPGSToField
(or, what I think should be called, fromGPSAction
). Would you like to make a PR? Alternatively I'll look into it when I get some time.
One problem with using postgresql-simple
's ToField
/Action
would be that to render them we ultimately have to use buildAction
and that runs in IO
(because it contacts the DB itself to do escaping!).
However, it's very difficult to smoothly transition between type class hierarchies in Haskell, so I think we stuck with it
No kidding, the books I would happily write on that topic if I had the time. Might be worth throwing something in the docs about the historical reasons but not super high priority IMO.
Have you found a way of doing this at all? As far as I know the only way that would work currently would be to serialise to a SqlText and then use unsafeCast on the value (and unsafeCoerceField on the type). Is that what you're doing?
I didn't need an unsafeCoerceField
as unsafeCast
was enough, but yes:
instance Default ToFields (V2 Double) (Field SqlGeographyPoint) where
def = toToFields $ \(V2 x y) -> let
geo = Geos.PointGeometry (Geos.Point $ Geos.Coordinate2 x y) (Just 4326)
in unsafeCast "geography(POINT, 4326)" . toFieldsI . T.decodeUtf8Lenient $ Geos.writeHex geo
One problem with using postgresql-simple's ToField/Action would be that to render them we ultimately have to use buildAction and that runs in IO (because it contacts the DB itself to do escaping!).
Hmm, interesting. I mean back to the whole symmetry thing, it's not all that crazy to me that ToFields
would be a little more complicated / IO-involved than the current simple function, given how much more machinery and IO is involved for FromFields
. Obviously I can imagine significant transition pains though.
I didn't need an
unsafeCoerceField
asunsafeCast
was enough
Ah yes, unsafeCast
is already very unsafe :) I should add a safer version of that.
Would something like this be helpful to create the ToFields
?
makeToToField ::
forall haskell sqlType1 sqlType2.
IsSqlType sqlType2 =>
(haskell -> Column.Field sqlType1) ->
C.ToFields haskell (Column.Field sqlType2)
Implemented at: https://github.com/tomjaguarpaw/haskell-opaleye/commit/db98600e2b4a3f6661c0afa9f21d904bd3a72577
I mean back to the whole symmetry thing, it's not all that crazy to me that
ToFields
would be a little more complicated / IO-involved than the current simple function
I'm still confused why escaping should require a trip to the database. Surely there must be a way of escaping that doesn't require knowing any dynamic property of the database!
In general it seems like there are a fair amount of asymmetries between
FromFields
andToFields
, including the specialDefaultFromField
class, but the asymmetry I'm most concerned with right now is the lack of afromPGSToField
and/orfromPGSFieldRenderer
. It seems like without it I have to reach forunsafeCast
orInternal
modules to serialize a custom type (in my case for PostGIS types). Perhaps with a tutorial (#592) I'll realize i'm missing something, but after following the types around a bit I'm unsure how to get the results I want without unsafe/Internal functions.