@@ -3,6 +3,7 @@
33 DataKinds
44 , FlexibleInstances
55 , FunctionalDependencies
6+ , GADTs
67 , LambdaCase
78 , MagicHash
89 , OverloadedStrings
@@ -15,6 +16,7 @@ module Squeel.PostgreSQL.Statement where
1516
1617import Data.Boolean
1718import Data.ByteString (ByteString)
19+import Data.Maybe
1820import Data.Monoid
1921import Data.String
2022import Data.Vinyl
@@ -28,9 +30,9 @@ import qualified Data.ByteString as ByteString
2830import Squeel.PostgreSQL.Schema
2931
3032newtype Expression
31− (params :: [PGType])
32− (tables :: [(Symbol,[(Symbol,PGType)])])
33− (ty :: PGType)
33+ (params :: [NullityType])
34+ (tables :: [(Symbol,[(Symbol,NullityType)])])
35+ (ty :: NullityType)
3436 = UnsafeExpression { renderExpression :: ByteString }
3537
3638class KnownNat n => HasParameter (n :: Nat) params ty | n params -> ty where
@@ -94,7 +96,7 @@ unsafeFunction
9496unsafeFunction fn x = UnsafeExpression $ mconcat
9597 [fn, "(", renderExpression x, ")"]
9698
97−instance PGNum ty => Num (Expression params tables ty) where
99+instance PGNum ty => Num (Expression params tables (nullity ty)) where
98100 (+) = unsafeBinaryOp "+"
99101 (-) = unsafeBinaryOp "-"
100102 (*) = unsafeBinaryOp "*"
@@ -102,7 +104,7 @@ instance PGNum ty => Num (Expression params tables ty) where
102104 signum = unsafeFunction "sign"
103105 fromInteger = UnsafeExpression . fromString . show
104106
105−instance IsString (Expression params tables 'PGText) where
107+instance IsString (Expression params tables (nullity 'PGText)) where
106108 fromString str = UnsafeExpression $
107109 "E\'" <> fromString (escape =<< str) <> "\'"
108110 where
@@ -117,7 +119,7 @@ instance IsString (Expression params tables 'PGText) where
117119 '\\' -> "\\\\"
118120 c -> [c]
119121
120−instance Boolean (Expression params tables 'PGBool) where
122+instance Boolean (Expression params tables ('NotNull 'PGBool)) where
121123 true = UnsafeExpression "TRUE"
122124 false = UnsafeExpression "FALSE"
123125 notB = unsafeUnaryOp "NOT"
@@ -125,10 +127,21 @@ instance Boolean (Expression params tables 'PGBool) where
125127 (||*) = unsafeBinaryOp "OR"
126128
127129type instance BooleanOf (Expression params tables ty) =
128− Expression params tables 'PGBool
130+ Expression params tables ('NotNull 'PGBool)
131+
132+coalesce
133+ :: [Expression params tables ('Null x)]
134+ -> Expression params tables ('NotNull x)
135+ -> Expression params tables ('NotNull x)
136+coalesce nulls notNull = UnsafeExpression $ mconcat
137+ [ "COALESCE("
138+ , ByteString.intercalate ", " $ map renderExpression nulls
139+ , ", ", renderExpression notNull
140+ , ")"
141+ ]
129142
130143caseWhenThenElse
131− :: [(Expression params tables 'PGBool, Expression params tables ty)]
144+ :: [(Expression params tables ('NotNull 'PGBool), Expression params tables ty)]
132145 -> Expression params tables ty
133146 -> Expression params tables ty
134147caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
@@ -157,45 +170,105 @@ instance OrdB (Expression params tables ty) where
157170 (<*) = unsafeBinaryOp "<"
158171 (<=*) = unsafeBinaryOp "<="
159172
160−newtype TableRef
161− (params :: [PGType])
162− (schema :: [(Symbol,[(Symbol,PGType)])])
163− (tables :: [(Symbol,[(Symbol,PGType)])])
164− = UnsafeTableRef { renderTableRef :: ByteString }
173+newtype TableExpression
174+ (params :: [NullityType])
175+ (schema :: [(Symbol,[(Symbol,NullityType)])])
176+ (columns :: [(Symbol,NullityType)])
177+ = UnsafeTableExpression { renderTableExpression :: ByteString }
178+
179+data JoinExpression params schema tables where
180+ Table
181+ :: Aliased (TableExpression params schema) table
182+ -> JoinExpression params schema '[table]
183+ Subselect
184+ :: Aliased (Selection params schema) table
185+ -> JoinExpression params schema '[table]
186+ Cross
187+ :: Aliased (TableExpression params schema) table
188+ -> JoinExpression params schema tables
189+ -> JoinExpression params schema (table ': tables)
190+ Inner
191+ :: Aliased (TableExpression params schema) table
192+ -> Expression params (table ': tables) ('NotNull 'PGBool)
193+ -> JoinExpression params schema tables
194+ -> JoinExpression params schema (table ': tables)
195+ LeftOuter
196+ :: Aliased (TableExpression params schema) right
197+ -> Expression params '[left,right] ('NotNull 'PGBool)
198+ -> JoinExpression params schema (tables)
199+ -> JoinExpression params schema (NullifyTable right ': left ': tables)
200+ RightOuter
201+ :: Aliased (TableExpression params schema) right
202+ -> Expression params '[left,right] ('NotNull 'PGBool)
203+ -> JoinExpression params schema (left : tables)
204+ -> JoinExpression params schema (right ': NullifyTable left ': tables)
205+ FullOuter
206+ :: Aliased (TableExpression params schema) right
207+ -> Expression params '[left,right] ('NotNull 'PGBool)
208+ -> JoinExpression params schema (left : tables)
209+ -> JoinExpression params schema
210+ (NullifyTable right ': NullifyTable left ': tables)
211+
212+renderJoinExpression :: JoinExpression params schema tables -> ByteString
213+renderJoinExpression = \case
214+ Table table -> renderAliased renderTableExpression table
215+ Subselect selection -> "SELECT " <> renderAliased renderSelection selection
216+ Cross table tables -> mconcat
217+ [ renderJoinExpression tables
218+ , " CROSS JOIN "
219+ , renderAliased renderTableExpression table
220+ ]
221+ Inner table on tables -> mconcat
222+ [ renderJoinExpression tables
223+ , " INNER JOIN "
224+ , renderAliased renderTableExpression table
225+ , " ON "
226+ , renderExpression on
227+ ]
228+ LeftOuter table on tables -> mconcat
229+ [ renderJoinExpression tables
230+ , " LEFT OUTER JOIN "
231+ , renderAliased renderTableExpression table
232+ , " ON "
233+ , renderExpression on
234+ ]
235+ RightOuter table on tables -> mconcat
236+ [ renderJoinExpression tables
237+ , " RIGHT OUTER JOIN "
238+ , renderAliased renderTableExpression table
239+ , " ON "
240+ , renderExpression on
241+ ]
242+ FullOuter table on tables -> mconcat
243+ [ renderJoinExpression tables
244+ , " FULL OUTER JOIN "
245+ , renderAliased renderTableExpression table
246+ , " ON "
247+ , renderExpression on
248+ ]
165249
166250class KnownSymbol table => HasTable table tables columns
167251 | table tables -> columns where
168− getTable :: Proxy# table -> TableRef params tables '[table ::: columns]
169− getTable table = UnsafeTableRef $ fromString $ symbolVal' table
252+ getTable :: Proxy# table -> TableExpression params tables columns
253+ getTable table = UnsafeTableExpression $ fromString $ symbolVal' table
170254instance {-# OVERLAPPING #-} KnownSymbol table
171255 => HasTable table ((table ::: columns) ': tables) columns
172256instance {-# OVERLAPPABLE #-}
173257 (KnownSymbol table, HasTable table schema columns)
174258 => HasTable table (table' ': schema) columns
175259
176−crossJoin
177− :: TableRef params schema '[table]
178− -> TableRef params schema tables
179− -> TableRef params schema (table ': tables)
180−crossJoin tab tabs = UnsafeTableRef $
181− renderTableRef tabs <> " CROSS JOIN " <> renderTableRef tab
182−
183−innerJoin
184− :: TableRef params schema '[table]
185− -> Expression params (table ': tables) 'PGBool
186− -> TableRef params schema tables
187− -> TableRef params schema (table ': tables)
188−innerJoin tab on tabs = UnsafeTableRef $
189− renderTableRef tabs
190− <> " INNER JOIN " <>
191− renderTableRef tab
192− <> " ON " <>
193− renderExpression on
260+instance HasTable table schema columns
261+ => IsLabel table (TableExpression params schema columns) where
262+ fromLabel = getTable
263+
264+instance HasTable table schema columns
265+ => IsLabel table (JoinExpression params schema '[table ::: columns]) where
266+ fromLabel p = Table $ fromLabel p
194267
195268data Clauses params tables = Clauses
196− { whereClause :: Maybe (Expression params tables 'PGBool)
197− , limitClause :: Maybe (Expression params '[] 'PGInt8)
198− , offsetClause :: Maybe (Expression params '[] 'PGInt8)
269+ { whereClause :: Maybe (Expression params tables ('NotNull 'PGBool))
270+ , limitClause :: Maybe (Expression params '[] ('NotNull 'PGInt8))
271+ , offsetClause :: Maybe (Expression params '[] ('NotNull 'PGInt8))
199272 }
200273
201274instance Monoid (Clauses params tables) where
@@ -218,84 +291,79 @@ instance Monoid (Clauses params tables) where
218291 (Just o1,Just o2) -> Just (o1 + o2)
219292 }
220293
221−data TableExpression
222− (params :: [PGType])
223− (schema :: [(Symbol,[(Symbol,PGType)])])
224− (tables :: [(Symbol,[(Symbol,PGType)])])
225− = TableExpression
226− { tableRef :: TableRef params schema tables
227− , clauses :: Clauses params tables
228− }
294+data FromExpression
295+ (params :: [NullityType])
296+ (schema :: [(Symbol,[(Symbol,NullityType)])])
297+ (tables :: [(Symbol,[(Symbol,NullityType)])])
298+ = FromExpression
299+ { joinExpression :: JoinExpression params schema tables
300+ , clauses :: Clauses params tables
301+ }
229302
230−tables
231− :: TableRef params schema tables
232− -> TableExpression params schema tables
233−tables tref = TableExpression tref mempty
303+join
304+ :: JoinExpression params schema tables
305+ -> FromExpression params schema tables
306+join tref = FromExpression tref mempty
234307
235−renderTabulation :: TableExpression params schema tables -> ByteString
236−renderTabulation (TableExpression tref (Clauses wh lim off))= mconcat
237− [ renderTableRef tref
308+renderFromExpression :: FromExpression params schema tables -> ByteString
309+renderFromExpression (FromExpression tref (Clauses wh lim off))= mconcat
310+ [ renderJoinExpression tref
238311 , maybe "" ((" WHERE " <>) . renderExpression) wh
239312 , maybe "" ((" LIMIT " <>) . renderExpression) lim
240313 , maybe "" ((" OFFSET " <>) . renderExpression) off
241314 ]
242315
243316instance (HasTable table schema columns, table ~ table')
244− => IsLabel table (TableRef params schema '[table' ::: columns]) where
245− fromLabel = getTable
246−
247−instance (HasTable table schema columns, table ~ table')
248− => IsLabel table (TableExpression params schema '[table' ::: columns]) where
249− fromLabel p = TableExpression (getTable p) mempty
317+ => IsLabel table (FromExpression params schema '[table' ::: columns]) where
318+ fromLabel p = FromExpression (fromLabel p) mempty
250319
251320where_
252− :: Expression params tables 'PGBool
253− -> TableExpression params schema tables
254− -> TableExpression params schema tables
255−where_ wh (TableExpression tabs clauses1) = TableExpression tabs
321+ :: Expression params tables ('NotNull 'PGBool)
322+ -> FromExpression params schema tables
323+ -> FromExpression params schema tables
324+where_ wh (FromExpression tabs clauses1) = FromExpression tabs
256325 (clauses1 <> Clauses (Just wh) Nothing Nothing)
257326
258327limit
259− :: Expression params '[] 'PGInt8
260− -> TableExpression params schema tables
261− -> TableExpression params schema tables
262−limit lim (TableExpression tabs clauses1) = TableExpression tabs
328+ :: Expression params '[] ('NotNull 'PGInt8)
329+ -> FromExpression params schema tables
330+ -> FromExpression params schema tables
331+limit lim (FromExpression tabs clauses1) = FromExpression tabs
263332 (clauses1 <> Clauses Nothing (Just lim) Nothing)
264333
265334offset
266− :: Expression params '[] 'PGInt8
267− -> TableExpression params schema tables
268− -> TableExpression params schema tables
269−offset off (TableExpression tabs clauses1) = TableExpression tabs
335+ :: Expression params '[] ('NotNull 'PGInt8)
336+ -> FromExpression params schema tables
337+ -> FromExpression params schema tables
338+offset off (FromExpression tabs clauses1) = FromExpression tabs
270339 (clauses1 <> Clauses Nothing Nothing (Just off))
271340
272341newtype Selection
273− (params :: [PGType])
274− (schema :: [(Symbol,[(Symbol,PGType)])])
275− (columns :: [(Symbol,PGType)])
276− = UnsafeSelection
277− { renderSelection :: ByteString }
342+ (params :: [NullityType])
343+ (schema :: [(Symbol,[(Symbol,NullityType)])])
344+ (columns :: [(Symbol,NullityType)])
345+ = UnsafeSelection { renderSelection :: ByteString }
278346
279347starFrom
280348 :: tables ~ '[table ::: columns]
281− => TableExpression params schema tables
349+ => FromExpression params schema tables
282350 -> Selection params schema columns
283−starFrom tabs = UnsafeSelection $ "* FROM " <> renderTabulation tabs
351+starFrom tabs = UnsafeSelection $ "* FROM " <> renderFromExpression tabs
284352
285−tableStarFrom
353+dotStarFrom
286354 :: HasTable table tables columns
287355 => Alias table
288− -> TableExpression params schema tables
356+ -> FromExpression params schema tables
289357 -> Selection params schema columns
290−Alias tab `tableStarFrom` tabs = UnsafeSelection $
291− fromString (symbolVal' tab) <> ".* FROM" <> renderTabulation tabs
358+Alias tab `dotStarFrom` tabs = UnsafeSelection $
359+ fromString (symbolVal' tab) <> ".* FROM " <> renderFromExpression tabs
292360
293361from
294362 :: Rec (Aliased (Expression params tables)) columns
295− -> TableExpression params schema tables
363+ -> FromExpression params schema tables
296364 -> Selection params schema columns
297365list `from` tabs = UnsafeSelection $
298− renderList list <> " FROM " <> renderTabulation tabs
366+ renderList list <> " FROM " <> renderFromExpression tabs
299367 where
300368 renderList
301369 = ByteString.intercalate ", "
@@ -303,17 +371,24 @@ list `from` tabs = UnsafeSelection $
303371 . rmap (Const . renderAliased renderExpression)
304372
305373newtype Statement
306− (params :: [PGType])
307− (schema0 :: [(Symbol,[(Symbol,PGType)])])
308− (schema1 :: [(Symbol,[(Symbol,PGType)])])
309− (columns :: [(Symbol,PGType)])
374+ (params :: [NullityType])
375+ (schema0 :: [(Symbol,[(Symbol,NullityType)])])
376+ (schema1 :: [(Symbol,[(Symbol,NullityType)])])
377+ (columns :: [(Symbol,NullityType)])
310378 = UnsafeStatement { renderStatement :: ByteString }
311379
380+(.>>.)
381+ :: Statement params schema0 schema1 '[]
382+ -> Statement params schema1 schema2 results
383+ -> Statement params schema0 schema2 results
384+statement1 .>>. statement2 = UnsafeStatement $
385+ renderStatement statement1 <> " " <> renderStatement statement2
386+
312387newtype PreparedStatement
313− (params :: [PGType])
314− (schema0 :: [(Symbol,[(Symbol,PGType)])])
315− (schema1 :: [(Symbol,[(Symbol,PGType)])])
316− (columns :: [(Symbol,PGType)])
388+ (params :: [NullityType])
389+ (schema0 :: [(Symbol,[(Symbol,NullityType)])])
390+ (schema1 :: [(Symbol,[(Symbol,NullityType)])])
391+ (columns :: [(Symbol,NullityType)])
317392 = UnsafePreparedStatement { renderPreparedStatement :: ByteString }
318393
319394select
@@ -323,10 +398,9 @@ select = UnsafeStatement . ("SELECT " <>) . (<> ";") . renderSelection
323398
324399subselect
325400 :: Aliased (Selection params schema) table
326− -> TableExpression params schema '[table]
327−subselect selection = TableExpression
328− { tableRef = UnsafeTableRef . ("SELECT " <>) $
329− renderAliased renderSelection selection
401+ -> FromExpression params schema '[table]
402+subselect selection = FromExpression
403+ { joinExpression = Subselect selection
330404 , clauses = mempty
331405 }
332406
@@ -373,3 +447,34 @@ instance {-# OVERLAPPING #-}
373447instance {-# OVERLAPPABLE #-}
374448 (KnownSymbol table, DropTable table schema0 schema1)
375449 => DropTable table (tabCols ': schema0) (tabCols ': schema1)
450+
451+set :: g x -> (Maybe `Compose` g) x
452+set = Compose . Just
453+
454+same :: (Maybe `Compose` g) x
455+same = Compose Nothing
456+
457+update
458+ :: HasTable table schema columns
459+ => Alias table
460+ -> Rec (Aliased (Maybe `Compose` Expression params '[table ::: columns])) columns
461+ -> Expression params '[table ::: columns] ('NotNull 'PGBool)
462+ -> Statement params schema schema '[]
463+update (Alias table) columns where' = UnsafeStatement $ mconcat
464+ [ "UPDATE "
465+ , fromString $ symbolVal' table
466+ , " SET "
467+ , ByteString.intercalate ", " . catMaybes . recordToList $
468+ rmap (Const . renderSet) columns
469+ , " WHERE ", renderExpression where'
470+ ] where
471+ renderSet
472+ :: Aliased (Maybe `Compose` Expression params tables) column
473+ -> Maybe ByteString
474+ renderSet = \case
475+ Compose (Just expression) `As` Alias column -> Just $ mconcat
476+ [ fromString $ symbolVal' column
477+ , " = "
478+ , renderExpression expression
479+ ]
480+ Compose Nothing `As` _ -> Nothing