2018-10-31 15:51:20 +03:00
|
|
|
module Hasura.RQL.DML.Select
|
|
|
|
( selectP2
|
|
|
|
, selectAggP2
|
2019-01-25 06:31:54 +03:00
|
|
|
, funcQueryTx
|
2018-10-31 15:51:20 +03:00
|
|
|
, convSelectQuery
|
|
|
|
, getSelectDeps
|
|
|
|
, module Hasura.RQL.DML.Select.Internal
|
2018-12-13 10:26:15 +03:00
|
|
|
, runSelect
|
2018-10-31 15:51:20 +03:00
|
|
|
)
|
|
|
|
where
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
import Data.Aeson.Types
|
2018-10-31 15:51:20 +03:00
|
|
|
import Instances.TH.Lift ()
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-10-31 15:51:20 +03:00
|
|
|
import qualified Data.HashMap.Strict as HM
|
|
|
|
import qualified Data.HashSet as HS
|
|
|
|
import qualified Data.List.NonEmpty as NE
|
|
|
|
import qualified Data.Sequence as DS
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.DML.Internal
|
2018-10-31 15:51:20 +03:00
|
|
|
import Hasura.RQL.DML.Select.Internal
|
2018-06-27 16:11:32 +03:00
|
|
|
import Hasura.RQL.GBoolExp
|
|
|
|
import Hasura.RQL.Types
|
|
|
|
import Hasura.SQL.Types
|
|
|
|
|
2018-10-31 15:51:20 +03:00
|
|
|
import qualified Database.PG.Query as Q
|
|
|
|
import qualified Hasura.SQL.DML as S
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
convSelCol :: (UserInfoM m, QErrM m, CacheRM m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> FieldInfoMap
|
|
|
|
-> SelPermInfo
|
|
|
|
-> SelCol
|
|
|
|
-> m [ExtCol]
|
|
|
|
convSelCol _ _ (SCExtSimple cn) =
|
|
|
|
return [ECSimple cn]
|
|
|
|
convSelCol fieldInfoMap _ (SCExtRel rn malias selQ) = do
|
|
|
|
-- Point to the name key
|
|
|
|
let pgWhenRelErr = "only relationships can be expanded"
|
|
|
|
relInfo <- withPathK "name" $
|
|
|
|
askRelType fieldInfoMap rn pgWhenRelErr
|
2018-11-16 15:40:23 +03:00
|
|
|
let (RelInfo _ _ _ relTab _) = relInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
(rfim, rspi) <- fetchRelDet rn relTab
|
|
|
|
resolvedSelQ <- resolveStar rfim rspi selQ
|
|
|
|
return [ECRel rn malias resolvedSelQ]
|
|
|
|
convSelCol fieldInfoMap spi (SCStar wildcard) =
|
|
|
|
convWildcard fieldInfoMap spi wildcard
|
|
|
|
|
|
|
|
convWildcard
|
2018-12-13 10:26:15 +03:00
|
|
|
:: (UserInfoM m, QErrM m, CacheRM m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> FieldInfoMap
|
|
|
|
-> SelPermInfo
|
|
|
|
-> Wildcard
|
|
|
|
-> m [ExtCol]
|
2018-11-16 15:40:23 +03:00
|
|
|
convWildcard fieldInfoMap (SelPermInfo cols _ _ _ _ _) wildcard =
|
2018-06-27 16:11:32 +03:00
|
|
|
case wildcard of
|
|
|
|
Star -> return simpleCols
|
|
|
|
(StarDot wc) -> (simpleCols ++) <$> (catMaybes <$> relExtCols wc)
|
|
|
|
where
|
|
|
|
(pgCols, relColInfos) = partitionFieldInfosWith (pgiName, id) $
|
|
|
|
HM.elems fieldInfoMap
|
|
|
|
|
|
|
|
simpleCols = map ECSimple $ filter (`HS.member` cols) pgCols
|
|
|
|
|
|
|
|
mkRelCol wc relInfo = do
|
|
|
|
let relName = riName relInfo
|
|
|
|
relTab = riRTable relInfo
|
|
|
|
relTabInfo <- fetchRelTabInfo relTab
|
|
|
|
mRelSelPerm <- askPermInfo' PASelect relTabInfo
|
|
|
|
|
2018-10-05 11:56:47 +03:00
|
|
|
forM mRelSelPerm $ \rspi -> do
|
|
|
|
rExtCols <- convWildcard (tiFieldInfoMap relTabInfo) rspi wc
|
|
|
|
return $ ECRel relName Nothing $
|
|
|
|
SelectG rExtCols Nothing Nothing Nothing Nothing
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
relExtCols wc = mapM (mkRelCol wc) relColInfos
|
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
resolveStar :: (UserInfoM m, QErrM m, CacheRM m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> FieldInfoMap
|
|
|
|
-> SelPermInfo
|
|
|
|
-> SelectQ
|
|
|
|
-> m SelectQExt
|
|
|
|
resolveStar fim spi (SelectG selCols mWh mOb mLt mOf) = do
|
|
|
|
procOverrides <- fmap (concat . catMaybes) $ withPathK "columns" $
|
|
|
|
indexedForM selCols $ \selCol -> case selCol of
|
|
|
|
(SCStar _) -> return Nothing
|
|
|
|
_ -> Just <$> convSelCol fim spi selCol
|
|
|
|
everything <- case wildcards of
|
|
|
|
[] -> return []
|
|
|
|
_ -> convWildcard fim spi $ maximum wildcards
|
|
|
|
let extCols = unionBy equals procOverrides everything
|
|
|
|
return $ SelectG extCols mWh mOb mLt mOf
|
|
|
|
where
|
|
|
|
wildcards = lefts $ map mkEither selCols
|
|
|
|
|
|
|
|
mkEither (SCStar wc) = Left wc
|
|
|
|
mkEither selCol = Right selCol
|
|
|
|
|
|
|
|
equals (ECSimple x) (ECSimple y) = x == y
|
|
|
|
equals (ECRel x _ _) (ECRel y _ _) = x == y
|
|
|
|
equals _ _ = False
|
|
|
|
|
2018-10-05 11:56:47 +03:00
|
|
|
convOrderByElem
|
2018-12-13 10:26:15 +03:00
|
|
|
:: (UserInfoM m, QErrM m, CacheRM m)
|
2018-10-05 11:56:47 +03:00
|
|
|
=> (FieldInfoMap, SelPermInfo)
|
|
|
|
-> OrderByCol
|
|
|
|
-> m AnnObCol
|
|
|
|
convOrderByElem (flds, spi) = \case
|
|
|
|
OCPG fldName -> do
|
|
|
|
fldInfo <- askFieldInfo flds fldName
|
|
|
|
case fldInfo of
|
|
|
|
FIColumn colInfo -> do
|
|
|
|
checkSelOnCol spi (pgiName colInfo)
|
|
|
|
let ty = pgiType colInfo
|
|
|
|
if ty == PGGeography || ty == PGGeometry
|
|
|
|
then throw400 UnexpectedPayload $ mconcat
|
|
|
|
[ fldName <<> " has type 'geometry'"
|
2018-06-27 16:11:32 +03:00
|
|
|
, " and cannot be used in order_by"
|
|
|
|
]
|
2018-10-05 11:56:47 +03:00
|
|
|
else return $ AOCPG colInfo
|
|
|
|
FIRelationship _ -> throw400 UnexpectedPayload $ mconcat
|
|
|
|
[ fldName <<> " is a"
|
2018-06-27 16:11:32 +03:00
|
|
|
, " relationship and should be expanded"
|
|
|
|
]
|
2018-10-05 11:56:47 +03:00
|
|
|
OCRel fldName rest -> do
|
|
|
|
fldInfo <- askFieldInfo flds fldName
|
|
|
|
case fldInfo of
|
|
|
|
FIColumn _ -> throw400 UnexpectedPayload $ mconcat
|
|
|
|
[ fldName <<> " is a Postgres column"
|
2018-06-27 16:11:32 +03:00
|
|
|
, " and cannot be chained further"
|
|
|
|
]
|
2018-10-05 11:56:47 +03:00
|
|
|
FIRelationship relInfo -> do
|
|
|
|
when (riType relInfo == ArrRel) $
|
|
|
|
throw400 UnexpectedPayload $ mconcat
|
|
|
|
[ fldName <<> " is an array relationship"
|
|
|
|
," and can't be used in 'order_by'"
|
|
|
|
]
|
|
|
|
(relFim, relSpi) <- fetchRelDet (riName relInfo) (riRTable relInfo)
|
2018-12-12 15:58:39 +03:00
|
|
|
AOCObj relInfo (spiFilter relSpi) <$>
|
2018-10-05 11:56:47 +03:00
|
|
|
convOrderByElem (relFim, relSpi) rest
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
convSelectQ
|
2019-03-01 14:45:04 +03:00
|
|
|
:: (UserInfoM m, QErrM m, CacheRM m, HasSQLGenCtx m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> FieldInfoMap -- Table information of current table
|
|
|
|
-> SelPermInfo -- Additional select permission info
|
|
|
|
-> SelectQExt -- Given Select Query
|
|
|
|
-> (PGColType -> Value -> m S.SQLExp)
|
2018-10-05 11:56:47 +03:00
|
|
|
-> m AnnSel
|
2018-06-27 16:11:32 +03:00
|
|
|
convSelectQ fieldInfoMap selPermInfo selQ prepValBuilder = do
|
|
|
|
|
2018-10-05 11:56:47 +03:00
|
|
|
annFlds <- withPathK "columns" $
|
2018-06-27 16:11:32 +03:00
|
|
|
indexedForM (sqColumns selQ) $ \case
|
|
|
|
(ECSimple pgCol) -> do
|
2018-10-05 11:56:47 +03:00
|
|
|
colInfo <- convExtSimple fieldInfoMap selPermInfo pgCol
|
|
|
|
return (fromPGCol pgCol, FCol colInfo)
|
2018-06-27 16:11:32 +03:00
|
|
|
(ECRel relName mAlias relSelQ) -> do
|
|
|
|
annRel <- convExtRel fieldInfoMap relName mAlias relSelQ prepValBuilder
|
2018-12-12 15:58:39 +03:00
|
|
|
return ( fromRel $ fromMaybe relName mAlias
|
|
|
|
, either FObj FArr annRel
|
|
|
|
)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-11-16 15:40:23 +03:00
|
|
|
-- let spiT = spiTable selPermInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
-- Convert where clause
|
|
|
|
wClause <- forM (sqWhere selQ) $ \be ->
|
|
|
|
withPathK "where" $
|
2018-11-16 15:40:23 +03:00
|
|
|
convBoolExp' fieldInfoMap selPermInfo be prepValBuilder
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-10-26 14:57:33 +03:00
|
|
|
annOrdByML <- forM (sqOrderBy selQ) $ \(OrderByExp obItems) ->
|
2018-10-05 11:56:47 +03:00
|
|
|
withPathK "order_by" $ indexedForM obItems $ mapM $
|
|
|
|
convOrderByElem (fieldInfoMap, selPermInfo)
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-10-26 14:57:33 +03:00
|
|
|
let annOrdByM = NE.nonEmpty =<< annOrdByML
|
|
|
|
|
2018-08-06 15:15:08 +03:00
|
|
|
-- validate limit and offset values
|
|
|
|
withPathK "limit" $ mapM_ onlyPositiveInt mQueryLimit
|
|
|
|
withPathK "offset" $ mapM_ onlyPositiveInt mQueryOffset
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-10-31 15:51:20 +03:00
|
|
|
let tabFrom = TableFrom (spiTable selPermInfo) Nothing
|
2018-10-26 12:02:44 +03:00
|
|
|
tabPerm = TablePerm (spiFilter selPermInfo) mPermLimit
|
2019-03-01 14:45:04 +03:00
|
|
|
tabArgs = TableArgs wClause annOrdByM mQueryLimit
|
|
|
|
(S.intToSQLExp <$> mQueryOffset) Nothing
|
|
|
|
|
|
|
|
strfyNum <- stringifyNum <$> askSQLGenCtx
|
|
|
|
return $ AnnSelG annFlds tabFrom tabPerm tabArgs strfyNum
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-08-06 15:15:08 +03:00
|
|
|
where
|
|
|
|
mQueryOffset = sqOffset selQ
|
|
|
|
mQueryLimit = sqLimit selQ
|
|
|
|
mPermLimit = spiLimit selPermInfo
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
convExtSimple
|
2018-12-13 10:26:15 +03:00
|
|
|
:: (UserInfoM m, QErrM m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> FieldInfoMap
|
|
|
|
-> SelPermInfo
|
|
|
|
-> PGCol
|
2018-10-05 11:56:47 +03:00
|
|
|
-> m PGColInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
convExtSimple fieldInfoMap selPermInfo pgCol = do
|
|
|
|
checkSelOnCol selPermInfo pgCol
|
2018-10-05 11:56:47 +03:00
|
|
|
askPGColInfo fieldInfoMap pgCol relWhenPGErr
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
|
|
|
relWhenPGErr = "relationships have to be expanded"
|
|
|
|
|
|
|
|
convExtRel
|
2019-03-01 14:45:04 +03:00
|
|
|
:: (UserInfoM m, QErrM m, CacheRM m, HasSQLGenCtx m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> FieldInfoMap
|
|
|
|
-> RelName
|
|
|
|
-> Maybe RelName
|
|
|
|
-> SelectQExt
|
|
|
|
-> (PGColType -> Value -> m S.SQLExp)
|
2018-12-12 15:58:39 +03:00
|
|
|
-> m (Either ObjSel ArrSel)
|
2018-06-27 16:11:32 +03:00
|
|
|
convExtRel fieldInfoMap relName mAlias selQ prepValBuilder = do
|
|
|
|
-- Point to the name key
|
|
|
|
relInfo <- withPathK "name" $
|
|
|
|
askRelType fieldInfoMap relName pgWhenRelErr
|
2018-11-16 15:40:23 +03:00
|
|
|
let (RelInfo _ relTy colMapping relTab _) = relInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
(relCIM, relSPI) <- fetchRelDet relName relTab
|
2018-10-05 11:56:47 +03:00
|
|
|
annSel <- convSelectQ relCIM relSPI selQ prepValBuilder
|
2018-12-12 15:58:39 +03:00
|
|
|
case relTy of
|
|
|
|
ObjRel -> do
|
|
|
|
when misused $ throw400 UnexpectedPayload objRelMisuseMsg
|
|
|
|
return $ Left $ AnnRelG (fromMaybe relName mAlias) colMapping annSel
|
|
|
|
ArrRel ->
|
|
|
|
return $ Right $ ASSimple $ AnnRelG (fromMaybe relName mAlias)
|
|
|
|
colMapping annSel
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
|
|
|
pgWhenRelErr = "only relationships can be expanded"
|
2018-10-05 11:56:47 +03:00
|
|
|
misused =
|
|
|
|
or [ isJust (sqWhere selQ)
|
|
|
|
, isJust (sqLimit selQ)
|
|
|
|
, isJust (sqOffset selQ)
|
|
|
|
, isJust (sqOrderBy selQ)
|
|
|
|
]
|
|
|
|
objRelMisuseMsg =
|
|
|
|
mconcat [ "when selecting an 'obj_relationship' "
|
|
|
|
, "'where', 'order_by', 'limit' and 'offset' "
|
|
|
|
, " can't be used"
|
|
|
|
]
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-10-31 15:51:20 +03:00
|
|
|
partAnnFlds
|
2018-12-12 15:58:39 +03:00
|
|
|
:: [AnnFld]
|
|
|
|
-> ([(PGCol, PGColType)], [Either ObjSel ArrSel])
|
2018-10-31 15:51:20 +03:00
|
|
|
partAnnFlds flds =
|
|
|
|
partitionEithers $ catMaybes $ flip map flds $ \case
|
|
|
|
FCol c -> Just $ Left (pgiName c, pgiType c)
|
2018-12-12 15:58:39 +03:00
|
|
|
FObj o -> Just $ Right $ Left o
|
|
|
|
FArr a -> Just $ Right $ Right a
|
2018-10-31 15:51:20 +03:00
|
|
|
FExp _ -> Nothing
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
getSelectDeps
|
2018-10-05 11:56:47 +03:00
|
|
|
:: AnnSel
|
2018-06-27 16:11:32 +03:00
|
|
|
-> [SchemaDependency]
|
2019-03-01 14:45:04 +03:00
|
|
|
getSelectDeps (AnnSelG flds tabFrm _ tableArgs _) =
|
2018-06-27 16:11:32 +03:00
|
|
|
mkParentDep tn
|
|
|
|
: fromMaybe [] whereDeps
|
|
|
|
<> colDeps
|
|
|
|
<> relDeps
|
|
|
|
<> nestedDeps
|
|
|
|
where
|
2018-10-26 12:02:44 +03:00
|
|
|
TableFrom tn _ = tabFrm
|
2018-10-05 11:56:47 +03:00
|
|
|
annWc = _taWhere tableArgs
|
2018-10-31 15:51:20 +03:00
|
|
|
(sCols, rCols) = partAnnFlds $ map snd flds
|
2018-12-12 15:58:39 +03:00
|
|
|
(objSels, arrSels) = partitionEithers rCols
|
2018-11-16 15:40:23 +03:00
|
|
|
colDeps = map (mkColDep "untyped" tn . fst) sCols
|
2018-12-12 15:58:39 +03:00
|
|
|
relDeps = map mkRelDep $ map aarName objSels
|
|
|
|
<> mapMaybe getRelName arrSels
|
|
|
|
nestedDeps = concatMap getSelectDeps $ map aarAnnSel objSels
|
|
|
|
<> mapMaybe getAnnSel arrSels
|
2018-11-16 15:40:23 +03:00
|
|
|
whereDeps = getBoolExpDeps tn <$> annWc
|
|
|
|
mkRelDep rn =
|
2018-06-27 16:11:32 +03:00
|
|
|
SchemaDependency (SOTableObj tn (TORel rn)) "untyped"
|
|
|
|
|
2018-12-12 15:58:39 +03:00
|
|
|
-- ignore aggregate selections to calculate schema deps
|
|
|
|
getRelName (ASSimple aar) = Just $ aarName aar
|
|
|
|
getRelName (ASAgg _) = Nothing
|
|
|
|
|
|
|
|
getAnnSel (ASSimple aar) = Just $ aarAnnSel aar
|
|
|
|
getAnnSel (ASAgg _) = Nothing
|
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
convSelectQuery
|
2019-03-01 14:45:04 +03:00
|
|
|
:: (UserInfoM m, QErrM m, CacheRM m, HasSQLGenCtx m)
|
2018-06-27 16:11:32 +03:00
|
|
|
=> (PGColType -> Value -> m S.SQLExp)
|
|
|
|
-> SelectQuery
|
2018-10-05 11:56:47 +03:00
|
|
|
-> m AnnSel
|
2018-06-27 16:11:32 +03:00
|
|
|
convSelectQuery prepArgBuilder (DMLQuery qt selQ) = do
|
|
|
|
tabInfo <- withPathK "table" $ askTabInfo qt
|
|
|
|
selPermInfo <- askSelPermInfo tabInfo
|
|
|
|
extSelQ <- resolveStar (tiFieldInfoMap tabInfo) selPermInfo selQ
|
|
|
|
validateHeaders $ spiRequiredHeaders selPermInfo
|
|
|
|
convSelectQ (tiFieldInfoMap tabInfo) selPermInfo extSelQ prepArgBuilder
|
|
|
|
|
2019-01-25 06:31:54 +03:00
|
|
|
funcQueryTx
|
|
|
|
:: S.FromItem -> QualifiedFunction -> QualifiedTable
|
2019-03-01 14:45:04 +03:00
|
|
|
-> TablePerm -> TableArgs -> Bool
|
2019-01-25 06:31:54 +03:00
|
|
|
-> (Either TableAggFlds AnnFlds, DS.Seq Q.PrepArg)
|
|
|
|
-> Q.TxE QErr RespBody
|
2019-03-01 14:45:04 +03:00
|
|
|
funcQueryTx frmItem fn tn tabPerm tabArgs strfyNum (eSelFlds, p) =
|
2019-01-25 06:31:54 +03:00
|
|
|
runIdentity . Q.getRow
|
|
|
|
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder sqlBuilder) (toList p) True
|
|
|
|
where
|
|
|
|
sqlBuilder = toSQL $
|
2019-03-01 14:45:04 +03:00
|
|
|
mkFuncSelectWith fn tn tabPerm tabArgs strfyNum eSelFlds frmItem
|
2019-01-25 06:31:54 +03:00
|
|
|
|
2018-10-31 15:51:20 +03:00
|
|
|
selectAggP2 :: (AnnAggSel, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
|
|
|
|
selectAggP2 (sel, p) =
|
|
|
|
runIdentity . Q.getRow
|
|
|
|
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder selectSQL) (toList p) True
|
|
|
|
where
|
|
|
|
selectSQL = toSQL $ mkAggSelect sel
|
|
|
|
|
2018-11-23 16:02:46 +03:00
|
|
|
-- selectP2 :: (QErrM m, CacheRWM m, MonadTx m, MonadIO m) => (SelectQueryP1, DS.Seq Q.PrepArg) -> m RespBody
|
2018-10-05 11:56:47 +03:00
|
|
|
selectP2 :: Bool -> (AnnSel, DS.Seq Q.PrepArg) -> Q.TxE QErr RespBody
|
|
|
|
selectP2 asSingleObject (sel, p) =
|
2018-06-27 16:11:32 +03:00
|
|
|
runIdentity . Q.getRow
|
|
|
|
<$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder selectSQL) (toList p) True
|
|
|
|
where
|
2018-10-05 11:56:47 +03:00
|
|
|
selectSQL = toSQL $ mkSQLSelect asSingleObject sel
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-12-13 10:26:15 +03:00
|
|
|
phaseOne
|
2019-03-01 14:45:04 +03:00
|
|
|
:: (QErrM m, UserInfoM m, CacheRM m, HasSQLGenCtx m)
|
2018-12-13 10:26:15 +03:00
|
|
|
=> SelectQuery -> m (AnnSel, DS.Seq Q.PrepArg)
|
|
|
|
phaseOne =
|
|
|
|
liftDMLP1 . convSelectQuery binRHSBuilder
|
|
|
|
|
|
|
|
phaseTwo :: (MonadTx m) => (AnnSel, DS.Seq Q.PrepArg) -> m RespBody
|
|
|
|
phaseTwo =
|
|
|
|
liftTx . selectP2 False
|
|
|
|
|
|
|
|
runSelect
|
2019-03-01 14:45:04 +03:00
|
|
|
:: (QErrM m, UserInfoM m, CacheRWM m, HasSQLGenCtx m, MonadTx m)
|
2018-12-13 10:26:15 +03:00
|
|
|
=> SelectQuery -> m RespBody
|
|
|
|
runSelect q =
|
|
|
|
phaseOne q >>= phaseTwo
|