@@ -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.
14+Let's see some examples. We'll need some imports
14>>> import Data.Int (Int16)
16>>> import Data.Int (Int16)
15>>> import Data.Text (Text)
17>>> import Data.Text (Text)
17−>>> data Row = Row { col1 :: Int16, col2 :: Text } deriving (Eq, GHC.Generic)
18−>>> instance Generic Row
19−>>> instance HasDatatypeInfo Row
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
22+Define a Haskell datatype `Row` that will serve as both the input and output of a simple
25+>>> data Row = Row { col1 :: Int16, col2 :: Text, col3 :: Maybe Bool } deriving (Eq, GHC.Generic)
26+>>> instance Generic Row
27+>>> instance HasDatatypeInfo Row
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
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.
40+>>> let input = Row 2 "hi" (Just True)
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
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.
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)
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)),
62− ('PGfixarray 3 ('NotNull ('PGfixarray 2 ('NotNull 'PGint2))))]
65−RowPG VRow :: [(Symbol, NullityType)]
66−= '["v1" ::: 'NotNull ('PGvararray ('NotNull 'PGint2)),
67− "v2" ::: 'NotNull ('PGfixarray 2 ('Null 'PGint2)),
70− ('PGfixarray 3 ('NotNull ('PGfixarray 2 ('NotNull 'PGint2))))]
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))
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
78+>>> :set -XOverloadedLists
79+>>> let input = Row [1,2] (Just 1,Nothing) ((1,2),(3,4),(5,6))
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
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
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.
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
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`.
115type Schema =
109type Schema =
@@ -126,28 +120,40 @@ let
126 createTypeCompositeFrom @Person #person
120 createTypeCompositeFrom @Person #person
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.
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)
138−>>> instance Generic TRow
135+>>> instance Generic Row
139−>>> instance HasDatatypeInfo TRow
136+>>> instance HasDatatypeInfo Row
140+ (Enumerated Chicken)
140−>>> let tparams = TRow (Enumerated Chicken) (Composite (Person "Faisal" 24))
141+ (Composite (Person "Faisal" 24))
142+ (Json (Person "Ahmad" 48))
145+Once again, define a round trip query.
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
150−And finally drop the types.
156+Finally, we can drop our type definitions.
@@ -160,9 +166,9 @@ Now let's run it.
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
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
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
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
@@ -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)