postgresql-simple-0.7.0.1: src/Database/PostgreSQL/Simple/Types.hs
{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor, GeneralizedNewtypeDeriving #-}
------------------------------------------------------------------------------
-- |
-- Module: Database.PostgreSQL.Simple.Types
-- Copyright: (c) 2011 MailRank, Inc.
-- (c) 2011-2012 Leon P Smith
-- License: BSD3
-- Maintainer: Leon P Smith <leon@melding-monads.com>
-- Stability: experimental
--
-- Basic types.
--
------------------------------------------------------------------------------
module Database.PostgreSQL.Simple.Types
(
Null(..)
, Default(..)
, Only(..)
, In(..)
, Binary(..)
, Identifier(..)
, QualifiedIdentifier(..)
, Query(..)
, Oid(..)
, (:.)(..)
, Savepoint(..)
, PGArray(..)
, Values(..)
) where
import Control.Arrow (first)
import Data.ByteString (ByteString)
import Data.Hashable (Hashable(hashWithSalt))
import Data.Foldable (toList)
import Data.Monoid (Monoid(..))
import Data.Semigroup
import Data.String (IsString(..))
import Data.Typeable (Typeable)
import Data.ByteString.Builder ( stringUtf8 )
import qualified Data.ByteString as B
import Data.Text (Text)
import qualified Data.Text as T
import Data.Tuple.Only (Only(..))
import Database.PostgreSQL.LibPQ (Oid(..))
import Database.PostgreSQL.Simple.Compat (toByteString)
-- | A placeholder for the SQL @NULL@ value.
data Null = Null
deriving (Read, Show, Typeable)
-- | A placeholder for the PostgreSQL @DEFAULT@ value.
data Default = Default
deriving (Read, Show, Typeable)
-- | A query string. This type is intended to make it difficult to
-- construct a SQL query by concatenating string fragments, as that is
-- an extremely common way to accidentally introduce SQL injection
-- vulnerabilities into an application.
--
-- This type is an instance of 'IsString', so the easiest way to
-- construct a query is to enable the @OverloadedStrings@ language
-- extension and then simply write the query in double quotes.
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import Database.PostgreSQL.Simple
-- >
-- > q :: Query
-- > q = "select ?"
--
-- The underlying type is a 'ByteString', and literal Haskell strings
-- that contain Unicode characters will be correctly transformed to
-- UTF-8.
newtype Query = Query {
fromQuery :: ByteString
} deriving (Eq, Ord, Typeable)
instance Show Query where
show = show . fromQuery
instance Read Query where
readsPrec i = fmap (first Query) . readsPrec i
instance IsString Query where
fromString = Query . toByteString . stringUtf8
instance Semigroup Query where
Query a <> Query b = Query (B.append a b)
{-# INLINE (<>) #-}
sconcat xs = Query (B.concat $ map fromQuery $ toList xs)
instance Monoid Query where
mempty = Query B.empty
-- | Wrap a list of values for use in an @IN@ clause. Replaces a
-- single \"@?@\" character with a parenthesized list of rendered
-- values.
--
-- Example:
--
-- > query c "select * from whatever where id in ?" (Only (In [3,4,5]))
--
-- Note that @In []@ expands to @(null)@, which works as expected in
-- the query above, but evaluates to the logical null value on every
-- row instead of @TRUE@. This means that changing the query above
-- to @... id NOT in ?@ and supplying the empty list as the parameter
-- returns zero rows, instead of all of them as one would expect.
--
-- Since postgresql doesn't seem to provide a syntax for actually specifying
-- an empty list, which could solve this completely, there are two
-- workarounds particularly worth mentioning, namely:
--
-- 1. Use postgresql-simple's 'Values' type instead, which can handle the
-- empty case correctly. Note however that while specifying the
-- postgresql type @"int4"@ is mandatory in the empty case, specifying
-- the haskell type @Values (Only Int)@ would not normally be needed in
-- realistic use cases.
--
-- > query c "select * from whatever where id not in ?"
-- > (Only (Values ["int4"] [] :: Values (Only Int)))
--
--
-- 2. Use sql's @COALESCE@ operator to turn a logical @null@ into the correct
-- boolean. Note however that the correct boolean depends on the use
-- case:
--
-- > query c "select * from whatever where coalesce(id NOT in ?, TRUE)"
-- > (Only (In [] :: In [Int]))
--
-- > query c "select * from whatever where coalesce(id IN ?, FALSE)"
-- > (Only (In [] :: In [Int]))
--
-- Note that at as of PostgreSQL 9.4, the query planner cannot see inside
-- the @COALESCE@ operator, so if you have an index on @id@ then you
-- probably don't want to write the last example with @COALESCE@, which
-- would result in a table scan. There are further caveats if @id@ can
-- be null or you want null treated sensibly as a component of @IN@ or
-- @NOT IN@.
newtype In a = In a
deriving (Eq, Ord, Read, Show, Typeable, Functor)
-- | Wrap binary data for use as a @bytea@ value.
newtype Binary a = Binary {fromBinary :: a}
deriving (Eq, Ord, Read, Show, Typeable, Functor)
-- | Wrap text for use as sql identifier, i.e. a table or column name.
newtype Identifier = Identifier {fromIdentifier :: Text}
deriving (Eq, Ord, Read, Show, Typeable, IsString)
instance Hashable Identifier where
hashWithSalt i (Identifier t) = hashWithSalt i t
-- | Wrap text for use as (maybe) qualified identifier, i.e. a table
-- with schema, or column with table.
data QualifiedIdentifier = QualifiedIdentifier (Maybe Text) Text
deriving (Eq, Ord, Read, Show, Typeable)
instance Hashable QualifiedIdentifier where
hashWithSalt i (QualifiedIdentifier q t) = hashWithSalt i (q, t)
-- | @\"foo.bar\"@ will get turned into
-- @QualifiedIdentifier (Just \"foo\") \"bar\"@, while @\"foo\"@ will get
-- turned into @QualifiedIdentifier Nothing \"foo\"@. Note this instance
-- is for convenience, and does not match postgres syntax. It
-- only examines the first period character, and thus cannot be used if the
-- qualifying identifier contains a period for example.
instance IsString QualifiedIdentifier where
fromString str = let (x,y) = T.break (== '.') (fromString str)
in if T.null y
then QualifiedIdentifier Nothing x
else QualifiedIdentifier (Just x) (T.tail y)
-- | Wrap a list for use as a PostgreSQL array.
newtype PGArray a = PGArray {fromPGArray :: [a]}
deriving (Eq, Ord, Read, Show, Typeable, Functor)
-- | A composite type to parse your custom data structures without
-- having to define dummy newtype wrappers every time.
--
--
-- > instance FromRow MyData where ...
--
-- > instance FromRow MyData2 where ...
--
--
-- then I can do the following for free:
--
-- @
-- res <- query' c "..."
-- forM res $ \\(MyData{..} :. MyData2{..}) -> do
-- ....
-- @
data h :. t = h :. t deriving (Eq,Ord,Show,Read,Typeable)
infixr 3 :.
newtype Savepoint = Savepoint Query
deriving (Eq, Ord, Show, Read, Typeable)
-- | Represents a @VALUES@ table literal, usable as an alternative to
-- 'Database.PostgreSQL.Simple.executeMany' and
-- 'Database.PostgreSQL.Simple.returning'. The main advantage is that
-- you can parametrize more than just a single @VALUES@ expression.
-- For example, here's a query to insert a thing into one table
-- and some attributes of that thing into another, returning the
-- new id generated by the database:
--
--
-- > query c [sql|
-- > WITH new_thing AS (
-- > INSERT INTO thing (name) VALUES (?) RETURNING id
-- > ), new_attributes AS (
-- > INSERT INTO thing_attributes
-- > SELECT new_thing.id, attrs.*
-- > FROM new_thing JOIN ? attrs ON TRUE
-- > ) SELECT * FROM new_thing
-- > |] ("foo", Values [ "int4", "text" ]
-- > [ ( 1 , "hello" )
-- > , ( 2 , "world" ) ])
--
-- (Note this example uses writable common table expressions,
-- which were added in PostgreSQL 9.1)
--
-- The second parameter gets expanded into the following SQL syntax:
--
-- > (VALUES (1::"int4",'hello'::"text"),(2,'world'))
--
-- When the list of attributes is empty, the second parameter expands to:
--
-- > (VALUES (null::"int4",null::"text") LIMIT 0)
--
-- By contrast, @executeMany@ and @returning@ don't issue the query
-- in the empty case, and simply return @0@ and @[]@ respectively.
-- This behavior is usually correct given their intended use cases,
-- but would certainly be wrong in the example above.
--
-- The first argument is a list of postgresql type names. Because this
-- is turned into a properly quoted identifier, the type name is case
-- sensitive and must be as it appears in the @pg_type@ table. Thus,
-- you must write @timestamptz@ instead of @timestamp with time zone@,
-- @int4@ instead of @integer@ or @serial@, @_int8@ instead of @bigint[]@,
-- etcetera.
--
-- You may omit the type names, however, if you do so the list
-- of values must be non-empty, and postgresql must be able to infer
-- the types of the columns from the surrounding context. If the first
-- condition is not met, postgresql-simple will throw an exception
-- without issuing the query. In the second case, the postgres server
-- will return an error which will be turned into a @SqlError@ exception.
--
-- See <https://www.postgresql.org/docs/9.5/static/sql-values.html> for
-- more information.
data Values a = Values [QualifiedIdentifier] [a]
deriving (Eq, Ord, Show, Read, Typeable)