graphql-engine/server/src-lib/Hasura/RQL/DML/Count.hs

128 lines
3.8 KiB
Haskell

module Hasura.RQL.DML.Count
( CountQueryP1(..)
, validateCountQWith
, validateCountQ
, runCount
, countQToTx
) where
import Hasura.Prelude
import qualified Data.ByteString.Builder as BB
import qualified Data.Sequence as DS
import Data.Aeson
import Instances.TH.Lift ()
import qualified Database.PG.Query as Q
import qualified Hasura.Backends.Postgres.SQL.DML as S
import Hasura.Backends.Postgres.SQL.Types
import Hasura.EncJSON
import Hasura.RQL.DML.Internal
import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
import Hasura.SQL.Types
data CountQueryP1
= CountQueryP1
{ cqp1Table :: !QualifiedTable
, cqp1Where :: !(AnnBoolExpSQL 'Postgres, Maybe (AnnBoolExpSQL 'Postgres))
, cqp1Distinct :: !(Maybe [PGCol])
} deriving (Eq)
mkSQLCount
:: CountQueryP1 -> S.Select
mkSQLCount (CountQueryP1 tn (permFltr, mWc) mDistCols) =
S.mkSelect
{ S.selExtr = [S.Extractor S.countStar Nothing]
, S.selFrom = Just $ S.FromExp
[S.mkSelFromExp False innerSel $ TableName "r"]
}
where
finalWC =
toSQLBoolExp (S.QualTable tn) $
maybe permFltr (andAnnBoolExps permFltr) mWc
innerSel = partSel
{ S.selFrom = Just $ S.mkSimpleFromExp tn
, S.selWhere = S.WhereFrag <$> Just finalWC
}
partSel = case mDistCols of
Just distCols ->
let extrs = flip map distCols $ \c -> S.Extractor (S.mkSIdenExp c) Nothing
in S.mkSelect
{ S.selDistinct = Just S.DistinctSimple
, S.selExtr = extrs
}
Nothing -> S.mkSelect
{ S.selExtr = [S.Extractor (S.SEStar Nothing) Nothing] }
-- SELECT count(*) FROM (SELECT DISTINCT c1, .. cn FROM .. WHERE ..) r;
-- SELECT count(*) FROM (SELECT * FROM .. WHERE ..) r;
validateCountQWith
:: (UserInfoM m, QErrM m, CacheRM m)
=> SessVarBldr 'Postgres m
-> (PGColumnType -> Value -> m S.SQLExp)
-> CountQuery
-> m CountQueryP1
validateCountQWith sessVarBldr prepValBldr (CountQuery qt mDistCols mWhere) = do
tableInfo <- askTabInfo qt
-- Check if select is allowed
selPerm <- modifyErr (<> selNecessaryMsg) $
askSelPermInfo tableInfo
let colInfoMap = _tciFieldInfoMap $ _tiCoreInfo tableInfo
forM_ mDistCols $ \distCols -> do
let distColAsrns = [ checkSelOnCol selPerm
, assertPGCol colInfoMap relInDistColsErr]
withPathK "distinct" $ verifyAsrns distColAsrns distCols
-- convert the where clause
annSQLBoolExp <- forM mWhere $ \be ->
withPathK "where" $
convBoolExp colInfoMap selPerm be sessVarBldr prepValBldr
resolvedSelFltr <- convAnnBoolExpPartialSQL sessVarBldr $
spiFilter selPerm
return $ CountQueryP1
qt
(resolvedSelFltr, annSQLBoolExp)
mDistCols
where
selNecessaryMsg =
"; \"count\" is only allowed if the role "
<> "has \"select\" permissions on the table"
relInDistColsErr =
"Relationships can't be used in \"distinct\"."
validateCountQ
:: (QErrM m, UserInfoM m, CacheRM m)
=> CountQuery -> m (CountQueryP1, DS.Seq Q.PrepArg)
validateCountQ =
runDMLP1T . validateCountQWith sessVarFromCurrentSetting binRHSBuilder
countQToTx
:: (QErrM m, MonadTx m)
=> (CountQueryP1, DS.Seq Q.PrepArg) -> m EncJSON
countQToTx (u, p) = do
qRes <- liftTx $ Q.rawQE dmlTxErrorHandler
(Q.fromBuilder countSQL) (toList p) True
return $ encJFromBuilder $ encodeCount qRes
where
countSQL = toSQL $ mkSQLCount u
encodeCount (Q.SingleRow (Identity c)) =
BB.byteString "{\"count\":" <> BB.intDec c <> BB.char7 '}'
runCount
:: (QErrM m, UserInfoM m, CacheRM m, MonadTx m)
=> CountQuery -> m EncJSON
runCount q =
validateCountQ q >>= countQToTx