{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeSynonymInstances #-} module Database.PostgreSQL.Stream.QueryBuilder ( -- ** Quasiquoter sql, -- ** Query formatting fmtQuery, fmtSQL, -- ** Typeclasses ToSQL(..), ToField(..), ) where import Database.PostgreSQL.Stream.Types import Data.Int import Data.Monoid import Data.UUID as UUID import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) import Language.Haskell.TH import Language.Haskell.TH.Quote import Data.ByteString import Data.ByteString.Search import Data.ByteString.Lazy (toStrict) import qualified Data.ByteString.Char8 as B8 ------------------------------------------------------------------------------- -- Arguments ------------------------------------------------------------------------------- class ToField a where toField :: a -> Action instance ToField Int where toField = Plain . B8.pack . show instance ToField Int32 where toField = Plain . B8.pack . show instance ToField Float where toField = Plain . B8.pack . show instance ToField Double where toField = Plain . B8.pack . show instance ToField ByteString where toField = Plain instance ToField Integer where toField = Plain . B8.pack . show instance ToField Char where toField = Plain . inQuotes . B8.pack . show instance ToField String where toField = Plain . inQuotes . B8.pack instance ToField Text where toField = Plain . inQuotes . encodeUtf8 -- SQL Identifier instance ToField Identifier where toField = Plain . unIdentifier -- SQL Expression instance ToField SQL where toField = Plain . unSQL -- Subquery (without substuttion, discards parameters) instance ToField Query where toField (Query a) = Plain a instance ToField UUID where toField = Plain . inQuotes . UUID.toASCIIBytes instance ToField Null where toField _ = Plain "null" instance (ToField a) => ToField (Only a) where toField (Only a) = toField a instance (ToField a) => ToField (Maybe a) where toField Nothing = toField Null toField (Just a) = toField a instance ToField Bool where toField True = Plain "true" toField False = Plain "false" instance ToField Action where toField = id inQuotes :: ByteString -> ByteString inQuotes x = "\'" <> x <> "\'" ------------------------------------------------------------------------------- -- ToSQL ------------------------------------------------------------------------------- class ToSQL a where toSQL :: a -> (ByteString -> ByteString) instance ToSQL () where toSQL _ = runFormatter [] instance (ToField a) => ToSQL (Only a) where toSQL (Only a) = runFormatter [toField a] instance (ToField a) => ToSQL [a] where toSQL a = runFormatter (fmap toField a) instance (ToField a) => ToSQL (Maybe a) where toSQL Nothing = runFormatter [] toSQL (Just a) = runFormatter [toField a] instance (ToField a, ToField b) => ToSQL (a,b) where toSQL (a,b) = runFormatter [toField a, toField b] instance (ToField a, ToField b, ToField c) => ToSQL (a,b,c) where toSQL (a,b,c) = runFormatter [toField a, toField b, toField c] instance (ToField a, ToField b, ToField c, ToField d) => ToSQL (a,b,c,d) where toSQL (a,b,c,d) = runFormatter [toField a, toField b, toField c, toField d] instance (ToField a, ToField b, ToField c, ToField d, ToField e) => ToSQL (a,b,c,d,e) where toSQL (a,b,c,d,e) = runFormatter [toField a, toField b, toField c, toField d, toField e] instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f) => ToSQL (a,b,c,d,e,f) where toSQL (a,b,c,d,e,f) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f] instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g) => ToSQL (a,b,c,d,e,f,g) where toSQL (a,b,c,d,e,f,g) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f, toField g] instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g, ToField h) => ToSQL (a,b,c,d,e,f,g,h) where toSQL (a,b,c,d,e,f,g,h) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f, toField g, toField h] instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, ToField g, ToField h, ToField i) => ToSQL (a,b,c,d,e,f,g,h,i) where toSQL (a,b,c,d,e,f,g,h,i) = runFormatter [toField a, toField b, toField c, toField d, toField e, toField f, toField g, toField h, toField i] ------------------------------------------------------------------------------- -- Formatter ------------------------------------------------------------------------------- render :: Action -> ByteString render (Plain x) = x render (Escape x) = error "Not implemented" render (EscapeIdentifier x) = error "Not implemented" sql :: QuasiQuoter sql = QuasiQuoter { quotePat = error "Patterns are not supported" , quoteType = error "Types are not supported" , quoteExp = sqlExp , quoteDec = error "Declarations are not supported" } sqlExp :: String -> Q Exp sqlExp = stringE -- Run the substitutions over a bytestring runFormatter :: [Action] -> ByteString -> ByteString runFormatter args input = loop args 1 input where loop (x:xs) i s = loop xs (i+1) $ toStrict (replace ("{" <> ix i <> "}") (render x) s) loop [] _ s = s ix :: Int -> ByteString ix = B8.pack . show ------------------------------------------------------------------------------- -- query ------------------------------------------------------------------------------- fmtQuery :: ToSQL a => Query -> a -> ByteString fmtQuery q args = toSQL args (fromQuery q) fmtSQL :: ToSQL a => Query -> a -> SQL fmtSQL q args = SQLExpr $ toSQL args (fromQuery q)