squeal-postgresql-0.3.0.0: src/Squeal/PostgreSQL/Expression.hs
{-|
Module: Squeal.PostgreSQL.Query
Description: Squeal expressions
Copyright: (c) Eitan Chatav, 2017
Maintainer: eitan@morphism.tech
Stability: experimental
Squeal expressions are the atoms used to build statements.
-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
{-# LANGUAGE
AllowAmbiguousTypes
, DeriveGeneric
, FlexibleContexts
, FlexibleInstances
, FunctionalDependencies
, GeneralizedNewtypeDeriving
, LambdaCase
, MagicHash
, OverloadedStrings
, ScopedTypeVariables
, TypeApplications
, TypeFamilies
, TypeInType
, TypeOperators
, UndecidableInstances
#-}
module Squeal.PostgreSQL.Expression
( -- * Expression
Expression (UnsafeExpression, renderExpression)
, HasParameter (parameter)
, param
-- ** Null
, null_
, notNull
, coalesce
, fromNull
, isNull
, isNotNull
, matchNull
, nullIf
-- ** Collections
, array
, row
-- ** Functions
, unsafeBinaryOp
, unsafeUnaryOp
, unsafeFunction
, atan2_
, cast
, quot_
, rem_
, trunc
, round_
, ceiling_
, greatest
, least
-- ** Conditions
, Condition
, true
, false
, not_
, (.&&)
, (.||)
, caseWhenThenElse
, ifThenElse
, (.==)
, (./=)
, (.>=)
, (.<)
, (.<=)
, (.>)
-- ** Time
, currentDate
, currentTime
, currentTimestamp
, localTime
, localTimestamp
-- ** Text
, lower
, upper
, charLength
, like
-- ** Aggregation
, unsafeAggregate, unsafeAggregateDistinct
, sum_, sumDistinct
, PGAvg (avg, avgDistinct)
, bitAnd, bitOr, boolAnd, boolOr
, bitAndDistinct, bitOrDistinct, boolAndDistinct, boolOrDistinct
, countStar
, count, countDistinct
, every, everyDistinct
, max_, maxDistinct, min_, minDistinct
-- * Types
, TypeExpression (UnsafeTypeExpression, renderTypeExpression)
, PGTyped (pgtype)
, bool
, int2
, smallint
, int4
, int
, integer
, int8
, bigint
, numeric
, float4
, real
, float8
, doublePrecision
, text
, char
, character
, varchar
, characterVarying
, bytea
, timestamp
, timestampWithTimeZone
, date
, time
, timeWithTimeZone
, interval
, uuid
, inet
, json
, jsonb
, vararray
, fixarray
-- * Re-export
, (&)
, NP ((:*), Nil)
) where
import Control.Category
import Control.DeepSeq
import Data.ByteString (ByteString)
import Data.Function ((&))
import Data.Semigroup
import Data.Ratio
import Data.String
import Generics.SOP hiding (from)
import GHC.OverloadedLabels
import GHC.TypeLits
import Prelude hiding (id, (.))
import qualified GHC.Generics as GHC
import Squeal.PostgreSQL.Render
import Squeal.PostgreSQL.Schema
{-----------------------------------------
column expressions
-----------------------------------------}
{- | `Expression`s are used in a variety of contexts,
such as in the target list of the `Squeal.PostgreSQL.Query.select` command,
as new column values in `Squeal.PostgreSQL.Manipulation.insertRow` or
`Squeal.PostgreSQL.Manipulation.update`,
or in search `Condition`s in a number of commands.
The expression syntax allows the calculation of
values from primitive expression using arithmetic, logical,
and other operations.
-}
newtype Expression
(schema :: SchemaType)
(relations :: RelationsType)
(grouping :: Grouping)
(params :: [NullityType])
(ty :: NullityType)
= UnsafeExpression { renderExpression :: ByteString }
deriving (GHC.Generic,Show,Eq,Ord,NFData)
instance RenderSQL (Expression schema relations grouping params ty) where
renderSQL = renderExpression
{- | A `HasParameter` constraint is used to indicate a value that is
supplied externally to a SQL statement.
`Squeal.PostgreSQL.PQ.manipulateParams`,
`Squeal.PostgreSQL.PQ.queryParams` and
`Squeal.PostgreSQL.PQ.traversePrepared` support specifying data values
separately from the SQL command string, in which case `param`s are used to
refer to the out-of-line data values.
-}
class KnownNat n => HasParameter
(n :: Nat)
(schema :: SchemaType)
(params :: [NullityType])
(ty :: NullityType)
| n params -> ty where
-- | `parameter` takes a `Nat` using type application and a `TypeExpression`.
--
-- >>> let expr = parameter @1 int4 :: Expression sch rels grp '[ 'Null 'PGint4] ('Null 'PGint4)
-- >>> printSQL expr
-- ($1 :: int4)
parameter
:: TypeExpression schema (PGTypeOf ty)
-> Expression schema relations grouping params ty
parameter ty = UnsafeExpression $ parenthesized $
"$" <> renderNat (Proxy @n) <+> "::"
<+> renderTypeExpression ty
instance {-# OVERLAPPING #-} HasParameter 1 schema (ty1:tys) ty1
instance {-# OVERLAPPABLE #-} (KnownNat n, HasParameter (n-1) schema params ty)
=> HasParameter n schema (ty' : params) ty
-- | `param` takes a `Nat` using type application and for basic types,
-- infers a `TypeExpression`.
--
-- >>> let expr = param @1 :: Expression sch rels grp '[ 'Null 'PGint4] ('Null 'PGint4)
-- >>> printSQL expr
-- ($1 :: int4)
param
:: forall n schema params relations grouping ty
. (PGTyped schema (PGTypeOf ty), HasParameter n schema params ty)
=> Expression schema relations grouping params ty -- ^ param
param = parameter @n pgtype
instance (HasUnique relation relations columns, Has column columns ty)
=> IsLabel column (Expression schema relations 'Ungrouped params ty) where
fromLabel = UnsafeExpression $ renderAlias (Alias @column)
instance (HasUnique relation relations columns, Has column columns ty)
=> IsLabel column
(Aliased (Expression schema relations 'Ungrouped params) (column ::: ty)) where
fromLabel = fromLabel @column `As` Alias @column
instance (HasUnique relation relations columns, Has column columns ty)
=> IsLabel column
(NP (Aliased (Expression schema relations 'Ungrouped params)) '[column ::: ty]) where
fromLabel = fromLabel @column :* Nil
instance (Has relation relations columns, Has column columns ty)
=> IsQualified relation column (Expression schema relations 'Ungrouped params ty) where
relation ! column = UnsafeExpression $
renderAlias relation <> "." <> renderAlias column
instance (Has relation relations columns, Has column columns ty)
=> IsQualified relation column
(Aliased (Expression schema relations 'Ungrouped params) (column ::: ty)) where
relation ! column = relation ! column `As` column
instance (Has relation relations columns, Has column columns ty)
=> IsQualified relation column
(NP (Aliased (Expression schema relations 'Ungrouped params)) '[column ::: ty]) where
relation ! column = relation ! column :* Nil
instance
( HasUnique relation relations columns
, Has column columns ty
, GroupedBy relation column bys
) => IsLabel column
(Expression schema relations ('Grouped bys) params ty) where
fromLabel = UnsafeExpression $ renderAlias (Alias @column)
instance
( HasUnique relation relations columns
, Has column columns ty
, GroupedBy relation column bys
) => IsLabel column
( Aliased (Expression schema relations ('Grouped bys) params)
(column ::: ty) ) where
fromLabel = fromLabel @column `As` Alias @column
instance
( HasUnique relation relations columns
, Has column columns ty
, GroupedBy relation column bys
) => IsLabel column
( NP (Aliased (Expression schema relations ('Grouped bys) params))
'[column ::: ty] ) where
fromLabel = fromLabel @column :* Nil
instance
( Has relation relations columns
, Has column columns ty
, GroupedBy relation column bys
) => IsQualified relation column
(Expression schema relations ('Grouped bys) params ty) where
relation ! column = UnsafeExpression $
renderAlias relation <> "." <> renderAlias column
instance
( Has relation relations columns
, Has column columns ty
, GroupedBy relation column bys
) => IsQualified relation column
(Aliased (Expression schema relations ('Grouped bys) params)
(column ::: ty)) where
relation ! column = relation ! column `As` column
instance
( Has relation relations columns
, Has column columns ty
, GroupedBy relation column bys
) => IsQualified relation column
( NP (Aliased (Expression schema relations ('Grouped bys) params))
'[column ::: ty]) where
relation ! column = relation ! column :* Nil
-- | analagous to `Nothing`
--
-- >>> printSQL null_
-- NULL
null_ :: Expression schema rels grouping params ('Null ty)
null_ = UnsafeExpression "NULL"
-- | analagous to `Just`
--
-- >>> printSQL $ notNull true
-- TRUE
notNull
:: Expression schema rels grouping params ('NotNull ty)
-> Expression schema rels grouping params ('Null ty)
notNull = UnsafeExpression . renderExpression
-- | return the leftmost value which is not NULL
--
-- >>> printSQL $ coalesce [null_, notNull true] false
-- COALESCE(NULL, TRUE, FALSE)
coalesce
:: [Expression schema relations grouping params ('Null ty)]
-- ^ @NULL@s may be present
-> Expression schema relations grouping params ('NotNull ty)
-- ^ @NULL@ is absent
-> Expression schema relations grouping params ('NotNull ty)
coalesce nullxs notNullx = UnsafeExpression $
"COALESCE" <> parenthesized (commaSeparated
((renderExpression <$> nullxs) <> [renderExpression notNullx]))
-- | analagous to `Data.Maybe.fromMaybe` using @COALESCE@
--
-- >>> printSQL $ fromNull true null_
-- COALESCE(NULL, TRUE)
fromNull
:: Expression schema relations grouping params ('NotNull ty)
-- ^ what to convert @NULL@ to
-> Expression schema relations grouping params ('Null ty)
-> Expression schema relations grouping params ('NotNull ty)
fromNull notNullx nullx = coalesce [nullx] notNullx
-- | >>> printSQL $ null_ & isNull
-- NULL IS NULL
isNull
:: Expression schema relations grouping params ('Null ty)
-- ^ possibly @NULL@
-> Condition schema relations grouping params
isNull x = UnsafeExpression $ renderExpression x <+> "IS NULL"
-- | >>> printSQL $ null_ & isNotNull
-- NULL IS NOT NULL
isNotNull
:: Expression schema relations grouping params ('Null ty)
-- ^ possibly @NULL@
-> Condition schema relations grouping params
isNotNull x = UnsafeExpression $ renderExpression x <+> "IS NOT NULL"
-- | analagous to `maybe` using @IS NULL@
--
-- >>> printSQL $ matchNull true not_ null_
-- CASE WHEN NULL IS NULL THEN TRUE ELSE (NOT NULL) END
matchNull
:: Expression schema relations grouping params (nullty)
-- ^ what to convert @NULL@ to
-> ( Expression schema relations grouping params ('NotNull ty)
-> Expression schema relations grouping params (nullty) )
-- ^ function to perform when @NULL@ is absent
-> Expression schema relations grouping params ('Null ty)
-> Expression schema relations grouping params (nullty)
matchNull y f x = ifThenElse (isNull x) y
(f (UnsafeExpression (renderExpression x)))
{-| right inverse to `fromNull`, if its arguments are equal then
`nullIf` gives @NULL@.
>>> :set -XTypeApplications -XDataKinds
>>> let expr = nullIf false (param @1) :: Expression schema rels grp '[ 'NotNull 'PGbool] ('Null 'PGbool)
>>> printSQL expr
NULL IF (FALSE, ($1 :: bool))
-}
nullIf
:: Expression schema relations grouping params ('NotNull ty)
-- ^ @NULL@ is absent
-> Expression schema relations grouping params ('NotNull ty)
-- ^ @NULL@ is absent
-> Expression schema relations grouping params ('Null ty)
nullIf x y = UnsafeExpression $ "NULL IF" <+> parenthesized
(renderExpression x <> ", " <> renderExpression y)
-- | >>> printSQL $ array [null_, notNull false, notNull true]
-- ARRAY[NULL, FALSE, TRUE]
array
:: [Expression schema relations grouping params ('Null ty)]
-- ^ array elements
-> Expression schema relations grouping params (nullity ('PGvararray ty))
array xs = UnsafeExpression $
"ARRAY[" <> commaSeparated (renderExpression <$> xs) <> "]"
instance (KnownSymbol label, label `In` labels) => IsPGlabel label
(Expression schema relations grouping params (nullity ('PGenum labels))) where
label = UnsafeExpression $ renderLabel (PGlabel @label)
-- | A row constructor is an expression that builds a row value
-- (also called a composite value) using values for its member fields.
--
-- >>> type Complex = PGcomposite '["real" ::: 'PGfloat8, "imaginary" ::: 'PGfloat8]
-- >>> let i = row (0 `As` #real :* 1 `As` #imaginary :* Nil) :: Expression '[] '[] 'Ungrouped '[] ('NotNull Complex)
-- >>> printSQL i
-- ROW(0, 1)
row
:: SListI (Nulls fields)
=> NP (Aliased (Expression schema relations grouping params)) (Nulls fields)
-- ^ zero or more expressions for the row field values
-> Expression schema relations grouping params (nullity ('PGcomposite fields))
row exprs = UnsafeExpression $ "ROW" <> parenthesized
(renderCommaSeparated (\ (expr `As` _) -> renderExpression expr) exprs)
instance Has field fields ty => IsLabel field
( Expression schema relation grouping params (nullity ('PGcomposite fields))
-> Expression schema relation grouping params ('Null ty) ) where
fromLabel expr = UnsafeExpression $
parenthesized (renderExpression expr) <> "." <>
fromString (symbolVal (Proxy @field))
instance Semigroup
(Expression schema relations grouping params (nullity ('PGvararray ty))) where
(<>) = unsafeBinaryOp "||"
instance Monoid
(Expression schema relations grouping params (nullity ('PGvararray ty))) where
mempty = array []
mappend = (<>)
-- | >>> let expr = greatest currentTimestamp [param @1] :: Expression sch rels grp '[ 'NotNull 'PGtimestamptz] ('NotNull 'PGtimestamptz)
-- >>> printSQL expr
-- GREATEST(CURRENT_TIMESTAMP, ($1 :: timestamp with time zone))
greatest
:: Expression schema relations grouping params (nullty)
-- ^ needs at least 1 argument
-> [Expression schema relations grouping params (nullty)]
-- ^ or more
-> Expression schema relations grouping params (nullty)
greatest x xs = UnsafeExpression $ "GREATEST("
<> commaSeparated (renderExpression <$> (x:xs)) <> ")"
-- | >>> printSQL $ least currentTimestamp [null_]
-- LEAST(CURRENT_TIMESTAMP, NULL)
least
:: Expression schema relations grouping params (nullty)
-- ^ needs at least 1 argument
-> [Expression schema relations grouping params (nullty)]
-- ^ or more
-> Expression schema relations grouping params (nullty)
least x xs = UnsafeExpression $ "LEAST("
<> commaSeparated (renderExpression <$> (x:xs)) <> ")"
-- | >>> printSQL $ unsafeBinaryOp "OR" true false
-- (TRUE OR FALSE)
unsafeBinaryOp
:: ByteString
-- ^ operator
-> Expression schema relations grouping params (ty0)
-> Expression schema relations grouping params (ty1)
-> Expression schema relations grouping params (ty2)
unsafeBinaryOp op x y = UnsafeExpression $ parenthesized $
renderExpression x <+> op <+> renderExpression y
-- | >>> printSQL $ unsafeUnaryOp "NOT" true
-- (NOT TRUE)
unsafeUnaryOp
:: ByteString
-- ^ operator
-> Expression schema relations grouping params (ty0)
-> Expression schema relations grouping params (ty1)
unsafeUnaryOp op x = UnsafeExpression $ parenthesized $
op <+> renderExpression x
-- | >>> printSQL $ unsafeFunction "f" true
-- f(TRUE)
unsafeFunction
:: ByteString
-- ^ function
-> Expression schema relations grouping params (xty)
-> Expression schema relations grouping params (yty)
unsafeFunction fun x = UnsafeExpression $
fun <> parenthesized (renderExpression x)
instance PGNum ty
=> Num (Expression schema relations grouping params (nullity ty)) where
(+) = unsafeBinaryOp "+"
(-) = unsafeBinaryOp "-"
(*) = unsafeBinaryOp "*"
abs = unsafeFunction "abs"
signum = unsafeFunction "sign"
fromInteger
= UnsafeExpression
. fromString
. show
instance (PGNum ty, PGFloating ty) => Fractional
(Expression schema relations grouping params (nullity ty)) where
(/) = unsafeBinaryOp "/"
fromRational x = fromInteger (numerator x) / fromInteger (denominator x)
instance (PGNum ty, PGFloating ty) => Floating
(Expression schema relations grouping params (nullity ty)) where
pi = UnsafeExpression "pi()"
exp = unsafeFunction "exp"
log = unsafeFunction "ln"
sqrt = unsafeFunction "sqrt"
b ** x = UnsafeExpression $
"power(" <> renderExpression b <> ", " <> renderExpression x <> ")"
logBase b y = log y / log b
sin = unsafeFunction "sin"
cos = unsafeFunction "cos"
tan = unsafeFunction "tan"
asin = unsafeFunction "asin"
acos = unsafeFunction "acos"
atan = unsafeFunction "atan"
sinh x = (exp x - exp (-x)) / 2
cosh x = (exp x + exp (-x)) / 2
tanh x = sinh x / cosh x
asinh x = log (x + sqrt (x*x + 1))
acosh x = log (x + sqrt (x*x - 1))
atanh x = log ((1 + x) / (1 - x)) / 2
-- | >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGfloat4)
-- expression = atan2_ pi 2
-- in printSQL expression
-- :}
-- atan2(pi(), 2)
atan2_
:: PGFloating float
=> Expression schema relations grouping params (nullity float)
-- ^ numerator
-> Expression schema relations grouping params (nullity float)
-- ^ denominator
-> Expression schema relations grouping params (nullity float)
atan2_ y x = UnsafeExpression $
"atan2(" <> renderExpression y <> ", " <> renderExpression x <> ")"
-- When a `cast` is applied to an `Expression` of a known type, it
-- represents a run-time type conversion. The cast will succeed only if a
-- suitable type conversion operation has been defined.
--
-- | >>> printSQL $ true & cast int4
-- (TRUE :: int4)
cast
:: TypeExpression schema ty1
-- ^ type to cast as
-> Expression schema relations grouping params (nullity ty0)
-- ^ value to convert
-> Expression schema relations grouping params (nullity ty1)
cast ty x = UnsafeExpression $ parenthesized $
renderExpression x <+> "::" <+> renderTypeExpression ty
-- | integer division, truncates the result
--
-- >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGint2)
-- expression = 5 `quot_` 2
-- in printSQL expression
-- :}
-- (5 / 2)
quot_
:: PGIntegral int
=> Expression schema relations grouping params (nullity int)
-- ^ numerator
-> Expression schema relations grouping params (nullity int)
-- ^ denominator
-> Expression schema relations grouping params (nullity int)
quot_ = unsafeBinaryOp "/"
-- | remainder upon integer division
--
-- >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGint2)
-- expression = 5 `rem_` 2
-- in printSQL expression
-- :}
-- (5 % 2)
rem_
:: PGIntegral int
=> Expression schema relations grouping params (nullity int)
-- ^ numerator
-> Expression schema relations grouping params (nullity int)
-- ^ denominator
-> Expression schema relations grouping params (nullity int)
rem_ = unsafeBinaryOp "%"
-- | >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGfloat4)
-- expression = trunc pi
-- in printSQL expression
-- :}
-- trunc(pi())
trunc
:: PGFloating frac
=> Expression schema relations grouping params (nullity frac)
-- ^ fractional number
-> Expression schema relations grouping params (nullity frac)
trunc = unsafeFunction "trunc"
-- | >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGfloat4)
-- expression = round_ pi
-- in printSQL expression
-- :}
-- round(pi())
round_
:: PGFloating frac
=> Expression schema relations grouping params (nullity frac)
-- ^ fractional number
-> Expression schema relations grouping params (nullity frac)
round_ = unsafeFunction "round"
-- | >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGfloat4)
-- expression = ceiling_ pi
-- in printSQL expression
-- :}
-- ceiling(pi())
ceiling_
:: PGFloating frac
=> Expression schema relations grouping params (nullity frac)
-- ^ fractional number
-> Expression schema relations grouping params (nullity frac)
ceiling_ = unsafeFunction "ceiling"
-- | A `Condition` is a boolean valued `Expression`. While SQL allows
-- conditions to have @NULL@, Squeal instead chooses to disallow @NULL@,
-- forcing one to handle the case of @NULL@ explicitly to produce
-- a `Condition`.
type Condition schema relations grouping params =
Expression schema relations grouping params ('NotNull 'PGbool)
-- | >>> printSQL true
-- TRUE
true :: Condition schema relations grouping params
true = UnsafeExpression "TRUE"
-- | >>> printSQL false
-- FALSE
false :: Condition schema relations grouping params
false = UnsafeExpression "FALSE"
-- | >>> printSQL $ not_ true
-- (NOT TRUE)
not_
:: Condition schema relations grouping params
-> Condition schema relations grouping params
not_ = unsafeUnaryOp "NOT"
-- | >>> printSQL $ true .&& false
-- (TRUE AND FALSE)
(.&&)
:: Condition schema relations grouping params
-> Condition schema relations grouping params
-> Condition schema relations grouping params
(.&&) = unsafeBinaryOp "AND"
-- | >>> printSQL $ true .|| false
-- (TRUE OR FALSE)
(.||)
:: Condition schema relations grouping params
-> Condition schema relations grouping params
-> Condition schema relations grouping params
(.||) = unsafeBinaryOp "OR"
-- | >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGint2)
-- expression = caseWhenThenElse [(true, 1), (false, 2)] 3
-- in printSQL expression
-- :}
-- CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END
caseWhenThenElse
:: [ ( Condition schema relations grouping params
, Expression schema relations grouping params (ty)
) ]
-- ^ whens and thens
-> Expression schema relations grouping params (ty)
-- ^ else
-> Expression schema relations grouping params (ty)
caseWhenThenElse whenThens else_ = UnsafeExpression $ mconcat
[ "CASE"
, mconcat
[ mconcat
[ " WHEN ", renderExpression when_
, " THEN ", renderExpression then_
]
| (when_,then_) <- whenThens
]
, " ELSE ", renderExpression else_
, " END"
]
-- | >>> :{
-- let
-- expression :: Expression schema relations grouping params (nullity 'PGint2)
-- expression = ifThenElse true 1 0
-- in printSQL expression
-- :}
-- CASE WHEN TRUE THEN 1 ELSE 0 END
ifThenElse
:: Condition schema relations grouping params
-> Expression schema relations grouping params (ty) -- ^ then
-> Expression schema relations grouping params (ty) -- ^ else
-> Expression schema relations grouping params (ty)
ifThenElse if_ then_ else_ = caseWhenThenElse [(if_,then_)] else_
-- | Comparison operations like `.==`, `./=`, `.>`, `.>=`, `.<` and `.<=`
-- will produce @NULL@s if one of their arguments is @NULL@.
--
-- >>> printSQL $ notNull true .== null_
-- (TRUE = NULL)
(.==)
:: Expression schema relations grouping params (nullity ty) -- ^ lhs
-> Expression schema relations grouping params (nullity ty) -- ^ rhs
-> Expression schema relations grouping params (nullity 'PGbool)
(.==) = unsafeBinaryOp "="
infix 4 .==
-- | >>> printSQL $ notNull true ./= null_
-- (TRUE <> NULL)
(./=)
:: Expression schema relations grouping params (nullity ty) -- ^ lhs
-> Expression schema relations grouping params (nullity ty) -- ^ rhs
-> Expression schema relations grouping params (nullity 'PGbool)
(./=) = unsafeBinaryOp "<>"
infix 4 ./=
-- | >>> printSQL $ notNull true .>= null_
-- (TRUE >= NULL)
(.>=)
:: Expression schema relations grouping params (nullity ty) -- ^ lhs
-> Expression schema relations grouping params (nullity ty) -- ^ rhs
-> Expression schema relations grouping params (nullity 'PGbool)
(.>=) = unsafeBinaryOp ">="
infix 4 .>=
-- | >>> printSQL $ notNull true .< null_
-- (TRUE < NULL)
(.<)
:: Expression schema relations grouping params (nullity ty) -- ^ lhs
-> Expression schema relations grouping params (nullity ty) -- ^ rhs
-> Expression schema relations grouping params (nullity 'PGbool)
(.<) = unsafeBinaryOp "<"
infix 4 .<
-- | >>> printSQL $ notNull true .<= null_
-- (TRUE <= NULL)
(.<=)
:: Expression schema relations grouping params (nullity ty) -- ^ lhs
-> Expression schema relations grouping params (nullity ty) -- ^ rhs
-> Expression schema relations grouping params (nullity 'PGbool)
(.<=) = unsafeBinaryOp "<="
infix 4 .<=
-- | >>> printSQL $ notNull true .> null_
-- (TRUE > NULL)
(.>)
:: Expression schema relations grouping params (nullity ty) -- ^ lhs
-> Expression schema relations grouping params (nullity ty) -- ^ rhs
-> Expression schema relations grouping params (nullity 'PGbool)
(.>) = unsafeBinaryOp ">"
infix 4 .>
-- | >>> printSQL currentDate
-- CURRENT_DATE
currentDate
:: Expression schema relations grouping params (nullity 'PGdate)
currentDate = UnsafeExpression "CURRENT_DATE"
-- | >>> printSQL currentTime
-- CURRENT_TIME
currentTime
:: Expression schema relations grouping params (nullity 'PGtimetz)
currentTime = UnsafeExpression "CURRENT_TIME"
-- | >>> printSQL currentTimestamp
-- CURRENT_TIMESTAMP
currentTimestamp
:: Expression schema relations grouping params (nullity 'PGtimestamptz)
currentTimestamp = UnsafeExpression "CURRENT_TIMESTAMP"
-- | >>> printSQL localTime
-- LOCALTIME
localTime
:: Expression schema relations grouping params (nullity 'PGtime)
localTime = UnsafeExpression "LOCALTIME"
-- | >>> printSQL localTimestamp
-- LOCALTIMESTAMP
localTimestamp
:: Expression schema relations grouping params (nullity 'PGtimestamp)
localTimestamp = UnsafeExpression "LOCALTIMESTAMP"
{-----------------------------------------
text
-----------------------------------------}
instance IsString
(Expression schema relations grouping params (nullity 'PGtext)) where
fromString str = UnsafeExpression $
"E\'" <> fromString (escape =<< str) <> "\'"
where
escape = \case
'\NUL' -> "\\0"
'\'' -> "''"
'"' -> "\\\""
'\b' -> "\\b"
'\n' -> "\\n"
'\r' -> "\\r"
'\t' -> "\\t"
'\\' -> "\\\\"
c -> [c]
instance Semigroup
(Expression schema relations grouping params (nullity 'PGtext)) where
(<>) = unsafeBinaryOp "||"
instance Monoid
(Expression schema relations grouping params (nullity 'PGtext)) where
mempty = fromString ""
mappend = (<>)
-- | >>> printSQL $ lower "ARRRGGG"
-- lower(E'ARRRGGG')
lower
:: Expression schema relations grouping params (nullity 'PGtext)
-- ^ string to lower case
-> Expression schema relations grouping params (nullity 'PGtext)
lower = unsafeFunction "lower"
-- | >>> printSQL $ upper "eeee"
-- upper(E'eeee')
upper
:: Expression schema relations grouping params (nullity 'PGtext)
-- ^ string to upper case
-> Expression schema relations grouping params (nullity 'PGtext)
upper = unsafeFunction "upper"
-- | >>> printSQL $ charLength "four"
-- char_length(E'four')
charLength
:: Expression schema relations grouping params (nullity 'PGtext)
-- ^ string to measure
-> Expression schema relations grouping params (nullity 'PGint4)
charLength = unsafeFunction "char_length"
-- | The `like` expression returns true if the @string@ matches
-- the supplied @pattern@. If @pattern@ does not contain percent signs
-- or underscores, then the pattern only represents the string itself;
-- in that case `like` acts like the equals operator. An underscore (_)
-- in pattern stands for (matches) any single character; a percent sign (%)
-- matches any sequence of zero or more characters.
--
-- >>> printSQL $ "abc" `like` "a%"
-- (E'abc' LIKE E'a%')
like
:: Expression schema relations grouping params (nullity 'PGtext)
-- ^ string
-> Expression schema relations grouping params (nullity 'PGtext)
-- ^ pattern
-> Expression schema relations grouping params (nullity 'PGbool)
like = unsafeBinaryOp "LIKE"
{-----------------------------------------
aggregation
-----------------------------------------}
-- | escape hatch to define aggregate functions
unsafeAggregate
:: ByteString -- ^ aggregate function
-> Expression schema relations 'Ungrouped params (xty)
-> Expression schema relations ('Grouped bys) params (yty)
unsafeAggregate fun x = UnsafeExpression $ mconcat
[fun, "(", renderExpression x, ")"]
-- | escape hatch to define aggregate functions over distinct values
unsafeAggregateDistinct
:: ByteString -- ^ aggregate function
-> Expression schema relations 'Ungrouped params (xty)
-> Expression schema relations ('Grouped bys) params (yty)
unsafeAggregateDistinct fun x = UnsafeExpression $ mconcat
[fun, "(DISTINCT ", renderExpression x, ")"]
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: 'Null 'PGnumeric]] ('Grouped bys) params ('Null 'PGnumeric)
-- expression = sum_ #col
-- in printSQL expression
-- :}
-- sum("col")
sum_
:: PGNum ty
=> Expression schema relations 'Ungrouped params (nullity ty)
-- ^ what to sum
-> Expression schema relations ('Grouped bys) params (nullity ty)
sum_ = unsafeAggregate "sum"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGnumeric]] ('Grouped bys) params (nullity 'PGnumeric)
-- expression = sumDistinct #col
-- in printSQL expression
-- :}
-- sum(DISTINCT "col")
sumDistinct
:: PGNum ty
=> Expression schema relations 'Ungrouped params (nullity ty)
-- ^ what to sum
-> Expression schema relations ('Grouped bys) params (nullity ty)
sumDistinct = unsafeAggregateDistinct "sum"
-- | A constraint for `PGType`s that you can take averages of and the resulting
-- `PGType`.
class PGAvg ty avg | ty -> avg where
avg, avgDistinct
:: Expression schema relations 'Ungrouped params (nullity ty)
-- ^ what to average
-> Expression schema relations ('Grouped bys) params (nullity avg)
avg = unsafeAggregate "avg"
avgDistinct = unsafeAggregateDistinct "avg"
instance PGAvg 'PGint2 'PGnumeric
instance PGAvg 'PGint4 'PGnumeric
instance PGAvg 'PGint8 'PGnumeric
instance PGAvg 'PGnumeric 'PGnumeric
instance PGAvg 'PGfloat4 'PGfloat8
instance PGAvg 'PGfloat8 'PGfloat8
instance PGAvg 'PGinterval 'PGinterval
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGint4]] (Grouped bys) params (nullity 'PGint4)
-- expression = bitAnd #col
-- in printSQL expression
-- :}
-- bit_and("col")
bitAnd
:: PGIntegral int
=> Expression schema relations 'Ungrouped params (nullity int)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity int)
bitAnd = unsafeAggregate "bit_and"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGint4]] (Grouped bys) params (nullity 'PGint4)
-- expression = bitOr #col
-- in printSQL expression
-- :}
-- bit_or("col")
bitOr
:: PGIntegral int
=> Expression schema relations 'Ungrouped params (nullity int)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity int)
bitOr = unsafeAggregate "bit_or"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGint4]] (Grouped bys) params (nullity 'PGint4)
-- expression = bitAndDistinct #col
-- in printSQL expression
-- :}
-- bit_and(DISTINCT "col")
bitAndDistinct
:: PGIntegral int
=> Expression schema relations 'Ungrouped params (nullity int)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity int)
bitAndDistinct = unsafeAggregateDistinct "bit_and"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGint4]] (Grouped bys) params (nullity 'PGint4)
-- expression = bitOrDistinct #col
-- in printSQL expression
-- :}
-- bit_or(DISTINCT "col")
bitOrDistinct
:: PGIntegral int
=> Expression schema relations 'Ungrouped params (nullity int)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity int)
bitOrDistinct = unsafeAggregateDistinct "bit_or"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGbool]] (Grouped bys) params (nullity 'PGbool)
-- expression = boolAnd #col
-- in printSQL expression
-- :}
-- bool_and("col")
boolAnd
:: Expression schema relations 'Ungrouped params (nullity 'PGbool)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
boolAnd = unsafeAggregate "bool_and"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGbool]] (Grouped bys) params (nullity 'PGbool)
-- expression = boolOr #col
-- in printSQL expression
-- :}
-- bool_or("col")
boolOr
:: Expression schema relations 'Ungrouped params (nullity 'PGbool)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
boolOr = unsafeAggregate "bool_or"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGbool]] (Grouped bys) params (nullity 'PGbool)
-- expression = boolAndDistinct #col
-- in printSQL expression
-- :}
-- bool_and(DISTINCT "col")
boolAndDistinct
:: Expression schema relations 'Ungrouped params (nullity 'PGbool)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
boolAndDistinct = unsafeAggregateDistinct "bool_and"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGbool]] (Grouped bys) params (nullity 'PGbool)
-- expression = boolOrDistinct #col
-- in printSQL expression
-- :}
-- bool_or(DISTINCT "col")
boolOrDistinct
:: Expression schema relations 'Ungrouped params (nullity 'PGbool)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
boolOrDistinct = unsafeAggregateDistinct "bool_or"
-- | A special aggregation that does not require an input
--
-- >>> printSQL countStar
-- count(*)
countStar
:: Expression schema relations ('Grouped bys) params ('NotNull 'PGint8)
countStar = UnsafeExpression $ "count(*)"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity ty]] (Grouped bys) params ('NotNull 'PGint8)
-- expression = count #col
-- in printSQL expression
-- :}
-- count("col")
count
:: Expression schema relations 'Ungrouped params ty
-- ^ what to count
-> Expression schema relations ('Grouped bys) params ('NotNull 'PGint8)
count = unsafeAggregate "count"
-- | >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity ty]] (Grouped bys) params ('NotNull 'PGint8)
-- expression = countDistinct #col
-- in printSQL expression
-- :}
-- count(DISTINCT "col")
countDistinct
:: Expression schema relations 'Ungrouped params ty
-- ^ what to count
-> Expression schema relations ('Grouped bys) params ('NotNull 'PGint8)
countDistinct = unsafeAggregateDistinct "count"
-- | synonym for `boolAnd`
--
-- >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGbool]] (Grouped bys) params (nullity 'PGbool)
-- expression = every #col
-- in printSQL expression
-- :}
-- every("col")
every
:: Expression schema relations 'Ungrouped params (nullity 'PGbool)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
every = unsafeAggregate "every"
-- | synonym for `boolAndDistinct`
--
-- >>> :{
-- let
-- expression :: Expression schema '[tab ::: '["col" ::: nullity 'PGbool]] (Grouped bys) params (nullity 'PGbool)
-- expression = everyDistinct #col
-- in printSQL expression
-- :}
-- every(DISTINCT "col")
everyDistinct
:: Expression schema relations 'Ungrouped params (nullity 'PGbool)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity 'PGbool)
everyDistinct = unsafeAggregateDistinct "every"
-- | minimum and maximum aggregation
max_, min_, maxDistinct, minDistinct
:: Expression schema relations 'Ungrouped params (nullity ty)
-- ^ what to aggregate
-> Expression schema relations ('Grouped bys) params (nullity ty)
max_ = unsafeAggregate "max"
min_ = unsafeAggregate "min"
maxDistinct = unsafeAggregateDistinct "max"
minDistinct = unsafeAggregateDistinct "min"
{-----------------------------------------
type expressions
-----------------------------------------}
-- | `TypeExpression`s are used in `cast`s and `createTable` commands.
newtype TypeExpression (schema :: SchemaType) (ty :: PGType)
= UnsafeTypeExpression { renderTypeExpression :: ByteString }
deriving (GHC.Generic,Show,Eq,Ord,NFData)
instance (Has alias schema ('Typedef ty))
=> IsLabel alias (TypeExpression schema ty) where
fromLabel = UnsafeTypeExpression (renderAlias (fromLabel @alias))
-- | logical Boolean (true/false)
bool :: TypeExpression schema 'PGbool
bool = UnsafeTypeExpression "bool"
-- | signed two-byte integer
int2, smallint :: TypeExpression schema 'PGint2
int2 = UnsafeTypeExpression "int2"
smallint = UnsafeTypeExpression "smallint"
-- | signed four-byte integer
int4, int, integer :: TypeExpression schema 'PGint4
int4 = UnsafeTypeExpression "int4"
int = UnsafeTypeExpression "int"
integer = UnsafeTypeExpression "integer"
-- | signed eight-byte integer
int8, bigint :: TypeExpression schema 'PGint8
int8 = UnsafeTypeExpression "int8"
bigint = UnsafeTypeExpression "bigint"
-- | arbitrary precision numeric type
numeric :: TypeExpression schema 'PGnumeric
numeric = UnsafeTypeExpression "numeric"
-- | single precision floating-point number (4 bytes)
float4, real :: TypeExpression schema 'PGfloat4
float4 = UnsafeTypeExpression "float4"
real = UnsafeTypeExpression "real"
-- | double precision floating-point number (8 bytes)
float8, doublePrecision :: TypeExpression schema 'PGfloat8
float8 = UnsafeTypeExpression "float8"
doublePrecision = UnsafeTypeExpression "double precision"
-- | variable-length character string
text :: TypeExpression schema 'PGtext
text = UnsafeTypeExpression "text"
-- | fixed-length character string
char, character
:: (KnownNat n, 1 <= n)
=> proxy n
-> TypeExpression schema ('PGchar n)
char p = UnsafeTypeExpression $ "char(" <> renderNat p <> ")"
character p = UnsafeTypeExpression $ "character(" <> renderNat p <> ")"
-- | variable-length character string
varchar, characterVarying
:: (KnownNat n, 1 <= n)
=> proxy n
-> TypeExpression schema ('PGvarchar n)
varchar p = UnsafeTypeExpression $ "varchar(" <> renderNat p <> ")"
characterVarying p = UnsafeTypeExpression $
"character varying(" <> renderNat p <> ")"
-- | binary data ("byte array")
bytea :: TypeExpression schema 'PGbytea
bytea = UnsafeTypeExpression "bytea"
-- | date and time (no time zone)
timestamp :: TypeExpression schema 'PGtimestamp
timestamp = UnsafeTypeExpression "timestamp"
-- | date and time, including time zone
timestampWithTimeZone :: TypeExpression schema 'PGtimestamptz
timestampWithTimeZone = UnsafeTypeExpression "timestamp with time zone"
-- | calendar date (year, month, day)
date :: TypeExpression schema 'PGdate
date = UnsafeTypeExpression "date"
-- | time of day (no time zone)
time :: TypeExpression schema 'PGtime
time = UnsafeTypeExpression "time"
-- | time of day, including time zone
timeWithTimeZone :: TypeExpression schema 'PGtimetz
timeWithTimeZone = UnsafeTypeExpression "time with time zone"
-- | time span
interval :: TypeExpression schema 'PGinterval
interval = UnsafeTypeExpression "interval"
-- | universally unique identifier
uuid :: TypeExpression schema 'PGuuid
uuid = UnsafeTypeExpression "uuid"
-- | IPv4 or IPv6 host address
inet :: TypeExpression schema 'PGinet
inet = UnsafeTypeExpression "inet"
-- | textual JSON data
json :: TypeExpression schema 'PGjson
json = UnsafeTypeExpression "json"
-- | binary JSON data, decomposed
jsonb :: TypeExpression schema 'PGjsonb
jsonb = UnsafeTypeExpression "jsonb"
-- | variable length array
vararray
:: TypeExpression schema pg
-> TypeExpression schema ('PGvararray pg)
vararray ty = UnsafeTypeExpression $ renderTypeExpression ty <> "[]"
-- | fixed length array
--
-- >>> renderTypeExpression (fixarray (Proxy @2) json)
-- "json[2]"
fixarray
:: KnownNat n
=> proxy n
-> TypeExpression schema pg
-> TypeExpression schema ('PGfixarray n pg)
fixarray p ty = UnsafeTypeExpression $
renderTypeExpression ty <> "[" <> renderNat p <> "]"
-- | `pgtype` is a demoted version of a `PGType`
class PGTyped schema (ty :: PGType) where pgtype :: TypeExpression schema ty
instance PGTyped schema 'PGbool where pgtype = bool
instance PGTyped schema 'PGint2 where pgtype = int2
instance PGTyped schema 'PGint4 where pgtype = int4
instance PGTyped schema 'PGint8 where pgtype = int8
instance PGTyped schema 'PGnumeric where pgtype = numeric
instance PGTyped schema 'PGfloat4 where pgtype = float4
instance PGTyped schema 'PGfloat8 where pgtype = float8
instance PGTyped schema 'PGtext where pgtype = text
instance (KnownNat n, 1 <= n)
=> PGTyped schema ('PGchar n) where pgtype = char (Proxy @n)
instance (KnownNat n, 1 <= n)
=> PGTyped schema ('PGvarchar n) where pgtype = varchar (Proxy @n)
instance PGTyped schema 'PGbytea where pgtype = bytea
instance PGTyped schema 'PGtimestamp where pgtype = timestamp
instance PGTyped schema 'PGtimestamptz where pgtype = timestampWithTimeZone
instance PGTyped schema 'PGdate where pgtype = date
instance PGTyped schema 'PGtime where pgtype = time
instance PGTyped schema 'PGtimetz where pgtype = timeWithTimeZone
instance PGTyped schema 'PGinterval where pgtype = interval
instance PGTyped schema 'PGuuid where pgtype = uuid
instance PGTyped schema 'PGjson where pgtype = json
instance PGTyped schema 'PGjsonb where pgtype = jsonb
instance PGTyped schema ty => PGTyped schema ('PGvararray ty) where
pgtype = vararray (pgtype @schema @ty)
instance (KnownNat n, PGTyped schema ty) => PGTyped schema ('PGfixarray n ty) where
pgtype = fixarray (Proxy @n) (pgtype @schema @ty)