Add SqlMarshaller GADT with applicative combinators
This commit is contained in:
@@ -18,6 +18,7 @@ library
|
|||||||
Orville.SQLite.FieldDefinition
|
Orville.SQLite.FieldDefinition
|
||||||
Orville.SQLite.Monad
|
Orville.SQLite.Monad
|
||||||
Orville.SQLite.RawSql
|
Orville.SQLite.RawSql
|
||||||
|
Orville.SQLite.SqlMarshaller
|
||||||
Orville.SQLite.SqlType
|
Orville.SQLite.SqlType
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
build-depends:
|
build-depends:
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
|
module Orville.SQLite.SqlMarshaller
|
||||||
|
( SqlMarshaller
|
||||||
|
, FieldInfo (..)
|
||||||
|
, marshallField
|
||||||
|
, marshallReadOnlyField
|
||||||
|
, marshallMaybe
|
||||||
|
, marshallerFieldInfo
|
||||||
|
, marshallerDerivedColumns
|
||||||
|
, marshallerEncodeWrite
|
||||||
|
, marshallerDecodeRow
|
||||||
|
) where
|
||||||
|
|
||||||
|
import qualified Database.SQLite3 as SQLite3
|
||||||
|
import Orville.SQLite.FieldDefinition
|
||||||
|
( FieldDefinition (..)
|
||||||
|
, Nullability (..)
|
||||||
|
, fieldColumnName
|
||||||
|
, fieldToSqlValue
|
||||||
|
, fieldFromSqlValue
|
||||||
|
, fieldIsNullable
|
||||||
|
, fieldSqlTypeName
|
||||||
|
)
|
||||||
|
|
||||||
|
data FieldInfo = FieldInfo
|
||||||
|
{ fieldInfoName :: String
|
||||||
|
, fieldInfoType :: String
|
||||||
|
, fieldInfoIsNullable :: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
data SqlMarshaller writeEntity readEntity where
|
||||||
|
MarshallPure :: readEntity -> SqlMarshaller writeEntity readEntity
|
||||||
|
MarshallApply ::
|
||||||
|
SqlMarshaller writeEntity (a -> b) ->
|
||||||
|
SqlMarshaller writeEntity a ->
|
||||||
|
SqlMarshaller writeEntity b
|
||||||
|
MarshallNest ::
|
||||||
|
(writeEntity -> a) ->
|
||||||
|
SqlMarshaller a readEntity ->
|
||||||
|
SqlMarshaller writeEntity readEntity
|
||||||
|
MarshallField ::
|
||||||
|
FieldDefinition nullability a ->
|
||||||
|
SqlMarshaller a a
|
||||||
|
MarshallMaybe ::
|
||||||
|
FieldDefinition 'Nullable a ->
|
||||||
|
SqlMarshaller (Maybe a) (Maybe a)
|
||||||
|
MarshallReadOnly ::
|
||||||
|
SqlMarshaller a readEntity ->
|
||||||
|
SqlMarshaller b readEntity
|
||||||
|
|
||||||
|
instance Functor (SqlMarshaller w) where
|
||||||
|
fmap f m = MarshallPure f `MarshallApply` m
|
||||||
|
|
||||||
|
instance Applicative (SqlMarshaller w) where
|
||||||
|
pure = MarshallPure
|
||||||
|
(<*>) = MarshallApply
|
||||||
|
|
||||||
|
marshallField ::
|
||||||
|
(writeEntity -> a) ->
|
||||||
|
FieldDefinition 'NotNull a ->
|
||||||
|
SqlMarshaller writeEntity a
|
||||||
|
marshallField accessor fieldDef =
|
||||||
|
MarshallNest accessor (MarshallField fieldDef)
|
||||||
|
|
||||||
|
marshallReadOnlyField ::
|
||||||
|
FieldDefinition nullability a ->
|
||||||
|
SqlMarshaller writeEntity a
|
||||||
|
marshallReadOnlyField fieldDef =
|
||||||
|
MarshallReadOnly (MarshallField fieldDef)
|
||||||
|
|
||||||
|
marshallMaybe ::
|
||||||
|
(writeEntity -> Maybe a) ->
|
||||||
|
FieldDefinition 'Nullable a ->
|
||||||
|
SqlMarshaller writeEntity (Maybe a)
|
||||||
|
marshallMaybe accessor fieldDef =
|
||||||
|
MarshallNest accessor (MarshallMaybe fieldDef)
|
||||||
|
|
||||||
|
marshallerFieldInfo ::
|
||||||
|
SqlMarshaller writeEntity readEntity ->
|
||||||
|
[FieldInfo]
|
||||||
|
marshallerFieldInfo marshaller =
|
||||||
|
reverse $ go marshaller []
|
||||||
|
where
|
||||||
|
go :: SqlMarshaller w r -> [FieldInfo] -> [FieldInfo]
|
||||||
|
go (MarshallPure _) acc = acc
|
||||||
|
go (MarshallApply m1 m2) acc = go m1 (go m2 acc)
|
||||||
|
go (MarshallNest _ m) acc = go m acc
|
||||||
|
go (MarshallField fieldDef) acc =
|
||||||
|
FieldInfo
|
||||||
|
(fieldColumnName fieldDef)
|
||||||
|
(fieldSqlTypeName fieldDef)
|
||||||
|
(fieldIsNullable fieldDef)
|
||||||
|
: acc
|
||||||
|
go (MarshallMaybe fieldDef) acc =
|
||||||
|
FieldInfo
|
||||||
|
(fieldColumnName fieldDef)
|
||||||
|
(fieldSqlTypeName fieldDef)
|
||||||
|
(True :: Bool)
|
||||||
|
: acc
|
||||||
|
go (MarshallReadOnly m) acc = go m acc
|
||||||
|
|
||||||
|
marshallerDerivedColumns ::
|
||||||
|
SqlMarshaller writeEntity readEntity ->
|
||||||
|
[String]
|
||||||
|
marshallerDerivedColumns marshaller =
|
||||||
|
reverse $ go marshaller []
|
||||||
|
where
|
||||||
|
go :: SqlMarshaller w r -> [String] -> [String]
|
||||||
|
go (MarshallPure _) acc = acc
|
||||||
|
go (MarshallApply m1 m2) acc = go m1 (go m2 acc)
|
||||||
|
go (MarshallNest _ m) acc = go m acc
|
||||||
|
go (MarshallField fieldDef) acc = fieldColumnName fieldDef : acc
|
||||||
|
go (MarshallMaybe fieldDef) acc = fieldColumnName fieldDef : acc
|
||||||
|
go (MarshallReadOnly m) acc = go m acc
|
||||||
|
|
||||||
|
marshallerEncodeWrite ::
|
||||||
|
SqlMarshaller writeEntity readEntity ->
|
||||||
|
writeEntity ->
|
||||||
|
[(String, SQLite3.SQLData)]
|
||||||
|
marshallerEncodeWrite marshaller entity =
|
||||||
|
reverse $ go marshaller entity []
|
||||||
|
where
|
||||||
|
go :: SqlMarshaller w r -> w -> [(String, SQLite3.SQLData)] -> [(String, SQLite3.SQLData)]
|
||||||
|
go (MarshallPure _) _ acc = acc
|
||||||
|
go (MarshallApply m1 m2) w acc =
|
||||||
|
go m1 w (go m2 w acc)
|
||||||
|
go (MarshallNest accessor m) w acc =
|
||||||
|
go m (accessor w) acc
|
||||||
|
go (MarshallField fieldDef) a acc =
|
||||||
|
(fieldColumnName fieldDef, fieldToSqlValue a fieldDef) : acc
|
||||||
|
go (MarshallMaybe fieldDef) a acc =
|
||||||
|
case a of
|
||||||
|
Nothing -> (fieldColumnName fieldDef, SQLite3.SQLNull) : acc
|
||||||
|
Just val -> (fieldColumnName fieldDef, fieldToSqlValue val fieldDef) : acc
|
||||||
|
go (MarshallReadOnly _) _ acc = acc
|
||||||
|
|
||||||
|
marshallerDecodeRow ::
|
||||||
|
SqlMarshaller writeEntity readEntity ->
|
||||||
|
[(String, SQLite3.SQLData)] ->
|
||||||
|
Either String readEntity
|
||||||
|
marshallerDecodeRow marshaller rowData =
|
||||||
|
go marshaller rowData
|
||||||
|
where
|
||||||
|
go :: SqlMarshaller w r -> [(String, SQLite3.SQLData)] -> Either String r
|
||||||
|
go (MarshallPure r) _ = Right r
|
||||||
|
go (MarshallApply m1 m2) rd = do
|
||||||
|
f <- go m1 rd
|
||||||
|
a <- go m2 rd
|
||||||
|
Right (f a)
|
||||||
|
go (MarshallNest _ m) rd = go m rd
|
||||||
|
go (MarshallField fieldDef) rd =
|
||||||
|
case lookup (fieldColumnName fieldDef) rd of
|
||||||
|
Just sqlVal -> fieldFromSqlValue sqlVal fieldDef
|
||||||
|
Nothing ->
|
||||||
|
Left $ "Column '" <> fieldColumnName fieldDef <> "' not found in result row"
|
||||||
|
go (MarshallMaybe fieldDef) rd =
|
||||||
|
case lookup (fieldColumnName fieldDef) rd of
|
||||||
|
Just SQLite3.SQLNull -> Right Nothing
|
||||||
|
Just sqlVal -> Just <$> fieldFromSqlValue sqlVal fieldDef
|
||||||
|
Nothing ->
|
||||||
|
Left $ "Column '" <> fieldColumnName fieldDef <> "' not found in result row"
|
||||||
|
go (MarshallReadOnly m) rd = go m rd
|
||||||
Reference in New Issue
Block a user