2018-12-13 10:26:15 +03:00
|
|
|
module Hasura.RQL.DML.Update
|
2021-09-24 01:56:37 +03:00
|
|
|
( runUpdate,
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
|
|
|
import Data.Aeson.Types
|
|
|
|
import Data.HashMap.Strict qualified as M
|
2021-11-18 21:02:58 +03:00
|
|
|
import Data.HashMap.Strict qualified as Map
|
2021-09-24 01:56:37 +03:00
|
|
|
import Data.Sequence qualified as DS
|
|
|
|
import Data.Text.Extended
|
Import `pg-client-hs` as `PG`
Result of executing the following commands:
```shell
# replace "as Q" imports with "as PG" (in retrospect this didn't need a regex)
git grep -lE 'as Q($|[^a-zA-Z])' -- '*.hs' | xargs sed -i -E 's/as Q($|[^a-zA-Z])/as PG\1/'
# replace " Q." with " PG."
git grep -lE ' Q\.' -- '*.hs' | xargs sed -i 's/ Q\./ PG./g'
# replace "(Q." with "(PG."
git grep -lE '\(Q\.' -- '*.hs' | xargs sed -i 's/(Q\./(PG./g'
# ditto, but for [, |, { and !
git grep -lE '\[Q\.' -- '*.hs' | xargs sed -i 's/\[Q\./\[PG./g'
git grep -l '|Q\.' -- '*.hs' | xargs sed -i 's/|Q\./|PG./g'
git grep -l '{Q\.' -- '*.hs' | xargs sed -i 's/{Q\./{PG./g'
git grep -l '!Q\.' -- '*.hs' | xargs sed -i 's/!Q\./!PG./g'
```
(Doing the `grep -l` before the `sed`, instead of `sed` on the entire codebase, reduces the number of `mtime` updates, and so reduces how many times a file gets recompiled while checking intermediate results.)
Finally, I manually removed a broken and unused `Arbitrary` instance in `Hasura.RQL.Network`. (It used an `import Test.QuickCheck.Arbitrary as Q` statement, which was erroneously caught by the first find-replace command.)
After this PR, `Q` is no longer used as an import qualifier. That was not the goal of this PR, but perhaps it's a useful fact for future efforts.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5933
GitOrigin-RevId: 8c84c59d57789111d40f5d3322c5a885dcfbf40e
2022-09-20 22:54:43 +03:00
|
|
|
import Database.PG.Query qualified as PG
|
2021-09-24 01:56:37 +03:00
|
|
|
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
|
2021-11-18 21:02:58 +03:00
|
|
|
import Hasura.Backends.Postgres.Types.Update
|
2021-09-24 01:56:37 +03:00
|
|
|
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
|
2022-04-27 16:57:28 +03:00
|
|
|
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
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.SQL.Types
|
2022-04-22 17:50:01 +03:00
|
|
|
import Hasura.Server.Types
|
2021-09-24 01:56:37 +03:00
|
|
|
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)
|
2018-06-27 16:11:32 +03:00
|
|
|
convInc f col colType val = do
|
2021-02-14 09:07:52 +03:00
|
|
|
prepExp <- f (CollectableTypeScalar colType) val
|
2018-07-20 13:51:20 +03:00
|
|
|
return (col, S.SEOpApp S.incOp [S.mkSIdenExp col, prepExp])
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
convMul ::
|
|
|
|
(QErrM m) =>
|
|
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
|
|
PGCol ->
|
|
|
|
ColumnType ('Postgres 'Vanilla) ->
|
|
|
|
Value ->
|
|
|
|
m (PGCol, S.SQLExp)
|
2018-06-27 16:11:32 +03:00
|
|
|
convMul f col colType val = do
|
2021-02-14 09:07:52 +03:00
|
|
|
prepExp <- f (CollectableTypeScalar colType) val
|
2018-07-20 13:51:20 +03:00
|
|
|
return (col, S.SEOpApp S.mulOp [S.mkSIdenExp col, prepExp])
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
convSet ::
|
|
|
|
(QErrM m) =>
|
|
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
|
|
PGCol ->
|
|
|
|
ColumnType ('Postgres 'Vanilla) ->
|
|
|
|
Value ->
|
|
|
|
m (PGCol, S.SQLExp)
|
2018-06-27 16:11:32 +03:00
|
|
|
convSet f col colType val = do
|
2021-02-14 09:07:52 +03:00
|
|
|
prepExp <- f (CollectableTypeScalar colType) val
|
2018-06-27 16:11:32 +03:00
|
|
|
return (col, prepExp)
|
|
|
|
|
2021-04-22 00:44:37 +03:00
|
|
|
convDefault :: (Monad m) => PGCol -> ColumnType ('Postgres 'Vanilla) -> () -> m (PGCol, S.SQLExp)
|
2018-06-27 16:11:32 +03:00
|
|
|
convDefault col _ _ = return (col, S.SEUnsafe "DEFAULT")
|
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
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)]
|
2019-02-11 15:45:30 +03:00
|
|
|
convOp fieldInfoMap preSetCols updPerm objs conv =
|
2018-06-27 16:11:32 +03:00
|
|
|
forM objs $ \(pgCol, a) -> do
|
2019-02-11 15:45:30 +03:00
|
|
|
-- if column has predefined value then throw error
|
|
|
|
when (pgCol `elem` preSetCols) $ throwNotUpdErr pgCol
|
Specialize `RQL.DML` to postgres.
### Description
When generalizing the code, back in late 2020, we over-eagerly generalized parts of the code that are specific to RQL's DML. This was in part due to the fact that, at the time, the DML types were all mixed alongside other types in `RQL.Types`. As a result, a lot of `RQL.DML.Internal` was generic over the backend type, instead of being specialized to `'Postgres 'Vanilla`.
A consequence of this is that, before this PR, `DML.Internal` ended up having a dependency on non-Postgres backends, due to the use of `annBoolExp`, which requires a `BackendMetadata` instance. Since the code was written in a generic manner, `DML.Internal` in turn depended on having the metadata instances in scope... This PR changes that to, instead, explicitly import the Postgres instance.
(Note that this module didn't import `RQL.Types.Metadata.Instances`, but depends on a module that imports it, and **orphan instances are transitively imported**, as evidenced by the need for that explicit import in #4568.)
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4573
GitOrigin-RevId: 7b82b5d7c23c03654518a1816802d400f37c3c64
2022-05-27 21:22:06 +03:00
|
|
|
checkPermOnCol PTUpdate allowedCols pgCol
|
2021-02-14 09:07:52 +03:00
|
|
|
colType <- askColumnType fieldInfoMap pgCol relWhenPgErr
|
2018-06-27 16:11:32 +03:00
|
|
|
res <- conv pgCol colType a
|
|
|
|
-- build a set expression's entry
|
|
|
|
withPathK (getPGColTxt pgCol) $ return res
|
|
|
|
where
|
2021-09-24 01:56:37 +03:00
|
|
|
allowedCols = upiCols updPerm
|
2018-06-27 16:11:32 +03:00
|
|
|
relWhenPgErr = "relationships can't be updated"
|
2019-02-11 15:45:30 +03:00
|
|
|
throwNotUpdErr c = do
|
2020-04-24 12:10:53 +03:00
|
|
|
roleName <- _uiRole <$> askUserInfo
|
2021-09-24 01:56:37 +03:00
|
|
|
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) =>
|
Specialize `RQL.DML` to postgres.
### Description
When generalizing the code, back in late 2020, we over-eagerly generalized parts of the code that are specific to RQL's DML. This was in part due to the fact that, at the time, the DML types were all mixed alongside other types in `RQL.Types`. As a result, a lot of `RQL.DML.Internal` was generic over the backend type, instead of being specialized to `'Postgres 'Vanilla`.
A consequence of this is that, before this PR, `DML.Internal` ended up having a dependency on non-Postgres backends, due to the use of `annBoolExp`, which requires a `BackendMetadata` instance. Since the code was written in a generic manner, `DML.Internal` in turn depended on having the metadata instances in scope... This PR changes that to, instead, explicitly import the Postgres instance.
(Note that this module didn't import `RQL.Types.Metadata.Instances`, but depends on a module that imports it, and **orphan instances are transitively imported**, as evidenced by the need for that explicit import in #4568.)
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4573
GitOrigin-RevId: 7b82b5d7c23c03654518a1816802d400f37c3c64
2022-05-27 21:22:06 +03:00
|
|
|
SessionVariableBuilder m ->
|
2021-09-24 01:56:37 +03:00
|
|
|
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
|
|
|
UpdateQuery ->
|
2021-11-25 00:39:42 +03:00
|
|
|
m (AnnotatedUpdate ('Postgres 'Vanilla))
|
2019-04-17 12:48:41 +03:00
|
|
|
validateUpdateQueryWith sessVarBldr prepValBldr uq = do
|
2018-06-27 16:11:32 +03:00
|
|
|
let tableName = uqTable uq
|
2022-04-26 18:12:47 +03:00
|
|
|
tableInfo <- withPathK "table" $ askTableInfoSource tableName
|
2019-11-20 21:21:30 +03:00
|
|
|
let coreInfo = _tiCoreInfo tableInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2018-10-12 15:06:12 +03:00
|
|
|
-- If it is view then check if it is updatable
|
2021-09-24 01:56:37 +03:00
|
|
|
mutableView
|
|
|
|
tableName
|
|
|
|
viIsUpdatable
|
|
|
|
(_tciViewInfo coreInfo)
|
|
|
|
"updatable"
|
2018-10-12 15:06:12 +03:00
|
|
|
|
2018-06-27 16:11:32 +03:00
|
|
|
-- 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
|
2021-09-24 01:56:37 +03:00
|
|
|
selPerm <-
|
|
|
|
modifyErr (<> selNecessaryMsg) $
|
|
|
|
askSelPermInfo tableInfo
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2019-11-20 21:21:30 +03:00
|
|
|
let fieldInfoMap = _tciFieldInfoMap coreInfo
|
2019-03-22 10:08:42 +03:00
|
|
|
allCols = getCols fieldInfoMap
|
2019-02-11 15:45:30 +03:00
|
|
|
preSetObj = upiSet updPerm
|
|
|
|
preSetCols = M.keys preSetObj
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
-- convert the object to SQL set expression
|
2021-09-24 01:56:37 +03:00
|
|
|
setItems <-
|
|
|
|
withPathK "$set" $
|
|
|
|
convOp fieldInfoMap preSetCols updPerm (M.toList $ uqSet uq) $ convSet prepValBldr
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
incItems <-
|
|
|
|
withPathK "$inc" $
|
|
|
|
convOp fieldInfoMap preSetCols updPerm (M.toList $ uqInc uq) $ convInc prepValBldr
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
mulItems <-
|
|
|
|
withPathK "$mul" $
|
|
|
|
convOp fieldInfoMap preSetCols updPerm (M.toList $ uqMul uq) $ convMul prepValBldr
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
defItems <-
|
|
|
|
withPathK "$default" $
|
|
|
|
convOp fieldInfoMap preSetCols updPerm ((,()) <$> uqDefault uq) convDefault
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
-- convert the returning cols into sql returing exp
|
|
|
|
mAnnRetCols <- forM mRetCols $ \retCols ->
|
2018-10-05 11:56:47 +03:00
|
|
|
withPathK "returning" $ checkRetCols fieldInfoMap selPerm retCols
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
resolvedPreSetItems <-
|
|
|
|
M.toList
|
|
|
|
<$> mapM (convPartialSQLExp sessVarBldr) preSetObj
|
2019-04-17 12:48:41 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
let setExpItems =
|
|
|
|
resolvedPreSetItems
|
|
|
|
++ setItems
|
|
|
|
++ incItems
|
|
|
|
++ mulItems
|
|
|
|
++ defItems
|
2018-06-27 16:11:32 +03:00
|
|
|
|
|
|
|
when (null setExpItems) $
|
|
|
|
throw400 UnexpectedPayload "atleast one of $set, $inc, $mul has to be present"
|
|
|
|
|
|
|
|
-- convert the where clause
|
2021-09-24 01:56:37 +03:00
|
|
|
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 $
|
2021-11-25 00:39:42 +03:00
|
|
|
AnnotatedUpdateG
|
2021-09-24 01:56:37 +03:00
|
|
|
tableName
|
|
|
|
(resolvedUpdFltr, annSQLBoolExp)
|
|
|
|
resolvedUpdCheck
|
2021-11-25 00:39:42 +03:00
|
|
|
(BackendUpdate $ Map.fromList $ fmap UpdateSet <$> setExpItems)
|
2021-09-24 01:56:37 +03:00
|
|
|
(mkDefaultMutFlds mAnnRetCols)
|
|
|
|
allCols
|
2022-07-19 09:55:42 +03:00
|
|
|
Nothing
|
2018-06-27 16:11:32 +03:00
|
|
|
where
|
|
|
|
mRetCols = uqReturning uq
|
|
|
|
selNecessaryMsg =
|
|
|
|
"; \"update\" is only allowed if the role "
|
2021-09-24 01:56:37 +03:00
|
|
|
<> "has \"select\" permission as \"where\" can't be used "
|
|
|
|
<> "without \"select\" permission on the table"
|
2018-06-27 16:11:32 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
validateUpdateQuery ::
|
|
|
|
(QErrM m, UserInfoM m, CacheRM m) =>
|
|
|
|
UpdateQuery ->
|
Import `pg-client-hs` as `PG`
Result of executing the following commands:
```shell
# replace "as Q" imports with "as PG" (in retrospect this didn't need a regex)
git grep -lE 'as Q($|[^a-zA-Z])' -- '*.hs' | xargs sed -i -E 's/as Q($|[^a-zA-Z])/as PG\1/'
# replace " Q." with " PG."
git grep -lE ' Q\.' -- '*.hs' | xargs sed -i 's/ Q\./ PG./g'
# replace "(Q." with "(PG."
git grep -lE '\(Q\.' -- '*.hs' | xargs sed -i 's/(Q\./(PG./g'
# ditto, but for [, |, { and !
git grep -lE '\[Q\.' -- '*.hs' | xargs sed -i 's/\[Q\./\[PG./g'
git grep -l '|Q\.' -- '*.hs' | xargs sed -i 's/|Q\./|PG./g'
git grep -l '{Q\.' -- '*.hs' | xargs sed -i 's/{Q\./{PG./g'
git grep -l '!Q\.' -- '*.hs' | xargs sed -i 's/!Q\./!PG./g'
```
(Doing the `grep -l` before the `sed`, instead of `sed` on the entire codebase, reduces the number of `mtime` updates, and so reduces how many times a file gets recompiled while checking intermediate results.)
Finally, I manually removed a broken and unused `Arbitrary` instance in `Hasura.RQL.Network`. (It used an `import Test.QuickCheck.Arbitrary as Q` statement, which was erroneously caught by the first find-replace command.)
After this PR, `Q` is no longer used as an import qualifier. That was not the goal of this PR, but perhaps it's a useful fact for future efforts.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5933
GitOrigin-RevId: 8c84c59d57789111d40f5d3322c5a885dcfbf40e
2022-09-20 22:54:43 +03:00
|
|
|
m (AnnotatedUpdate ('Postgres 'Vanilla), DS.Seq PG.PrepArg)
|
2021-01-07 12:04:22 +03:00
|
|
|
validateUpdateQuery query = do
|
|
|
|
let source = uqSource query
|
2022-04-26 18:12:47 +03:00
|
|
|
tableCache :: TableCache ('Postgres 'Vanilla) <- fold <$> askTableCache source
|
2021-09-24 01:56:37 +03:00
|
|
|
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
|
2021-06-11 06:26:50 +03:00
|
|
|
runUpdate q = do
|
2021-04-22 00:44:37 +03:00
|
|
|
sourceConfig <- askSourceConfig @('Postgres 'Vanilla) (uqSource q)
|
2021-06-11 06:26:50 +03:00
|
|
|
userInfo <- askUserInfo
|
2021-01-29 08:48:17 +03:00
|
|
|
strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
|
2021-01-07 12:04:22 +03:00
|
|
|
validateUpdateQuery q
|
Import `pg-client-hs` as `PG`
Result of executing the following commands:
```shell
# replace "as Q" imports with "as PG" (in retrospect this didn't need a regex)
git grep -lE 'as Q($|[^a-zA-Z])' -- '*.hs' | xargs sed -i -E 's/as Q($|[^a-zA-Z])/as PG\1/'
# replace " Q." with " PG."
git grep -lE ' Q\.' -- '*.hs' | xargs sed -i 's/ Q\./ PG./g'
# replace "(Q." with "(PG."
git grep -lE '\(Q\.' -- '*.hs' | xargs sed -i 's/(Q\./(PG./g'
# ditto, but for [, |, { and !
git grep -lE '\[Q\.' -- '*.hs' | xargs sed -i 's/\[Q\./\[PG./g'
git grep -l '|Q\.' -- '*.hs' | xargs sed -i 's/|Q\./|PG./g'
git grep -l '{Q\.' -- '*.hs' | xargs sed -i 's/{Q\./{PG./g'
git grep -l '!Q\.' -- '*.hs' | xargs sed -i 's/!Q\./!PG./g'
```
(Doing the `grep -l` before the `sed`, instead of `sed` on the entire codebase, reduces the number of `mtime` updates, and so reduces how many times a file gets recompiled while checking intermediate results.)
Finally, I manually removed a broken and unused `Arbitrary` instance in `Hasura.RQL.Network`. (It used an `import Test.QuickCheck.Arbitrary as Q` statement, which was erroneously caught by the first find-replace command.)
After this PR, `Q` is no longer used as an import qualifier. That was not the goal of this PR, but perhaps it's a useful fact for future efforts.
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5933
GitOrigin-RevId: 8c84c59d57789111d40f5d3322c5a885dcfbf40e
2022-09-20 22:54:43 +03:00
|
|
|
>>= runTxWithCtx (_pscExecCtx sourceConfig) PG.ReadWrite
|
2021-09-24 01:56:37 +03:00
|
|
|
. flip runReaderT emptyQueryTagsComment
|
2022-07-19 09:55:42 +03:00
|
|
|
. execUpdateQuery strfyNum Nothing userInfo
|