{-# LANGUAGE CPP, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, DataKinds, DefaultSignatures, TemplateHaskell, TypeFamilies #-} #if __GLASGOW_HASKELL__ >= 800 {-# LANGUAGE UndecidableSuperClasses #-} #endif -- | -- Module: Database.PostgreSQL.Typed.Dynamic -- Copyright: 2015 Dylan Simon -- -- Automatic (dynamic) marshalling of PostgreSQL values based on Haskell types (not SQL statements). -- This is intended for direct construction of queries and query data, bypassing the normal SQL type inference. module Database.PostgreSQL.Typed.Dynamic ( PGRep(..) , pgTypeOf , pgTypeOfProxy , pgEncodeRep , pgDecodeRep , pgLiteralRep , pgLiteralString , pgSafeLiteral , pgSafeLiteralString , pgSubstituteLiterals ) where #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif #ifdef VERSION_aeson import qualified Data.Aeson as JSON #endif import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BSC import Data.ByteString.Internal (w2c) import qualified Data.ByteString.Lazy as BSL import Data.Int import Data.Monoid ((<>)) import Data.Proxy (Proxy) #ifdef VERSION_scientific import Data.Scientific (Scientific) #endif import Data.String (fromString) #ifdef VERSION_text import qualified Data.Text as T #endif import qualified Data.Time as Time #ifdef VERSION_uuid import qualified Data.UUID as UUID #endif import GHC.TypeLits (Symbol) import Language.Haskell.Meta.Parse (parseExp) import qualified Language.Haskell.TH as TH import Database.PostgreSQL.Typed.Types import Database.PostgreSQL.Typed.SQLToken -- |Represents canonical/default PostgreSQL representation for various Haskell types, allowing convenient type-driven marshalling. class (PGParameter (PGRepType a) a, PGColumn (PGRepType a) a) => PGRep a where -- |The PostgreSOL type that this type should be converted to. type PGRepType a :: Symbol pgTypeOf :: a -> PGTypeID (PGRepType a) pgTypeOf _ = PGTypeProxy pgTypeOfProxy :: Proxy a -> PGTypeID (PGRepType a) pgTypeOfProxy _ = PGTypeProxy -- |Encode a value using 'pgEncodeValue'. pgEncodeRep :: PGRep a => a -> PGValue pgEncodeRep x = pgEncodeValue unknownPGTypeEnv (pgTypeOf x) x -- |Produce a literal value for interpolation in a SQL statement using 'pgLiteral'. Using 'pgSafeLiteral' is usually safer as it includes type cast. pgLiteralRep :: PGRep a => a -> BS.ByteString pgLiteralRep x = pgLiteral (pgTypeOf x) x -- |Decode a value using 'pgDecodeValue'. pgDecodeRep :: forall a . PGRep a => PGValue -> a pgDecodeRep = pgDecodeValue unknownPGTypeEnv (PGTypeProxy :: PGTypeID (PGRepType a)) -- |Produce a raw SQL literal from a value. Using 'pgSafeLiteral' is usually safer when interpolating in a SQL statement. pgLiteralString :: PGRep a => a -> String pgLiteralString = BSC.unpack . pgLiteralRep -- |Produce a safely type-cast literal value for interpolation in a SQL statement, e.g., "'123'::integer". pgSafeLiteral :: PGRep a => a -> BS.ByteString pgSafeLiteral x = pgLiteralRep x <> BSC.pack "::" <> pgNameBS (pgTypeName (pgTypeOf x)) -- |Identical to @'BSC.unpack' . 'pgSafeLiteral'@ but more efficient. pgSafeLiteralString :: PGRep a => a -> String pgSafeLiteralString x = pgLiteralString x ++ "::" ++ map w2c (pgNameBytes (pgTypeName (pgTypeOf x))) instance PGRep a => PGRep (Maybe a) where type PGRepType (Maybe a) = PGRepType a instance PGRep () where type PGRepType () = "void" instance PGRep Bool where type PGRepType Bool = "boolean" instance PGRep OID where type PGRepType OID = "oid" instance PGRep Int16 where type PGRepType Int16 = "smallint" instance PGRep Int32 where type PGRepType Int32 = "integer" instance PGRep Int64 where type PGRepType Int64 = "bigint" instance PGRep Float where type PGRepType Float = "real" instance PGRep Double where type PGRepType Double = "double precision" instance PGRep Char where type PGRepType Char = "\"char\"" instance PGRep String where type PGRepType String = "text" instance PGRep BS.ByteString where type PGRepType BS.ByteString = "text" instance PGRep PGName where type PGRepType PGName = "text" -- superset of "name" #ifdef VERSION_text instance PGRep T.Text where type PGRepType T.Text = "text" #endif instance PGRep Time.Day where type PGRepType Time.Day = "date" instance PGRep Time.TimeOfDay where type PGRepType Time.TimeOfDay = "time without time zone" instance PGRep (Time.TimeOfDay, Time.TimeZone) where type PGRepType (Time.TimeOfDay, Time.TimeZone) = "time with time zone" instance PGRep Time.LocalTime where type PGRepType Time.LocalTime = "timestamp without time zone" instance PGRep Time.UTCTime where type PGRepType Time.UTCTime = "timestamp with time zone" instance PGRep Time.DiffTime where type PGRepType Time.DiffTime = "interval" instance PGRep Rational where type PGRepType Rational = "numeric" #ifdef VERSION_scientific instance PGRep Scientific where type PGRepType Scientific = "numeric" #endif #ifdef VERSION_uuid instance PGRep UUID.UUID where type PGRepType UUID.UUID = "uuid" #endif #ifdef VERSION_aeson instance PGRep JSON.Value where type PGRepType JSON.Value = "jsonb" #endif -- |Create an expression that literally substitutes each instance of @${expr}@ for the result of @pgSafeLiteral expr@, producing a lazy 'BSL.ByteString'. -- This lets you do safe, type-driven literal substitution into SQL fragments without needing a full query, bypassing placeholder inference and any prepared queries, for example when using 'Database.PostgreSQL.Typed.Protocol.pgSimpleQuery' or 'Database.PostgreSQL.Typed.Protocol.pgSimpleQueries_'. -- Unlike most other TH functions, this does not require any database connection. pgSubstituteLiterals :: String -> TH.ExpQ pgSubstituteLiterals sql = TH.AppE (TH.VarE 'BSL.fromChunks) . TH.ListE <$> mapM sst (sqlTokens sql) where sst (SQLExpr e) = do v <- either (fail . (++) ("Failed to parse expression {" ++ e ++ "}: ")) return $ parseExp e return $ TH.VarE 'pgSafeLiteral `TH.AppE` v sst t = return $ TH.VarE 'fromString `TH.AppE` TH.LitE (TH.StringL $ show t)