on dev help sign in
binary haddocks
commitc51650eb6c9f81967ad9a6d36474706a883d1435 authorEitan Chatav <eitan.chatav@gmail.com> dateTue 4 Sep 2018, 15:17 (8 years ago) parent3004caa
1 file changed+89−80
squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+89−80
squeal-postgresql/src/Squeal/PostgreSQL/Binary.hs+89−80
@@ -11,83 +11,78 @@ Instances are governed by the `Generic` and `HasDatatypeInfo` typeclasses, so yo
1111do not need to define your own instances to decode retrieved rows into Haskell values or
1212to encode Haskell values into statement parameters.
1313
14+Let's see some examples. We'll need some imports
15+
1416>>> import Data.Int (Int16)
1517>>> 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−
2118>>> import Control.Monad (void)
2219>>> import Control.Monad.Base (liftBase)
2320>>> import Squeal.PostgreSQL
2421
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
2528>>> :{
2629let
27− query :: Query '[] (TuplePG Row) (RowPG Row)
28− query = values_ (param @1 `as` #col1 :* param @2 `as` #col2)
30+ roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
31+ roundTrip = values_ $
32+ parameter @1 int2 `as` #col1 :*
33+ parameter @2 text `as` #col2 :*
34+ parameter @3 bool `as` #col3
2935:}
3036
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)
3141>>> :{
32−let
33− roundtrip :: IO ()
34− roundtrip = void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
35− result <- runQueryParams query (2 :: Int16, "hi" :: Text)
36− Just row <- firstRow result
37− liftBase . print $ row == Row 2 "hi"
42+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
43+ result <- runQueryParams roundTrip input
44+ Just output <- firstRow result
45+ liftBase . print $ input == output
3846:}
39−
40−>>> roundtrip
4147True
4248
43−In addition to being able to encode and decode basic Haskell types like `Int16` and `Text`,
44−Squeal permits you to encode and decode Haskell types to
45−Postgres array, enumerated and composite types.
49+In addition to being able to encode and decode basic Haskell types
50+like `Int16` and `Text`, Squeal permits you to encode and decode Haskell types to
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.
4655
4756>>> :{
48−data VRow = VRow
49− { v1 :: Vector Int16
50− , v2 :: (Maybe Int16,Maybe Int16)
51− , v3 :: ((Int16,Int16),(Int16,Int16),(Int16,Int16))
57+data Row = Row
58+ { col1 :: Vector Int16
59+ , col2 :: (Maybe Int16,Maybe Int16)
60+ , col3 :: ((Int16,Int16),(Int16,Int16),(Int16,Int16))
5261 } deriving (Eq, GHC.Generic)
5362:}
5463
55−>>> instance Generic VRow
56−>>> instance HasDatatypeInfo VRow
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))))]
64+>>> instance Generic Row
65+>>> instance HasDatatypeInfo Row
7166
72−>>> :set -XOverloadedLists
73−>>> let vparams = VRow [1,2] (Just 1,Nothing) ((1,2), (3,4), (5,6))
67+Once again, we define a simple round trip query.
7468
7569>>> :{
7670let
77− query :: Query '[] (TuplePG VRow) (RowPG VRow)
78− query = values_ (param @1 `as` #v1 :* param @2 `as` #v2 :* param @3 `as` #v3)
71+ roundTrip :: Query '[] (TuplePG Row) (RowPG Row)
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
7976:}
8077
78+>>> :set -XOverloadedLists
79+>>> let input = Row [1,2] (Just 1,Nothing) ((1,2),(3,4),(5,6))
8180>>> :{
82−let
83− roundtrip :: IO ()
84− roundtrip = void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
85− result <- runQueryParams query vparams
86− Just row <- firstRow result
87− liftBase . print $ row == vparams
81+void . withConnection "host=localhost port=5432 dbname=exampledb" $ do
82+ result <- runQueryParams roundTrip input
83+ Just output <- firstRow result
84+ liftBase . print $ input == output
8885:}
89−
90−>>> roundtrip
9186True
9287
9388Enumerated (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.
10095>>> instance HasDatatypeInfo Schwarma
10196
10297A 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
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.
98+it is essentially just a list of field names and their data types.
10699
107100>>> data Person = Person {name :: Text, age :: Int32} deriving (Eq, Show, GHC.Generic)
108101>>> instance Generic Person
109102>>> instance HasDatatypeInfo Person
103+>>> instance Aeson.FromJSON Person
104+>>> instance Aeson.ToJSON Person
110105
111−We can create the equivalent Postgres types directly from their Haskell types. First,
112−define a type instances for `PG`.
106+We can create the equivalent Postgres types directly from their Haskell types.
113107
114108>>> :{
115109type Schema =
@@ -126,28 +120,40 @@ let
126120 createTypeCompositeFrom @Person #person
127121:}
128122
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.
130126
131127>>> :{
132−data TRow = TRow
128+data Row = Row
133129 { schwarma :: Enumerated Schwarma
134− , person :: Composite Person
130+ , person1 :: Composite Person
131+ , person2 :: Json Person
135132 } deriving (Eq, GHC.Generic)
136133:}
137134
138−>>> instance Generic TRow
139−>>> instance HasDatatypeInfo TRow
140−>>> let tparams = TRow (Enumerated Chicken) (Composite (Person "Faisal" 24))
135+>>> instance Generic Row
136+>>> instance HasDatatypeInfo Row
137+>>> :{
138+let
139+ input = Row
140+ (Enumerated Chicken)
141+ (Composite (Person "Faisal" 24))
142+ (Json (Person "Ahmad" 48))
143+:}
144+
145+Once again, define a round trip query.
141146
142147>>> :{
143148let
144− query :: Query Schema (TuplePG TRow) (RowPG TRow)
145− query = values_ $
149+ roundTrip :: Query Schema (TuplePG Row) (RowPG Row)
150+ roundTrip = values_ $
146151 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
148154:}
149155
150−And finally drop the types.
156+Finally, we can drop our type definitions.
151157
152158>>> :{
153159let
@@ -160,9 +166,9 @@ Now let's run it.
160166>>> :{
161167let
162168 session = do
163− result1 <- runQueryParams query tparams
164− Just row <- firstRow result1
165− liftBase . print $ row == tparams
169+ result <- runQueryParams roundTrip input
170+ Just output <- firstRow result
171+ liftBase . print $ input == output
166172in
167173 void . withConnection "host=localhost port=5432 dbname=exampledb" $
168174 define setup
@@ -205,10 +211,9 @@ module Squeal.PostgreSQL.Binary
205211 ) where
206212
207213import BinaryParser
208−import ByteString.StrictBuilder
214+import ByteString.StrictBuilder (builderLength, int32BE, int64BE, word32BE)
209215import Control.Arrow (left)
210216import Control.Monad
211−import Data.Aeson hiding (Null)
212217import Data.Int
213218import Data.Kind
214219import Data.Monoid hiding (All)
@@ -222,6 +227,7 @@ import Generics.SOP.Record
222227import GHC.TypeLits
223228import Network.IP.Addr
224229
230+import qualified Data.Aeson as Aeson
225231import qualified Data.ByteString.Lazy as Lazy (ByteString)
226232import qualified Data.ByteString.Lazy as Lazy.ByteString
227233import qualified Data.ByteString as Strict (ByteString)
@@ -284,11 +290,14 @@ instance ToParam LocalTime 'PGtimestamp where
284290instance ToParam UTCTime 'PGtimestamptz where
285291 toParam = K . Encoding.timestamptz_int
286292instance ToParam DiffTime 'PGinterval where toParam = K . Encoding.interval_int
287−instance ToParam Value 'PGjson where toParam = K . Encoding.json_ast
288−instance ToParam Value 'PGjsonb where toParam = K . Encoding.jsonb_ast
289−instance ToJSON x => ToParam (Jsonb x) 'PGjsonb where
293+instance ToParam Aeson.Value 'PGjson where toParam = K . Encoding.json_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
298+instance Aeson.ToJSON x => ToParam (Jsonb x) 'PGjsonb where
290299 toParam = K . Encoding.jsonb_bytes
291− . Lazy.ByteString.toStrict . encode . getJsonb
300+ . Lazy.ByteString.toStrict . Aeson.encode . getJsonb
292301instance ToArray x ('NotNull ('PGvararray ty))
293302 => ToParam x ('PGvararray ty) where
294303 toParam
@@ -476,14 +485,14 @@ instance FromValue 'PGtimestamptz UTCTime where
476485 fromValue = Decoding.timestamptz_int
477486instance FromValue 'PGinterval DiffTime where
478487 fromValue = Decoding.interval_int
479−instance FromValue 'PGjson Value where fromValue = Decoding.json_ast
480−instance FromValue 'PGjsonb Value where fromValue = Decoding.jsonb_ast
481−instance FromJSON x => FromValue 'PGjson (Json x) where
488+instance FromValue 'PGjson Aeson.Value where fromValue = Decoding.json_ast
489+instance FromValue 'PGjsonb Aeson.Value where fromValue = Decoding.jsonb_ast
490+instance Aeson.FromJSON x => FromValue 'PGjson (Json x) where
482491 fromValue = Json <$>
483− Decoding.json_bytes (left Strict.Text.pack . eitherDecodeStrict)
484−instance FromJSON x => FromValue 'PGjsonb (Jsonb x) where
492+ Decoding.json_bytes (left Strict.Text.pack . Aeson.eitherDecodeStrict)
493+instance Aeson.FromJSON x => FromValue 'PGjsonb (Jsonb x) where
485494 fromValue = Jsonb <$>
486− Decoding.jsonb_bytes (left Strict.Text.pack . eitherDecodeStrict)
495+ Decoding.jsonb_bytes (left Strict.Text.pack . Aeson.eitherDecodeStrict)
487496instance FromArray ('NotNull ('PGvararray ty)) y
488497 => FromValue ('PGvararray ty) y where
489498 fromValue = Decoding.array (fromArray @('NotNull ('PGvararray ty)) @y)