@@ -1,12 +1,167 @@
1−{-|
2−Module: Squeal.PostgreSQL
3−Description: Squeel export module
4−Copyright: (c) Eitan Chatav, 2017
5−Maintainer: eitan@morphism.tech
6−Stability: experimental
7−
8−`Squeal.PostgreSQL` re-exports @squeel-postgresql@'s main modules.
9−-}
1+-- | Module: Squeal.PostgreSQL
2+-- Description: Squeel export module
3+-- Copyright: (c) Eitan Chatav, 2017
4+-- Maintainer: eitan@morphism.tech
5+-- Stability: experimental
6+--
7+-- Squeal is a deep embedding of PostgreSQL in Haskell. Let's see an example!
8+--
9+-- First, we need some language extensions because Squeal uses modern GHC
10+-- features.
11+--
12+-- > {-# LANGUAGE
13+-- > DataKinds
14+-- > , DeriveGeneric
15+-- > , OverloadedLabels
16+-- > , OverloadedStrings
17+-- > , TypeApplications
18+-- > , TypeOperators
19+-- > #-}
20+--
21+-- Here comes the @Main@ module and imports.
22+--
23+-- > module Main (main) where
24+-- >
25+-- > import Control.Monad.Base (liftBase)
26+-- > import Data.Int (Int32)
27+-- > import Data.Text (Text)
28+-- >
29+-- > import Squeal.PostgreSQL
30+--
31+-- We'll use generics to easily convert between Haskell of PostgreSQL values.
32+--
33+-- > import qualified Generics.SOP as SOP
34+-- > import qualified GHC.Generics as GHC
35+--
36+-- The first step is to define the schema of our database. This is where
37+-- we use @DataKinds@ and @TypeOperators@. The schema consists of a type-level
38+-- list of tables, a `:::` pairing of a type level string or
39+-- `Data.TypeLit.Symbol` and a list a columns, itself a `:::` pairing of a
40+-- `Data.TypeLit.Symbol` and a `ColumnType`. The `ColumnType` describes the
41+-- PostgreSQL type of the column as well as whether or not it may contain
42+-- @NULL@ and whether or not inserts and updates can use a @DEFAULT@. For our
43+-- schema, we'll define two tables, a users table and an emails table.
44+--
45+-- > type Schema =
46+-- > '[ "users" :::
47+-- > '[ "id" ::: 'Optional ('NotNull 'PGint4)
48+-- > , "name" ::: 'Required ('NotNull 'PGtext)
49+-- > ]
50+-- > , "emails" :::
51+-- > '[ "id" ::: 'Optional ('NotNull 'PGint4)
52+-- > , "user_id" ::: 'Required ('NotNull 'PGint4)
53+-- > , "email" ::: 'Required ('Null 'PGtext)
54+-- > ]
55+-- > ]
56+--
57+-- Next, we'll write `Definition`s to set up and tear down the schema. In
58+-- Squeal, a `Definition` is a `createTable`, `alterTable` or `dropTable`
59+-- command and has two type parameters, corresponding to the schema
60+-- before being run and the schema after. We can compose definitions using
61+-- `>>>`.
62+--
63+-- > setup :: Definition '[] Schema
64+-- > setup =
65+-- > createTable #users
66+-- > ( serial `As` #id :*
67+-- > (text & notNull) `As` #name :* Nil )
68+-- > [ primaryKey (Column #id :* Nil) ]
69+-- > >>>
70+-- > createTable #emails
71+-- > ( serial `As` #id :*
72+-- > (int & notNull) `As` #user_id :*
73+-- > text `As` #email :* Nil )
74+-- > [ primaryKey (Column #id :* Nil)
75+-- > , foreignKey (Column #user_id :* Nil) #users (Column #id :* Nil)
76+-- > OnDeleteCascade OnUpdateCascade ]
77+--
78+-- Notice that @setup@ starts with an empty schema @'[]@ and produces @Schema@.
79+-- In our `createTable` commands we included `TableConstraint`s to define
80+-- primary and foreign keys, making them somewhat complex. Our tear down
81+-- `Definition` is simpler.
82+--
83+-- > teardown :: Definition Schema '[]
84+-- > teardown = dropTable #emails >>> dropTable #users
85+--
86+-- Next, we'll write `Manipulation`s to insert data into our two tables.
87+-- A `Manipulation` is a `insertInto`, `update` or `deleteFrom` command and
88+-- has three type parameters, the schema it refers to, a list of parameters
89+-- it can take as input, and a list of columns it produces as output. When
90+-- we insert into the users table, we will need a parameter for the @name@
91+-- field but not for the @id@ field since it's optional, we can use a default
92+-- value. However, since the emails table refers to the users table, we will
93+-- need to retrieve the user id and insert it into the emails table. Take
94+-- a careful look at the type and definition of both of our inserts.
95+--
96+-- > insertUser :: Manipulation Schema
97+-- > '[ 'Required ('NotNull 'PGtext)]
98+-- > '[ "fromOnly" ::: 'Required ('NotNull 'PGint4) ]
99+-- > insertUser = insertInto #users
100+-- > ( Values (def `As` #id :* param @1 `As` #name :* Nil) [] )
101+-- > OnConflictDoNothing (Returning (#id `As` #fromOnly :* Nil))
102+-- >
103+-- > insertEmail :: Manipulation Schema
104+-- > '[ 'Required ('NotNull 'PGint4), 'Required ('Null 'PGtext)] '[]
105+-- > insertEmail = insertInto #emails ( Values
106+-- > ( def `As` #id :*
107+-- > param @1 `As` #user_id :*
108+-- > param @2 `As` #email :* Nil) [] )
109+-- > OnConflictDoNothing (Returning Nil)
110+--
111+-- Next we write a `Query` to retrieve users from the database. We're not
112+-- interested in the ids here, just the usernames and email addresses. We
113+-- need to use an inner join to get the right result. A `Query` is like a
114+-- `Manipulation` with the same kind of type parameters.
115+--
116+-- > getUsers :: Query Schema '[]
117+-- > '[ "userName" ::: 'Required ('NotNull 'PGtext)
118+-- > , "userEmail" ::: 'Required ('Null 'PGtext) ]
119+-- > getUsers = select
120+-- > (#u ! #name `As` #userName :* #e ! #email `As` #userEmail :* Nil)
121+-- > ( from (Table (#users `As` #u)
122+-- > & InnerJoin (Table (#emails `As` #e))
123+-- > (#u ! #id .== #e ! #user_id)) )
124+--
125+-- Now that we've defined the SQL side of things, we'll need a Haskell type
126+-- for users. We give the type `Generics.SOP.Generic` and
127+-- `Generics.SOP.HasDatatypeInfo` instances so that we can decode the rows
128+-- we receive when we run @getUsers@. Notice that the record fields of the
129+-- @User@ type match the column names of @getUsers@.
130+--
131+-- > data User = User { userName :: Text, userEmail :: Maybe Text }
132+-- > deriving (Show, GHC.Generic)
133+-- > instance SOP.Generic User
134+-- > instance SOP.HasDatatypeInfo User
135+--
136+-- Let's also create some users to add to the database.
137+--
138+-- > users :: [User]
139+-- > users =
140+-- > [ User "Alice" (Just "alice@gmail.com")
141+-- > , User "Bob" Nothing
142+-- > , User "Carole" (Just "carole@hotmail.com")
143+-- > ]
144+--
145+-- Now we can put together all the pieces into a program. The program
146+-- connects to the database, sets up the schema, inserts the user data
147+-- (using prepared statements as an optimization), queries the user
148+-- data and prints it out and finally closes the connection. Since the
149+-- `Connection` tracks the schema of the database, we get a new one
150+-- when we execute our statements.
151+--
152+-- > main :: IO ()
153+-- > main = do
154+-- > connection0 <- connectdb "host=localhost port=5432 dbname=exampledb"
155+-- > connection1 <- execPQ (define setup) connection0
156+-- > connection2 <- flip execPQ connection1 $ do
157+-- > idResults <- traversePrepared insertUser (Only . userName <$> users)
158+-- > ids <- traverse (fmap fromOnly . getRow (RowNumber 0)) idResults
159+-- > traversePrepared_ insertEmail (zip (ids :: [Int32]) (userEmail <$> users))
160+-- > usersResult <- runQuery getUsers
161+-- > usersRows <- getRows usersResult
162+-- > liftBase $ print (usersRows :: [User])
163+-- > connection3 <- execPQ (define teardown) connection2
164+-- > finish connection3
10165
11166module Squeal.PostgreSQL
12167 ( module Squeal.PostgreSQL.Binary