@@ -1,9 +1,12 @@
1+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
1{-# LANGUAGE
2{-# LANGUAGE
3 , FlexibleContexts
4 , FlexibleContexts
4 , FlexibleInstances
5 , FlexibleInstances
6+ , FunctionalDependencies
6 , LambdaCase
8 , LambdaCase
7 , MultiParamTypeClasses
10 , MultiParamTypeClasses
8 , OverloadedStrings
11 , OverloadedStrings
9 , PolyKinds
12 , PolyKinds
@@ -12,6 +15,7 @@
12 , TypeApplications
15 , TypeApplications
13 , TypeFamilies
16 , TypeFamilies
14 , TypeOperators
17 , TypeOperators
18+ , UndecidableInstances
17module Squeel.PostgreSQL.Query where
21module Squeel.PostgreSQL.Query where
@@ -19,106 +23,94 @@ module Squeel.PostgreSQL.Query where
19import Data.Boolean
23import Data.Boolean
20import Data.ByteString (ByteString)
24import Data.ByteString (ByteString)
21import Data.Monoid
25import Data.Monoid
23import Data.String
26import Data.String
24import Data.Vinyl
27import Data.Vinyl
25import Data.Vinyl.Functor
28import Data.Vinyl.Functor
26import GHC.OverloadedLabels
30import GHC.OverloadedLabels
27import GHC.TypeLits
31import GHC.TypeLits
29import qualified Data.ByteString as ByteString
33import qualified Data.ByteString as ByteString
30−import qualified Data.ByteString.Char8 as Char8
34+
32−import Squeel.PostgreSQL.Type
35+import Squeel.PostgreSQL.Schema
34−class HasField label xs x where
35− fieldName :: Proxy label -> Proxy xs -> Proxy x -> ByteString
36−instance {-# OVERLAPPING #-} KnownSymbol label
37− => HasField label ('(label, x) ': xs) x where
38− fieldName _ _ _ = Char8.pack (symbolVal (Proxy @label))
39−instance {-# OVERLAPPABLE #-} HasField label xs y
40− => HasField label (x ': xs) y where
41− fieldName _ _ _ = fieldName (Proxy @label) (Proxy @xs) (Proxy @y)
43−data Alias label = Alias
44−data Aliased obj labeled where
45− As :: obj x -> Alias label -> Aliased obj '(label, x)
46−instance IsLabel label (Alias label) where fromLabel _ = Alias
47−instance IsLabel label (obj x)
48− => IsLabel label (Aliased obj '(label, x)) where
49− fromLabel p = fromLabel p `As` fromLabel p
51−aliasName :: forall label. KnownSymbol label => Alias label -> ByteString
52−aliasName _ = Char8.pack (symbolVal (Proxy @label))
54−alias :: Aliased obj '(label, x) -> Alias label
57−unalias :: Aliased obj '(label, x) -> obj x
58−unalias (obj `As` _) = obj
62− => (obj x -> ByteString)
63− -> Aliased obj '(label, x)
65−renderAliased render (obj `As` label) =
66− render obj <> " AS " <> Char8.pack (symbolVal label)
68−class UnzipAliased (xs :: [(Symbol,k)]) where
69− type Unaliased xs :: [k]
71− :: Rec (Aliased obj) xs
72− -> ([ByteString], Rec obj (Unaliased xs))
73−instance UnzipAliased '[] where
74− type Unaliased '[] = '[]
75− unzipAliased _ = ([], RNil)
76−instance (KnownSymbol label, UnzipAliased xs)
77− => UnzipAliased ( '(label, x) ': xs) where
78− type Unaliased ( '(label, x) ': xs) = x ': Unaliased xs
79− unzipAliased (x :& xs) =
81− (labels,values) = unzipAliased xs
83− (aliasName (alias x) : labels, unalias x :& values)
85−class AllAliased xs where
87− :: (forall x. obj x -> ByteString)
88− -> Rec (Aliased obj) xs
90−instance KnownSymbol label => AllAliased '[ '(label, x)] where
91− renderAllAliased render
92− (aliased :& RNil :: Rec (Aliased obj) '[ '(label, x)]) =
93− renderAliased render aliased
94−instance (KnownSymbol label, AllAliased (x' ': xs))
95− => AllAliased ('(label, x) ': (x' ': xs)) where
96− renderAllAliased render
97− (x :& xs :: Rec (Aliased obj) ('(label, x) ': x' : xs)) =
98− renderAliased render x <> ", " <> renderAllAliased render xs
100newtype Expression
37newtype Expression
101− (ps :: [PGType])
38+ (params :: [PGType])
102− (xs :: [(Symbol,PGType)])
39+ (tables :: [(Symbol,[(Symbol,PGType)])])
103− (y :: PGType) =
40+ (ty :: PGType)
104− UnsafeExpression { renderExpression :: ByteString }
41+ = UnsafeExpression { renderExpression :: ByteString }
43+class KnownNat n => HasParameter (n :: Nat) params ty | n params -> ty where
44+ param :: Proxy# n -> Expression params tables ty
45+ param p = UnsafeExpression $ ("$" <>) $ fromString $ show $ natVal' p
46+instance {-# OVERLAPPING #-} HasParameter 1 (ty1:tys) ty1
47+instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) params ty)
48+ => HasParameter n (ty' : params) ty
106−param1 :: Expression (p1:ps) cols p1
50+param1 :: Expression (ty1:tys) tables ty1
107−param1 = UnsafeExpression "$1"
51+param1 = param (proxy# :: Proxy# 1)
108−param2 :: Expression (p1:p2:ps) cols p2
52+param2 :: Expression (ty1:ty2:tys) tables ty2
109−param2 = UnsafeExpression "$2"
53+param2 = param (proxy# :: Proxy# 2)
110−param3 :: Expression (p1:p2:p3:ps) cols p3
54+param3 :: Expression (ty1:ty2:ty3:tys) tables ty3
111−param3 = UnsafeExpression "$3"
55+param3 = param (proxy# :: Proxy# 3)
112−param4 :: Expression (p1:p2:p3:p4:ps) cols p4
56+param4 :: Expression (ty1:ty2:ty3:ty4:tys) tables ty4
113−param4 = UnsafeExpression "$4"
57+param4 = param (proxy# :: Proxy# 4)
114−param5 :: Expression (p1:p2:p3:p4:p5:ps) cols p5
58+param5 :: Expression (ty1:ty2:ty3:ty4:ty5:tys) tables ty5
115−param5 = UnsafeExpression "$5"
59+param5 = param (proxy# :: Proxy# 5)
61+class KnownSymbol column => HasColumn column columns ty
62+ | column columns -> ty where
63+ getColumn :: Proxy# column -> Expression params '[table ::: columns] ty
64+ getColumn column = UnsafeExpression $ fromString $ symbolVal' column
65+instance {-# OVERLAPPING #-} KnownSymbol column
66+ => HasColumn column ((column ::: ty) ': tys) ty
67+instance {-# OVERLAPPABLE #-} (KnownSymbol column, HasColumn column table ty)
68+ => HasColumn column (ty' ': table) ty
70+instance HasColumn column columns ty
117−instance HasField label xs x => IsLabel label (Expression ps xs x) where
71+ => IsLabel column (Expression params '[table ::: columns] ty) where
72+ fromLabel = getColumn
75+ :: forall columns column params tables table ty
76+ . (HasTable table tables columns, HasColumn column columns ty)
77+ => Alias table -> Alias column -> Expression params tables ty
118− fromLabel _ = UnsafeExpression $
78+Alias table .&. Alias column = UnsafeExpression $
119− fieldName (Proxy @label) (Proxy @xs) (Proxy @x)
79+ fromString (symbolVal' table)
81+ fromString (symbolVal' column)
85+ -> Expression params tables ty0
86+ -> Expression params tables ty1
87+ -> Expression params tables ty2
88+unsafeBinaryOp op x y = UnsafeExpression $ mconcat
89+ ["(", renderExpression x, " ", op, " ", renderExpression y, ")"]
93+ -> Expression params tables ty0
94+ -> Expression params tables ty1
95+unsafeUnaryOp op x = UnsafeExpression $ mconcat
96+ ["(", op, " ", renderExpression x, ")"]
100+ -> Expression params tables xty
101+ -> Expression params tables yty
102+unsafeFunction fn x = UnsafeExpression $ mconcat
103+ [fn, "(", renderExpression x, ")"]
105+instance PGNum ty => Num (Expression params tables ty) where
106+ (+) = unsafeBinaryOp "+"
107+ (-) = unsafeBinaryOp "-"
108+ (*) = unsafeBinaryOp "*"
109+ abs = unsafeUnaryOp "@"
110+ signum = unsafeFunction "sign"
111+ fromInteger = UnsafeExpression . fromString . show
121−instance IsString (Expression ps xs 'PGText) where
113+instance IsString (Expression params tables 'PGText) where
122 fromString str = UnsafeExpression $
114 fromString str = UnsafeExpression $
123 "E\'" <> fromString (escape =<< str) <> "\'"
115 "E\'" <> fromString (escape =<< str) <> "\'"
@@ -133,169 +125,201 @@ instance IsString (Expression ps xs 'PGText) where
133 '\\' -> "\\\\"
125 '\\' -> "\\\\"
136−instance Num (Expression ps xs 'PGInt2) where
137− fromInteger n = UnsafeExpression . fromString $ show n
138− x + y = UnsafeExpression $ renderExpression x <> "+" <> renderExpression y
139− x * y = UnsafeExpression $ renderExpression x <> "*" <> renderExpression y
140− UnsafeExpression x - UnsafeExpression y = UnsafeExpression $ x <> "+" <> y
141− abs (UnsafeExpression x) = UnsafeExpression $ "@" <> x
142− signum (UnsafeExpression x) = UnsafeExpression $ "sign(" <> x <> ")"
144−instance Num (Expression ps xs 'PGInt4) where
145− fromInteger n = UnsafeExpression . fromString $ show n
146− x + y = UnsafeExpression $ renderExpression x <> "+" <> renderExpression y
147− x * y = UnsafeExpression $ renderExpression x <> "*" <> renderExpression y
148− UnsafeExpression x - UnsafeExpression y = UnsafeExpression $ x <> "+" <> y
149− abs (UnsafeExpression x) = UnsafeExpression $ "@" <> x
150− signum (UnsafeExpression x) = UnsafeExpression $ "sign(" <> x <> ")"
152−instance Num (Expression ps xs 'PGInt8) where
153− fromInteger n = UnsafeExpression . fromString $ show n
154− x + y = UnsafeExpression $ renderExpression x <> "+" <> renderExpression y
155− x * y = UnsafeExpression $ renderExpression x <> "*" <> renderExpression y
156− UnsafeExpression x - UnsafeExpression y = UnsafeExpression $ x <> "+" <> y
157− abs (UnsafeExpression x) = UnsafeExpression $ "@" <> x
158− signum (UnsafeExpression x) = UnsafeExpression $ "sign(" <> x <> ")"
160−instance Boolean (Expression ps xs 'PGBool) where
128+instance Boolean (Expression params tables 'PGBool) where
161 true = UnsafeExpression "TRUE"
129 true = UnsafeExpression "TRUE"
162 false = UnsafeExpression "FALSE"
130 false = UnsafeExpression "FALSE"
163− notB x = UnsafeExpression $ "NOT " <> renderExpression x
131+ notB = unsafeUnaryOp "NOT"
164− x &&* y = UnsafeExpression $
132+ (&&*) = unsafeBinaryOp "AND"
165− renderExpression x <> " AND " <> renderExpression y
133+ (||*) = unsafeBinaryOp "OR"
166− x ||* y = UnsafeExpression $
167− renderExpression x <> " OR " <> renderExpression y
169−type instance BooleanOf (Expression ps xs y) = Expression ps xs 'PGBool
135+type instance BooleanOf (Expression params tables ty) =
136+ Expression params tables 'PGBool
171−instance IfB (Expression ps xs y) where
138+instance IfB (Expression params tables ty) where
172− ifB predicate then_ else_ = UnsafeExpression $ mconcat
139+ ifB if_ then_ else_ = UnsafeExpression $ mconcat
173− [ "CASE WHEN ",renderExpression predicate
140+ [ "CASE WHEN ",renderExpression if_
174 , " THEN ",renderExpression then_
141 , " THEN ",renderExpression then_
175 , " ELSE ",renderExpression else_
142 , " ELSE ",renderExpression else_
179−instance EqB (Expression ps xs y) where
146+instance EqB (Expression params tables ty) where
180− x ==* y = UnsafeExpression $ renderExpression x <> "=" <> renderExpression y
147+ (==*) = unsafeBinaryOp "="
181− x /=* y = UnsafeExpression $ renderExpression x <> "<>" <> renderExpression y
148+ (/=*) = unsafeBinaryOp "<>"
183−instance OrdB (Expression ps xs y) where
150+instance OrdB (Expression params tables ty) where
184− x >* y = UnsafeExpression $ renderExpression x <> ">" <> renderExpression y
151+ (>*) = unsafeBinaryOp ">"
185− x >=* y = UnsafeExpression $ renderExpression x <> ">=" <> renderExpression y
152+ (>=*) = unsafeBinaryOp ">="
186− x <* y = UnsafeExpression $ renderExpression x <> "<" <> renderExpression y
153+ (<*) = unsafeBinaryOp "<"
154+ (<=*) = unsafeBinaryOp "<="
157+ (params :: [PGType])
158+ (schema :: [(Symbol,[(Symbol,PGType)])])
159+ (tables :: [(Symbol,[(Symbol,PGType)])])
160+ = UnsafeTableRef { renderTableRef :: ByteString }
162+class KnownSymbol table => HasTable table tables columns
163+ | table tables -> columns where
164+ getTable :: Proxy# table -> TableRef params tables '[table ::: columns]
165+ getTable table = UnsafeTableRef $ fromString $ symbolVal' table
166+instance {-# OVERLAPPING #-} KnownSymbol table
167+ => HasTable table ((table ::: columns) ': tables) columns
168+instance {-# OVERLAPPABLE #-}
169+ (KnownSymbol table, HasTable table schema columns)
170+ => HasTable table (table' ': schema) columns
173+ :: TableRef params schema '[table]
174+ -> TableRef params schema tables
175+ -> TableRef params schema (table ': tables)
176+crossJoin table tables = UnsafeTableRef $
177+ "(" <> renderTableRef tables <> " CROSS JOIN " <> renderTableRef table <> ")"
180+ :: TableRef params tables '[left]
181+ -> Expression params '[left,right] 'PGBool
182+ -> TableRef params tables (right ': rest)
183+ -> TableRef params tables (left ': right ': rest)
184+innerJoin table on tables = UnsafeTableRef $
185+ renderTableRef tables
186+ <> " INNER JOIN " <>
187+ renderTableRef table
187− x <=* y = UnsafeExpression $ renderExpression x <> "<=" <> renderExpression y
189+ renderExpression on
189newtype Projection
191newtype Projection
190− (ps :: [PGType])
192+ (params :: [PGType])
191− (xs :: [(Symbol,PGType)])
193+ (tables :: [(Symbol,[(Symbol,PGType)])])
192− (ys :: [(Symbol,PGType)]) =
194+ (columns :: [(Symbol,PGType)]) =
193 UnsafeProjection { renderProjection :: ByteString }
195 UnsafeProjection { renderProjection :: ByteString }
195−star :: Projection ps xs xs
197+star :: Projection params '[table ::: columns] columns
196star = UnsafeProjection "*"
198star = UnsafeProjection "*"
201+ :: HasTable table tables columns
202+ => Alias table -> Projection params tables columns
203+tableStar (Alias table) = UnsafeProjection $
204+ fromString (symbolVal' table) <> ".*"
200− => Rec (Aliased (Expression ps xs)) ys
207+ :: Rec (Aliased (Expression params tables)) columns
201− -> Projection ps xs ys
208+ -> Projection params tables columns
202−project = UnsafeProjection . renderAllAliased renderExpression
210+ = UnsafeProjection
203−
211+ . ByteString.intercalate ", "
204−data Relation ps xss xs = UnsafeRelation
212+ . recordToList
205− { relation :: ByteString
213+ . rmap (Const . renderAliased renderExpression)
216+ (params :: [PGType])
217+ (schema :: [(Symbol,[(Symbol,PGType)])])
218+ (tables :: [(Symbol,[(Symbol,PGType)])])
220+ { fromClause :: TableRef params schema tables
206− , restriction :: Maybe (Expression ps xs 'PGBool)
221+ , whereClause :: Maybe (Expression params tables 'PGBool)
207− , limitation :: Maybe (Expression ps '[] 'PGInt8)
222+ , limitClause :: Maybe (Expression params '[] 'PGInt8)
208− , offsetting :: Maybe (Expression ps '[] 'PGInt8)
223+ , offsetClause :: Maybe (Expression params '[] 'PGInt8)
211−renderRelation :: Relation ps xss xs -> ByteString
226+tabulation :: TableRef params schema tables -> Tabulation params schema tables
212−renderRelation (UnsafeRelation rel wh lim off) = rel
227+tabulation tab = Tabulation tab Nothing Nothing Nothing
229+renderTabulation :: Tabulation params schema tables -> ByteString
230+renderTabulation (Tabulation fr wh lim off)= mconcat
213− <> maybe "" ((" WHERE " <>) . renderExpression) wh
232+ , maybe "" ((" WHERE " <>) . renderExpression) wh
214− <> maybe "" ((" LIMIT " <>) . renderExpression) lim
233+ , maybe "" ((" LIMIT " <>) . renderExpression) lim
215− <> maybe "" ((" OFFSET " <>) . renderExpression) off
234+ , maybe "" ((" OFFSET " <>) . renderExpression) off
237+instance HasTable table schema columns
238+ => IsLabel table (TableRef params schema '[table ::: columns]) where
239+ fromLabel = getTable
241+instance HasTable table schema columns
242+ => IsLabel table (Tabulation params schema '[table ::: columns]) where
243+ fromLabel p = Tabulation (getTable p) Nothing Nothing Nothing
217−-- | where_ is a morphism of monoids `PGBool -> End Relation` under (&&*), true
219− :: Expression ps xs 'PGBool
246+ :: Expression params tables 'PGBool
220− -> Relation ps xss xs
247+ -> Tabulation params schema tables
221− -> Relation ps xss xs
248+ -> Tabulation params schema tables
222−where_ condition ys = ys
249+where_ condition tables = tables
223− { restriction = case restriction ys of
250+ { whereClause = case whereClause tables of
224 Nothing -> Just condition
251 Nothing -> Just condition
225 Just conditions -> Just (conditions &&* condition)
252 Just conditions -> Just (conditions &&* condition)
228−-- | limit is a morphism of semigroups `PGInt8 -> End Relation` under minB
230− :: Expression ps '[] 'PGInt8
256+ :: Expression params '[] 'PGInt8
231− -> Relation ps xss xs
257+ -> Tabulation params schema tables
232− -> Relation ps xss xs
258+ -> Tabulation params schema tables
233−limit n ys = ys
259+limit n tables = tables
234− { limitation = case limitation ys of
260+ { limitClause = case limitClause tables of
235 Nothing -> Just n
261 Nothing -> Just n
236 Just n' -> Just (n' `minB` n)
262 Just n' -> Just (n' `minB` n)
239−-- | offset is a morphism of monoids `PGInt8 -> End Relation` under (+), 0
241− :: Expression ps '[] 'PGInt8
266+ :: Expression params '[] 'PGInt8
242− -> Relation ps xss xs
267+ -> Tabulation params schema tables
243− -> Relation ps xss xs
268+ -> Tabulation params schema tables
244−offset n ys = ys
269+offset n tables = tables
245− { offsetting = case offsetting ys of
270+ { offsetClause = case offsetClause tables of
246 Nothing -> Just n
271 Nothing -> Just n
247 Just n' -> Just (n' + n)
272 Just n' -> Just (n' + n)
250−instance HasField label xss xs
251− => IsLabel label (Relation ps xss xs) where
252− fromLabel _ = UnsafeRelation
253− { relation = fieldName (Proxy @label) (Proxy @xss) (Proxy @xs)
254− , restriction = Nothing
255− , limitation = Nothing
256− , offsetting = Nothing
259−newtype Selection ps xss ys = UnsafeSelection
275+newtype Selection params schema columns = UnsafeSelection
260 { renderSelection :: ByteString }
276 { renderSelection :: ByteString }
262−from :: Projection ps xs ys -> Relation ps xss xs -> Selection ps xss ys
278+from
279+ :: Projection params tables columns
280+ -> Tabulation params schema tables
281+ -> Selection params schema columns
263−ys `from` xs = UnsafeSelection $
282+from projection tables = UnsafeSelection $
264− renderProjection ys <> " FROM " <> renderRelation xs
283+ renderProjection projection <> " FROM " <> renderTabulation tables
266−subselect :: Selection ps xss ys -> Relation ps xss ys
285+newtype Query params schema0 schema1 columns = UnsafeQuery
286+ { renderQuery :: ByteString }
288+newtype PreparedQuery params schema0 schema1 columns = UnsafePreparedQuery
289+ { renderPreparedQuery :: ByteString }
292+ :: Selection params schema columns
293+ -> Query params schema schema columns
294+select = UnsafeQuery . ("SELECT " <>) . (<> ";") . renderSelection
297+ :: Aliased (Selection params schema) table
298+ -> Tabulation params schema '[table]
267−subselect selection = UnsafeRelation
299+subselect selection = Tabulation
300+ { fromClause = UnsafeTableRef . ("SELECT " <>) $
268− { relation = "SELECT " <> renderSelection selection
301+ renderAliased renderSelection selection
269− , restriction = Nothing
302+ , whereClause = Nothing
270− , limitation = Nothing
303+ , limitClause = Nothing
271− , offsetting = Nothing
304+ , offsetClause = Nothing
276− (xss :: [(Symbol,[(Symbol,PGType)])])
277− (yss :: [(Symbol,[(Symbol,PGType)])])
278− (zs :: [(Symbol,PGType)]) =
279− UnsafeQuery { renderQuery :: ByteString }
280−newtype PreparedQuery ps xss yss zs =
281− UnsafePreparedQuery { renderPreparedQuery :: ByteString }
283−select :: Selection ps xss ys -> Query ps xss xss ys
284−select selection = UnsafeQuery $
285− "SELECT " <> renderSelection selection <> ";"
287insertInto
307insertInto
288− :: forall xss xs label ps
308+ :: forall schema columns table params
289− . (HasField label xss xs, UnzipAliased xs)
309+ . HasTable table schema columns
290− => Proxy label
310+ => Alias table
291− -> Rec (Aliased (Expression ps '[])) xs
311+ -> Rec (Aliased (Expression params '[])) columns
292− -> Query ps xss xss '[]
312+ -> Query params schema schema '[]
293−insertInto table values = UnsafeQuery $ "INSERT INTO "
313+insertInto (Alias table) expressions = UnsafeQuery $ "INSERT INTO "
294− <> fieldName table (Proxy @xss) (Proxy @xs)
314+ <> fromString (symbolVal' table)
295− <> " (" <> ByteString.intercalate ", " names
315+ <> " (" <> ByteString.intercalate ", " aliases
296 <> ") VALUES ("
316 <> ") VALUES ("
297− <> ByteString.intercalate ", "
317+ <> ByteString.intercalate ", " values
298− (recordToList (rmap (Const . renderExpression) exprs))
301− (names,exprs) = unzipAliased values
320+ aliases = recordToList $ rmap
321+ (\ (_ `As` Alias name) -> Const (fromString (symbolVal' name)))
323+ values = recordToList $ rmap
324+ (\ (expression `As` _) -> Const (renderExpression expression))