mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
Breaking up the Postgres implementation of the update-schema into reusable components
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/2889 GitOrigin-RevId: 49c5d59a6f817832f11b1773b078aa24cc650ab5
This commit is contained in:
parent
f7e13cb7c9
commit
84027dad04
@ -424,9 +424,10 @@ library
|
||||
, Hasura.Backends.Postgres.Translate.Types
|
||||
, Hasura.Backends.Postgres.Translate.Update
|
||||
, Hasura.Backends.Postgres.Types.BoolExp
|
||||
, Hasura.Backends.Postgres.Types.CitusExtraTableMetadata
|
||||
, Hasura.Backends.Postgres.Types.Column
|
||||
, Hasura.Backends.Postgres.Types.Table
|
||||
, Hasura.Backends.Postgres.Types.CitusExtraTableMetadata
|
||||
, Hasura.Backends.Postgres.Types.Update
|
||||
|
||||
, Hasura.Backends.MySQL.DataLoader.Execute
|
||||
, Hasura.Backends.MySQL.DataLoader.Plan
|
||||
|
@ -226,7 +226,6 @@ msDBMutationPlan ::
|
||||
msDBMutationPlan userInfo stringifyNum sourceName sourceConfig mrf = do
|
||||
go <$> case mrf of
|
||||
MDBInsert annInsert -> executeInsert userInfo stringifyNum sourceConfig annInsert
|
||||
MDBUpdate _annUpdate -> throw400 NotSupported "update mutations are not supported in MSSQL"
|
||||
MDBDelete _annDelete -> throw400 NotSupported "delete mutations are not supported in MSSQL"
|
||||
MDBFunction {} -> throw400 NotSupported "function mutations are not supported in MSSQL"
|
||||
where
|
||||
|
@ -109,7 +109,7 @@ execUpdateQuery ::
|
||||
) =>
|
||||
Bool ->
|
||||
UserInfo ->
|
||||
(AnnUpd ('Postgres pgKind), DS.Seq Q.PrepArg) ->
|
||||
(AnnotatedUpdateNode ('Postgres pgKind), DS.Seq Q.PrepArg) ->
|
||||
m EncJSON
|
||||
execUpdateQuery strfyNum userInfo (u, p) =
|
||||
runMutation
|
||||
|
@ -32,6 +32,7 @@ import Hasura.Backends.Postgres.SQL.Types qualified as PG
|
||||
import Hasura.Backends.Postgres.SQL.Value qualified as PG
|
||||
import Hasura.Backends.Postgres.Translate.Select (PostgresAnnotatedFieldJSON)
|
||||
import Hasura.Backends.Postgres.Translate.Select qualified as DS
|
||||
import Hasura.Backends.Postgres.Types.Update
|
||||
import Hasura.Base.Error (QErr)
|
||||
import Hasura.EncJSON (EncJSON, encJFromJValue)
|
||||
import Hasura.GraphQL.Execute.Backend
|
||||
@ -196,13 +197,13 @@ convertUpdate ::
|
||||
PostgresAnnotatedFieldJSON pgKind
|
||||
) =>
|
||||
UserInfo ->
|
||||
IR.AnnUpdG ('Postgres pgKind) (Const Void) (UnpreparedValue ('Postgres pgKind)) ->
|
||||
IR.AnnotatedUpdateNodeG ('Postgres pgKind) (Const Void) (UnpreparedValue ('Postgres pgKind)) ->
|
||||
Bool ->
|
||||
QueryTagsComment ->
|
||||
m (Tracing.TraceT (Q.TxET QErr IO) EncJSON)
|
||||
convertUpdate userInfo updateOperation stringifyNum queryTags = do
|
||||
preparedUpdate <- traverse (prepareWithoutPlan userInfo) updateOperation
|
||||
if null $ IR.uqp1OpExps updateOperation
|
||||
if null $ updateOperations . IR.uqp1BackendIR $ updateOperation
|
||||
then pure $ pure $ IR.buildEmptyMutResp $ IR.uqp1Output preparedUpdate
|
||||
else
|
||||
pure $
|
||||
|
@ -10,16 +10,16 @@ import Data.Aeson qualified as J
|
||||
import Data.Has
|
||||
import Data.HashMap.Strict qualified as Map
|
||||
import Data.HashMap.Strict.Extended qualified as M
|
||||
import Data.HashMap.Strict.InsOrd.Extended qualified as OMap
|
||||
import Data.List.NonEmpty qualified as NE
|
||||
import Data.Parser.JSONPath
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Extended
|
||||
import Hasura.Backends.Postgres.SQL.DML as PG hiding (CountType)
|
||||
import Hasura.Backends.Postgres.SQL.DML as PG hiding (CountType, incOp)
|
||||
import Hasura.Backends.Postgres.SQL.Types as PG hiding (FunctionName, TableName)
|
||||
import Hasura.Backends.Postgres.SQL.Value as PG
|
||||
import Hasura.Backends.Postgres.Types.BoolExp
|
||||
import Hasura.Backends.Postgres.Types.Column
|
||||
import Hasura.Backends.Postgres.Types.Update as PGIR
|
||||
import Hasura.Base.Error
|
||||
import Hasura.GraphQL.Parser hiding (EnumValueInfo, field)
|
||||
import Hasura.GraphQL.Parser qualified as P
|
||||
@ -39,7 +39,6 @@ import Hasura.GraphQL.Schema.Table
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR
|
||||
import Hasura.RQL.IR.Select qualified as IR
|
||||
import Hasura.RQL.IR.Update qualified as IR
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.SQL.AnyBackend qualified as AB
|
||||
import Hasura.SQL.Types
|
||||
@ -52,8 +51,11 @@ import Language.GraphQL.Draft.Syntax qualified as G
|
||||
-- Some functions of 'BackendSchema' differ across different Postgres "kinds",
|
||||
-- or call to functions (such as those related to Relay) that have not been
|
||||
-- generalized to all kinds of Postgres and still explicitly work on Vanilla
|
||||
-- Postgres. This class alllows each "kind" to specify its own specific
|
||||
-- Postgres. This class allows each "kind" to specify its own specific
|
||||
-- implementation. All common code is directly part of `BackendSchema`.
|
||||
--
|
||||
-- Note: Users shouldn't ever put this as a constraint. Use `BackendSchema
|
||||
-- ('Postgres pgKind)` instead.
|
||||
class PostgresSchema (pgKind :: PostgresKind) where
|
||||
pgkBuildTableRelayQueryFields ::
|
||||
BS.MonadBuildSchema ('Postgres pgKind) r m n =>
|
||||
@ -121,7 +123,7 @@ instance
|
||||
buildTableQueryFields = GSB.buildTableQueryFields
|
||||
buildTableRelayQueryFields = pgkBuildTableRelayQueryFields
|
||||
buildTableInsertMutationFields = GSB.buildTableInsertMutationFields
|
||||
buildTableUpdateMutationFields = GSB.buildTableUpdateMutationFields updateOperators
|
||||
buildTableUpdateMutationFields = GSB.buildTableUpdateMutationFields (\ti updP -> fmap BackendUpdate <$> updateOperators ti updP) -- TODO: simplify this!
|
||||
buildTableDeleteMutationFields = GSB.buildTableDeleteMutationFields
|
||||
buildFunctionQueryFields = GSB.buildFunctionQueryFields
|
||||
buildFunctionRelayQueryFields = pgkBuildFunctionRelayQueryFields
|
||||
@ -645,140 +647,407 @@ mkCountType _ Nothing = PG.CTStar
|
||||
mkCountType (Just True) (Just cols) = PG.CTDistinct cols
|
||||
mkCountType _ (Just cols) = PG.CTSimple cols
|
||||
|
||||
-- | @UpdateOperator b m n t@ represents one single update operator for a
|
||||
-- backend @b@, parsing a value of type @t@. @UpdateOperator b m n@ is a
|
||||
-- @Functor@, which (apart from the type variable @b@) is what enables
|
||||
-- multi-backend support.
|
||||
--
|
||||
-- Use the 'Functor (UpdateOperator b m n)' instance to inject the
|
||||
-- @UpdateOperator b m n (UnpreparedValue b)@ operators into backend-specific
|
||||
-- IR types that encode update operators.
|
||||
data UpdateOperator b m n t = UpdateOperator
|
||||
{ updateOperatorApplicableColumn :: ColumnInfo b -> Bool,
|
||||
updateOperatorParser ::
|
||||
G.Name ->
|
||||
TableName b ->
|
||||
NonEmpty (ColumnInfo b) ->
|
||||
m (InputFieldsParser n (HashMap (Column b) t))
|
||||
}
|
||||
deriving (Functor)
|
||||
|
||||
-- | The top-level component for building update operators parsers.
|
||||
--
|
||||
-- * It implements the 'preset' functionality from Update Permissions (see
|
||||
-- <https://hasura.io/docs/latest/graphql/core/auth/authorization/permission-rules.html#column-presets
|
||||
-- Permissions user docs>)
|
||||
-- * It validates that that the update fields parsed are sound when taken as a
|
||||
-- whole, i.e. that some changes are actually specified (either in the
|
||||
-- mutation query text or in update preset columns) and that each column is
|
||||
-- only used in one operator.
|
||||
buildUpdateOperators ::
|
||||
forall b n t m.
|
||||
(BackendSchema b, MonadSchema n m, MonadError QErr m) =>
|
||||
-- | Columns with @preset@ expressions
|
||||
(HashMap (Column b) t) ->
|
||||
-- | Update operators to include in the Schema
|
||||
[UpdateOperator b m n t] ->
|
||||
TableInfo b ->
|
||||
UpdPermInfo b ->
|
||||
m (InputFieldsParser n (HashMap (Column b) t))
|
||||
buildUpdateOperators presetCols ops tableInfo updatePermissions = do
|
||||
parsers :: InputFieldsParser n [HashMap (Column b) t] <-
|
||||
sequenceA . catMaybes <$> traverse (runUpdateOperator tableInfo updatePermissions) ops
|
||||
pure $
|
||||
parsers
|
||||
`P.bindFields` ( \opExps -> do
|
||||
let withPreset = presetCols : opExps
|
||||
mergeDisjoint @b withPreset
|
||||
)
|
||||
|
||||
-- | The columns that have 'preset' definitions applied to them. (see
|
||||
-- <https://hasura.io/docs/latest/graphql/core/auth/authorization/permission-rules.html#column-presets
|
||||
-- Permissions user docs>)
|
||||
presetColumns :: UpdPermInfo b -> HashMap (Column b) (UnpreparedValue b)
|
||||
presetColumns = fmap partialSQLExpToUnpreparedValue . upiSet
|
||||
|
||||
-- | Produce an InputFieldsParser from an UpdateOperator, but only if the operator
|
||||
-- applies to the table (i.e., it admits a non-empty column set).
|
||||
runUpdateOperator ::
|
||||
forall b m n t.
|
||||
(Backend b, MonadSchema n m, MonadError QErr m) =>
|
||||
TableInfo b ->
|
||||
UpdPermInfo b ->
|
||||
UpdateOperator b m n t ->
|
||||
m
|
||||
( Maybe
|
||||
( InputFieldsParser
|
||||
n
|
||||
(HashMap (Column b) t)
|
||||
)
|
||||
)
|
||||
runUpdateOperator tableInfo updatePermissions UpdateOperator {..} = do
|
||||
let tableName = tableInfoName tableInfo
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
columns <- tableUpdateColumns tableInfo updatePermissions
|
||||
|
||||
let applicableCols :: Maybe (NonEmpty (ColumnInfo b)) =
|
||||
nonEmpty . filter updateOperatorApplicableColumn $ columns
|
||||
|
||||
(sequenceA :: Maybe (m a) -> m (Maybe a))
|
||||
(applicableCols <&> updateOperatorParser tableGQLName tableName)
|
||||
|
||||
-- | Ensure that /some/ updates have been specified in a mutation.
|
||||
ensureNonEmpty ::
|
||||
forall b m t.
|
||||
(MonadParse m, Backend b) =>
|
||||
[Text] ->
|
||||
[HashMap (Column b) t] ->
|
||||
m ()
|
||||
ensureNonEmpty allowedOperators parsedResults =
|
||||
when (null $ M.unions parsedResults) $
|
||||
parseError $
|
||||
"At least any one of "
|
||||
<> commaSeparated allowedOperators
|
||||
<> " is expected"
|
||||
|
||||
-- | Merge the results of parsed update operators. Throws an error if the same
|
||||
-- column has been specified in multiple operators.
|
||||
mergeDisjoint ::
|
||||
forall b m t.
|
||||
(Backend b, MonadParse m) =>
|
||||
[HashMap (Column b) t] ->
|
||||
m (HashMap (Column b) t)
|
||||
mergeDisjoint parsedResults = do
|
||||
let unioned = M.unionsAll parsedResults
|
||||
duplicates =
|
||||
M.keys $
|
||||
M.filter
|
||||
( \case
|
||||
_ :| [] -> False
|
||||
_ -> True
|
||||
)
|
||||
unioned
|
||||
|
||||
unless (null duplicates) $
|
||||
parseError
|
||||
( "Column found in multiple operators: "
|
||||
<> commaSeparated (map dquote duplicates)
|
||||
<> "."
|
||||
)
|
||||
|
||||
return $ M.map NE.head unioned
|
||||
|
||||
setOp ::
|
||||
forall b n r m.
|
||||
( BackendSchema b,
|
||||
MonadReader r m,
|
||||
Has MkTypename r,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m
|
||||
) =>
|
||||
UpdateOperator b m n (UnpreparedValue b)
|
||||
setOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = const True
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let typedParser columnInfo =
|
||||
fmap P.mkParameter
|
||||
<$> BS.columnParser
|
||||
(pgiType columnInfo)
|
||||
(G.Nullability $ pgiIsNullable columnInfo)
|
||||
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_set")
|
||||
typedParser
|
||||
columns
|
||||
"sets the columns of the filtered rows to the given values"
|
||||
(G.Description $ "input type for updating data in table " <>> tableName)
|
||||
|
||||
incOp ::
|
||||
forall b m n r.
|
||||
( Backend b,
|
||||
MonadReader r m,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
BackendSchema b,
|
||||
Has MkTypename r
|
||||
) =>
|
||||
UpdateOperator b m n (UnpreparedValue b)
|
||||
incOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = isNumCol
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let typedParser columnInfo =
|
||||
fmap P.mkParameter
|
||||
<$> BS.columnParser
|
||||
(pgiType columnInfo)
|
||||
(G.Nullability $ pgiIsNullable columnInfo)
|
||||
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_inc")
|
||||
typedParser
|
||||
columns
|
||||
"increments the numeric columns with given value of the filtered values"
|
||||
(G.Description $ "input type for incrementing numeric columns in table " <>> tableName)
|
||||
|
||||
-- | Update operator that prepends a value to a column containing jsonb arrays.
|
||||
--
|
||||
-- Note: Currently this is Postgres specific because json columns have not been ported
|
||||
-- to other backends yet.
|
||||
prependOp ::
|
||||
forall pgKind m n r.
|
||||
( BackendSchema ('Postgres pgKind),
|
||||
MonadReader r m,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
Has MkTypename r
|
||||
) =>
|
||||
UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
prependOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let typedParser columnInfo =
|
||||
fmap P.mkParameter
|
||||
<$> BS.columnParser
|
||||
(pgiType columnInfo)
|
||||
(G.Nullability $ pgiIsNullable columnInfo)
|
||||
|
||||
desc = "prepend existing jsonb value of filtered columns with new jsonb value"
|
||||
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_prepend")
|
||||
typedParser
|
||||
columns
|
||||
desc
|
||||
desc
|
||||
|
||||
-- | Update operator that appends a value to a column containing jsonb arrays.
|
||||
--
|
||||
-- Note: Currently this is Postgres specific because json columns have not been ported
|
||||
-- to other backends yet.
|
||||
appendOp ::
|
||||
forall pgKind m n r.
|
||||
( BackendSchema ('Postgres pgKind),
|
||||
MonadReader r m,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
Has MkTypename r
|
||||
) =>
|
||||
UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
appendOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let typedParser columnInfo =
|
||||
fmap P.mkParameter
|
||||
<$> BS.columnParser
|
||||
(pgiType columnInfo)
|
||||
(G.Nullability $ pgiIsNullable columnInfo)
|
||||
|
||||
desc = "append existing jsonb value of filtered columns with new jsonb value"
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_append")
|
||||
typedParser
|
||||
columns
|
||||
desc
|
||||
desc
|
||||
|
||||
-- | Update operator that deletes a value at a specified key from a column
|
||||
-- containing jsonb objects.
|
||||
--
|
||||
-- Note: Currently this is Postgres specific because json columns have not been ported
|
||||
-- to other backends yet.
|
||||
deleteKeyOp ::
|
||||
forall pgKind m n r.
|
||||
( BackendSchema ('Postgres pgKind),
|
||||
MonadReader r m,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
Has MkTypename r
|
||||
) =>
|
||||
UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
deleteKeyOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let nullableTextParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGText) (G.Nullability True)
|
||||
desc = "delete key/value pair or string element. key/value pairs are matched based on their key value"
|
||||
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_delete_key")
|
||||
nullableTextParser
|
||||
columns
|
||||
desc
|
||||
desc
|
||||
|
||||
-- | Update operator that deletes a value at a specific index from a column
|
||||
-- containing jsonb arrays.
|
||||
--
|
||||
-- Note: Currently this is Postgres specific because json columns have not been ported
|
||||
-- to other backends yet.
|
||||
deleteElemOp ::
|
||||
forall pgKind m n r.
|
||||
( BackendSchema ('Postgres pgKind),
|
||||
MonadReader r m,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
Has MkTypename r
|
||||
) =>
|
||||
UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
deleteElemOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let nonNullableIntParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGInteger) (G.Nullability False)
|
||||
desc =
|
||||
"delete the array element with specified index (negative integers count from the end). "
|
||||
<> "throws an error if top level container is not an array"
|
||||
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_delete_elem")
|
||||
nonNullableIntParser
|
||||
columns
|
||||
desc
|
||||
desc
|
||||
|
||||
-- | Update operator that deletes a field at a certan path from a column
|
||||
-- containing jsonb objects.
|
||||
--
|
||||
-- Note: Currently this is Postgres specific because json columns have not been ported
|
||||
-- to other backends yet.
|
||||
deleteAtPathOp ::
|
||||
forall pgKind m n r.
|
||||
( BackendSchema ('Postgres pgKind),
|
||||
MonadReader r m,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
Has MkTypename r
|
||||
) =>
|
||||
UpdateOperator ('Postgres pgKind) m n [UnpreparedValue ('Postgres pgKind)]
|
||||
deleteAtPathOp = UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let nonNullableTextListParser _ = P.list . fmap (P.mkParameter) <$> columnParser (ColumnScalar PGText) (G.Nullability False)
|
||||
desc = "delete the field or element with specified path (for JSON arrays, negative integers count from the end)"
|
||||
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_delete_at_path")
|
||||
nonNullableTextListParser
|
||||
columns
|
||||
desc
|
||||
desc
|
||||
|
||||
-- | Construct a parser for a single update operator.
|
||||
--
|
||||
-- @updateOperator _ "op" fp MkOp ["col1","col2"]@ gives a parser that accepts
|
||||
-- objects in the shape of:
|
||||
--
|
||||
-- > op: {
|
||||
-- > col1: "x",
|
||||
-- > col2: "y"
|
||||
-- > }
|
||||
--
|
||||
-- And (morally) parses into values:
|
||||
--
|
||||
-- > M.fromList [("col1", MkOp (fp "x")), ("col2", MkOp (fp "y"))]
|
||||
updateOperator ::
|
||||
forall n r m b a.
|
||||
(MonadParse n, MonadReader r m, Has MkTypename r, Backend b) =>
|
||||
G.Name ->
|
||||
G.Name ->
|
||||
(ColumnInfo b -> m (Parser 'Both n a)) ->
|
||||
NonEmpty (ColumnInfo b) -> -- TODO: Should actually be a nonempty set - do we have a lib for that?
|
||||
G.Description ->
|
||||
G.Description ->
|
||||
m (InputFieldsParser n (HashMap (Column b) a))
|
||||
updateOperator tableGQLName opName mkParser columns opDesc objDesc = do
|
||||
fieldParsers :: NonEmpty (InputFieldsParser n (Maybe (Column b, a))) <-
|
||||
for columns \columnInfo -> do
|
||||
let fieldName = pgiName columnInfo
|
||||
fieldDesc = pgiDescription columnInfo
|
||||
fieldParser <- mkParser columnInfo
|
||||
pure $
|
||||
P.fieldOptional fieldName fieldDesc fieldParser
|
||||
`mapField` \value -> (pgiColumn columnInfo, value)
|
||||
|
||||
objName <- P.mkTypename $ tableGQLName <> opName <> $$(G.litName "_input")
|
||||
|
||||
pure $
|
||||
fmap (M.fromList . (fold :: Maybe [(Column b, a)] -> [(Column b, a)])) $
|
||||
P.fieldOptional opName (Just opDesc) $
|
||||
P.object objName (Just objDesc) $
|
||||
(catMaybes . toList) <$> sequenceA fieldParsers
|
||||
{-# ANN updateOperator ("HLint: ignore Use tuple-section" :: String) #-}
|
||||
|
||||
-- | Various update operators
|
||||
updateOperators ::
|
||||
forall pgKind m n r.
|
||||
(BackendSchema ('Postgres pgKind), MonadSchema n m, MonadTableInfo r m, Has MkTypename r) =>
|
||||
-- | table info
|
||||
( MonadParse n,
|
||||
MonadReader r m,
|
||||
Has MkTypename r,
|
||||
MonadError QErr m,
|
||||
MonadSchema n m,
|
||||
BackendSchema ('Postgres pgKind)
|
||||
) =>
|
||||
TableInfo ('Postgres pgKind) ->
|
||||
-- | update permissions of the table
|
||||
UpdPermInfo ('Postgres pgKind) ->
|
||||
m (InputFieldsParser n [(Column ('Postgres pgKind), IR.UpdOpExpG (UnpreparedValue ('Postgres pgKind)))])
|
||||
updateOperators tableInfo updatePermissions = do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
columns <- tableUpdateColumns tableInfo updatePermissions
|
||||
let numericCols = onlyNumCols columns
|
||||
jsonCols = onlyJSONBCols columns
|
||||
parsers <-
|
||||
catMaybes
|
||||
<$> sequenceA
|
||||
[ updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_set")
|
||||
typedParser
|
||||
IR.UpdSet
|
||||
columns
|
||||
"sets the columns of the filtered rows to the given values"
|
||||
(G.Description $ "input type for updating data in table " <>> tableName),
|
||||
updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_inc")
|
||||
typedParser
|
||||
IR.UpdInc
|
||||
numericCols
|
||||
"increments the numeric columns with given value of the filtered values"
|
||||
(G.Description $ "input type for incrementing numeric columns in table " <>> tableName),
|
||||
let desc = "prepend existing jsonb value of filtered columns with new jsonb value"
|
||||
in updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_prepend")
|
||||
typedParser
|
||||
IR.UpdPrepend
|
||||
jsonCols
|
||||
desc
|
||||
desc,
|
||||
let desc = "append existing jsonb value of filtered columns with new jsonb value"
|
||||
in updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_append")
|
||||
typedParser
|
||||
IR.UpdAppend
|
||||
jsonCols
|
||||
desc
|
||||
desc,
|
||||
let desc = "delete key/value pair or string element. key/value pairs are matched based on their key value"
|
||||
in updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_delete_key")
|
||||
nullableTextParser
|
||||
IR.UpdDeleteKey
|
||||
jsonCols
|
||||
desc
|
||||
desc,
|
||||
let desc =
|
||||
"delete the array element with specified index (negative integers count from the end). "
|
||||
<> "throws an error if top level container is not an array"
|
||||
in updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_delete_elem")
|
||||
nonNullableIntParser
|
||||
IR.UpdDeleteElem
|
||||
jsonCols
|
||||
desc
|
||||
desc,
|
||||
let desc = "delete the field or element with specified path (for JSON arrays, negative integers count from the end)"
|
||||
in updateOperator
|
||||
tableGQLName
|
||||
$$(G.litName "_delete_at_path")
|
||||
(fmap P.list . nonNullableTextParser)
|
||||
IR.UpdDeleteAtPath
|
||||
jsonCols
|
||||
desc
|
||||
desc
|
||||
]
|
||||
let allowedOperators = fst <$> parsers
|
||||
pure $
|
||||
fmap catMaybes (sequenceA $ snd <$> parsers)
|
||||
`P.bindFields` \opExps -> do
|
||||
-- there needs to be at least one operator in the update, even if it is empty
|
||||
let presetColumns = Map.toList $ IR.UpdSet . partialSQLExpToUnpreparedValue <$> upiSet updatePermissions
|
||||
when (null opExps && null presetColumns) $
|
||||
parseError $
|
||||
"at least any one of " <> commaSeparated allowedOperators <> " is expected"
|
||||
|
||||
-- no column should appear twice
|
||||
let flattenedExps = concat opExps
|
||||
erroneousExps = OMap.filter ((> 1) . length) $ OMap.groupTuples flattenedExps
|
||||
unless (OMap.null erroneousExps) $
|
||||
parseError $
|
||||
"column found in multiple operators; "
|
||||
<> T.intercalate
|
||||
". "
|
||||
[ dquote columnName <> " in " <> commaSeparated (IR.updateOperatorText <$> ops)
|
||||
| (columnName, ops) <- OMap.toList erroneousExps
|
||||
]
|
||||
|
||||
pure $ presetColumns <> flattenedExps
|
||||
where
|
||||
tableName = tableInfoName tableInfo
|
||||
typedParser columnInfo = fmap P.mkParameter <$> columnParser (pgiType columnInfo) (G.Nullability $ pgiIsNullable columnInfo)
|
||||
nonNullableTextParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGText) (G.Nullability False)
|
||||
nullableTextParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGText) (G.Nullability True)
|
||||
nonNullableIntParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGInteger) (G.Nullability False)
|
||||
|
||||
onlyJSONBCols = filter (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
|
||||
updateOperator ::
|
||||
G.Name ->
|
||||
G.Name ->
|
||||
(ColumnInfo b -> m (Parser 'Both n a)) ->
|
||||
(a -> IR.UpdOpExpG (UnpreparedValue b)) ->
|
||||
[ColumnInfo b] ->
|
||||
G.Description ->
|
||||
G.Description ->
|
||||
m (Maybe (Text, InputFieldsParser n (Maybe [(Column b, IR.UpdOpExpG (UnpreparedValue b))])))
|
||||
updateOperator tableGQLName opName mkParser updOpExp columns opDesc objDesc =
|
||||
whenMaybe (not $ null columns) do
|
||||
fields <- for columns \columnInfo -> do
|
||||
let fieldName = pgiName columnInfo
|
||||
fieldDesc = pgiDescription columnInfo
|
||||
fieldParser <- mkParser columnInfo
|
||||
pure $
|
||||
P.fieldOptional fieldName fieldDesc fieldParser
|
||||
`mapField` \value -> (pgiColumn columnInfo, updOpExp value)
|
||||
objName <- P.mkTypename $ tableGQLName <> opName <> $$(G.litName "_input")
|
||||
pure $
|
||||
(G.unName opName,) $
|
||||
P.fieldOptional opName (Just opDesc) $
|
||||
P.object objName (Just objDesc) $
|
||||
catMaybes <$> sequenceA fields
|
||||
m (InputFieldsParser n (HashMap (Column ('Postgres pgKind)) (UpdOpExpG (UnpreparedValue ('Postgres pgKind)))))
|
||||
updateOperators tableInfo updatePermissions =
|
||||
buildUpdateOperators
|
||||
(PGIR.UpdSet <$> presetColumns updatePermissions)
|
||||
[ PGIR.UpdSet <$> setOp,
|
||||
PGIR.UpdInc <$> incOp,
|
||||
PGIR.UpdPrepend <$> prependOp,
|
||||
PGIR.UpdAppend <$> appendOp,
|
||||
PGIR.UpdDeleteKey <$> deleteKeyOp,
|
||||
PGIR.UpdDeleteElem <$> deleteElemOp,
|
||||
PGIR.UpdDeleteAtPath <$> deleteAtPathOp
|
||||
]
|
||||
tableInfo
|
||||
updatePermissions
|
||||
|
@ -15,6 +15,7 @@ import Hasura.Backends.Postgres.SQL.Types qualified as PG
|
||||
import Hasura.Backends.Postgres.SQL.Value qualified as PG
|
||||
import Hasura.Backends.Postgres.Types.BoolExp qualified as PG
|
||||
import Hasura.Backends.Postgres.Types.CitusExtraTableMetadata qualified as Citus
|
||||
import Hasura.Backends.Postgres.Types.Update qualified as PG
|
||||
import Hasura.Base.Error
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Backend
|
||||
@ -28,6 +29,9 @@ import Hasura.SQL.Tag
|
||||
-- Some types of 'Backend' differ across different Postgres "kinds". This
|
||||
-- class alllows each "kind" to specify its own specific implementation. All
|
||||
-- common code is directly part of the `Backend` instance.
|
||||
--
|
||||
-- Note: Users shouldn't ever put this as a constraint. Use `Backend ('Postgres
|
||||
-- pgKind)` instead.
|
||||
class
|
||||
( Representable (PgExtraTableMetadata pgKind),
|
||||
J.ToJSON (PgExtraTableMetadata pgKind),
|
||||
@ -71,6 +75,8 @@ instance
|
||||
type SQLExpression ('Postgres pgKind) = PG.SQLExp
|
||||
type SQLOperator ('Postgres pgKind) = PG.SQLOp
|
||||
|
||||
type BackendUpdate ('Postgres pgKind) = PG.BackendUpdate
|
||||
|
||||
type ExtraTableMetadata ('Postgres pgKind) = PgExtraTableMetadata pgKind
|
||||
type ExtraInsertData ('Postgres pgKind) = ()
|
||||
|
||||
|
@ -3,11 +3,13 @@ module Hasura.Backends.Postgres.Translate.Update
|
||||
)
|
||||
where
|
||||
|
||||
import Data.HashMap.Strict qualified as Map
|
||||
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
||||
import Hasura.Backends.Postgres.SQL.Types
|
||||
import Hasura.Backends.Postgres.Translate.BoolExp
|
||||
import Hasura.Backends.Postgres.Translate.Insert
|
||||
import Hasura.Backends.Postgres.Translate.Returning
|
||||
import Hasura.Backends.Postgres.Types.Update
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.Update
|
||||
import Hasura.RQL.Types
|
||||
@ -15,9 +17,9 @@ import Hasura.SQL.Types
|
||||
|
||||
mkUpdateCTE ::
|
||||
Backend ('Postgres pgKind) =>
|
||||
AnnUpd ('Postgres pgKind) ->
|
||||
AnnotatedUpdateNode ('Postgres pgKind) ->
|
||||
S.CTE
|
||||
mkUpdateCTE (AnnUpd tn opExps (permFltr, wc) chk _ columnsInfo) =
|
||||
mkUpdateCTE (AnnotatedUpdateNode tn (permFltr, wc) chk (BackendUpdate opExps) _ columnsInfo) =
|
||||
S.CTEUpdate update
|
||||
where
|
||||
update =
|
||||
@ -27,7 +29,7 @@ mkUpdateCTE (AnnUpd tn opExps (permFltr, wc) chk _ columnsInfo) =
|
||||
$ [ S.selectStar,
|
||||
asCheckErrorExtractor $ insertCheckConstraint checkExpr
|
||||
]
|
||||
setExp = S.SetExp $ map (expandOperator columnsInfo) opExps
|
||||
setExp = S.SetExp $ map (expandOperator columnsInfo) (Map.toList opExps)
|
||||
tableFltr = Just $ S.WhereFrag tableFltrExpr
|
||||
tableFltrExpr = toSQLBoolExp (S.QualTable tn) $ andAnnBoolExps permFltr wc
|
||||
checkExpr = toSQLBoolExp (S.QualTable tn) chk
|
||||
|
34
server/src-lib/Hasura/Backends/Postgres/Types/Update.hs
Normal file
34
server/src-lib/Hasura/Backends/Postgres/Types/Update.hs
Normal file
@ -0,0 +1,34 @@
|
||||
-- | This module defines the Update-related IR types specific to Postgres.
|
||||
module Hasura.Backends.Postgres.Types.Update
|
||||
( BackendUpdate (..),
|
||||
UpdOpExpG (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Hasura.Backends.Postgres.SQL.Types (PGCol)
|
||||
import Hasura.Prelude
|
||||
|
||||
-- | The PostgreSQL-specific data of an Update expression.
|
||||
--
|
||||
-- This is parameterised over @v@ which enables different phases of IR
|
||||
-- transformation to maintain the overall structure while enriching/transforming
|
||||
-- the data at the leaves.
|
||||
data BackendUpdate v = BackendUpdate
|
||||
{ -- | The update operations to perform on each colum.
|
||||
updateOperations :: !(HashMap PGCol (UpdOpExpG v))
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable, Generic, Data)
|
||||
|
||||
-- | The various @update operators@ supported by PostgreSQL,
|
||||
-- i.e. the @_set@, @_inc@ operators that appear in the schema.
|
||||
--
|
||||
-- See <https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/update.html#postgres-update-mutation Update Mutations User docs>
|
||||
data UpdOpExpG v
|
||||
= UpdSet !v
|
||||
| UpdInc !v
|
||||
| UpdAppend !v
|
||||
| UpdPrepend !v
|
||||
| UpdDeleteKey !v
|
||||
| UpdDeleteElem !v
|
||||
| UpdDeleteAtPath ![v]
|
||||
deriving (Functor, Foldable, Traversable, Generic, Data)
|
@ -83,7 +83,9 @@ boolExp sourceName tableInfo selectPermissions = memoizeOn 'boolExp (sourceName,
|
||||
FIRelationship relationshipInfo -> do
|
||||
remoteTableInfo <- askTableInfo sourceName $ riRTable relationshipInfo
|
||||
remotePermissions <- lift $ tableSelectPermissions remoteTableInfo
|
||||
let remoteTableFilter = (fmap . fmap) partialSQLExpToUnpreparedValue $ maybe annBoolExpTrue spiFilter remotePermissions
|
||||
let remoteTableFilter =
|
||||
fmap partialSQLExpToUnpreparedValue
|
||||
<$> maybe annBoolExpTrue spiFilter remotePermissions
|
||||
remoteBoolExp <- lift $ boolExp sourceName remoteTableInfo remotePermissions
|
||||
pure $ fmap (AVRelationship relationshipInfo . andAnnBoolExps remoteTableFilter) remoteBoolExp
|
||||
FIComputedField ComputedFieldInfo {..} -> do
|
||||
|
@ -174,11 +174,11 @@ buildTableInsertMutationFields
|
||||
buildTableUpdateMutationFields ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
-- | Action that builds the @update operators@ the backend supports
|
||||
-- | TODO: Docs. WAS: Action that builds the @update operators@ the backend supports
|
||||
( TableInfo b ->
|
||||
UpdPermInfo b ->
|
||||
m
|
||||
(InputFieldsParser n [(Column b, UpdOpExpG (UnpreparedValue b))])
|
||||
(InputFieldsParser n (BackendUpdate b (UnpreparedValue b)))
|
||||
) ->
|
||||
-- | The source that the table lives in
|
||||
SourceName ->
|
||||
|
@ -407,7 +407,7 @@ updateTable ::
|
||||
( TableInfo b ->
|
||||
UpdPermInfo b ->
|
||||
m
|
||||
(InputFieldsParser n [(Column b, UpdOpExpG (UnpreparedValue b))])
|
||||
(InputFieldsParser n (BackendUpdate b (UnpreparedValue b)))
|
||||
) ->
|
||||
-- | table source
|
||||
SourceName ->
|
||||
@ -421,13 +421,13 @@ updateTable ::
|
||||
UpdPermInfo b ->
|
||||
-- | select permissions of the table (if any)
|
||||
Maybe (SelPermInfo b) ->
|
||||
m (FieldParser n (IR.AnnUpdG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b)))
|
||||
updateTable updateOperators sourceName tableInfo fieldName description updatePerms selectPerms = do
|
||||
m (FieldParser n (IR.AnnotatedUpdateNodeG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b)))
|
||||
updateTable updateIR sourceName tableInfo fieldName description updatePerms selectPerms = do
|
||||
let tableName = tableInfoName tableInfo
|
||||
columns = tableColumns tableInfo
|
||||
whereName = $$(G.litName "where")
|
||||
whereDesc = "filter the rows which have to be updated"
|
||||
opArgs <- updateOperators tableInfo updatePerms
|
||||
opArgs <- updateIR tableInfo updatePerms
|
||||
whereArg <- P.field whereName (Just whereDesc) <$> boolExp sourceName tableInfo selectPerms
|
||||
selection <- mutationSelectionSet sourceName tableInfo selectPerms
|
||||
let argsParser = liftA2 (,) opArgs whereArg
|
||||
@ -445,7 +445,7 @@ updateTableByPk ::
|
||||
-- | Update Operators
|
||||
( TableInfo b ->
|
||||
UpdPermInfo b ->
|
||||
m (InputFieldsParser n [(Column b, UpdOpExpG (UnpreparedValue b))])
|
||||
m (InputFieldsParser n (BackendUpdate b (UnpreparedValue b)))
|
||||
) ->
|
||||
-- | table source
|
||||
SourceName ->
|
||||
@ -459,14 +459,14 @@ updateTableByPk ::
|
||||
UpdPermInfo b ->
|
||||
-- | select permissions of the table
|
||||
SelPermInfo b ->
|
||||
m (Maybe (FieldParser n (IR.AnnUpdG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b))))
|
||||
updateTableByPk updateOperators sourceName tableInfo fieldName description updatePerms selectPerms = runMaybeT $ do
|
||||
m (Maybe (FieldParser n (IR.AnnotatedUpdateNodeG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b))))
|
||||
updateTableByPk backendIR sourceName tableInfo fieldName description updatePerms selectPerms = runMaybeT $ do
|
||||
let columns = tableColumns tableInfo
|
||||
tableName = tableInfoName tableInfo
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
pkArgs <- MaybeT $ primaryKeysArguments tableInfo selectPerms
|
||||
lift $ do
|
||||
opArgs <- updateOperators tableInfo updatePerms
|
||||
opArgs <- backendIR tableInfo updatePerms
|
||||
selection <- tableSelectionSet sourceName tableInfo selectPerms
|
||||
pkObjectName <- P.mkTypename $ tableGQLName <> $$(G.litName "_pk_columns_input")
|
||||
let pkFieldName = $$(G.litName "pk_columns")
|
||||
@ -485,16 +485,16 @@ mkUpdateObject ::
|
||||
TableName b ->
|
||||
[ColumnInfo b] ->
|
||||
UpdPermInfo b ->
|
||||
( ( [(Column b, IR.UpdOpExpG (UnpreparedValue b))],
|
||||
( ( BackendUpdate b (UnpreparedValue b),
|
||||
AnnBoolExp b (UnpreparedValue b)
|
||||
),
|
||||
IR.MutationOutputG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b)
|
||||
) ->
|
||||
IR.AnnUpdG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b)
|
||||
mkUpdateObject table columns updatePerms ((opExps, whereExp), mutationOutput) =
|
||||
IR.AnnUpd
|
||||
IR.AnnotatedUpdateNodeG b (IR.RemoteSelect UnpreparedValue) (UnpreparedValue b)
|
||||
mkUpdateObject table columns updatePerms ((backendIR, whereExp), mutationOutput) =
|
||||
IR.AnnotatedUpdateNode
|
||||
{ IR.uqp1Table = table,
|
||||
IR.uqp1OpExps = opExps,
|
||||
IR.uqp1BackendIR = backendIR,
|
||||
IR.uqp1Where = (permissionFilter, whereExp),
|
||||
IR.uqp1Check = checkExp,
|
||||
IR.uqp1Output = mutationOutput,
|
||||
|
@ -163,6 +163,8 @@ tableColumns tableInfo =
|
||||
columnInfo (FIColumn ci) = Just ci
|
||||
columnInfo _ = Nothing
|
||||
|
||||
-- | Get the columns of a table that my be selected under the given select
|
||||
-- permissions.
|
||||
tableSelectColumns ::
|
||||
forall m n r b.
|
||||
(Backend b, MonadSchema n m, MonadTableInfo r m, MonadRole r m) =>
|
||||
@ -176,6 +178,8 @@ tableSelectColumns sourceName tableInfo permissions =
|
||||
columnInfo (FIColumn ci) = Just ci
|
||||
columnInfo _ = Nothing
|
||||
|
||||
-- | Get the columns of a table that my be updated under the given update
|
||||
-- permissions.
|
||||
tableUpdateColumns ::
|
||||
forall m n b.
|
||||
(Backend b, MonadSchema n m) =>
|
||||
|
@ -8,6 +8,7 @@ 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 Q
|
||||
@ -17,6 +18,7 @@ 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
|
||||
@ -98,7 +100,7 @@ validateUpdateQueryWith ::
|
||||
SessionVariableBuilder ('Postgres 'Vanilla) m ->
|
||||
ValueParser ('Postgres 'Vanilla) m S.SQLExp ->
|
||||
UpdateQuery ->
|
||||
m (AnnUpd ('Postgres 'Vanilla))
|
||||
m (AnnotatedUpdateNode ('Postgres 'Vanilla))
|
||||
validateUpdateQueryWith sessVarBldr prepValBldr uq = do
|
||||
let tableName = uqTable uq
|
||||
tableInfo <- withPathK "table" $ askTabInfoSource tableName
|
||||
@ -177,11 +179,11 @@ validateUpdateQueryWith sessVarBldr prepValBldr uq = do
|
||||
(upiCheck updPerm)
|
||||
|
||||
return $
|
||||
AnnUpd
|
||||
AnnotatedUpdateNode
|
||||
tableName
|
||||
(fmap UpdSet <$> setExpItems)
|
||||
(resolvedUpdFltr, annSQLBoolExp)
|
||||
resolvedUpdCheck
|
||||
(BackendUpdate $ Map.fromList $ fmap UpdSet <$> setExpItems)
|
||||
(mkDefaultMutFlds mAnnRetCols)
|
||||
allCols
|
||||
where
|
||||
@ -194,7 +196,7 @@ validateUpdateQueryWith sessVarBldr prepValBldr uq = do
|
||||
validateUpdateQuery ::
|
||||
(QErrM m, UserInfoM m, CacheRM m) =>
|
||||
UpdateQuery ->
|
||||
m (AnnUpd ('Postgres 'Vanilla), DS.Seq Q.PrepArg)
|
||||
m (AnnotatedUpdateNode ('Postgres 'Vanilla), DS.Seq Q.PrepArg)
|
||||
validateUpdateQuery query = do
|
||||
let source = uqSource query
|
||||
tableCache :: TableCache ('Postgres 'Vanilla) <- askTableCache source
|
||||
|
@ -41,7 +41,7 @@ data RootField (db :: BackendType -> Type) remote action raw where
|
||||
|
||||
data MutationDB (b :: BackendType) (r :: BackendType -> Type) v
|
||||
= MDBInsert (AnnInsert b r v)
|
||||
| MDBUpdate (AnnUpdG b r v)
|
||||
| MDBUpdate (AnnotatedUpdateNodeG b r v)
|
||||
| MDBDelete (AnnDelG b r v)
|
||||
| -- | This represents a VOLATILE function, and is AnnSimpleSelG for easy
|
||||
-- re-use of non-VOLATILE function tracking code.
|
||||
|
@ -1,8 +1,6 @@
|
||||
module Hasura.RQL.IR.Update
|
||||
( AnnUpd,
|
||||
AnnUpdG (..),
|
||||
UpdOpExpG (..),
|
||||
updateOperatorText,
|
||||
( AnnotatedUpdateNode,
|
||||
AnnotatedUpdateNodeG (..),
|
||||
)
|
||||
where
|
||||
|
||||
@ -14,11 +12,12 @@ import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Column
|
||||
import Hasura.SQL.Backend
|
||||
|
||||
data AnnUpdG (b :: BackendType) (r :: BackendType -> Type) v = AnnUpd
|
||||
data AnnotatedUpdateNodeG (b :: BackendType) (r :: BackendType -> Type) v = AnnotatedUpdateNode
|
||||
{ uqp1Table :: !(TableName b),
|
||||
uqp1OpExps :: ![(Column b, UpdOpExpG v)],
|
||||
uqp1Where :: !(AnnBoolExp b v, AnnBoolExp b v),
|
||||
uqp1Check :: !(AnnBoolExp b v),
|
||||
-- | All the backend-specific data related to an update mutation
|
||||
uqp1BackendIR :: BackendUpdate b v,
|
||||
-- we don't prepare the arguments for returning
|
||||
-- however the session variable can still be
|
||||
-- converted as desired
|
||||
@ -27,28 +26,4 @@ data AnnUpdG (b :: BackendType) (r :: BackendType -> Type) v = AnnUpd
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable)
|
||||
|
||||
type AnnUpd b = AnnUpdG b (Const Void) (SQLExpression b)
|
||||
|
||||
data UpdOpExpG v
|
||||
= UpdSet !v
|
||||
| UpdInc !v
|
||||
| UpdAppend !v
|
||||
| UpdPrepend !v
|
||||
| UpdDeleteKey !v
|
||||
| UpdDeleteElem !v
|
||||
| UpdDeleteAtPath ![v]
|
||||
deriving (Functor, Foldable, Traversable, Generic, Data)
|
||||
|
||||
-- NOTE: This function can be improved, because we use
|
||||
-- the literal values defined below in the 'updateOperators'
|
||||
-- function in 'Hasura.GraphQL.Schema.Mutation'. It would
|
||||
-- be nice if we could avoid duplicating the string literal
|
||||
-- values
|
||||
updateOperatorText :: UpdOpExpG a -> Text
|
||||
updateOperatorText (UpdSet _) = "_set"
|
||||
updateOperatorText (UpdInc _) = "_inc"
|
||||
updateOperatorText (UpdAppend _) = "_append"
|
||||
updateOperatorText (UpdPrepend _) = "_prepend"
|
||||
updateOperatorText (UpdDeleteKey _) = "_delete_key"
|
||||
updateOperatorText (UpdDeleteElem _) = "_delete_elem"
|
||||
updateOperatorText (UpdDeleteAtPath _) = "_delete_at_path"
|
||||
type AnnotatedUpdateNode b = AnnotatedUpdateNodeG b (Const Void) (SQLExpression b)
|
||||
|
@ -104,7 +104,11 @@ class
|
||||
Eq (XNodesAgg b),
|
||||
Show (XNodesAgg b),
|
||||
Eq (XRelay b),
|
||||
Show (XRelay b)
|
||||
Show (XRelay b),
|
||||
-- Intermediate Representations
|
||||
Functor (BackendUpdate b),
|
||||
Foldable (BackendUpdate b),
|
||||
Traversable (BackendUpdate b)
|
||||
) =>
|
||||
Backend (b :: BackendType)
|
||||
where
|
||||
@ -129,6 +133,14 @@ class
|
||||
|
||||
type ExtraTableMetadata b :: Type
|
||||
|
||||
-- Backend-specific IR types
|
||||
|
||||
-- | Intermediate Representation of Update Mutations.
|
||||
-- The default implementation makes update expressions uninstantiable.
|
||||
type BackendUpdate b :: Type -> Type
|
||||
|
||||
type BackendUpdate b = Const Void
|
||||
|
||||
-- | Extra backend specific context needed for insert mutations.
|
||||
type ExtraInsertData b :: Type
|
||||
|
||||
|
@ -7,6 +7,7 @@ module Hasura.RQL.Types.Column
|
||||
isScalarColumnWhere,
|
||||
ValueParser,
|
||||
onlyNumCols,
|
||||
isNumCol,
|
||||
onlyComparableCols,
|
||||
parseScalarValueColumnType,
|
||||
parseScalarValuesColumnType,
|
||||
@ -218,7 +219,10 @@ instance Backend b => ToJSON (ColumnInfo b) where
|
||||
type PrimaryKeyColumns b = NESeq (ColumnInfo b)
|
||||
|
||||
onlyNumCols :: forall b. Backend b => [ColumnInfo b] -> [ColumnInfo b]
|
||||
onlyNumCols = filter (isScalarColumnWhere (isNumType @b) . pgiType)
|
||||
onlyNumCols = filter isNumCol
|
||||
|
||||
isNumCol :: forall b. Backend b => ColumnInfo b -> Bool
|
||||
isNumCol = isScalarColumnWhere (isNumType @b) . pgiType
|
||||
|
||||
onlyComparableCols :: forall b. Backend b => [ColumnInfo b] -> [ColumnInfo b]
|
||||
onlyComparableCols = filter (isScalarColumnWhere (isComparableType @b) . pgiType)
|
||||
|
@ -6,7 +6,7 @@ response:
|
||||
- extensions:
|
||||
path: "$.selectionSet.update_article.args"
|
||||
code: validation-failed
|
||||
message: column found in multiple operators; "author_id" in _set, _inc. "id" in _set, _inc
|
||||
message: 'Column found in multiple operators: "author_id", "id".'
|
||||
|
||||
query:
|
||||
query: |
|
||||
|
Loading…
Reference in New Issue
Block a user