on dev help sign in
binary haddocks
c51650eEitan Chatav
squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+89−80
@@ -11,83 +11,78 @@ Instances are governed by the `Generic` and `HasDatatypeInfo` typeclasses, so yo
11do not need to define your own instances to decode retrieved rows into Haskell values or
11do not need to define your own instances to decode retrieved rows into Haskell values or
12to encode Haskell values into statement parameters.
12to encode Haskell values into statement parameters.
13
13
14+Let's see some examples. We'll need some imports
15+
14>>> import Data.Int (Int16)
16>>> import Data.Int (Int16)
15>>> import Data.Text (Text)
17>>> import Data.Text (Text)
16−
17−>>> data Row = Row { col1 :: Int16, col2 :: Text } deriving (Eq, GHC.Generic)
18−>>> instance Generic Row
19−>>> instance HasDatatypeInfo Row
20−
21>>> import Control.Monad (void)
18>>> import Control.Monad (void)
22>>> import Control.Monad.Base (liftBase)
19>>> import Control.Monad.Base (liftBase)
23>>> import Squeal.PostgreSQL
20>>> import Squeal.PostgreSQL
24
21
22+Define a Haskell datatype `Row` that will serve as both the input and output of a simple
23+round trip query.
24+
25+>>> data Row = Row { col1 :: Int16, col2 :: Text, col3 :: Maybe Bool } deriving (Eq, GHC.Generic)
26+>>> instance Generic Row
27+>>> instance HasDatatypeInfo Row
25>>> :{
28>>> :{
26let
29let
27− query :: Query '[] (TuplePG Row) (RowPG Row)
30+ roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
28− query = values_ (param @1 `as` #col1 :* param @2 `as` #col2)
31+ roundTrip = values_ $
32+ parameter @1 int2 `as` #col1 :*
33+ parameter @2 text `as` #col2 :*
34+ parameter @3 bool `as` #col3
29:}
35:}
30
36
37+So long as we can encode the parameters and then decode the result of the query,
38+the input and output should be equal.
39+
40+>>> let input = Row 2 "hi" (Just True)
31>>> :{
41>>> :{
32−let
33− roundtrip :: IO ()
34− roundtrip = void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
42+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
35− result <- runQueryParams query (2 :: Int16, "hi" :: Text)
43+ result <- runQueryParams roundTrip input
36− Just row <- firstRow result
44+ Just output <- firstRow result
37− liftBase . print $ row == Row 2 "hi"
45+ liftBase . print $ input == output
38:}
46:}
39−
40−>>> roundtrip
41True
47True
42
48
43−In addition to being able to encode and decode basic Haskell types like `Int16` and `Text`,
49+In addition to being able to encode and decode basic Haskell types
44−Squeal permits you to encode and decode Haskell types to
50+like `Int16` and `Text`, Squeal permits you to encode and decode Haskell types to
45−Postgres array, enumerated and composite types.
51+Postgres array, enumerated and composite types and json. Let's see another example,
52+this time using the `Vector` type which corresponds to variable length arrays
53+and homogeneous tuples which correspond to fixed length arrays. We can even
54+create multi-dimensional fixed length arrays.
46
55
47>>> :{
56>>> :{
48−data VRow = VRow
57+data Row = Row
49− { v1 :: Vector Int16
58+ { col1 :: Vector Int16
50− , v2 :: (Maybe Int16,Maybe Int16)
59+ , col2 :: (Maybe Int16,Maybe Int16)
51− , v3 :: ((Int16,Int16),(Int16,Int16),(Int16,Int16))
60+ , col3 :: ((Int16,Int16),(Int16,Int16),(Int16,Int16))
52 } deriving (Eq, GHC.Generic)
61 } deriving (Eq, GHC.Generic)
53:}
62:}
54
63
55−>>> instance Generic VRow
64+>>> instance Generic Row
56−>>> instance HasDatatypeInfo VRow
65+>>> instance HasDatatypeInfo Row
57−>>> :kind! TuplePG VRow
58−TuplePG VRow :: [NullityType]
59−= '['NotNull ('PGvararray ('NotNull 'PGint2)),
60− 'NotNull ('PGfixarray 2 ('Null 'PGint2)),
61− 'NotNull
62− ('PGfixarray 3 ('NotNull ('PGfixarray 2 ('NotNull 'PGint2))))]
63−
64−>>> :kind! RowPG VRow
65−RowPG VRow :: [(Symbol, NullityType)]
66−= '["v1" ::: 'NotNull ('PGvararray ('NotNull 'PGint2)),
67− "v2" ::: 'NotNull ('PGfixarray 2 ('Null 'PGint2)),
68− "v3"
69− ::: 'NotNull
70− ('PGfixarray 3 ('NotNull ('PGfixarray 2 ('NotNull 'PGint2))))]
71
66
72−>>> :set -XOverloadedLists
67+Once again, we define a simple round trip query.
73−>>> let vparams = VRow [1,2] (Just 1,Nothing) ((1,2), (3,4), (5,6))
74
68
75>>> :{
69>>> :{
76let
70let
77− query :: Query '[] (TuplePG VRow) (RowPG VRow)
71+ roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
78− query = values_ (param @1 `as` #v1 :* param @2 `as` #v2 :* param @3 `as` #v3)
72+ roundTrip = values_ $
73+ parameter @1 (int2 & vararray) `as` #col1 :*
74+ parameter @2 (int2 & fixarray @2) `as` #col2 :*
75+ parameter @3 (int2 & fixarray @2 & fixarray @3) `as` #col3
79:}
76:}
80
77
78+>>> :set -XOverloadedLists
79+>>> let input = Row [1,2] (Just 1,Nothing) ((1,2),(3,4),(5,6))
81>>> :{
80>>> :{
82−let
83− roundtrip :: IO ()
84− roundtrip = void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
81+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
85− result <- runQueryParams query vparams
82+ result <- runQueryParams roundTrip input
86− Just row <- firstRow result
83+ Just output <- firstRow result
87− liftBase . print $ row == vparams
84+ liftBase . print $ input == output
88:}
85:}
89−
90−>>> roundtrip
91True
86True
92
87
93Enumerated (enum) types are data types that comprise a static, ordered set of values.
88Enumerated (enum) types are data types that comprise a static, ordered set of values.
@@ -100,16 +95,15 @@ or a set of status values for a piece of data.
100>>> instance HasDatatypeInfo Schwarma
95>>> instance HasDatatypeInfo Schwarma
101
96
102A composite type represents the structure of a row or record;
97A composite type represents the structure of a row or record;
103−it is essentially just a list of field names and their data types. They are almost
98+it is essentially just a list of field names and their data types.
104−equivalent to Haskell record types. However, because of the potential presence of @NULL@
105−all the record fields must be `Maybe`s of basic types.
106
99
107>>> data Person = Person {name :: Text, age :: Int32} deriving (Eq, Show, GHC.Generic)
100>>> data Person = Person {name :: Text, age :: Int32} deriving (Eq, Show, GHC.Generic)
108>>> instance Generic Person
101>>> instance Generic Person
109>>> instance HasDatatypeInfo Person
102>>> instance HasDatatypeInfo Person
103+>>> instance Aeson.FromJSON Person
104+>>> instance Aeson.ToJSON Person
110
105
111−We can create the equivalent Postgres types directly from their Haskell types. First,
106+We can create the equivalent Postgres types directly from their Haskell types.
112−define a type instances for `PG`.
113
107
114>>> :{
108>>> :{
115type Schema =
109type Schema =
@@ -126,28 +120,40 @@ let
126 createTypeCompositeFrom @Person #person
120 createTypeCompositeFrom @Person #person
127:}
121:}
128
122
129−Then we can perform roundtrip queries;
123+Let's demonstrate how to associate our Haskell types `Schwarma` and `Person`
124+with enumerated, composite or json types in Postgres. First create a Haskell
125+`Row` type using the `Enumerated`, `Composite` and `Json` newtypes as fields.
130
126
131>>> :{
127>>> :{
132−data TRow = TRow
128+data Row = Row
133 { schwarma :: Enumerated Schwarma
129 { schwarma :: Enumerated Schwarma
134− , person :: Composite Person
130+ , person1 :: Composite Person
131+ , person2 :: Json Person
135 } deriving (Eq, GHC.Generic)
132 } deriving (Eq, GHC.Generic)
136:}
133:}
137
134
138−>>> instance Generic TRow
135+>>> instance Generic Row
139−>>> instance HasDatatypeInfo TRow
136+>>> instance HasDatatypeInfo Row
137+>>> :{
138+let
139+ input = Row
140+ (Enumerated Chicken)
140−>>> let tparams = TRow (Enumerated Chicken) (Composite (Person "Faisal" 24))
141+ (Composite (Person "Faisal" 24))
142+ (Json (Person "Ahmad" 48))
143+:}
144+
145+Once again, define a round trip query.
141
146
142>>> :{
147>>> :{
143let
148let
144− query :: Query Schema (TuplePG TRow) (RowPG TRow)
149+ roundTrip :: Query Schema (TuplePG Row) (RowPG Row)
145− query = values_ $
150+ roundTrip = values_ $
146 parameter @1 (typedef #schwarma) `as` #schwarma :*
151 parameter @1 (typedef #schwarma) `as` #schwarma :*
147− parameter @2 (typedef #person) `as` #person
152+ parameter @2 (typedef #person) `as` #person1 :*
153+ parameter @3 json `as` #person2
148:}
154:}
149
155
150−And finally drop the types.
156+Finally, we can drop our type definitions.
151
157
152>>> :{
158>>> :{
153let
159let
@@ -160,9 +166,9 @@ Now let's run it.
160>>> :{
166>>> :{
161let
167let
162 session = do
168 session = do
163− result1 <- runQueryParams query tparams
169+ result <- runQueryParams roundTrip input
164− Just row <- firstRow result1
170+ Just output <- firstRow result
165− liftBase . print $ row == tparams
171+ liftBase . print $ input == output
166in
172in
167 void . withConnection "host=localhost port=5432 dbname=exampledb" $
173 void . withConnection "host=localhost port=5432 dbname=exampledb" $
168 define setup
174 define setup
@@ -205,10 +211,9 @@ module Squeal.PostgreSQL.Binary
205 ) where
211 ) where
206
212
207import BinaryParser
213import BinaryParser
208−import ByteString.StrictBuilder
214+import ByteString.StrictBuilder (builderLength, int32BE, int64BE, word32BE)
209import Control.Arrow (left)
215import Control.Arrow (left)
210import Control.Monad
216import Control.Monad
211−import Data.Aeson hiding (Null)
212import Data.Int
217import Data.Int
213import Data.Kind
218import Data.Kind
214import Data.Monoid hiding (All)
219import Data.Monoid hiding (All)
@@ -222,6 +227,7 @@ import Generics.SOP.Record
222import GHC.TypeLits
227import GHC.TypeLits
223import Network.IP.Addr
228import Network.IP.Addr
224
229
230+import qualified Data.Aeson as Aeson
225import qualified Data.ByteString.Lazy as Lazy (ByteString)
231import qualified Data.ByteString.Lazy as Lazy (ByteString)
226import qualified Data.ByteString.Lazy as Lazy.ByteString
232import qualified Data.ByteString.Lazy as Lazy.ByteString
227import qualified Data.ByteString as Strict (ByteString)
233import qualified Data.ByteString as Strict (ByteString)
@@ -284,11 +290,14 @@ instance ToParam LocalTime 'PGtimestamp where
284instance ToParam UTCTime 'PGtimestamptz where
290instance ToParam UTCTime 'PGtimestamptz where
285 toParam = K . Encoding.timestamptz_int
291 toParam = K . Encoding.timestamptz_int
286instance ToParam DiffTime 'PGinterval where toParam = K . Encoding.interval_int
292instance ToParam DiffTime 'PGinterval where toParam = K . Encoding.interval_int
287−instance ToParam Value 'PGjson where toParam = K . Encoding.json_ast
293+instance ToParam Aeson.Value 'PGjson where toParam = K . Encoding.json_ast
288−instance ToParam Value 'PGjsonb where toParam = K . Encoding.jsonb_ast
294+instance ToParam Aeson.Value 'PGjsonb where toParam = K . Encoding.jsonb_ast
295+instance Aeson.ToJSON x => ToParam (Json x) 'PGjson where
296+ toParam = K . Encoding.json_bytes
297+ . Lazy.ByteString.toStrict . Aeson.encode . getJson
289−instance ToJSON x => ToParam (Jsonb x) 'PGjsonb where
298+instance Aeson.ToJSON x => ToParam (Jsonb x) 'PGjsonb where
290 toParam = K . Encoding.jsonb_bytes
299 toParam = K . Encoding.jsonb_bytes
291− . Lazy.ByteString.toStrict . encode . getJsonb
300+ . Lazy.ByteString.toStrict . Aeson.encode . getJsonb
292instance ToArray x ('NotNull ('PGvararray ty))
301instance ToArray x ('NotNull ('PGvararray ty))
293 => ToParam x ('PGvararray ty) where
302 => ToParam x ('PGvararray ty) where
294 toParam
303 toParam
@@ -476,14 +485,14 @@ instance FromValue 'PGtimestamptz UTCTime where
476 fromValue = Decoding.timestamptz_int
485 fromValue = Decoding.timestamptz_int
477instance FromValue 'PGinterval DiffTime where
486instance FromValue 'PGinterval DiffTime where
478 fromValue = Decoding.interval_int
487 fromValue = Decoding.interval_int
479−instance FromValue 'PGjson Value where fromValue = Decoding.json_ast
488+instance FromValue 'PGjson Aeson.Value where fromValue = Decoding.json_ast
480−instance FromValue 'PGjsonb Value where fromValue = Decoding.jsonb_ast
489+instance FromValue 'PGjsonb Aeson.Value where fromValue = Decoding.jsonb_ast
481−instance FromJSON x => FromValue 'PGjson (Json x) where
490+instance Aeson.FromJSON x => FromValue 'PGjson (Json x) where
482 fromValue = Json <$>
491 fromValue = Json <$>
483− Decoding.json_bytes (left Strict.Text.pack . eitherDecodeStrict)
492+ Decoding.json_bytes (left Strict.Text.pack . Aeson.eitherDecodeStrict)
484−instance FromJSON x => FromValue 'PGjsonb (Jsonb x) where
493+instance Aeson.FromJSON x => FromValue 'PGjsonb (Jsonb x) where
485 fromValue = Jsonb <$>
494 fromValue = Jsonb <$>
486− Decoding.jsonb_bytes (left Strict.Text.pack . eitherDecodeStrict)
495+ Decoding.jsonb_bytes (left Strict.Text.pack . Aeson.eitherDecodeStrict)
487instance FromArray ('NotNull ('PGvararray ty)) y
496instance FromArray ('NotNull ('PGvararray ty)) y
488 => FromValue ('PGvararray ty) y where
497 => FromValue ('PGvararray ty) y where
489 fromValue = Decoding.array (fromArray @('NotNull ('PGvararray ty)) @y)
498 fromValue = Decoding.array (fromArray @('NotNull ('PGvararray ty)) @y)