@@ -7,38 +7,71 @@ Stability: experimental
77
88Binary encoding and decoding between Haskell and PostgreSQL types.
99
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
13+classes.
1114
12−Let's see an example of a round trip, inserting a row containing an value of enumerated type
13−and a value of composite type by encoding Haskell values into Postgres binary format
14−and then decoding them back into Haskell.
15+>>> import Data.Int (Int16)
16+>>> import Data.Text (Text)
1517
16−>>> import Control.Monad
17−>>> import Control.Monad.Base
18−>>> import Data.Text
18+>>> data Row = Row { col1 :: Int16, col2 :: Text } deriving (Eq, GHC.Generic)
19+>>> instance Generic Row
20+>>> instance HasDatatypeInfo Row
1921
22+>>> import Control.Monad (void)
23+>>> import Control.Monad.Base (liftBase)
2024>>> import Squeal.PostgreSQL
21−>>> import qualified Squeal.PostgreSQL as SQL
25+
26+>>> :{
27+let
28+ query :: Query '[]
29+ '[ 'NotNull 'PGint2, 'NotNull 'PGtext]
30+ '["col1" ::: 'NotNull 'PGint2, "col2" ::: 'NotNull 'PGtext]
31+ query = values_ (param @1 `As` #col1 :* param @2 `As` #col2 :* Nil)
32+:}
33+
34+>>> :{
35+let
36+ roundtrip :: IO ()
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"
41+:}
42+
43+>>> roundtrip
44+True
45+
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.
49+
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.
2254
2355>>> data Schwarma = Beef | Lamb | Chicken deriving (Show, GHC.Generic)
2456>>> instance Generic Schwarma
2557>>> instance HasDatatypeInfo Schwarma
2658
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.
63+
2764>>> data Person = Person {name :: Maybe Text, age :: Maybe Int32} deriving (Show, GHC.Generic)
2865>>> instance Generic Person
2966>>> instance HasDatatypeInfo Person
3067
31−>>> :set -XTypeFamilies -XTypeInType -XUndecidableInstances
68+We can create the equivalent Postgres types directly from their Haskell types.
69+
3270>>> :{
33−type family Schema :: SchemaType where
34− Schema =
35− '[ "schwarma" ::: 'Typedef (EnumWith Schwarma)
36− , "person" ::: 'Typedef (CompositeWith Person)
37− , "tab" ::: 'Table ('[] :=>
38− '[ "col1" ::: 'NoDef :=> 'NotNull (EnumWith Schwarma)
39− , "col2" ::: 'NoDef :=> 'NotNull (CompositeWith Person)
40− ])
41− ]
71+type Schema =
72+ '[ "schwarma" ::: 'Typedef (EnumWith Schwarma)
73+ , "person" ::: 'Typedef (CompositeWith Person)
74+ ]
4275:}
4376
4477>>> :{
@@ -46,37 +79,51 @@ let
4679 setup :: Definition '[] Schema
4780 setup =
4881 createTypeEnumWith @Schwarma #schwarma >>>
49− createTypeCompositeWith @Person #person >>>
50− createTable #tab (
51− notNullable #schwarma `As` #col1 :*
52− notNullable #person `As` #col2 :* Nil
53− ) Nil
82+ createTypeCompositeWith @Person #person
83+:}
84+
85+Then we can perform roundtrip queries;
86+
87+>>> :{
88+let
89+ qry1 :: Query Schema
90+ '[ 'NotNull (EnumWith Schwarma)]
91+ '["fromOnly" ::: 'NotNull (EnumWith Schwarma)]
92+ qry1 = values_ (parameter @1 #schwarma `As` #fromOnly :* Nil)
93+:}
94+
95+>>> :{
96+let
97+ qry2 :: Query Schema
98+ '[ 'NotNull (CompositeWith Person)]
99+ '["fromOnly" ::: 'NotNull (CompositeWith Person)]
100+ qry2 = values_ (parameter @1 #person `As` #fromOnly :* Nil)
101+:}
102+
103+And finally drop the types.
104+
105+>>> :{
106+let
54107 teardown :: Definition Schema '[]
55− teardown = dropTable #tab >>> dropType #schwarma >>> dropType #person
56− manip :: Manipulation Schema '[ 'NotNull (EnumWith Schwarma), 'NotNull (CompositeWith Person)] '[]
57− manip =
58− insertRow_ #tab (
59− Set (parameter @1 #schwarma) `As` #col1 :*
60− Set (parameter @2 #person) `As` #col2 :* Nil)
61− qry1 :: Query Schema '[] '["fromOnly" ::: 'NotNull (EnumWith Schwarma)]
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))
108+ teardown = dropType #schwarma >>> dropType #person
109+:}
110+
111+Now let's run it.
112+
113+>>> :{
114+let
65115 session = do
66− manipulateParams manip (Chicken, Person (Just "Faisal") (Just 24))
67− result1 <- runQuery qry1
116+ result1 <- runQueryParams qry1 (Only Chicken)
68117 Just (Only schwarma) <- firstRow result1
69118 liftBase $ print (schwarma :: Schwarma)
70− result2 <- runQuery qry2
119+ result2 <- runQueryParams qry2 (Only (Person (Just "Faisal") (Just 24)))
71120 Just (Only person) <- firstRow result2
72121 liftBase $ print (person :: Person)
73−:}
74−
75−>>> :{
76−void . withConnection "host=localhost port=5432 dbname=exampledb" $
77− define setup
78− & pqThen session
79− & pqThen (define teardown)
122+in
123+ void . withConnection "host=localhost port=5432 dbname=exampledb" $
124+ define setup
125+ & pqThen session
126+ & pqThen (define teardown)
80127:}
81128Chicken
82129Person {name = Just "Faisal", age = Just 24}