{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE CPP #-}
module Database.Beam.Postgres.PgSpecific
(
TsVectorConfig, TsVector(..)
, toTsVector, english
, TsQuery(..), (@@)
, toTsQuery
, PgJSON(..), PgJSONB(..)
, IsPgJSON(..)
, PgJSONEach(..), PgJSONKey(..), PgJSONElement(..)
, (@>), (<@), (->#), (->$)
, (->>#), (->>$), (#>), (#>>)
, (?), (?|), (?&)
, withoutKey, withoutIdx
, withoutKeys
, pgJsonArrayLength
, pgJsonbUpdate, pgJsonbSet
, pgJsonbPretty
, PgMoney(..), pgMoney
, pgScaleMoney_
, pgDivideMoney_, pgDivideMoneys_
, pgAddMoney_, pgSubtractMoney_
, pgSumMoneyOver_, pgAvgMoneyOver_
, pgSumMoney_, pgAvgMoney_
, PgPoint(..), PgLine(..), PgLineSegment(..)
, PgBox(..), PgPath(..), PgPolygon(..)
, PgCircle(..)
, PgSetOf, pgUnnest
, pgUnnestArray, pgUnnestArrayWithOrdinality
, PgArrayValueContext, PgIsArrayContext
, array_, arrayOf_, (++.)
, pgArrayAgg, pgArrayAggOver
, (!.), arrayDims_
, arrayUpper_, arrayLower_
, arrayUpperUnsafe_, arrayLowerUnsafe_
, arrayLength_, arrayLengthUnsafe_
, isSupersetOf_, isSubsetOf_
, PgRange(..), PgRangeBound(..), PgBoundType(..)
, PgIsRange(..)
, PgInt4Range, PgInt8Range, PgNumRange
, PgTsRange, PgTsTzRange, PgDateRange
, range_
, inclusive, exclusive, unbounded
, (-@>-), (-@>), (-<@-), (<@-)
, (-&&-), (-<<-), (->>-)
, (-&<-), (-&>-), (--|--)
, (-+-), (-*-), (-.-)
, rLower_, rUpper_, isEmpty_
, lowerInc_, upperInc_, lowerInf_, upperInf_
, rangeMerge_
, pgBoolOr, pgBoolAnd, pgStringAgg, pgStringAggOver
, pgNubBy_
, now_, ilike_
)
where
import Database.Beam hiding (char, double)
import Database.Beam.Backend.SQL
import Database.Beam.Migrate ( HasDefaultSqlDataType(..) )
import Database.Beam.Postgres.Syntax
import Database.Beam.Postgres.Types
import Database.Beam.Query.Internal
import Database.Beam.Schema.Tables
import Control.Monad.Free
import Control.Monad.State.Strict (evalState, put, get)
import Data.Aeson
import Data.Attoparsec.ByteString
import Data.Attoparsec.ByteString.Char8
import Data.ByteString (ByteString)
import Data.ByteString.Builder
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString.Lazy as BL
import Data.Foldable
import Data.Hashable
import qualified Data.List.NonEmpty as NE
import Data.Proxy
import Data.Scientific (Scientific, formatScientific, FPFormat(Fixed))
import Data.String
import qualified Data.Text as T
import Data.Time (LocalTime)
import Data.Type.Bool
import qualified Data.Vector as V
#if !MIN_VERSION_base(4, 11, 0)
import Data.Semigroup
#endif
import qualified Database.PostgreSQL.Simple.FromField as Pg
import qualified Database.PostgreSQL.Simple.ToField as Pg
import qualified Database.PostgreSQL.Simple.TypeInfo.Static as Pg
import qualified Database.PostgreSQL.Simple.Range as Pg
import GHC.TypeLits
import GHC.Exts hiding (toList)
now_ :: QExpr Postgres s LocalTime
now_ = QExpr (\_ -> PgExpressionSyntax (emit "NOW()"))
ilike_ :: BeamSqlBackendIsString Postgres text
=> QExpr Postgres s text
-> QExpr Postgres s text
-> QExpr Postgres s Bool
ilike_ (QExpr a) (QExpr b) = QExpr (pgBinOp "ILIKE" <$> a <*> b)
newtype TsVector = TsVector ByteString
deriving (Show, Eq, Ord)
newtype TsVectorConfig = TsVectorConfig ByteString
deriving (Show, Eq, Ord, IsString)
instance Pg.FromField TsVector where
fromField field d =
if Pg.typeOid field /= Pg.typoid pgTsVectorTypeInfo
then Pg.returnError Pg.Incompatible field ""
else case d of
Just d' -> pure (TsVector d')
Nothing -> Pg.returnError Pg.UnexpectedNull field ""
instance Pg.ToField TsVector where
toField (TsVector d) =
Pg.Many [ Pg.Plain "($$"
, Pg.Plain (byteString d)
, Pg.Plain "$$::tsvector)" ]
instance FromBackendRow Postgres TsVector
instance HasSqlEqualityCheck Postgres TsVectorConfig
instance HasSqlQuantifiedEqualityCheck Postgres TsVectorConfig
instance HasSqlEqualityCheck Postgres TsVector
instance HasSqlQuantifiedEqualityCheck Postgres TsVector
english :: TsVectorConfig
english = TsVectorConfig "english"
toTsVector :: BeamSqlBackendIsString Postgres str
=> Maybe TsVectorConfig -> QGenExpr context Postgres s str
-> QGenExpr context Postgres s TsVector
toTsVector Nothing (QExpr x) =
QExpr (fmap (\(PgExpressionSyntax x') ->
PgExpressionSyntax $
emit "to_tsvector(" <> x' <> emit ")") x)
toTsVector (Just (TsVectorConfig configNm)) (QExpr x) =
QExpr (fmap (\(PgExpressionSyntax x') -> PgExpressionSyntax $
emit "to_tsvector('" <> escapeString configNm <> emit "', " <> x' <> emit ")") x)
(@@) :: QGenExpr context Postgres s TsVector
-> QGenExpr context Postgres s TsQuery
-> QGenExpr context Postgres s Bool
QExpr vec @@ QExpr q =
QExpr (pgBinOp "@@" <$> vec <*> q)
newtype TsQuery = TsQuery ByteString
deriving (Show, Eq, Ord)
instance HasSqlEqualityCheck Postgres TsQuery
instance HasSqlQuantifiedEqualityCheck Postgres TsQuery
instance Pg.FromField TsQuery where
fromField field d =
if Pg.typeOid field /= Pg.typoid pgTsQueryTypeInfo
then Pg.returnError Pg.Incompatible field ""
else case d of
Just d' -> pure (TsQuery d')
Nothing -> Pg.returnError Pg.UnexpectedNull field ""
instance FromBackendRow Postgres TsQuery
toTsQuery :: BeamSqlBackendIsString Postgres str
=> Maybe TsVectorConfig -> QGenExpr context Postgres s str
-> QGenExpr context Postgres s TsQuery
toTsQuery Nothing (QExpr x) =
QExpr (fmap (\(PgExpressionSyntax x') ->
PgExpressionSyntax $
emit "to_tsquery(" <> x' <> emit ")") x)
toTsQuery (Just (TsVectorConfig configNm)) (QExpr x) =
QExpr (fmap (\(PgExpressionSyntax x') -> PgExpressionSyntax $
emit "to_tsquery('" <> escapeString configNm <> emit "', " <> x' <> emit ")") x)
(!.) :: Integral ix
=> QGenExpr context Postgres s (V.Vector a)
-> QGenExpr context Postgres s ix
-> QGenExpr context Postgres s a
QExpr v !. QExpr ix =
QExpr (index <$> v <*> ix)
where
index (PgExpressionSyntax v') (PgExpressionSyntax ix') =
PgExpressionSyntax (emit "(" <> v' <> emit ")[" <> ix' <> emit "]")
arrayDims_ :: BeamSqlBackendIsString Postgres text
=> QGenExpr context Postgres s (V.Vector a)
-> QGenExpr context Postgres s text
arrayDims_ (QExpr v) = QExpr (fmap (\(PgExpressionSyntax v') -> PgExpressionSyntax (emit "array_dims(" <> v' <> emit ")")) v)
type family CountDims (v :: *) :: Nat where
CountDims (V.Vector a) = 1 + CountDims a
CountDims a = 0
type family WithinBounds (dim :: Nat) (v :: *) :: Constraint where
WithinBounds dim v =
If ((dim <=? CountDims v) && (1 <=? dim))
(() :: Constraint)
(TypeError ( ('Text "Dimension " ':<>: 'ShowType dim ':<>: 'Text " is out of bounds.") ':$$:
('Text "The type " ':<>: 'ShowType v ':<>: 'Text " has " ':<>: 'ShowType (CountDims v) ':<>: 'Text " dimension(s).") ':$$:
('Text "Hint: The dimension should be a natural between 1 and " ':<>: 'ShowType (CountDims v)) ))
arrayUpper_, arrayLower_
:: forall (dim :: Nat) context num v s.
(KnownNat dim, WithinBounds dim (V.Vector v), Integral num)
=> QGenExpr context Postgres s (V.Vector v)
-> QGenExpr context Postgres s num
arrayUpper_ v =
unsafeRetype (arrayUpperUnsafe_ v (val_ (natVal (Proxy @dim) :: Integer)) :: QGenExpr context Postgres s (Maybe Integer))
arrayLower_ v =
unsafeRetype (arrayLowerUnsafe_ v (val_ (natVal (Proxy @dim) :: Integer)) :: QGenExpr context Postgres s (Maybe Integer))
arrayUpperUnsafe_, arrayLowerUnsafe_
:: (Integral dim, Integral length)
=> QGenExpr context Postgres s (V.Vector v)
-> QGenExpr context Postgres s dim
-> QGenExpr context Postgres s (Maybe length)
arrayUpperUnsafe_ (QExpr v) (QExpr dim) =
QExpr (fmap (PgExpressionSyntax . mconcat) . sequenceA $
[ pure (emit "array_upper(")
, fromPgExpression <$> v
, pure (emit ", ")
, fromPgExpression <$> dim
, pure (emit ")") ])
arrayLowerUnsafe_ (QExpr v) (QExpr dim) =
QExpr (fmap (PgExpressionSyntax . mconcat) . sequenceA $
[ pure (emit "array_lower(")
, fromPgExpression <$> v
, pure (emit ", ")
, fromPgExpression <$> dim
, pure (emit ")") ])
arrayLength_
:: forall (dim :: Nat) ctxt num v s.
(KnownNat dim, WithinBounds dim (V.Vector v), Integral num)
=> QGenExpr ctxt Postgres s (V.Vector v)
-> QGenExpr ctxt Postgres s num
arrayLength_ v =
unsafeRetype (arrayLengthUnsafe_ v (val_ (natVal (Proxy @dim) :: Integer)) :: QGenExpr ctxt Postgres s (Maybe Integer))
arrayLengthUnsafe_
:: (Integral dim, Integral num)
=> QGenExpr ctxt Postgres s (V.Vector v)
-> QGenExpr ctxt Postgres s dim
-> QGenExpr ctxt Postgres s (Maybe num)
arrayLengthUnsafe_ (QExpr a) (QExpr dim) =
QExpr $ fmap (PgExpressionSyntax . mconcat) $ sequenceA $
[ pure (emit "array_length(")
, fromPgExpression <$> a
, pure (emit ", ")
, fromPgExpression <$> dim
, pure (emit ")") ]
isSupersetOf_ :: QGenExpr ctxt Postgres s (V.Vector a)
-> QGenExpr ctxt Postgres s (V.Vector a)
-> QGenExpr ctxt Postgres s Bool
isSupersetOf_ (QExpr haystack) (QExpr needles) =
QExpr (pgBinOp "@>" <$> haystack <*> needles)
isSubsetOf_ :: QGenExpr ctxt Postgres s (V.Vector a)
-> QGenExpr ctxt Postgres s (V.Vector a)
-> QGenExpr ctxt Postgres s Bool
isSubsetOf_ (QExpr needles) (QExpr haystack) =
QExpr (pgBinOp "<@" <$> needles <*> haystack)
(++.) :: QGenExpr ctxt Postgres s (V.Vector a)
-> QGenExpr ctxt Postgres s (V.Vector a)
-> QGenExpr ctxt Postgres s (V.Vector a)
QExpr a ++. QExpr b =
QExpr (pgBinOp "||" <$> a <*> b)
data PgArrayValueContext
class PgIsArrayContext ctxt where
mkArraySyntax :: Proxy ctxt -> PgSyntax -> PgSyntax
mkArraySyntax _ s = emit "ARRAY" <> s
instance PgIsArrayContext PgArrayValueContext where
mkArraySyntax _ = id
instance PgIsArrayContext QValueContext
instance PgIsArrayContext QAggregateContext
instance PgIsArrayContext QWindowingContext
array_ :: forall context f s a.
(PgIsArrayContext context, Foldable f)
=> f (QGenExpr context Postgres s a)
-> QGenExpr context Postgres s (V.Vector a)
array_ vs =
QExpr $ fmap (PgExpressionSyntax . mkArraySyntax (Proxy @context) . mconcat) $
sequenceA [ pure (emit "[")
, pgSepBy (emit ", ") <$> mapM (\(QExpr e) -> fromPgExpression <$> e) (toList vs)
, pure (emit "]") ]
arrayOf_ :: Q Postgres db s (QExpr Postgres s a)
-> QGenExpr context Postgres s (V.Vector a)
arrayOf_ q =
let QExpr sub = subquery_ q
in QExpr (\t -> let PgExpressionSyntax sub' = sub t
in PgExpressionSyntax (emit "ARRAY(" <> sub' <> emit ")"))
data PgBoundType
= Inclusive
| Exclusive
deriving (Show, Generic)
instance Hashable PgBoundType
lBound :: PgBoundType -> ByteString
lBound Inclusive = "["
lBound Exclusive = "("
uBound :: PgBoundType -> ByteString
uBound Inclusive = "]"
uBound Exclusive = ")"
data PgRangeBound a = PgRangeBound PgBoundType (Maybe a) deriving (Show, Generic)
inclusive :: a -> PgRangeBound a
inclusive = PgRangeBound Inclusive . Just
exclusive :: a -> PgRangeBound a
exclusive = PgRangeBound Exclusive . Just
unbounded :: PgRangeBound a
unbounded = PgRangeBound Exclusive Nothing
data PgRange (n :: *) a
= PgEmptyRange
| PgRange (PgRangeBound a) (PgRangeBound a)
deriving (Show, Generic)
instance Hashable a => Hashable (PgRangeBound a)
instance Hashable a => Hashable (PgRange n a)
class PgIsRange n where
rangeName :: ByteString
data PgInt4Range
instance PgIsRange PgInt4Range where
rangeName = "int4range"
data PgInt8Range
instance PgIsRange PgInt8Range where
rangeName = "int8range"
data PgNumRange
instance PgIsRange PgNumRange where
rangeName = "numrange"
data PgTsRange
instance PgIsRange PgTsRange where
rangeName = "tsrange"
data PgTsTzRange
instance PgIsRange PgTsTzRange where
rangeName = "tstzrange"
data PgDateRange
instance PgIsRange PgDateRange where
rangeName = "daterange"
instance (Pg.FromField a, Typeable a, Typeable n, Ord a) => Pg.FromField (PgRange n a) where
fromField field d = do
pgR :: Pg.PGRange a <- Pg.fromField field d
if Pg.isEmpty pgR
then pure PgEmptyRange
else let Pg.PGRange lRange rRange = pgR
in pure $ PgRange (boundConv lRange) (boundConv rRange)
boundConv :: Pg.RangeBound a -> PgRangeBound a
boundConv Pg.NegInfinity = PgRangeBound Exclusive Nothing
boundConv Pg.PosInfinity = PgRangeBound Exclusive Nothing
boundConv (Pg.Inclusive a) = PgRangeBound Inclusive (Just a)
boundConv (Pg.Exclusive a) = PgRangeBound Exclusive (Just a)
instance (Pg.ToField (Pg.PGRange a)) => Pg.ToField (PgRange n a) where
toField PgEmptyRange = Pg.toField (Pg.empty :: Pg.PGRange a)
toField (PgRange (PgRangeBound lt lb) (PgRangeBound ut ub)) = Pg.toField r'
where
r' = Pg.PGRange lb' ub'
lb' = case (lt, lb) of (_, Nothing) -> Pg.NegInfinity
(Inclusive, Just a) -> Pg.Inclusive a
(Exclusive, Just a) -> Pg.Exclusive a
ub' = case (ut, ub) of (_, Nothing) -> Pg.PosInfinity
(Inclusive, Just a) -> Pg.Inclusive a
(Exclusive, Just a) -> Pg.Exclusive a
instance HasSqlEqualityCheck Postgres (PgRange n a)
instance HasSqlQuantifiedEqualityCheck Postgres (PgRange n a)
instance (Pg.FromField a, Typeable a, Typeable n, Ord a) => FromBackendRow Postgres (PgRange n a)
instance (HasSqlValueSyntax PgValueSyntax a, PgIsRange n) =>
HasSqlValueSyntax PgValueSyntax (PgRange n a) where
sqlValueSyntax PgEmptyRange =
PgValueSyntax $
emit "'empty'::" <> escapeIdentifier (rangeName @n)
sqlValueSyntax (PgRange (PgRangeBound lbt mlBound) (PgRangeBound rbt muBound)) =
PgValueSyntax $
escapeIdentifier (rangeName @n) <> pgParens (pgSepBy (emit ", ") [lb, rb, bounds])
where
lb = sqlValueSyntax' mlBound
rb = sqlValueSyntax' muBound
bounds = emit "'" <> emit (lBound lbt <> uBound rbt) <> emit "'"
sqlValueSyntax' = fromPgValue . sqlValueSyntax
binOpDefault :: ByteString
-> QGenExpr context Postgres s a
-> QGenExpr context Postgres s b
-> QGenExpr context Postgres s c
binOpDefault symbol (QExpr r1) (QExpr r2) = QExpr (pgBinOp symbol <$> r1 <*> r2)
(-@>-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(-@>-) = binOpDefault "@>"
(-@>) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s a
-> QGenExpr context Postgres s Bool
(-@>) = binOpDefault "@>"
(-<@-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(-<@-) = binOpDefault "<@"
(<@-) :: QGenExpr context Postgres s a
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(<@-) = binOpDefault "<@"
(-&&-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(-&&-) = binOpDefault "&&"
(-<<-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(-<<-) = binOpDefault "<<"
(->>-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(->>-) = binOpDefault ">>"
(-&<-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(-&<-) = binOpDefault "&<"
(-&>-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(-&>-) = binOpDefault "&>"
(--|--) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
(--|--) = binOpDefault "-|-"
(-+-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
(-+-) = binOpDefault "+"
(-*-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
(-*-) = binOpDefault "*"
(-.-) :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
(-.-) = binOpDefault "-"
defUnaryFn :: ByteString
-> QGenExpr context Postgres s a
-> QGenExpr context Postgres s b
defUnaryFn fn (QExpr s) = QExpr (pgExprFrom <$> s)
where
pgExprFrom s' = PgExpressionSyntax (emit fn <> emit "(" <> fromPgExpression s' <> emit ")")
rLower_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (Maybe a)
rLower_ = defUnaryFn "LOWER"
rUpper_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (Maybe a)
rUpper_ = defUnaryFn "UPPER"
isEmpty_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
isEmpty_ = defUnaryFn "ISEMPTY"
lowerInc_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
lowerInc_ = defUnaryFn "LOWER_INC"
upperInc_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
upperInc_ = defUnaryFn "UPPER_INC"
lowerInf_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
lowerInf_ = defUnaryFn "LOWER_INF"
upperInf_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s Bool
upperInf_ = defUnaryFn "UPPER_INF"
rangeMerge_ :: QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
-> QGenExpr context Postgres s (PgRange n a)
rangeMerge_ (QExpr r1) (QExpr r2) = QExpr (pgExprFrom <$> r1 <*> r2)
where
pgExprFrom r1' r2' =
PgExpressionSyntax
(emit "RANGE_MERGE(" <>
fromPgExpression r1' <>
emit ", " <>
fromPgExpression r2' <>
emit ")")
range_ :: forall n a context s. PgIsRange n
=> PgBoundType
-> PgBoundType
-> QGenExpr context Postgres s (Maybe a)
-> QGenExpr context Postgres s (Maybe a)
-> QGenExpr context Postgres s (PgRange n a)
range_ lbt ubt (QExpr e1) (QExpr e2) = QExpr (pgExprFrom <$> e1 <*> e2)
where
bounds = emit "'" <> emit (lBound lbt <> uBound ubt) <> emit "'"
pgExprFrom e1' e2' =
PgExpressionSyntax
(escapeIdentifier (rangeName @n) <>
pgParens (pgSepBy (emit ", ") [fromPgExpression e1', fromPgExpression e2', bounds]))
newtype PgJSON a = PgJSON a
deriving ( Show, Eq, Ord, Hashable, Monoid, Semigroup )
instance HasSqlEqualityCheck Postgres (PgJSON a)
instance HasSqlQuantifiedEqualityCheck Postgres (PgJSON a)
instance (Typeable x, FromJSON x) => Pg.FromField (PgJSON x) where
fromField field d =
if Pg.typeOid field /= Pg.typoid Pg.json
then Pg.returnError Pg.Incompatible field ""
else case decodeStrict =<< d of
Just d' -> pure (PgJSON d')
Nothing -> Pg.returnError Pg.UnexpectedNull field ""
instance (Typeable a, FromJSON a) => FromBackendRow Postgres (PgJSON a)
instance ToJSON a => HasSqlValueSyntax PgValueSyntax (PgJSON a) where
sqlValueSyntax (PgJSON a) =
PgValueSyntax $
emit "'" <> escapeString (BL.toStrict (encode a)) <> emit "'::json"
newtype PgJSONB a = PgJSONB a
deriving ( Show, Eq, Ord, Hashable, Monoid, Semigroup )
instance HasSqlEqualityCheck Postgres (PgJSONB a)
instance HasSqlQuantifiedEqualityCheck Postgres (PgJSONB a)
instance (Typeable x, FromJSON x) => Pg.FromField (PgJSONB x) where
fromField field d =
if Pg.typeOid field /= Pg.typoid Pg.jsonb
then Pg.returnError Pg.Incompatible field ""
else case decodeStrict =<< d of
Just d' -> pure (PgJSONB d')
Nothing -> Pg.returnError Pg.UnexpectedNull field ""
instance (Typeable a, FromJSON a) => FromBackendRow Postgres (PgJSONB a)
instance ToJSON a => HasSqlValueSyntax PgValueSyntax (PgJSONB a) where
sqlValueSyntax (PgJSONB a) =
PgValueSyntax $
emit "'" <> escapeString (BL.toStrict (encode a)) <> emit "'::jsonb"
data PgJSONEach valType f
= PgJSONEach
{ pgJsonEachKey :: C f T.Text
, pgJsonEachValue :: C f valType
} deriving Generic
instance Beamable (PgJSONEach valType)
data PgJSONKey f = PgJSONKey { pgJsonKey :: C f T.Text }
deriving Generic
instance Beamable PgJSONKey
data PgJSONElement a f = PgJSONElement { pgJsonElement :: C f a }
deriving Generic
instance Beamable (PgJSONElement a)
class IsPgJSON (json :: * -> *) where
pgJsonEach :: QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (PgSetOf (PgJSONEach (json Value)))
pgJsonEachText :: QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (PgSetOf (PgJSONEach T.Text))
pgJsonKeys :: QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (PgSetOf PgJSONKey)
pgJsonArrayElements :: QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (PgSetOf (PgJSONElement (json Value)))
pgJsonArrayElementsText :: QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (PgSetOf (PgJSONElement T.Text))
pgJsonTypeOf :: QGenExpr ctxt Postgres s (json a) -> QGenExpr ctxt Postgres s T.Text
pgJsonStripNulls :: QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (json b)
pgJsonAgg :: QExpr Postgres s a -> QAgg Postgres s (json a)
pgJsonObjectAgg :: QExpr Postgres s key -> QExpr Postgres s value
-> QAgg Postgres s (json a)
instance IsPgJSON PgJSON where
pgJsonEach (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_each") . pgParens . fromPgExpression) a
pgJsonEachText (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_each_text") . pgParens . fromPgExpression) a
pgJsonKeys (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_object_keys") . pgParens . fromPgExpression) a
pgJsonArrayElements (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_array_elements") . pgParens . fromPgExpression) a
pgJsonArrayElementsText (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_array_elements_text") . pgParens . fromPgExpression) a
pgJsonTypeOf (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_typeof") . pgParens . fromPgExpression) a
pgJsonStripNulls (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_strip_nulls") . pgParens . fromPgExpression) a
pgJsonAgg (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_agg") . pgParens . fromPgExpression) a
pgJsonObjectAgg (QExpr keys) (QExpr values) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "json_object_agg") . pgParens . mconcat) $
sequenceA $ [ fromPgExpression <$> keys, pure (emit ", ")
, fromPgExpression <$> values ]
instance IsPgJSON PgJSONB where
pgJsonEach (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_each") . pgParens . fromPgExpression) a
pgJsonEachText (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_each_text") . pgParens . fromPgExpression) a
pgJsonKeys (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_object_keys") . pgParens . fromPgExpression) a
pgJsonArrayElements (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_array_elements") . pgParens . fromPgExpression) a
pgJsonArrayElementsText (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_array_elements_text") . pgParens . fromPgExpression) a
pgJsonTypeOf (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_typeof") . pgParens . fromPgExpression) a
pgJsonStripNulls (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_strip_nulls") . pgParens . fromPgExpression) a
pgJsonAgg (QExpr a) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_agg") . pgParens . fromPgExpression) a
pgJsonObjectAgg (QExpr keys) (QExpr values) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_object_agg") . pgParens . mconcat) $
sequenceA $ [ fromPgExpression <$> keys, pure (emit ", ")
, fromPgExpression <$> values ]
(@>), (<@) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (json b)
-> QGenExpr ctxt Postgres s Bool
QExpr a @> QExpr b =
QExpr (pgBinOp "@>" <$> a <*> b)
QExpr a <@ QExpr b =
QExpr (pgBinOp "<@" <$> a <*> b)
(->#) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s Int
-> QGenExpr ctxt Postgres s (json b)
QExpr a -># QExpr b =
QExpr (pgBinOp "->" <$> a <*> b)
(->$) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s T.Text
-> QGenExpr ctxt Postgres s (json b)
QExpr a ->$ QExpr b =
QExpr (pgBinOp "->" <$> a <*> b)
(->>#) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s Int
-> QGenExpr ctxt Postgres s T.Text
QExpr a ->># QExpr b =
QExpr (pgBinOp "->>" <$> a <*> b)
(->>$) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s T.Text
-> QGenExpr ctxt Postgres s T.Text
QExpr a ->>$ QExpr b =
QExpr (pgBinOp "->>" <$> a <*> b)
(#>) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (V.Vector T.Text)
-> QGenExpr ctxt Postgres s (json b)
QExpr a #> QExpr b =
QExpr (pgBinOp "#>" <$> a <*> b)
(#>>) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (V.Vector T.Text)
-> QGenExpr ctxt Postgres s T.Text
QExpr a #>> QExpr b =
QExpr (pgBinOp "#>>" <$> a <*> b)
(?) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s T.Text
-> QGenExpr ctxt Postgres s Bool
QExpr a ? QExpr b =
QExpr (pgBinOp "?" <$> a <*> b)
(?|), (?&) :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (V.Vector T.Text)
-> QGenExpr ctxt Postgres s Bool
QExpr a ?| QExpr b =
QExpr (pgBinOp "?|" <$> a <*> b)
QExpr a ?& QExpr b =
QExpr (pgBinOp "?&" <$> a <*> b)
withoutKey :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s T.Text
-> QGenExpr ctxt Postgres s (json b)
QExpr a `withoutKey` QExpr b =
QExpr (pgBinOp "-" <$> a <*> b)
withoutIdx :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s Int
-> QGenExpr ctxt Postgres s (json b)
QExpr a `withoutIdx` QExpr b =
QExpr (pgBinOp "-" <$> a <*> b)
withoutKeys :: IsPgJSON json
=> QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s (V.Vector T.Text)
-> QGenExpr ctxt Postgres s (json b)
QExpr a `withoutKeys` QExpr b =
QExpr (pgBinOp "#-" <$> a <*> b)
pgJsonArrayLength :: IsPgJSON json => QGenExpr ctxt Postgres s (json a)
-> QGenExpr ctxt Postgres s Int
pgJsonArrayLength (QExpr a) =
QExpr $ \tbl ->
PgExpressionSyntax (emit "json_array_length(" <> fromPgExpression (a tbl) <> emit ")")
pgJsonbUpdate, pgJsonbSet
:: QGenExpr ctxt Postgres s (PgJSONB a)
-> QGenExpr ctxt Postgres s (V.Vector T.Text)
-> QGenExpr ctxt Postgres s (PgJSONB b)
-> QGenExpr ctxt Postgres s (PgJSONB a)
pgJsonbUpdate (QExpr a) (QExpr path) (QExpr newVal) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_set") . pgParens . mconcat) $ sequenceA $
[ fromPgExpression <$> a, pure (emit ", "), fromPgExpression <$> path, pure (emit ", "), fromPgExpression <$> newVal ]
pgJsonbSet (QExpr a) (QExpr path) (QExpr newVal) =
QExpr $ fmap (PgExpressionSyntax . mappend (emit "jsonb_set") . pgParens . mconcat) $ sequenceA $
[ fromPgExpression <$> a, pure (emit ", "), fromPgExpression <$> path, pure (emit ", "), fromPgExpression <$> newVal, pure (emit ", true") ]
pgJsonbPretty :: QGenExpr ctxt Postgres s (PgJSONB a)
-> QGenExpr ctxt Postgres s T.Text
pgJsonbPretty (QExpr a) =
QExpr (\tbl -> PgExpressionSyntax (emit "jsonb_pretty" <> pgParens (fromPgExpression (a tbl))))
pgArrayAgg :: QExpr Postgres s a
-> QAgg Postgres s (V.Vector a)
pgArrayAgg = pgArrayAggOver allInGroup_
pgArrayAggOver :: Maybe PgAggregationSetQuantifierSyntax
-> QExpr Postgres s a
-> QAgg Postgres s (V.Vector a)
pgArrayAggOver quantifier (QExpr a) =
QExpr $ \tbl ->
PgExpressionSyntax $
emit "array_agg" <>
pgParens ( maybe mempty (\q -> fromPgAggregationSetQuantifier q <> emit " ") quantifier <>
fromPgExpression (a tbl))
pgBoolOr :: QExpr Postgres s a
-> QAgg Postgres s (Maybe Bool)
pgBoolOr (QExpr a) =
QExpr $ \tbl -> PgExpressionSyntax $
emit "bool_or" <> pgParens (fromPgExpression (a tbl))
pgBoolAnd :: QExpr Postgres s a
-> QAgg Postgres s (Maybe Bool)
pgBoolAnd (QExpr a) =
QExpr $ \tbl -> PgExpressionSyntax $
emit "bool_and" <> pgParens (fromPgExpression (a tbl))
pgStringAgg :: BeamSqlBackendIsString Postgres str
=> QExpr Postgres s str
-> QExpr Postgres s str
-> QAgg Postgres s (Maybe str)
pgStringAgg = pgStringAggOver allInGroup_
pgStringAggOver :: BeamSqlBackendIsString Postgres str
=> Maybe PgAggregationSetQuantifierSyntax
-> QExpr Postgres s str
-> QExpr Postgres s str
-> QAgg Postgres s (Maybe str)
pgStringAggOver quantifier (QExpr v) (QExpr delim) =
QExpr $ \tbl -> PgExpressionSyntax $
emit "string_agg" <>
pgParens ( maybe mempty (\q -> fromPgAggregationSetQuantifier q <> emit " ") quantifier <>
fromPgExpression (v tbl) <> emit ", " <>
fromPgExpression (delim tbl))
pgNubBy_ :: ( Projectible Postgres key
, Projectible Postgres r )
=> (r -> key)
-> Q Postgres db s r
-> Q Postgres db s r
pgNubBy_ mkKey (Q q) =
Q . liftF $
QDistinct (\r pfx -> pgSelectSetQuantifierDistinctOn
(project (Proxy @Postgres) (mkKey r) pfx))
q id
newtype PgMoney = PgMoney { fromPgMoney :: ByteString }
deriving (Show, Read, Eq, Ord)
instance Pg.FromField PgMoney where
fromField field Nothing = Pg.returnError Pg.UnexpectedNull field ""
fromField field (Just d) =
if Pg.typeOid field /= Pg.typoid Pg.money
then Pg.returnError Pg.Incompatible field ""
else pure (PgMoney d)
instance Pg.ToField PgMoney where
toField (PgMoney a) = Pg.toField a
instance HasSqlEqualityCheck Postgres PgMoney
instance HasSqlQuantifiedEqualityCheck Postgres PgMoney
instance FromBackendRow Postgres PgMoney
instance HasSqlValueSyntax PgValueSyntax PgMoney where
sqlValueSyntax (PgMoney a) = sqlValueSyntax a
pgMoney :: Real a => a -> PgMoney
pgMoney val = PgMoney (BC.pack (formatScientific Fixed Nothing exactVal))
where
exactVal = fromRational (toRational val) :: Scientific
pgScaleMoney_ :: Num a
=> QGenExpr context Postgres s a
-> QGenExpr context Postgres s PgMoney
-> QGenExpr context Postgres s PgMoney
pgScaleMoney_ (QExpr scale) (QExpr v) =
QExpr (pgBinOp "*" <$> scale <*> v)
pgDivideMoney_ :: Num a
=> QGenExpr context Postgres s PgMoney
-> QGenExpr context Postgres s a
-> QGenExpr context Postgres s PgMoney
pgDivideMoney_ (QExpr v) (QExpr scale) =
QExpr (pgBinOp "/" <$> v <*> scale)
pgDivideMoneys_ :: Num a
=> QGenExpr context Postgres s PgMoney
-> QGenExpr context Postgres s PgMoney
-> QGenExpr context Postgres s a
pgDivideMoneys_ (QExpr a) (QExpr b) =
QExpr (pgBinOp "/" <$> a <*> b)
pgAddMoney_, pgSubtractMoney_
:: QGenExpr context Postgres s PgMoney
-> QGenExpr context Postgres s PgMoney
-> QGenExpr context Postgres s PgMoney
pgAddMoney_ (QExpr a) (QExpr b) =
QExpr (pgBinOp "+" <$> a <*> b)
pgSubtractMoney_ (QExpr a) (QExpr b) =
QExpr (pgBinOp "-" <$> a <*> b)
pgSumMoneyOver_, pgAvgMoneyOver_
:: Maybe PgAggregationSetQuantifierSyntax
-> QExpr Postgres s PgMoney -> QExpr Postgres s PgMoney
pgSumMoneyOver_ q (QExpr a) = QExpr (sumE q <$> a)
pgAvgMoneyOver_ q (QExpr a) = QExpr (avgE q <$> a)
pgSumMoney_, pgAvgMoney_ :: QExpr Postgres s PgMoney
-> QExpr Postgres s PgMoney
pgSumMoney_ = pgSumMoneyOver_ allInGroup_
pgAvgMoney_ = pgAvgMoneyOver_ allInGroup_
data PgPoint = PgPoint {-# UNPACK #-} !Double {-# UNPACK #-} !Double
deriving (Show, Eq, Ord)
data PgLine = PgLine {-# UNPACK #-} !Double
{-# UNPACK #-} !Double
{-# UNPACK #-} !Double
deriving (Show, Eq, Ord)
data PgLineSegment = PgLineSegment {-# UNPACK #-} !PgPoint {-# UNPACK #-} !PgPoint
deriving (Show, Eq, Ord)
data PgBox = PgBox {-# UNPACK #-} !PgPoint {-# UNPACK #-} !PgPoint
deriving (Show)
instance Eq PgBox where
PgBox a1 b1 == PgBox a2 b2 =
(a1 == a2 && b1 == b2) ||
(a1 == b2 && b1 == a2)
data PgPath
= PgPathOpen (NE.NonEmpty PgPoint)
| PgPathClosed (NE.NonEmpty PgPoint)
deriving (Show, Eq, Ord)
data PgPolygon
= PgPolygon (NE.NonEmpty PgPoint)
deriving (Show, Eq, Ord)
data PgCircle = PgCircle {-# UNPACK #-} !PgPoint {-# UNPACK #-} !Double
deriving (Show, Eq, Ord)
encodePgPoint :: PgPoint -> Builder
encodePgPoint (PgPoint x y) =
"(" <> doubleDec x <> "," <> doubleDec y <> ")"
instance HasSqlValueSyntax PgValueSyntax PgPoint where
sqlValueSyntax pt =
PgValueSyntax $ emitBuilder ("'" <> encodePgPoint pt <> "'")
instance HasSqlValueSyntax PgValueSyntax PgLine where
sqlValueSyntax (PgLine a b c) =
PgValueSyntax $ emitBuilder ("'{" <> doubleDec a <> "," <> doubleDec b <> "," <> doubleDec c <> "}'")
instance HasSqlValueSyntax PgValueSyntax PgLineSegment where
sqlValueSyntax (PgLineSegment a b) =
PgValueSyntax $ emitBuilder ("'(" <> encodePgPoint a <> "," <> encodePgPoint b <> ")'")
instance HasSqlValueSyntax PgValueSyntax PgBox where
sqlValueSyntax (PgBox a b) =
PgValueSyntax $ emitBuilder ("'(" <> encodePgPoint a <> "," <> encodePgPoint b <> ")'")
instance Pg.FromField PgPoint where
fromField field Nothing = Pg.returnError Pg.UnexpectedNull field ""
fromField field (Just d) =
if Pg.typeOid field /= Pg.typoid Pg.point
then Pg.returnError Pg.Incompatible field ""
else case parseOnly pgPointParser d of
Left err -> Pg.returnError Pg.ConversionFailed field ("PgPoint: " ++ err)
Right pt -> pure pt
instance FromBackendRow Postgres PgPoint
pgPointParser :: Parser PgPoint
pgPointParser = PgPoint <$> (char '(' *> double <* char ',')
<*> (double <* char ')')
instance Pg.FromField PgBox where
fromField field Nothing = Pg.returnError Pg.UnexpectedNull field ""
fromField field (Just d) =
if Pg.typeOid field /= Pg.typoid Pg.box
then Pg.returnError Pg.Incompatible field ""
else case parseOnly boxParser d of
Left err -> Pg.returnError Pg.ConversionFailed field ("PgBox: " ++ err)
Right box -> pure box
where
boxParser = PgBox <$> (pgPointParser <* char ',')
<*> pgPointParser
instance FromBackendRow Postgres PgBox
data PgSetOf (tbl :: (* -> *) -> *)
pgUnnest' :: forall tbl db s
. Beamable tbl
=> (TablePrefix -> PgSyntax)
-> Q Postgres db s (QExprTable Postgres s tbl)
pgUnnest' q =
Q (liftF (QAll (\pfx alias ->
PgFromSyntax . mconcat $
[ q pfx, emit " "
, pgQuotedIdentifier alias
, pgParens (pgSepBy (emit ", ") (allBeamValues (\(Columnar' (TableField _ nm)) -> pgQuotedIdentifier nm) tblFields))
])
(tableFieldsToExpressions tblFields)
(\_ -> Nothing) snd))
where
tblFields :: TableSettings tbl
tblFields =
evalState (zipBeamFieldsM (\_ _ ->
do i <- get
put (i + 1)
let fieldNm = fromString ("r" ++ show i)
pure (Columnar' (TableField (pure fieldNm) fieldNm)))
tblSkeleton tblSkeleton) (0 :: Int)
pgUnnest :: forall tbl db s
. Beamable tbl
=> QExpr Postgres s (PgSetOf tbl)
-> Q Postgres db s (QExprTable Postgres s tbl)
pgUnnest (QExpr q) =
pgUnnest' (\t -> pgParens (fromPgExpression (q t)))
data PgUnnestArrayTbl a f = PgUnnestArrayTbl (C f a)
deriving Generic
instance Beamable (PgUnnestArrayTbl a)
pgUnnestArray :: QExpr Postgres s (V.Vector a)
-> Q Postgres db s (QExpr Postgres s a)
pgUnnestArray (QExpr q) =
fmap (\(PgUnnestArrayTbl x) -> x) $
pgUnnest' (\t -> emit "UNNEST" <> pgParens (fromPgExpression (q t)))
data PgUnnestArrayWithOrdinalityTbl a f = PgUnnestArrayWithOrdinalityTbl (C f Int) (C f a)
deriving Generic
instance Beamable (PgUnnestArrayWithOrdinalityTbl a)
pgUnnestArrayWithOrdinality :: QExpr Postgres s (V.Vector a)
-> Q Postgres db s (QExpr Postgres s Int, QExpr Postgres s a)
pgUnnestArrayWithOrdinality (QExpr q) =
fmap (\(PgUnnestArrayWithOrdinalityTbl i x) -> (i, x)) $
pgUnnest' (\t -> emit "UNNEST" <> pgParens (fromPgExpression (q t)) <> emit " WITH ORDINALITY")
instance HasDefaultSqlDataType Postgres PgPoint where
defaultSqlDataType _ _ _ = pgPointType
instance HasDefaultSqlDataType Postgres PgLine where
defaultSqlDataType _ _ _ = pgLineType
instance HasDefaultSqlDataType Postgres PgLineSegment where
defaultSqlDataType _ _ _ = pgLineSegmentType
instance HasDefaultSqlDataType Postgres PgBox where
defaultSqlDataType _ _ _ = pgBoxType
instance HasDefaultSqlDataType Postgres TsQuery where
defaultSqlDataType _ _ _ = pgTsQueryType
instance HasDefaultSqlDataType Postgres TsVector where
defaultSqlDataType _ _ _ = pgTsVectorType
instance HasDefaultSqlDataType Postgres (PgJSON a) where
defaultSqlDataType _ _ _ = pgJsonType
instance HasDefaultSqlDataType Postgres (PgJSONB a) where
defaultSqlDataType _ _ _ = pgJsonbType
instance HasDefaultSqlDataType Postgres PgMoney where
defaultSqlDataType _ _ _ = pgMoneyType
instance HasDefaultSqlDataType Postgres a
=> HasDefaultSqlDataType Postgres (V.Vector a) where
defaultSqlDataType _ be embedded =
pgUnboundedArrayType (defaultSqlDataType (Proxy :: Proxy a) be embedded)