566 lines
21 KiB
Haskell
566 lines
21 KiB
Haskell
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
|
|
--
|
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
|
|
|
|
module Database.Esqueleto.Utils
|
|
( true, false
|
|
, justVal, justValList, toValues
|
|
, isJust, alt
|
|
, isInfixOf, hasInfix
|
|
, strConcat, substring
|
|
, (=?.), (?=.)
|
|
, or, and
|
|
, any, all
|
|
, subSelectAnd, subSelectOr
|
|
, mkExactFilter, mkExactFilterWith
|
|
, mkExactFilterLast, mkExactFilterLastWith
|
|
, mkContainsFilter, mkContainsFilterWith
|
|
, mkDayFilter, mkDayFilterFrom, mkDayFilterTo
|
|
, mkExistsFilter
|
|
, anyFilter, allFilter
|
|
, orderByList
|
|
, orderByOrd, orderByEnum
|
|
, strip, lower, ciEq
|
|
, selectExists, selectNotExists
|
|
, SqlHashable
|
|
, sha256
|
|
, maybe, maybe2, maybeEq, guardMaybe, unsafeCoalesce
|
|
, bool
|
|
, max, min
|
|
, greatest, least
|
|
, abs
|
|
, SqlProject(..)
|
|
, (->.), (#>>.)
|
|
, fromSqlKey
|
|
, unKey
|
|
, selectCountRows, selectCountDistinct
|
|
, selectMaybe
|
|
, day, diffDays, diffTimes
|
|
, exprLift
|
|
, explicitUnsafeCoerceSqlExprValue
|
|
, module Database.Esqueleto.Utils.TH
|
|
) where
|
|
|
|
|
|
import ClassyPrelude.Yesod hiding (isInfixOf, any, all, or, and, isJust, maybe, bool, max, min, abs)
|
|
import Data.Universe
|
|
import qualified Data.Set as Set
|
|
import qualified Data.List as List
|
|
import qualified Data.Foldable as F
|
|
import Data.List.NonEmpty (NonEmpty(..))
|
|
import qualified Database.Esqueleto.Legacy as E
|
|
import qualified Database.Esqueleto.Experimental as Ex
|
|
import qualified Database.Esqueleto.PostgreSQL as E
|
|
import qualified Database.Esqueleto.Internal.Internal as E
|
|
import Database.Esqueleto.Utils.TH
|
|
|
|
import qualified Data.Text.Lazy as Lazy (Text)
|
|
import qualified Data.ByteString.Lazy as Lazy (ByteString)
|
|
|
|
import Crypto.Hash (Digest, SHA256)
|
|
|
|
import Data.Coerce (Coercible)
|
|
|
|
import Data.Time.Clock (NominalDiffTime)
|
|
|
|
import qualified Data.Text.Lazy.Builder as Text.Builder
|
|
|
|
import Data.Monoid (Last(..))
|
|
|
|
{-# ANN any ("HLint: ignore Use any" :: String) #-}
|
|
{-# ANN all ("HLint: ignore Use all" :: String) #-}
|
|
|
|
|
|
{-# ANN any ("HLint: ignore Use any" :: String) #-}
|
|
{-# ANN all ("HLint: ignore Use all" :: String) #-}
|
|
|
|
|
|
--
|
|
-- Description : Convenience for using `Esqueleto`,
|
|
-- intended to be imported qualified
|
|
-- just like @Esqueleto@
|
|
|
|
|
|
-- ezero = E.val (0 :: Int64)
|
|
|
|
-- | Often needed with this concrete type
|
|
true :: E.SqlExpr (E.Value Bool)
|
|
true = E.val True
|
|
|
|
-- | Often needed with this concrete type
|
|
false :: E.SqlExpr (E.Value Bool)
|
|
false = E.val False
|
|
|
|
-- Timestamp larger than any other; not sure if this is a good idea to use
|
|
-- infinity :: E.SqlExpr (E.Value UTCTime)
|
|
-- infinity = unsafeSqlValue "'infinity'"
|
|
|
|
justVal :: PersistField typ => typ -> E.SqlExpr (E.Value (Maybe typ))
|
|
-- justVal = E.val . Just
|
|
justVal = E.just . E.val
|
|
|
|
justValList :: PersistField typ => [typ] -> E.SqlExpr (E.ValueList (Maybe typ))
|
|
-- justValList = E.valList . map Just
|
|
justValList = E.justList . E.valList
|
|
|
|
toValues :: PersistField typ => NonEmpty typ -> Ex.From (Ex.SqlExpr (Ex.Value typ)) -- E.From does not work here! Requires Experimental!
|
|
toValues = E.values . fmap Ex.val
|
|
|
|
infixl 4 =?.
|
|
(=?.) :: PersistField typ => E.SqlExpr (E.Value typ) -> E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value Bool)
|
|
(=?.) = (E.==.) . E.just
|
|
|
|
infixl 4 ?=.
|
|
(?=.) :: PersistField typ => E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value typ) -> E.SqlExpr (E.Value Bool)
|
|
(?=.) a b = a E.==. E.just b
|
|
|
|
-- | Negation of `isNothing` which is missing
|
|
isJust :: PersistField typ => E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value Bool)
|
|
isJust = E.not_ . E.isNothing
|
|
|
|
-- | Deprecated, use coalesce directly
|
|
alt :: PersistField typ => E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value (Maybe typ)) -> E.SqlExpr (E.Value (Maybe typ))
|
|
-- alt a b = E.case_ [(isJust a, a), (isJust b, b)] b
|
|
alt a b = E.coalesce [a,b]
|
|
|
|
infix 4 `isInfixOf`, `hasInfix`
|
|
|
|
-- | Check if the first string is contained in the text derived from the second argument
|
|
isInfixOf :: ( E.SqlString s1
|
|
, E.SqlString s2
|
|
)
|
|
=> E.SqlExpr (E.Value s1) -> E.SqlExpr (E.Value s2) -> E.SqlExpr (E.Value Bool)
|
|
isInfixOf needle strExpr = E.castString strExpr `E.ilike` (E.%) E.++. needle E.++. (E.%)
|
|
|
|
hasInfix :: ( E.SqlString s1
|
|
, E.SqlString s2
|
|
)
|
|
=> E.SqlExpr (E.Value s2) -> E.SqlExpr (E.Value s1) -> E.SqlExpr (E.Value Bool)
|
|
hasInfix = flip isInfixOf
|
|
|
|
infixl 6 `strConcat`
|
|
|
|
strConcat :: E.SqlString s
|
|
=> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s)
|
|
strConcat = E.unsafeSqlBinOp " || "
|
|
|
|
substring :: ( E.SqlString str
|
|
, Num from, Num for
|
|
)
|
|
=> E.SqlExpr (E.Value str)
|
|
-> E.SqlExpr (E.Value from)
|
|
-> E.SqlExpr (E.Value for)
|
|
-> E.SqlExpr (E.Value str)
|
|
substring (E.ERaw _m1 f1) (E.ERaw _m2 f2) (E.ERaw _m3 f3)
|
|
= E.ERaw E.noMeta $ \_nParens info ->
|
|
let (strTLB, strVals) = f1 E.Parens info
|
|
(fromiTLB, fromiVals) = f2 E.Parens info
|
|
(foriTLB, foriVals) = f3 E.Parens info
|
|
in ( "SUBSTRING" <> E.parens (E.parens strTLB <> " FROM " <> E.parens fromiTLB <> " FOR " <> E.parens foriTLB)
|
|
, strVals <> fromiVals <> foriVals
|
|
)
|
|
|
|
explicitUnsafeCoerceSqlExprValue :: forall b a.
|
|
Text
|
|
-> E.SqlExpr (E.Value a)
|
|
-> E.SqlExpr (E.Value b)
|
|
explicitUnsafeCoerceSqlExprValue typ (E.ERaw _m1 f1) = E.ERaw E.noMeta $ \_nParens info ->
|
|
let (valTLB, valVals) = f1 E.Parens info
|
|
in ( E.parens valTLB <> " :: " <> Text.Builder.fromText typ
|
|
, valVals
|
|
)
|
|
|
|
and, or :: Foldable f => f (E.SqlExpr (E.Value Bool)) -> E.SqlExpr (E.Value Bool)
|
|
and = F.foldr (E.&&.) true
|
|
or = F.foldr (E.||.) false
|
|
|
|
-- | Given a test and a set of values, check whether anyone succeeds the test
|
|
-- WARNING: SQL leaves it explicitely unspecified whether `||` is short curcuited (i.e. lazily evaluated)
|
|
any :: MonoFoldable f => (Element f -> E.SqlExpr (E.Value Bool)) -> f -> E.SqlExpr (E.Value Bool)
|
|
any test = or . map test . otoList
|
|
|
|
-- | Given a test and a set of values, check whether all succeeds the test
|
|
-- WARNING: SQL leaves it explicitely unspecified whether `&&` is short curcuited (i.e. lazily evaluated)
|
|
all :: MonoFoldable f => (Element f -> E.SqlExpr (E.Value Bool)) -> f -> E.SqlExpr (E.Value Bool)
|
|
all test = and . map test . otoList
|
|
|
|
subSelectAnd, subSelectOr :: E.SqlQuery (E.SqlExpr (E.Value Bool)) -> E.SqlExpr (E.Value Bool)
|
|
subSelectAnd q = parens . E.subSelectUnsafe $ flip (E.unsafeSqlAggregateFunction "bool_and" E.AggModeAll) [] <$> q
|
|
subSelectOr q = parens . E.subSelectUnsafe $ flip (E.unsafeSqlAggregateFunction "bool_or" E.AggModeAll) [] <$> q
|
|
|
|
parens :: E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a)
|
|
parens = E.unsafeSqlFunction ""
|
|
|
|
|
|
-- Allow usage of Tuples as DbtRowKey, i.e. SqlIn instances for tuples
|
|
$(sqlInTuples [2..16])
|
|
|
|
-- | Example for usage of unValueN
|
|
_exampleUnValueN :: (E.Value a, E.Value b, E.Value c) -> (a,b,c)
|
|
_exampleUnValueN = $(unValueN 3)
|
|
|
|
-- | Example for usage of unValueNIs
|
|
_exampleUnValueNIs :: (E.Value a, b, E.Value c) -> (a,b,c)
|
|
_exampleUnValueNIs = $(unValueNIs 3 [1,3])
|
|
|
|
-- | Example for usage of sqlIJproj
|
|
_queryFeaturesDegree :: (a `E.InnerJoin` b `E.InnerJoin` c) -> b
|
|
_queryFeaturesDegree = $(sqlIJproj 3 2)
|
|
|
|
|
|
-- | generic filter creation for dbTable
|
|
-- Given a lens-like function, make filter for exact matches in a collection
|
|
-- (Generalizing from Set to Foldable ok here, but gives ambigouus types elsewhere)
|
|
mkExactFilter :: (PersistField a)
|
|
=> (t -> E.SqlExpr (E.Value a)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Set.Set a -- ^ needle collection
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkExactFilter = mkExactFilterWith id
|
|
|
|
-- | like `mkExactFilter` but allows for conversion; convenient in conjunction with `anyFilter` and `allFilter`
|
|
mkExactFilterWith :: (PersistField b)
|
|
=> (a -> b) -- ^ type conversion
|
|
-> (t -> E.SqlExpr (E.Value b)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Set.Set a -- ^ needle collection
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkExactFilterWith cast lenslike row criterias
|
|
| Set.null criterias = true
|
|
| otherwise = lenslike row `E.in_` E.valList (cast <$> Set.toList criterias)
|
|
|
|
-- | generic filter creation for dbTable
|
|
-- Given a lens-like function, make filter for exact matches against last element of a collection
|
|
mkExactFilterLast :: (PersistField a)
|
|
=> (t -> E.SqlExpr (E.Value a)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Last a -- ^ needle
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkExactFilterLast = mkExactFilterLastWith id
|
|
|
|
-- | like `mkExactFilterLast` but allows for conversion; convenient in conjunction with `anyFilter` and `allFilter`
|
|
mkExactFilterLastWith :: (PersistField b)
|
|
=> (a -> b) -- ^ type conversion
|
|
-> (t -> E.SqlExpr (E.Value b)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Last a -- ^ needle
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkExactFilterLastWith cast lenslike row criterias
|
|
| Last (Just crit) <- criterias = lenslike row E.==. E.val (cast crit)
|
|
| otherwise = true
|
|
|
|
-- | generic filter creation for dbTable
|
|
-- Given a lens-like function, make filter searching for needles in String-like elements
|
|
-- (Keep Set here to ensure that there are no duplicates)
|
|
mkContainsFilter :: E.SqlString a
|
|
=> (t -> E.SqlExpr (E.Value a)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Set.Set a -- ^ needle collection
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkContainsFilter = mkContainsFilterWith id
|
|
|
|
-- | like `mkContainsFilter` but allows for conversion; convenient in conjunction with `anyFilter` and `allFilter`
|
|
mkContainsFilterWith :: E.SqlString b
|
|
=> (a -> b)
|
|
-> (t -> E.SqlExpr (E.Value b)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Set.Set a -- ^ needle collection
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkContainsFilterWith cast lenslike row criterias
|
|
| Set.null criterias = true
|
|
| otherwise = any (hasInfix $ lenslike row) (E.val . cast <$> Set.toList criterias)
|
|
|
|
|
|
mkDayFilter :: (t -> E.SqlExpr (E.Value UTCTime)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Last Day -- ^ a day to filter for
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkDayFilter lenslike row criterias
|
|
| Last (Just crit) <- criterias = day (lenslike row) E.==. E.val crit
|
|
| otherwise = true
|
|
|
|
|
|
mkDayFilterFrom :: (t -> E.SqlExpr (E.Value UTCTime)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Last Day -- ^ a day range to filter for
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkDayFilterFrom lenslike row criterias
|
|
| Last (Just crit) <- criterias = day (lenslike row) E.>=. E.val crit
|
|
| otherwise = true
|
|
|
|
mkDayFilterTo :: (t -> E.SqlExpr (E.Value UTCTime)) -- ^ getter from query to searched element
|
|
-> t -- ^ query row
|
|
-> Last Day -- ^ a day range to filter for
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkDayFilterTo lenslike row criterias
|
|
| Last (Just crit) <- criterias = day (lenslike row) E.<=. E.val crit
|
|
| otherwise = true
|
|
|
|
|
|
mkExistsFilter :: PathPiece a
|
|
=> (t -> a -> E.SqlQuery ())
|
|
-> t
|
|
-> Set.Set a
|
|
-> E.SqlExpr (E.Value Bool)
|
|
mkExistsFilter query row criterias
|
|
| Set.null criterias = true
|
|
| otherwise = any (E.exists . query row) $ Set.toList criterias
|
|
|
|
-- | Combine several filters, using logical or
|
|
anyFilter :: Foldable f
|
|
=> f (t -> cs -> E.SqlExpr (E.Value Bool))
|
|
-> (t -> cs -> E.SqlExpr (E.Value Bool))
|
|
anyFilter fltrs needle criterias = F.foldr aux false fltrs
|
|
where
|
|
aux fltr acc = fltr needle criterias E.||. acc
|
|
|
|
-- | Combine several filters, using logical and
|
|
allFilter :: Foldable f
|
|
=> f (t -> cs -> E.SqlExpr (E.Value Bool))
|
|
-> (t -> cs -> E.SqlExpr (E.Value Bool))
|
|
allFilter fltrs needle criterias = F.foldr aux true fltrs
|
|
where
|
|
aux fltr acc = fltr needle criterias E.&&. acc
|
|
|
|
|
|
orderByList :: PersistField a => [a] -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int)
|
|
orderByList vals
|
|
= let sortUni = zip [1..] vals -- memoize this, might not work due to polymorphism
|
|
in \x -> E.case_ [ (x E.==. E.val u, E.val i) | (i,u) <- sortUni ] (E.val . succ $ List.length vals)
|
|
|
|
orderByOrd :: (Ord a, Finite a, PersistField a) => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int)
|
|
orderByOrd = orderByList $ List.sort universeF
|
|
|
|
orderByEnum :: (Enum a, Finite a, PersistField a) => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int)
|
|
orderByEnum = orderByList $ List.sortOn fromEnum universeF
|
|
|
|
lower :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s)
|
|
lower = E.unsafeSqlFunction "LOWER"
|
|
|
|
strip :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s)
|
|
strip = E.unsafeSqlFunction "TRIM"
|
|
|
|
infix 4 `ciEq`
|
|
|
|
ciEq :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool)
|
|
ciEq a b = lower a E.==. lower b
|
|
|
|
|
|
selectExists, selectNotExists :: forall m a. MonadIO m => E.SqlQuery a -> E.SqlReadT m Bool
|
|
selectExists query = do
|
|
res <- E.select . return . E.exists $ void query
|
|
|
|
case res of
|
|
[E.Value b] -> return b
|
|
_other -> error "SELECT EXISTS ... returned zero or more than one rows"
|
|
selectNotExists = fmap not . selectExists
|
|
|
|
|
|
class SqlHashable a
|
|
instance SqlHashable Text
|
|
instance SqlHashable ByteString
|
|
instance SqlHashable Lazy.Text
|
|
instance SqlHashable Lazy.ByteString
|
|
|
|
|
|
sha256 :: SqlHashable a => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value (Digest SHA256))
|
|
sha256 = E.unsafeSqlFunction "digest" . (, E.val "sha256" :: E.SqlExpr (E.Value Text))
|
|
|
|
|
|
maybe :: (PersistField a, PersistField b)
|
|
=> E.SqlExpr (E.Value b)
|
|
-> (E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value b))
|
|
-> E.SqlExpr (E.Value (Maybe a))
|
|
-> E.SqlExpr (E.Value b)
|
|
maybe onNothing onJust val = E.case_
|
|
[ E.when_
|
|
(E.not_ $ E.isNothing val)
|
|
E.then_
|
|
(onJust $ E.veryUnsafeCoerceSqlExprValue val)
|
|
]
|
|
(E.else_ onNothing)
|
|
|
|
maybe2 :: (PersistField a, PersistField b, PersistField c)
|
|
=> E.SqlExpr (E.Value c)
|
|
-> (E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value b) -> E.SqlExpr (E.Value c))
|
|
-> E.SqlExpr (E.Value (Maybe a))
|
|
-> E.SqlExpr (E.Value (Maybe b))
|
|
-> E.SqlExpr (E.Value c)
|
|
maybe2 onNothing onJust val1 val2 = E.case_
|
|
[ E.when_
|
|
(isJust val1 E.&&. isJust val2)
|
|
E.then_
|
|
(onJust (E.veryUnsafeCoerceSqlExprValue val1) (E.veryUnsafeCoerceSqlExprValue val2))
|
|
]
|
|
(E.else_ onNothing)
|
|
|
|
infix 4 `maybeEq`
|
|
|
|
maybeEq :: PersistField a
|
|
=> E.SqlExpr (E.Value (Maybe a))
|
|
-> E.SqlExpr (E.Value (Maybe a))
|
|
-> E.SqlExpr (E.Value Bool)
|
|
-- ^ `E.==.` but treat `E.nothing` as identical
|
|
maybeEq a b = E.case_
|
|
[ E.when_
|
|
(E.isNothing a)
|
|
E.then_
|
|
(E.isNothing b)
|
|
, E.when_
|
|
(E.isNothing b)
|
|
E.then_
|
|
false -- (E.isNothing a)
|
|
]
|
|
(E.else_ $ a E.==. b)
|
|
|
|
guardMaybe :: PersistField a
|
|
=> E.SqlExpr (E.Value (Maybe a))
|
|
-> E.SqlQuery (E.SqlExpr (E.Value a))
|
|
guardMaybe mVal = do
|
|
E.where_ $ isJust mVal
|
|
return $ E.veryUnsafeCoerceSqlExprValue mVal
|
|
|
|
bool :: PersistField a
|
|
=> E.SqlExpr (E.Value a)
|
|
-> E.SqlExpr (E.Value a)
|
|
-> E.SqlExpr (E.Value Bool)
|
|
-> E.SqlExpr (E.Value a)
|
|
bool onFalse onTrue val = E.case_
|
|
[ E.when_
|
|
val
|
|
E.then_
|
|
onTrue
|
|
]
|
|
(E.else_ onFalse)
|
|
|
|
-- called see greatest and least within postgresql
|
|
-- TODO: this is buggy! Both return always the first argument if any argument is NULL!
|
|
max, min :: PersistField a
|
|
=> E.SqlExpr (E.Value a)
|
|
-> E.SqlExpr (E.Value a)
|
|
-> E.SqlExpr (E.Value a)
|
|
max a b = bool a b $ b E.>. a
|
|
min a b = bool a b $ b E.<. a
|
|
|
|
-- these alternatives for max/min ought to be more efficient; note that NULL is avoided by greatest/least
|
|
greatest :: PersistField a => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a)
|
|
greatest a b = E.unsafeSqlFunction "GREATEST" $ E.toArgList (a,b)
|
|
|
|
least :: PersistField a => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a)
|
|
least a b = E.unsafeSqlFunction "LEAST" $ E.toArgList (a,b)
|
|
|
|
abs :: (PersistField a, Num a)
|
|
=> E.SqlExpr (E.Value a)
|
|
-> E.SqlExpr (E.Value a)
|
|
abs x = bool (E.val 0 E.-. x) x $ x E.>. E.val 0
|
|
|
|
unsafeCoalesce :: E.PersistField a => [E.SqlExpr (E.Value (Maybe a))] -> E.SqlExpr (E.Value a)
|
|
unsafeCoalesce = E.veryUnsafeCoerceSqlExprValue . E.coalesce
|
|
|
|
|
|
class (PersistEntity entity, PersistField value, PersistField value') => SqlProject entity value entity' value' | entity value entity' -> value', entity value value' -> entity' where
|
|
sqlProject :: E.SqlExpr entity' -> EntityField entity value -> E.SqlExpr (E.Value value')
|
|
unSqlProject :: forall p1 p2. p1 entity -> p2 entity' -> value -> value'
|
|
unSqlProjectExpr :: forall p1 p2. p1 entity -> p2 entity' -> E.SqlExpr (E.Value value) -> E.SqlExpr (E.Value value')
|
|
|
|
instance (PersistEntity val, PersistField typ) => SqlProject val typ (E.Entity val) typ where
|
|
sqlProject = (E.^.)
|
|
unSqlProject _ _ = id
|
|
unSqlProjectExpr _ _ = id
|
|
|
|
instance (PersistEntity val, PersistField typ) => SqlProject val typ (Maybe (E.Entity val)) (Maybe typ) where
|
|
sqlProject = (E.?.)
|
|
unSqlProject _ _ = Just
|
|
unSqlProjectExpr _ _ = E.just
|
|
|
|
infixl 8 ->.
|
|
|
|
(->.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value b)
|
|
(->.) expr t = E.unsafeSqlBinOp "->" expr $ E.val t
|
|
|
|
infixl 8 #>>.
|
|
|
|
(#>>.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value (Maybe Text))
|
|
(#>>.) expr t = E.unsafeSqlBinOp "#>>" expr $ E.val t
|
|
|
|
|
|
fromSqlKey :: (ToBackendKey SqlBackend entity, PersistField (Key entity)) => E.SqlExpr (E.Value (Key entity)) -> E.SqlExpr (E.Value Int64)
|
|
fromSqlKey = E.veryUnsafeCoerceSqlExprValue
|
|
|
|
unKey :: ( Coercible (Key entity) a
|
|
, PersistField (Key entity), PersistField a
|
|
)
|
|
=> E.SqlExpr (E.Value (Key entity)) -> E.SqlExpr (E.Value a)
|
|
unKey = E.veryUnsafeCoerceSqlExprValue
|
|
|
|
|
|
selectCountRows :: (Num a, PersistField a, MonadIO m) => E.SqlQuery ignored -> E.SqlReadT m a
|
|
selectCountRows q = do
|
|
res <- E.select $ E.countRows <$ q
|
|
case res of
|
|
[E.Value res']
|
|
-> return res'
|
|
_other
|
|
-> error "E.countRows did not return exactly one result"
|
|
|
|
selectCountDistinct :: (Num a, PersistField a, MonadIO m) => E.SqlQuery (E.SqlExpr (E.Value typ)) -> E.SqlReadT m a
|
|
selectCountDistinct q = do
|
|
res <- E.select $ E.countDistinct <$> q
|
|
case res of
|
|
[E.Value res']
|
|
-> return res'
|
|
_other
|
|
-> error "E.countDistinct did not return exactly one result"
|
|
|
|
selectMaybe :: (E.SqlSelect a r, MonadIO m) => E.SqlQuery a -> E.SqlReadT m (Maybe r)
|
|
selectMaybe = fmap listToMaybe . E.select . (<* E.limit 1)
|
|
|
|
|
|
day :: E.SqlExpr (E.Value UTCTime) -> E.SqlExpr (E.Value Day)
|
|
day = E.unsafeSqlCastAs "date"
|
|
|
|
infixl 6 `diffDays`, `diffTimes`
|
|
|
|
diffDays :: E.SqlExpr (E.Value Day) -> E.SqlExpr (E.Value Day) -> E.SqlExpr (E.Value Int)
|
|
-- ^ PostgreSQL is weird.
|
|
diffDays a b = E.veryUnsafeCoerceSqlExprValue $ a E.-. b
|
|
|
|
diffTimes :: E.SqlExpr (E.Value UTCTime) -> E.SqlExpr (E.Value UTCTime) -> E.SqlExpr (E.Value NominalDiffTime)
|
|
diffTimes a b = unsafeExtract "EPOCH" $ a E.-. b
|
|
|
|
unsafeExtract :: String -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value b)
|
|
unsafeExtract extr (E.ERaw _mF vF) = E.ERaw E.noMeta $ \_nParens info ->
|
|
let (vTLB, vVals) = vF E.Parens info
|
|
in ( "EXTRACT" <> E.parens (fromString extr <> " FROM " <> E.parens vTLB)
|
|
, vVals
|
|
)
|
|
|
|
|
|
class ExprLift e a | e -> a where
|
|
exprLift :: a -> e
|
|
|
|
instance PersistField a => ExprLift (E.SqlExpr (E.Value a)) a where
|
|
exprLift = E.val
|
|
|
|
instance (PersistField a, PersistField b, Finite a) => ExprLift (E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value b)) (a -> b) where
|
|
exprLift f v = E.case_
|
|
[ E.when_ (v E.==. E.val v') E.then_ (E.val $ f v')
|
|
| v' <- universeF
|
|
]
|
|
(E.else_ $ E.veryUnsafeCoerceSqlExprValue (E.nothing :: E.SqlExpr (E.Value (Maybe ()))))
|
|
|
|
instance (PersistField a1, PersistField a2, PersistField b, Finite a1, Finite a2) => ExprLift (E.SqlExpr (E.Value a1) -> E.SqlExpr (E.Value a2) -> E.SqlExpr (E.Value b)) (a1 -> a2 -> b) where
|
|
exprLift f v1 v2 = E.case_
|
|
[ E.when_ ( v1 E.==. E.val v1'
|
|
E.&&. v2 E.==. E.val v2'
|
|
)
|
|
E.then_ (E.val $ f v1' v2')
|
|
| v1' <- universeF
|
|
, v2' <- universeF
|
|
]
|
|
(E.else_ $ E.else_ $ E.veryUnsafeCoerceSqlExprValue (E.nothing :: E.SqlExpr (E.Value (Maybe ()))))
|
|
|