@@ -7,38 +7,71 @@ Stability: experimental
8Binary encoding and decoding between Haskell and PostgreSQL types.
8Binary encoding and decoding between Haskell and PostgreSQL types.
10−Instances are governed by the `Generic` and `HasDatatypeInfo` typeclasses.
10+Instances are governed by the `Generic` and `HasDatatypeInfo` typeclasses, so you absolutely
11+do not need to define your own instances to decode retrieved rows into Haskell values or
12+to encode Haskell values into statement parameters. You only need to derive those type
12−Let's see an example of a round trip, inserting a row containing an value of enumerated type
15+>>> import Data.Int (Int16)
13−and a value of composite type by encoding Haskell values into Postgres binary format
16+>>> import Data.Text (Text)
14−and then decoding them back into Haskell.
16−>>> import Control.Monad
18+>>> data Row = Row { col1 :: Int16, col2 :: Text } deriving (Eq, GHC.Generic)
17−>>> import Control.Monad.Base
19+>>> instance Generic Row
18−>>> import Data.Text
20+>>> instance HasDatatypeInfo Row
22+>>> import Control.Monad (void)
23+>>> import Control.Monad.Base (liftBase)
20>>> import Squeal.PostgreSQL
24>>> import Squeal.PostgreSQL
21−>>> import qualified Squeal.PostgreSQL as SQL
25+
29+ '[ 'NotNull 'PGint2, 'NotNull 'PGtext]
30+ '["col1" ::: 'NotNull 'PGint2, "col2" ::: 'NotNull 'PGtext]
31+ query = values_ (param @1 `As` #col1 :* param @2 `As` #col2 :* Nil)
37+ roundtrip = void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
38+ result <- runQueryParams query (2 :: Int16, "hi" :: Text)
39+ Just row <- firstRow result
40+ liftBase . print $ row == Row 2 "hi"
46+In addition to being able to encode and decode basic Haskell types like `Int16` and `Text`,
47+Squeal permits you to encode and decode Haskell types which are equivalent to
48+Postgres enumerated and composite types.
50+Enumerated (enum) types are data types that comprise a static, ordered set of values.
51+They are equivalent to Haskell algebraic data types whose constructors are nullary.
52+An example of an enum type might be the days of the week,
53+or a set of status values for a piece of data.
23>>> data Schwarma = Beef | Lamb | Chicken deriving (Show, GHC.Generic)
55>>> data Schwarma = Beef | Lamb | Chicken deriving (Show, GHC.Generic)
24>>> instance Generic Schwarma
56>>> instance Generic Schwarma
25>>> instance HasDatatypeInfo Schwarma
57>>> instance HasDatatypeInfo Schwarma
59+A composite type represents the structure of a row or record;
60+it is essentially just a list of field names and their data types. They are almost
61+equivalent to Haskell record types. However, because of the potential presence of @NULL@
62+all the record fields must be `Maybe`s of basic types.
27>>> data Person = Person {name :: Maybe Text, age :: Maybe Int32} deriving (Show, GHC.Generic)
64>>> data Person = Person {name :: Maybe Text, age :: Maybe Int32} deriving (Show, GHC.Generic)
28>>> instance Generic Person
65>>> instance Generic Person
29>>> instance HasDatatypeInfo Person
66>>> instance HasDatatypeInfo Person
31−>>> :set -XTypeFamilies -XTypeInType -XUndecidableInstances
68+We can create the equivalent Postgres types directly from their Haskell types.
33−type family Schema :: SchemaType where
71+type Schema =
35− '[ "schwarma" ::: 'Typedef (EnumWith Schwarma)
72+ '[ "schwarma" ::: 'Typedef (EnumWith Schwarma)
36− , "person" ::: 'Typedef (CompositeWith Person)
73+ , "person" ::: 'Typedef (CompositeWith Person)
37− , "tab" ::: 'Table ('[] :=>
38− '[ "col1" ::: 'NoDef :=> 'NotNull (EnumWith Schwarma)
39− , "col2" ::: 'NoDef :=> 'NotNull (CompositeWith Person)
@@ -46,37 +79,51 @@ let
46 setup :: Definition '[] Schema
79 setup :: Definition '[] Schema
48 createTypeEnumWith @Schwarma #schwarma >>>
81 createTypeEnumWith @Schwarma #schwarma >>>
49− createTypeCompositeWith @Person #person >>>
82+ createTypeCompositeWith @Person #person
50− createTable #tab (
83+:}
51− notNullable #schwarma `As` #col1 :*
84+
52− notNullable #person `As` #col2 :* Nil
85+Then we can perform roundtrip queries;
90+ '[ 'NotNull (EnumWith Schwarma)]
91+ '["fromOnly" ::: 'NotNull (EnumWith Schwarma)]
92+ qry1 = values_ (parameter @1 #schwarma `As` #fromOnly :* Nil)
98+ '[ 'NotNull (CompositeWith Person)]
99+ '["fromOnly" ::: 'NotNull (CompositeWith Person)]
100+ qry2 = values_ (parameter @1 #person `As` #fromOnly :* Nil)
103+And finally drop the types.
54 teardown :: Definition Schema '[]
107 teardown :: Definition Schema '[]
55− teardown = dropTable #tab >>> dropType #schwarma >>> dropType #person
108+ teardown = dropType #schwarma >>> dropType #person
56− manip :: Manipulation Schema '[ 'NotNull (EnumWith Schwarma), 'NotNull (CompositeWith Person)] '[]
109+:}
58− insertRow_ #tab (
111+Now let's run it.
59− Set (parameter @1 #schwarma) `As` #col1 :*
112+
60− Set (parameter @2 #person) `As` #col2 :* Nil)
113+>>> :{
61− qry1 :: Query Schema '[] '["fromOnly" ::: 'NotNull (EnumWith Schwarma)]
114+let
62− qry1 = select (#col1 `As` #fromOnly :* Nil) (SQL.from (table #tab))
63− qry2 :: Query Schema '[] '["fromOnly" ::: 'NotNull (CompositeWith Person)]
64− qry2 = select (#col2 `As` #fromOnly :* Nil) (SQL.from (table #tab))
65 session = do
115 session = do
66− manipulateParams manip (Chicken, Person (Just "Faisal") (Just 24))
67− result1 <- runQuery qry1
116+ result1 <- runQueryParams qry1 (Only Chicken)
68 Just (Only schwarma) <- firstRow result1
117 Just (Only schwarma) <- firstRow result1
69 liftBase $ print (schwarma :: Schwarma)
118 liftBase $ print (schwarma :: Schwarma)
70− result2 <- runQuery qry2
119+ result2 <- runQueryParams qry2 (Only (Person (Just "Faisal") (Just 24)))
71 Just (Only person) <- firstRow result2
120 Just (Only person) <- firstRow result2
72 liftBase $ print (person :: Person)
121 liftBase $ print (person :: Person)
76−void . withConnection "host=localhost port=5432 dbname=exampledb" $
123+ void . withConnection "host=localhost port=5432 dbname=exampledb" $
77− define setup
124+ define setup
78− & pqThen session
125+ & pqThen session
79− & pqThen (define teardown)
126+ & pqThen (define teardown)
82Person {name = Just "Faisal", age = Just 24}
129Person {name = Just "Faisal", age = Just 24}