@@ -9,6 +9,7 @@
99 , FlexibleInstances
1010 , MagicHash
1111 , MultiParamTypeClasses
12+ , OverloadedStrings
1213 , RankNTypes
1314 , ScopedTypeVariables
1415 , TypeApplications
@@ -24,7 +25,10 @@ import Control.Monad.Base
2425import Control.Monad.Except
2526import Control.Monad.Trans.Control
2627import Data.ByteString (ByteString)
28+import Data.Foldable
2729import Data.Function ((&))
30+import Data.Monoid
31+import Data.Traversable
2832import Generics.SOP
2933import GHC.Exts hiding (fromList)
3034import GHC.TypeLits
@@ -43,18 +47,18 @@ newtype PQ
4347 (schema1 :: [(Symbol,[(Symbol,ColumnType)])])
4448 (m :: * -> *)
4549 (x :: *) =
46− PQ { unPQ :: Connection schema0 -> m (x, Connection schema1) }
50+ PQ { runPQ :: Connection schema0 -> m (x, Connection schema1) }
4751 deriving Functor
4852
49−evalPQ :: Functor m => Connection schema0 -> PQ schema0 schema1 m x -> m x
50−evalPQ conn (PQ pq) = fmap fst $ pq conn
53+evalPQ :: Functor m => PQ schema0 schema1 m x -> Connection schema0 -> m x
54+evalPQ (PQ pq) = fmap fst . pq
5155
52−runPQ
56+execPQ
5357 :: Functor m
54− => Connection schema0
55− -> PQ schema0 schema1 m x
58+ => PQ schema0 schema1 m x
59+ -> Connection schema0
5660 -> m (Connection schema1)
57−runPQ conn (PQ pq) = fmap snd $ pq conn
61+execPQ (PQ pq) = fmap snd . pq
5862
5963pqAp
6064 :: Monad m
@@ -73,7 +77,7 @@ pqBind
7377 -> PQ schema0 schema2 m y
7478pqBind f (PQ x) = PQ $ \ conn -> do
7579 (x', conn') <- x conn
76− unPQ (f x') conn'
80+ runPQ (f x') conn'
7781
7882pqThen
7983 :: Monad m
@@ -82,44 +86,156 @@ pqThen
8286 -> PQ schema0 schema2 m y
8387pqThen pq2 pq1 = pq1 & pqBind (\ _ -> pq2)
8488
85−pqExec
89+define
8690 :: MonadBase IO io
8791 => Definition schema0 schema1
88− -> PQ schema0 schema1 io (Maybe (Result '[]))
89−pqExec (UnsafeDefinition q) = PQ $ \ (Connection conn) -> do
90− result <- liftBase $ LibPQ.exec conn q
91− return (Result <$> result, Connection conn)
92−
93−pqThenExec
92+ -> PQ schema0 schema1 io (Result '[])
93+define (UnsafeDefinition q) = PQ $ \ (Connection conn) -> do
94+ resultMaybe <- liftBase $ LibPQ.exec conn q
95+ case resultMaybe of
96+ Nothing -> error
97+ "define: LibPQ.exec returned no results"
98+ Just result -> return (Result result, Connection conn)
99+
100+pqThenDefine
94101 :: MonadBase IO io
95102 => Definition schema1 schema2
96103 -> PQ schema0 schema1 io x
97− -> PQ schema0 schema2 io (Maybe (Result '[]))
98−pqThenExec = pqThen . pqExec
104+ -> PQ schema0 schema2 io (Result '[])
105+pqThenDefine = pqThen . define
99106
100−class Monad m => MonadPQ schema m | m -> schema where
107+class Monad pq => MonadPQ schema pq | pq -> schema where
101108
102− pqExecParams
109+ manipulateParams
103110 :: ToParams x params
104− => Manipulation schema params ys -> x -> m (Maybe (Result ys))
105− default pqExecParams
106− :: (MonadTrans t, MonadPQ schema m1, m ~ t m1)
111+ => Manipulation schema params ys -> x -> pq (Result ys)
112+ default manipulateParams
113+ :: (MonadTrans t, MonadPQ schema pq1, pq ~ t pq1)
107114 => ToParams x params
108− => Manipulation schema params ys -> x -> m (Maybe (Result ys))
109− pqExecParams manipulation params = lift $ pqExecParams manipulation params
115+ => Manipulation schema params ys -> x -> pq (Result ys)
116+ manipulateParams manipulation params = lift $
117+ manipulateParams manipulation params
110118
111− pqExecNil :: Manipulation schema '[] ys -> m (Maybe (Result ys))
112− pqExecNil statement = pqExecParams statement ()
119+ manipulate :: Manipulation schema '[] ys -> pq (Result ys)
120+ manipulate statement = manipulateParams statement ()
121+
122+ queryParams
123+ :: ToParams x params
124+ => Query schema params ys -> x -> pq (Result ys)
125+ queryParams = manipulateParams . queryStatement
126+
127+ query :: Query schema '[] ys -> pq (Result ys)
128+ query q = queryParams q ()
129+
130+ traversePrepared
131+ :: (ToParams x params, Traversable list)
132+ => Manipulation schema params ys -> list x -> pq (list (Result ys))
133+ default traversePrepared
134+ :: (MonadTrans t, MonadPQ schema pq1, pq ~ t pq1)
135+ => (ToParams x params, Traversable list)
136+ => Manipulation schema params ys -> list x -> pq (list (Result ys))
137+ traversePrepared manipulation params = lift $
138+ traversePrepared manipulation params
139+
140+ forPrepared
141+ :: (ToParams x params, Traversable list)
142+ => list x -> Manipulation schema params ys -> pq (list (Result ys))
143+ forPrepared = flip traversePrepared
144+
145+ traversePrepared_
146+ :: (ToParams x params, Foldable list)
147+ => Manipulation schema params ys -> list x -> pq ()
148+ default traversePrepared_
149+ :: (MonadTrans t, MonadPQ schema pq1, pq ~ t pq1)
150+ => (ToParams x params, Foldable list)
151+ => Manipulation schema params ys -> list x -> pq ()
152+ traversePrepared_ manipulation params = lift $
153+ traversePrepared_ manipulation params
154+
155+ forPrepared_
156+ :: (ToParams x params, Traversable list)
157+ => list x -> Manipulation schema params ys -> pq ()
158+ forPrepared_ = flip traversePrepared_
159+
160+ liftPQ :: (LibPQ.Connection -> IO a) -> pq a
161+ default liftPQ
162+ :: (MonadTrans t, MonadPQ schema pq1, pq ~ t pq1)
163+ => (LibPQ.Connection -> IO a) -> pq a
164+ liftPQ = lift . liftPQ
113165
114166instance MonadBase IO io => MonadPQ schema (PQ schema schema io) where
115167
116− pqExecParams (UnsafeManipulation q :: Manipulation schema ps ys) (params :: x) =
117− PQ $ \ (Connection conn) -> do
118− let
119− toParam' bytes = (LibPQ.invalidOid,bytes,LibPQ.Binary)
120− params' = fmap (fmap toParam') (hcollapse (toParams @x @ps params))
121− result <- liftBase $ LibPQ.execParams conn q params' LibPQ.Binary
122− return (Result <$> result, Connection conn)
168+ manipulateParams
169+ (UnsafeManipulation q :: Manipulation schema ps ys) (params :: x) =
170+ PQ $ \ (Connection conn) -> do
171+ let
172+ toParam' bytes = (LibPQ.invalidOid,bytes,LibPQ.Binary)
173+ params' = fmap (fmap toParam') (hcollapse (toParams @x @ps params))
174+ resultMaybe <- liftBase $ LibPQ.execParams conn q params' LibPQ.Binary
175+ case resultMaybe of
176+ Nothing -> error
177+ "manipulateParams: LibPQ.execParams returned no results"
178+ Just result -> return (Result result, Connection conn)
179+
180+ traversePrepared
181+ (UnsafeManipulation q :: Manipulation schema xs ys) (list :: list x) =
182+ PQ $ \ (Connection conn) -> do
183+ let temp = "temporary_statement"
184+ prepResultMaybe <- liftBase $ LibPQ.prepare conn temp q Nothing
185+ case prepResultMaybe of
186+ Nothing -> error
187+ "traversePrepared: LibPQ.prepare returned no results"
188+ Just _prepResult -> return () -- todo: check status of prepResult
189+ results <- for list $ \ params -> do
190+ let
191+ toParam' bytes = (bytes,LibPQ.Binary)
192+ params' = fmap (fmap toParam') (hcollapse (toParams @x @xs params))
193+ resultMaybe <- liftBase $
194+ LibPQ.execPrepared conn temp params' LibPQ.Binary
195+ case resultMaybe of
196+ Nothing -> error
197+ "traversePrepared: LibPQ.execParams returned no results"
198+ Just result -> return $ Result result
199+ deallocResultMaybe <- liftBase $
200+ LibPQ.exec conn ("DEALLOCATE " <> temp <> ";")
201+ case deallocResultMaybe of
202+ Nothing -> error
203+ "traversePrepared: LibPQ.exec DEALLOCATE returned no results"
204+ Just _deallocResult -> return ()
205+ -- todo: check status of deallocResult
206+ return (results, Connection conn)
207+
208+ traversePrepared_
209+ (UnsafeManipulation q :: Manipulation schema xs ys) (list :: list x) =
210+ PQ $ \ (Connection conn) -> do
211+ let temp = "temporary_statement"
212+ prepResultMaybe <- liftBase $ LibPQ.prepare conn temp q Nothing
213+ case prepResultMaybe of
214+ Nothing -> error
215+ "traversePrepared_: LibPQ.prepare returned no results"
216+ Just _prepResult -> return () -- todo: check status of prepResult
217+ for_ list $ \ params -> do
218+ let
219+ toParam' bytes = (bytes,LibPQ.Binary)
220+ params' = fmap (fmap toParam') (hcollapse (toParams @x @xs params))
221+ resultMaybe <- liftBase $
222+ LibPQ.execPrepared conn temp params' LibPQ.Binary
223+ case resultMaybe of
224+ Nothing -> error
225+ "traversePrepared_: LibPQ.execParams returned no results"
226+ Just _result -> return ()
227+ deallocResultMaybe <- liftBase $
228+ LibPQ.exec conn ("DEALLOCATE " <> temp <> ";")
229+ case deallocResultMaybe of
230+ Nothing -> error
231+ "traversePrepared: LibPQ.exec DEALLOCATE returned no results"
232+ Just _deallocResult -> return ()
233+ -- todo: check status of deallocResult
234+ return ((), Connection conn)
235+
236+ liftPQ pq = PQ $ \ (Connection conn) -> do
237+ y <- liftBase $ pq conn
238+ return (y, Connection conn)
123239
124240instance Monad m => Applicative (PQ schema schema m) where
125241 pure x = PQ $ \ conn -> pure (x, conn)
@@ -142,7 +258,7 @@ type PQRun schema =
142258
143259pqliftWith :: Functor m => (PQRun schema -> m a) -> PQ schema schema m a
144260pqliftWith f = PQ $ \ conn ->
145− fmap (\ x -> (x, conn)) (f $ \ pq -> unPQ pq conn)
261+ fmap (\ x -> (x, conn)) (f $ \ pq -> runPQ pq conn)
146262
147263instance MonadBaseControl b m => MonadBaseControl b (PQ schema schema m) where
148264 type StM (PQ schema schema m) x = StM m (x, Connection schema)
@@ -157,12 +273,12 @@ finish :: MonadBase IO io => Connection schema -> io ()
157273finish = liftBase . LibPQ.finish . unConnection
158274
159275withConnection
160− :: MonadBaseControl IO io
276+ :: forall schema io x
277+ . MonadBaseControl IO io
161278 => ByteString
162279 -> (Connection schema -> io x)
163280 -> io x
164−withConnection connString action =
165− bracket (connectdb connString) finish action
281+withConnection connString = bracket (connectdb connString) finish
166282
167283newtype Result (xs :: [(Symbol,ColumnType)])
168284 = Result { unResult :: LibPQ.Result }
@@ -192,15 +308,79 @@ getValue
192308 (RowNumber r)
193309 (UnsafeColumnNumber c :: ColumnNumber n columns colty)
194310 (Result result)
195− = fromColumnValue @colty . K <$>
196− liftBase (LibPQ.getvalue result r c)
311+ = fmap (fromColumnValue @colty . K) $ liftBase $ do
312+ numRows <- LibPQ.ntuples result
313+ when (numRows < r) $ error $
314+ "getValue: expected at least " <> show r <> "rows but only saw "
315+ <> show numRows
316+ LibPQ.getvalue result r c
197317
198318getRow
199319 :: (FromRow columns y, MonadBase IO io)
200320 => RowNumber -> Result columns -> io y
201−getRow (RowNumber r) (Result result :: Result columns) = do
321+getRow (RowNumber r) (Result result :: Result columns) = liftBase $do
322+ numRows <- LibPQ.ntuples result
323+ when (numRows < r) $ error $
324+ "getRow: expected at least " <> show r <> "rows but only saw "
325+ <> show numRows
202326 let len = fromIntegral (lengthSList (Proxy @columns))
203− row' <- traverse (liftBase . LibPQ.getvalue result r) [0 .. len - 1]
327+ numCols <- LibPQ.nfields result
328+ when (numCols /= len) $ error $
329+ "getRow: expected at least " <> show len <> "columns but only saw "
330+ <> show numCols
331+ row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
204332 case fromList row' of
205333 Nothing -> error "getRow: found unexpected length"
206334 Just row -> return $ fromRow @columns row
335+
336+nextRow
337+ :: (FromRow columns y, MonadBase IO io)
338+ => Result columns -> RowNumber -> io (Maybe (RowNumber,y))
339+nextRow (Result result :: Result columns) (RowNumber r) = liftBase $ do
340+ numRows <- LibPQ.ntuples result -- todo: cache this
341+ if numRows < r then return Nothing else do
342+ let len = fromIntegral (lengthSList (Proxy @columns))
343+ numCols <- LibPQ.nfields result -- todo: cache this
344+ when (numCols /= len) $ error $
345+ "nextRow: expected at least " <> show len <> "columns but only saw "
346+ <> show numCols
347+ row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
348+ case fromList row' of
349+ Nothing -> error "nextRow: found unexpected length"
350+ Just row -> return $ Just (RowNumber (r+1), fromRow @columns row)
351+
352+getRows :: (FromRow columns y, MonadBase IO io) => Result columns -> io [y]
353+getRows (Result result :: Result columns) = liftBase $ do
354+ let len = fromIntegral (lengthSList (Proxy @columns))
355+ numCols <- LibPQ.nfields result
356+ when (numCols /= len) $ error $
357+ "getRow: expected at least " <> show len <> "columns but only saw "
358+ <> show numCols
359+ numRows <- LibPQ.ntuples result
360+ for [0 .. numRows - 1] $ \ r -> do
361+ row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
362+ case fromList row' of
363+ Nothing -> error "getRows: found unexpected length"
364+ Just row -> return $ fromRow @columns row
365+
366+getRowMaybe
367+ :: (FromRow columns y, MonadBase IO io)
368+ => RowNumber -> Result columns -> io (Maybe y)
369+getRowMaybe (RowNumber r) (Result result :: Result columns) = liftBase $do
370+ numRows <- LibPQ.ntuples result
371+ if numRows < r then return Nothing else do
372+ let len = fromIntegral (lengthSList (Proxy @columns))
373+ numCols <- LibPQ.nfields result
374+ when (numCols /= len) $ error $
375+ "getRow: expected at least " <> show len <> "columns but only saw "
376+ <> show numCols
377+ row' <- traverse (LibPQ.getvalue result r) [0 .. len - 1]
378+ case fromList row' of
379+ Nothing -> error "getRow: found unexpected length"
380+ Just row -> return . Just $ fromRow @columns row
381+
382+liftResult
383+ :: MonadBase IO io
384+ => (LibPQ.Result -> IO x)
385+ -> Result results -> io x
386+liftResult f (Result result) = liftBase $ f result