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

141 lines
4.6 KiB
Haskell
Raw Normal View History

module Hasura.RQL.DML.Count
( CountQueryP1(..)
, validateCountQWith
, validateCountQ
, runCount
, countQToTx
) where
2018-06-27 16:11:32 +03:00
2020-08-27 19:36:39 +03:00
import Hasura.Prelude
import qualified Data.ByteString.Builder as BB
import qualified Data.Sequence as DS
import Control.Monad.Trans.Control (MonadBaseControl)
2018-06-27 16:11:32 +03:00
import Data.Aeson
import qualified Database.PG.Query as Q
import qualified Hasura.Backends.Postgres.SQL.DML as S
import qualified Hasura.Tracing as Tracing
2018-06-27 16:11:32 +03:00
import Hasura.Backends.Postgres.SQL.Types
import Hasura.Backends.Postgres.Translate.BoolExp
import Hasura.Base.Error
import Hasura.EncJSON
2018-06-27 16:11:32 +03:00
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Types
import Hasura.RQL.IR.BoolExp
2018-06-27 16:11:32 +03:00
import Hasura.RQL.Types
import Hasura.RQL.Types.Run
2018-06-27 16:11:32 +03:00
import Hasura.SQL.Types
import Hasura.Session
2018-06-27 16:11:32 +03:00
data CountQueryP1
= CountQueryP1
{ cqp1Table :: !QualifiedTable
, cqp1Where :: !(AnnBoolExpSQL ('Postgres 'Vanilla), Maybe (AnnBoolExpSQL ('Postgres 'Vanilla)))
2018-06-27 16:11:32 +03:00
, cqp1Distinct :: !(Maybe [PGCol])
} deriving (Eq)
2018-06-27 16:11:32 +03:00
mkSQLCount
:: CountQueryP1 -> S.Select
mkSQLCount (CountQueryP1 tn (permFltr, mWc) mDistCols) =
S.mkSelect
{ S.selExtr = [S.Extractor S.countStar Nothing]
2018-06-27 16:11:32 +03:00
, S.selFrom = Just $ S.FromExp
[S.mkSelFromExp False innerSel $ TableName "r"]
}
where
finalWC =
toSQLBoolExp (S.QualTable tn) $
maybe permFltr (andAnnBoolExps permFltr) mWc
2018-06-27 16:11:32 +03:00
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] }
2018-06-27 16:11:32 +03:00
-- SELECT count(*) FROM (SELECT DISTINCT c1, .. cn FROM .. WHERE ..) r;
-- SELECT count(*) FROM (SELECT * FROM .. WHERE ..) r;
validateCountQWith
:: (UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m)
=> SessVarBldr ('Postgres 'Vanilla) m
-> (ColumnType ('Postgres 'Vanilla) -> Value -> m S.SQLExp)
2018-06-27 16:11:32 +03:00
-> CountQuery
-> m CountQueryP1
validateCountQWith sessVarBldr prepValBldr (CountQuery qt _ mDistCols mWhere) = do
tableInfo <- askTabInfoSource qt
2018-06-27 16:11:32 +03:00
-- Check if select is allowed
selPerm <- modifyErr (<> selNecessaryMsg) $
askSelPermInfo tableInfo
let colInfoMap = _tciFieldInfoMap $ _tiCoreInfo tableInfo
2018-06-27 16:11:32 +03:00
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 qt (valueParserWithCollectableType prepValBldr)
2019-04-17 12:48:41 +03:00
resolvedSelFltr <- convAnnBoolExpPartialSQL sessVarBldr $
spiFilter selPerm
2018-06-27 16:11:32 +03:00
return $ CountQueryP1
qt
2019-04-17 12:48:41 +03:00
(resolvedSelFltr, annSQLBoolExp)
2018-06-27 16:11:32 +03:00
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 query = do
let source = cqSource query
tableCache :: TableCache ('Postgres 'Vanilla) <- askTableCache source
flip runTableCacheRT (source, tableCache) $ runDMLP1T $
validateCountQWith sessVarFromCurrentSetting binRHSBuilder query
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
2018-06-27 16:11:32 +03:00
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
, MonadIO m, MonadBaseControl IO m
, Tracing.MonadTrace m, MetadataM m
)
=> CountQuery -> m EncJSON
runCount q = do
sourceConfig <- askSourceConfig @('Postgres 'Vanilla) (cqSource q)
validateCountQ q >>= runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadOnly . countQToTx