mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-19 13:31:43 +03:00
342391f39d
This upgrades the version of Ormolu required by the HGE repository to v0.5.0.1, and reformats all code accordingly. Ormolu v0.5 reformats code that uses infix operators. This is mostly useful, adding newlines and indentation to make it clear which operators are applied first, but in some cases, it's unpleasant. To make this easier on the eyes, I had to do the following: * Add a few fixity declarations (search for `infix`) * Add parentheses to make precedence clear, allowing Ormolu to keep everything on one line * Rename `relevantEq` to `(==~)` in #6651 and set it to `infix 4` * Add a few _.ormolu_ files (thanks to @hallettj for helping me get started), mostly for Autodocodec operators that don't have explicit fixity declarations In general, I think these changes are quite reasonable. They mostly affect indentation. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6675 GitOrigin-RevId: cd47d87f1d089fb0bc9dcbbe7798dbceedcd7d83
238 lines
7.1 KiB
Haskell
238 lines
7.1 KiB
Haskell
module Hasura.RQL.DML.Update
|
|
( runUpdate,
|
|
)
|
|
where
|
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
|
import Data.Aeson.Types
|
|
import Data.HashMap.Strict qualified as M
|
|
import Data.HashMap.Strict qualified as Map
|
|
import Data.Sequence qualified as DS
|
|
import Data.Text.Extended
|
|
import Database.PG.Query qualified as PG
|
|
import Hasura.Backends.Postgres.Connection
|
|
import Hasura.Backends.Postgres.Execute.Mutation
|
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
|
import Hasura.Backends.Postgres.SQL.Types
|
|
import Hasura.Backends.Postgres.Translate.Returning
|
|
import Hasura.Backends.Postgres.Types.Table
|
|
import Hasura.Backends.Postgres.Types.Update
|
|
import Hasura.Base.Error
|
|
import Hasura.EncJSON
|
|
import Hasura.Prelude
|
|
import Hasura.QueryTags
|
|
import Hasura.RQL.DML.Internal
|
|
import Hasura.RQL.DML.Types
|
|
import Hasura.RQL.IR.BoolExp
|
|
import Hasura.RQL.IR.Update
|
|
import Hasura.RQL.Types.Column
|
|
import Hasura.RQL.Types.Common
|
|
import Hasura.RQL.Types.Metadata
|
|
import Hasura.RQL.Types.Permission
|
|
import Hasura.RQL.Types.SchemaCache
|
|
import Hasura.RQL.Types.Table
|
|
import Hasura.SQL.Backend
|
|
import Hasura.SQL.Types
|
|
import Hasura.Server.Types
|
|
import Hasura.Session
|
|
import Hasura.Tracing qualified as Tracing
|
|
|
|
convInc ::
|
|
(QErrM m) =>
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
PGCol ->
|
|
ColumnType ('Postgres 'Vanilla) ->
|
|
Value ->
|
|
m (PGCol, S.SQLExp)
|
|
convInc f col colType val = do
|
|
prepExp <- f (CollectableTypeScalar colType) val
|
|
return (col, S.SEOpApp S.incOp [S.mkSIdenExp col, prepExp])
|
|
|
|
convMul ::
|
|
(QErrM m) =>
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
PGCol ->
|
|
ColumnType ('Postgres 'Vanilla) ->
|
|
Value ->
|
|
m (PGCol, S.SQLExp)
|
|
convMul f col colType val = do
|
|
prepExp <- f (CollectableTypeScalar colType) val
|
|
return (col, S.SEOpApp S.mulOp [S.mkSIdenExp col, prepExp])
|
|
|
|
convSet ::
|
|
(QErrM m) =>
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
PGCol ->
|
|
ColumnType ('Postgres 'Vanilla) ->
|
|
Value ->
|
|
m (PGCol, S.SQLExp)
|
|
convSet f col colType val = do
|
|
prepExp <- f (CollectableTypeScalar colType) val
|
|
return (col, prepExp)
|
|
|
|
convDefault :: (Monad m) => PGCol -> ColumnType ('Postgres 'Vanilla) -> () -> m (PGCol, S.SQLExp)
|
|
convDefault col _ _ = return (col, S.SEUnsafe "DEFAULT")
|
|
|
|
convOp ::
|
|
(UserInfoM m, QErrM m) =>
|
|
FieldInfoMap (FieldInfo ('Postgres 'Vanilla)) ->
|
|
[PGCol] ->
|
|
UpdPermInfo ('Postgres 'Vanilla) ->
|
|
[(PGCol, a)] ->
|
|
(PGCol -> ColumnType ('Postgres 'Vanilla) -> a -> m (PGCol, S.SQLExp)) ->
|
|
m [(PGCol, S.SQLExp)]
|
|
convOp fieldInfoMap preSetCols updPerm objs conv =
|
|
forM objs $ \(pgCol, a) -> do
|
|
-- if column has predefined value then throw error
|
|
when (pgCol `elem` preSetCols) $ throwNotUpdErr pgCol
|
|
checkPermOnCol PTUpdate allowedCols pgCol
|
|
colType <- askColumnType fieldInfoMap pgCol relWhenPgErr
|
|
res <- conv pgCol colType a
|
|
-- build a set expression's entry
|
|
withPathK (getPGColTxt pgCol) $ return res
|
|
where
|
|
allowedCols = upiCols updPerm
|
|
relWhenPgErr = "relationships can't be updated"
|
|
throwNotUpdErr c = do
|
|
roleName <- _uiRole <$> askUserInfo
|
|
throw400 NotSupported $
|
|
"column "
|
|
<> c <<> " is not updatable"
|
|
<> " for role "
|
|
<> roleName <<> "; its value is predefined in permission"
|
|
|
|
validateUpdateQueryWith ::
|
|
(UserInfoM m, QErrM m, TableInfoRM ('Postgres 'Vanilla) m) =>
|
|
SessionVariableBuilder m ->
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
UpdateQuery ->
|
|
m (AnnotatedUpdate ('Postgres 'Vanilla))
|
|
validateUpdateQueryWith sessVarBldr prepValBldr uq = do
|
|
let tableName = uqTable uq
|
|
tableInfo <- withPathK "table" $ askTableInfoSource tableName
|
|
let coreInfo = _tiCoreInfo tableInfo
|
|
|
|
-- If it is view then check if it is updatable
|
|
mutableView
|
|
tableName
|
|
viIsUpdatable
|
|
(_tciViewInfo coreInfo)
|
|
"updatable"
|
|
|
|
-- Check if the role has update permissions
|
|
updPerm <- askUpdPermInfo tableInfo
|
|
|
|
-- Check if all dependent headers are present
|
|
validateHeaders $ upiRequiredHeaders updPerm
|
|
|
|
-- Check if select is allowed
|
|
selPerm <-
|
|
modifyErr (<> selNecessaryMsg) $
|
|
askSelPermInfo tableInfo
|
|
|
|
let fieldInfoMap = _tciFieldInfoMap coreInfo
|
|
allCols = getCols fieldInfoMap
|
|
preSetObj = upiSet updPerm
|
|
preSetCols = M.keys preSetObj
|
|
|
|
-- convert the object to SQL set expression
|
|
setItems <-
|
|
withPathK "$set" $
|
|
convOp fieldInfoMap preSetCols updPerm (M.toList $ uqSet uq) $
|
|
convSet prepValBldr
|
|
|
|
incItems <-
|
|
withPathK "$inc" $
|
|
convOp fieldInfoMap preSetCols updPerm (M.toList $ uqInc uq) $
|
|
convInc prepValBldr
|
|
|
|
mulItems <-
|
|
withPathK "$mul" $
|
|
convOp fieldInfoMap preSetCols updPerm (M.toList $ uqMul uq) $
|
|
convMul prepValBldr
|
|
|
|
defItems <-
|
|
withPathK "$default" $
|
|
convOp fieldInfoMap preSetCols updPerm ((,()) <$> uqDefault uq) convDefault
|
|
|
|
-- convert the returning cols into sql returing exp
|
|
mAnnRetCols <- forM mRetCols $ \retCols ->
|
|
withPathK "returning" $ checkRetCols fieldInfoMap selPerm retCols
|
|
|
|
resolvedPreSetItems <-
|
|
M.toList
|
|
<$> mapM (convPartialSQLExp sessVarBldr) preSetObj
|
|
|
|
let setExpItems =
|
|
resolvedPreSetItems
|
|
++ setItems
|
|
++ incItems
|
|
++ mulItems
|
|
++ defItems
|
|
|
|
when (null setExpItems) $
|
|
throw400 UnexpectedPayload "atleast one of $set, $inc, $mul has to be present"
|
|
|
|
-- convert the where clause
|
|
annSQLBoolExp <-
|
|
withPathK "where" $
|
|
convBoolExp fieldInfoMap selPerm (uqWhere uq) sessVarBldr tableName prepValBldr
|
|
|
|
resolvedUpdFltr <-
|
|
convAnnBoolExpPartialSQL sessVarBldr $
|
|
upiFilter updPerm
|
|
resolvedUpdCheck <-
|
|
fromMaybe gBoolExpTrue
|
|
<$> traverse
|
|
(convAnnBoolExpPartialSQL sessVarBldr)
|
|
(upiCheck updPerm)
|
|
|
|
return $
|
|
AnnotatedUpdateG
|
|
tableName
|
|
(resolvedUpdFltr, annSQLBoolExp)
|
|
resolvedUpdCheck
|
|
(BackendUpdate $ Map.fromList $ fmap UpdateSet <$> setExpItems)
|
|
(mkDefaultMutFlds mAnnRetCols)
|
|
allCols
|
|
Nothing
|
|
where
|
|
mRetCols = uqReturning uq
|
|
selNecessaryMsg =
|
|
"; \"update\" is only allowed if the role "
|
|
<> "has \"select\" permission as \"where\" can't be used "
|
|
<> "without \"select\" permission on the table"
|
|
|
|
validateUpdateQuery ::
|
|
(QErrM m, UserInfoM m, CacheRM m) =>
|
|
UpdateQuery ->
|
|
m (AnnotatedUpdate ('Postgres 'Vanilla), DS.Seq PG.PrepArg)
|
|
validateUpdateQuery query = do
|
|
let source = uqSource query
|
|
tableCache :: TableCache ('Postgres 'Vanilla) <- fold <$> askTableCache source
|
|
flip runTableCacheRT (source, tableCache) $
|
|
runDMLP1T $
|
|
validateUpdateQueryWith sessVarFromCurrentSetting (valueParserWithCollectableType binRHSBuilder) query
|
|
|
|
runUpdate ::
|
|
forall m.
|
|
( QErrM m,
|
|
UserInfoM m,
|
|
CacheRM m,
|
|
HasServerConfigCtx m,
|
|
MonadBaseControl IO m,
|
|
MonadIO m,
|
|
Tracing.MonadTrace m,
|
|
MetadataM m
|
|
) =>
|
|
UpdateQuery ->
|
|
m EncJSON
|
|
runUpdate q = do
|
|
sourceConfig <- askSourceConfig @('Postgres 'Vanilla) (uqSource q)
|
|
userInfo <- askUserInfo
|
|
strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
|
|
validateUpdateQuery q
|
|
>>= runTxWithCtx (_pscExecCtx sourceConfig) PG.ReadWrite
|
|
. flip runReaderT emptyQueryTagsComment
|
|
. execUpdateQuery strfyNum Nothing userInfo
|