mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-05 06:18:04 +03:00
Refactor XOnConflict and ExtraInsertData
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3055 GitOrigin-RevId: 06f7f92f0e09695f5f7bc02df457d3b96ac9f5f6
This commit is contained in:
parent
e4064c9a90
commit
2fbcd783e7
@ -428,6 +428,7 @@ library
|
||||
, Hasura.Backends.Postgres.Types.BoolExp
|
||||
, Hasura.Backends.Postgres.Types.CitusExtraTableMetadata
|
||||
, Hasura.Backends.Postgres.Types.Column
|
||||
, Hasura.Backends.Postgres.Types.Insert
|
||||
, Hasura.Backends.Postgres.Types.Table
|
||||
, Hasura.Backends.Postgres.Types.Update
|
||||
|
||||
@ -585,6 +586,7 @@ library
|
||||
, Hasura.RQL.DML.Update
|
||||
, Hasura.RQL.DML.Types
|
||||
, Hasura.RQL.IR.BoolExp
|
||||
, Hasura.RQL.IR.Conflict
|
||||
, Hasura.RQL.IR.Delete
|
||||
, Hasura.RQL.IR.Insert
|
||||
, Hasura.RQL.IR.OrderBy
|
||||
@ -641,6 +643,7 @@ library
|
||||
, Hasura.GraphQL.Schema.BoolExp
|
||||
, Hasura.GraphQL.Schema.Build
|
||||
, Hasura.GraphQL.Schema.Common
|
||||
, Hasura.GraphQL.Schema.Conflict
|
||||
, Hasura.GraphQL.Schema.Introspect
|
||||
, Hasura.GraphQL.Schema.Instances
|
||||
, Hasura.GraphQL.Schema.Mutation
|
||||
|
@ -59,9 +59,6 @@ instance BackendSchema 'BigQuery where
|
||||
-- SQL literals
|
||||
columnDefaultValue = error "TODO: Make impossible by the type system. BigQuery doesn't support insertions."
|
||||
|
||||
-- Extra insert data
|
||||
getExtraInsertData = const ()
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Top level parsers
|
||||
|
||||
|
@ -35,10 +35,8 @@ instance Backend 'BigQuery where
|
||||
type XRelay 'BigQuery = XDisable
|
||||
type XNodesAgg 'BigQuery = XEnable
|
||||
type XNestedInserts 'BigQuery = XDisable
|
||||
type XOnConflict 'BigQuery = XDisable
|
||||
|
||||
type ExtraTableMetadata 'BigQuery = ()
|
||||
type ExtraInsertData 'BigQuery = ()
|
||||
|
||||
functionArgScalarType :: FunctionArgType 'BigQuery -> ScalarType 'BigQuery
|
||||
functionArgScalarType = absurd
|
||||
|
@ -1071,7 +1071,7 @@ fromInsert IR.AnnInsert {..} =
|
||||
insertRows = normalizeInsertRows _aiData $ map (IR.getInsertColumns) _aiInsObj
|
||||
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
||||
insertValues = map (Values . map snd) insertRows
|
||||
primaryKeyColumns = map OutputColumn $ _mssqlPrimaryKeyColumns _aiExtraInsertData
|
||||
primaryKeyColumns = map OutputColumn $ _mssqlPrimaryKeyColumns _aiBackendInsert
|
||||
in Insert _aiTableName insertColumnNames (Output Inserted primaryKeyColumns) insertValues
|
||||
|
||||
-- | Normalize a row by adding missing columns with 'DEFAULT' value and sort by column name to make sure
|
||||
@ -1097,7 +1097,7 @@ fromInsert IR.AnnInsert {..} =
|
||||
normalizeInsertRows :: IR.AnnIns 'MSSQL [] Expression -> [[(Column 'MSSQL, Expression)]] -> [[(Column 'MSSQL, Expression)]]
|
||||
normalizeInsertRows IR.AnnIns {..} insertRows =
|
||||
let isIdentityColumn column =
|
||||
IR.pgiColumn column `elem` _mssqlIdentityColumns _aiExtraInsertData
|
||||
IR.pgiColumn column `elem` _mssqlIdentityColumns _aiBackendInsert
|
||||
allColumnsWithDefaultValue =
|
||||
-- DEFAULT or NULL are not allowed as explicit identity values.
|
||||
map ((,DefaultExpression) . IR.pgiColumn) $ filter (not . isIdentityColumn) _aiTableCols
|
||||
|
@ -307,7 +307,7 @@ executeInsert userInfo stringifyNum sourceConfig annInsert = do
|
||||
|
||||
buildInsertTx :: AnnInsert 'MSSQL Void Expression -> Tx.TxET QErr IO EncJSON
|
||||
buildInsertTx insert = do
|
||||
let identityColumns = _mssqlIdentityColumns $ _aiExtraInsertData $ _aiData insert
|
||||
let identityColumns = _mssqlIdentityColumns $ _aiBackendInsert $ _aiData insert
|
||||
insertColumns = concatMap (map fst . getInsertColumns) $ _aiInsObj $ _aiData insert
|
||||
|
||||
-- Set identity insert to ON if insert object contains identity columns
|
||||
|
@ -65,12 +65,6 @@ instance BackendSchema 'MSSQL where
|
||||
-- SQL literals
|
||||
columnDefaultValue = msColumnDefaultValue
|
||||
|
||||
-- Extra insert data
|
||||
getExtraInsertData tableInfo =
|
||||
let pkeyColumns = fmap (map pgiColumn . toList . _pkColumns) . _tciPrimaryKey . _tiCoreInfo $ tableInfo
|
||||
identityColumns = _tciExtraTableMetadata $ _tiCoreInfo tableInfo
|
||||
in MSSQLExtraInsertData (fromMaybe [] pkeyColumns) identityColumns
|
||||
|
||||
-- | MSSQL only supports inserts into tables that have a primary key defined.
|
||||
supportsInserts :: TableInfo 'MSSQL -> Bool
|
||||
supportsInserts = isJust . _tciPrimaryKey . _tiCoreInfo
|
||||
@ -111,6 +105,7 @@ msBuildTableInsertMutationFields
|
||||
mUpdPerms
|
||||
| supportsInserts tableInfo =
|
||||
GSB.buildTableInsertMutationFields
|
||||
(\_sourceName tableInfo' _selectPermMaybe _updPermMaybe -> return (pure $ getExtraInsertData tableInfo'))
|
||||
sourceName
|
||||
tableName
|
||||
tableInfo
|
||||
@ -120,6 +115,12 @@ msBuildTableInsertMutationFields
|
||||
mUpdPerms
|
||||
| otherwise = return []
|
||||
|
||||
getExtraInsertData :: TableInfo 'MSSQL -> MSSQLExtraInsertData v
|
||||
getExtraInsertData tableInfo =
|
||||
let pkeyColumns = fmap (map pgiColumn . toList . _pkColumns) . _tciPrimaryKey . _tiCoreInfo $ tableInfo
|
||||
identityColumns = _tciExtraTableMetadata $ _tiCoreInfo tableInfo
|
||||
in MSSQLExtraInsertData (fromMaybe [] pkeyColumns) identityColumns
|
||||
|
||||
-- Replace the instance implementation of 'buildTableUpdateMutationFields' with
|
||||
-- the below when we have an executable implementation of updates, in order to
|
||||
-- enable the update schema.
|
||||
|
@ -37,13 +37,12 @@ instance Backend 'MSSQL where
|
||||
type BackendUpdate 'MSSQL = MSSQL.BackendUpdate
|
||||
|
||||
type ExtraTableMetadata 'MSSQL = [MSSQL.ColumnName] -- List of identity columns
|
||||
type ExtraInsertData 'MSSQL = MSSQL.MSSQLExtraInsertData
|
||||
type BackendInsert 'MSSQL = MSSQL.MSSQLExtraInsertData
|
||||
|
||||
type XComputedField 'MSSQL = XDisable
|
||||
type XRelay 'MSSQL = XDisable
|
||||
type XNodesAgg 'MSSQL = XEnable
|
||||
type XNestedInserts 'MSSQL = XDisable
|
||||
type XOnConflict 'MSSQL = XDisable
|
||||
|
||||
functionArgScalarType :: FunctionArgType 'MSSQL -> ScalarType 'MSSQL
|
||||
functionArgScalarType = absurd
|
||||
|
@ -5,8 +5,10 @@ module Hasura.Backends.MSSQL.Types.Insert
|
||||
where
|
||||
|
||||
import Hasura.Backends.MSSQL.Types.Internal
|
||||
import Hasura.Prelude
|
||||
|
||||
data MSSQLExtraInsertData = MSSQLExtraInsertData
|
||||
data MSSQLExtraInsertData v = MSSQLExtraInsertData
|
||||
{ _mssqlPrimaryKeyColumns :: ![ColumnName],
|
||||
_mssqlIdentityColumns :: ![ColumnName]
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable)
|
||||
|
@ -46,7 +46,6 @@ instance BackendSchema 'MySQL where
|
||||
computedField = error "computedField: MySQL backend does not support this operation yet."
|
||||
node = error "node: MySQL backend does not support this operation yet."
|
||||
columnDefaultValue = error "columnDefaultValue: MySQL backend does not support this operation yet."
|
||||
getExtraInsertData = const ()
|
||||
|
||||
mysqlTableArgs ::
|
||||
forall r m n.
|
||||
|
@ -34,9 +34,7 @@ instance Backend 'MySQL where
|
||||
type XRelay 'MySQL = Void
|
||||
type XNodesAgg 'MySQL = XEnable
|
||||
type ExtraTableMetadata 'MySQL = ()
|
||||
type ExtraInsertData 'MySQL = ()
|
||||
type XNestedInserts 'MySQL = XDisable
|
||||
type XOnConflict 'MySQL = XDisable
|
||||
|
||||
functionArgScalarType :: FunctionArgType 'MySQL -> ScalarType 'MySQL
|
||||
functionArgScalarType = error "functionArgScalarType: not implemented yet"
|
||||
|
@ -20,6 +20,7 @@ import Hasura.Backends.Postgres.Translate.Insert qualified as PGT
|
||||
import Hasura.Backends.Postgres.Translate.Mutation qualified as PGT
|
||||
import Hasura.Backends.Postgres.Translate.Returning qualified as PGT
|
||||
import Hasura.Backends.Postgres.Translate.Select (PostgresAnnotatedFieldJSON)
|
||||
import Hasura.Backends.Postgres.Types.Insert
|
||||
import Hasura.Base.Error
|
||||
import Hasura.EncJSON
|
||||
import Hasura.Prelude
|
||||
@ -73,7 +74,7 @@ insertMultipleObjects ::
|
||||
insertMultipleObjects multiObjIns additionalColumns userInfo mutationOutput planVars stringifyNum =
|
||||
bool withoutRelsInsert withRelsInsert anyRelsToInsert
|
||||
where
|
||||
IR.AnnIns insObjs table conflictClause checkCondition columnInfos defVals () = multiObjIns
|
||||
IR.AnnIns insObjs table checkCondition columnInfos defVals (BackendInsert conflictClause) = multiObjIns
|
||||
allInsObjRels = concatMap IR.getInsertObjectRelationships insObjs
|
||||
allInsArrRels = concatMap IR.getInsertArrayRelationships insObjs
|
||||
anyRelsToInsert = not $ null allInsArrRels && null allInsObjRels
|
||||
@ -88,7 +89,7 @@ insertMultipleObjects multiObjIns additionalColumns userInfo mutationOutput plan
|
||||
table
|
||||
columnNames
|
||||
columnValues
|
||||
(snd <$> conflictClause)
|
||||
conflictClause
|
||||
checkCondition
|
||||
mutationOutput
|
||||
columnInfos
|
||||
@ -99,7 +100,7 @@ insertMultipleObjects multiObjIns additionalColumns userInfo mutationOutput plan
|
||||
|
||||
withRelsInsert = do
|
||||
insertRequests <- indexedForM insObjs \obj -> do
|
||||
let singleObj = IR.AnnIns (IR.Single obj) table conflictClause checkCondition columnInfos defVals ()
|
||||
let singleObj = IR.AnnIns (IR.Single obj) table checkCondition columnInfos defVals (BackendInsert conflictClause)
|
||||
insertObject singleObj additionalColumns userInfo planVars stringifyNum
|
||||
let affectedRows = sum $ map fst insertRequests
|
||||
columnValues = mapMaybe snd insertRequests
|
||||
@ -139,7 +140,7 @@ insertObject singleObjIns additionalColumns userInfo planVars stringifyNum = Tra
|
||||
objRelDeterminedCols = concatMap snd objInsRes
|
||||
finalInsCols = columns <> objRelDeterminedCols <> additionalColumns
|
||||
|
||||
cte <- mkInsertQ table (snd <$> onConflict) finalInsCols defaultValues checkCond
|
||||
cte <- mkInsertQ table onConflict finalInsCols defaultValues checkCond
|
||||
|
||||
PGE.MutateResp affRows colVals <-
|
||||
liftTx $
|
||||
@ -151,7 +152,7 @@ insertObject singleObjIns additionalColumns userInfo planVars stringifyNum = Tra
|
||||
|
||||
return (totAffRows, colValM)
|
||||
where
|
||||
IR.AnnIns (IR.Single annObj) table onConflict checkCond allColumns defaultValues () = singleObjIns
|
||||
IR.AnnIns (IR.Single annObj) table checkCond allColumns defaultValues (BackendInsert onConflict) = singleObjIns
|
||||
columns = IR.getInsertColumns annObj
|
||||
objectRels = IR.getInsertObjectRelationships annObj
|
||||
arrayRels = IR.getInsertArrayRelationships annObj
|
||||
@ -172,15 +173,7 @@ insertObject singleObjIns additionalColumns userInfo planVars stringifyNum = Tra
|
||||
objToArr IR.RelIns {..} = IR.RelIns (singleToMulti _riAnnIns) _riRelInfo
|
||||
|
||||
singleToMulti :: forall a b. IR.SingleObjIns b a -> IR.MultiObjIns b a
|
||||
singleToMulti IR.AnnIns {..} =
|
||||
IR.AnnIns
|
||||
[IR.unSingle _aiInsObj]
|
||||
_aiTableName
|
||||
_aiConflictClause
|
||||
_aiCheckCond
|
||||
_aiTableCols
|
||||
_aiDefVals
|
||||
_aiExtraInsertData
|
||||
singleToMulti annIns = annIns {IR._aiInsObj = [IR.unSingle $ IR._aiInsObj annIns]}
|
||||
|
||||
withArrRels ::
|
||||
Maybe (ColumnValues ('Postgres pgKind) TxtEncodedVal) ->
|
||||
|
@ -19,6 +19,7 @@ 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.Insert as PGIR
|
||||
import Hasura.Backends.Postgres.Types.Update as PGIR
|
||||
import Hasura.Base.Error
|
||||
import Hasura.GraphQL.Parser hiding (EnumValueInfo, field)
|
||||
@ -33,13 +34,24 @@ import Hasura.GraphQL.Schema.Backend qualified as BS
|
||||
import Hasura.GraphQL.Schema.BoolExp
|
||||
import Hasura.GraphQL.Schema.Build qualified as GSB
|
||||
import Hasura.GraphQL.Schema.Common
|
||||
import Hasura.GraphQL.Schema.Conflict
|
||||
import Hasura.GraphQL.Schema.Mutation qualified as GSB
|
||||
import Hasura.GraphQL.Schema.Select
|
||||
import Hasura.GraphQL.Schema.Update qualified as SU
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR
|
||||
import Hasura.RQL.IR.BoolExp
|
||||
import Hasura.RQL.IR.Root (RemoteRelationshipField)
|
||||
import Hasura.RQL.IR.Select
|
||||
( QueryDB (QDBConnection),
|
||||
)
|
||||
import Hasura.RQL.IR.Select qualified as IR
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.RQL.Types.Backend (Backend (..))
|
||||
import Hasura.RQL.Types.Column
|
||||
import Hasura.RQL.Types.Common
|
||||
import Hasura.RQL.Types.Function (FunctionInfo)
|
||||
import Hasura.RQL.Types.SourceCustomization (mkRootFieldName)
|
||||
import Hasura.RQL.Types.Table (SelPermInfo, TableInfo, UpdPermInfo)
|
||||
import Hasura.SQL.Backend (BackendType (Postgres), PostgresKind (Citus, Vanilla))
|
||||
import Hasura.SQL.Types
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
|
||||
@ -117,7 +129,7 @@ instance
|
||||
-- top level parsers
|
||||
buildTableQueryFields = GSB.buildTableQueryFields
|
||||
buildTableRelayQueryFields = pgkBuildTableRelayQueryFields
|
||||
buildTableInsertMutationFields = GSB.buildTableInsertMutationFields
|
||||
buildTableInsertMutationFields = GSB.buildTableInsertMutationFields backendInsertParser
|
||||
buildTableUpdateMutationFields = GSB.buildTableUpdateMutationFields (\ti updP -> fmap BackendUpdate <$> updateOperators ti updP) -- TODO: https://github.com/hasura/graphql-engine-mono/issues/2955
|
||||
buildTableDeleteMutationFields = GSB.buildTableDeleteMutationFields
|
||||
buildFunctionQueryFields = GSB.buildFunctionQueryFields
|
||||
@ -126,7 +138,7 @@ instance
|
||||
|
||||
-- table components
|
||||
tableArguments = defaultTableArgs
|
||||
mkRelationshipParser = GSB.mkDefaultRelationshipParser ()
|
||||
mkRelationshipParser = GSB.mkDefaultRelationshipParser backendInsertParser ()
|
||||
|
||||
-- backend extensions
|
||||
relayExtension = pgkRelayExtension @pgKind
|
||||
@ -134,7 +146,6 @@ instance
|
||||
|
||||
-- indivdual components
|
||||
columnParser = columnParser
|
||||
conflictObject = GSB.defaultConflictObject ()
|
||||
jsonPathArg = jsonPathArg
|
||||
orderByOperators = orderByOperators
|
||||
comparisonExps = comparisonExps
|
||||
@ -146,8 +157,16 @@ instance
|
||||
-- SQL literals
|
||||
columnDefaultValue = const PG.columnDefaultValue
|
||||
|
||||
-- Extra insert data
|
||||
getExtraInsertData = const ()
|
||||
backendInsertParser ::
|
||||
forall pgKind m r n.
|
||||
MonadBuildSchema ('Postgres pgKind) r m n =>
|
||||
SourceName ->
|
||||
TableInfo ('Postgres pgKind) ->
|
||||
Maybe (SelPermInfo ('Postgres pgKind)) ->
|
||||
Maybe (UpdPermInfo ('Postgres pgKind)) ->
|
||||
m (InputFieldsParser n (PGIR.BackendInsert ('Postgres pgKind) (UnpreparedValue ('Postgres pgKind))))
|
||||
backendInsertParser sourceName tableInfo selectPerms updatePerms =
|
||||
fmap BackendInsert <$> onConflictFieldParser sourceName tableInfo selectPerms updatePerms
|
||||
|
||||
----------------------------------------------------------------
|
||||
-- Top level parsers
|
||||
@ -166,7 +185,7 @@ buildTableRelayQueryFields sourceName tableName tableInfo gqlName pkeyColumns se
|
||||
let fieldDesc = Just $ G.Description $ "fetch data from the table: " <>> tableName
|
||||
fieldName <- mkRootFieldName $ gqlName <> $$(G.litName "_connection")
|
||||
fmap afold $
|
||||
optionalFieldParser (QDBConnection) $
|
||||
optionalFieldParser QDBConnection $
|
||||
selectTableConnection sourceName tableInfo fieldName fieldDesc pkeyColumns selPerms
|
||||
|
||||
buildFunctionRelayQueryFields ::
|
||||
@ -182,7 +201,7 @@ buildFunctionRelayQueryFields ::
|
||||
buildFunctionRelayQueryFields sourceName functionName functionInfo tableName pkeyColumns selPerms = do
|
||||
let fieldDesc = Just $ G.Description $ "execute function " <> functionName <<> " which returns " <>> tableName
|
||||
fmap afold $
|
||||
optionalFieldParser (QDBConnection) $
|
||||
optionalFieldParser QDBConnection $
|
||||
selectFunctionConnection sourceName functionInfo fieldDesc pkeyColumns selPerms
|
||||
|
||||
----------------------------------------------------------------
|
||||
@ -643,7 +662,7 @@ prependOp ::
|
||||
SU.UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
prependOp = SU.UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
updateOperatorApplicableColumn = isScalarColumnWhere (== PGJSONB) . pgiType
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let typedParser columnInfo =
|
||||
@ -677,7 +696,7 @@ appendOp ::
|
||||
SU.UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
appendOp = SU.UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
updateOperatorApplicableColumn = isScalarColumnWhere (== PGJSONB) . pgiType
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let typedParser columnInfo =
|
||||
@ -711,7 +730,7 @@ deleteKeyOp ::
|
||||
SU.UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
deleteKeyOp = SU.UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
updateOperatorApplicableColumn = isScalarColumnWhere (== PGJSONB) . pgiType
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let nullableTextParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGText) (G.Nullability True)
|
||||
@ -741,7 +760,7 @@ deleteElemOp ::
|
||||
SU.UpdateOperator ('Postgres pgKind) m n (UnpreparedValue ('Postgres pgKind))
|
||||
deleteElemOp = SU.UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
updateOperatorApplicableColumn = isScalarColumnWhere (== PGJSONB) . pgiType
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let nonNullableIntParser _ = fmap P.mkParameter <$> columnParser (ColumnScalar PGInteger) (G.Nullability False)
|
||||
@ -773,10 +792,10 @@ deleteAtPathOp ::
|
||||
SU.UpdateOperator ('Postgres pgKind) m n [UnpreparedValue ('Postgres pgKind)]
|
||||
deleteAtPathOp = SU.UpdateOperator {..}
|
||||
where
|
||||
updateOperatorApplicableColumn = (isScalarColumnWhere (== PGJSONB) . pgiType)
|
||||
updateOperatorApplicableColumn = isScalarColumnWhere (== PGJSONB) . pgiType
|
||||
|
||||
updateOperatorParser tableGQLName tableName columns = do
|
||||
let nonNullableTextListParser _ = P.list . fmap (P.mkParameter) <$> columnParser (ColumnScalar PGText) (G.Nullability False)
|
||||
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)"
|
||||
|
||||
SU.updateOperator
|
||||
|
@ -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.Insert qualified as PG (BackendInsert)
|
||||
import Hasura.Backends.Postgres.Types.Update qualified as PG
|
||||
import Hasura.Base.Error
|
||||
import Hasura.Prelude
|
||||
@ -78,13 +79,12 @@ instance
|
||||
type BackendUpdate ('Postgres pgKind) = PG.BackendUpdate
|
||||
|
||||
type ExtraTableMetadata ('Postgres pgKind) = PgExtraTableMetadata pgKind
|
||||
type ExtraInsertData ('Postgres pgKind) = ()
|
||||
type BackendInsert ('Postgres pgKind) = PG.BackendInsert ('Postgres pgKind)
|
||||
|
||||
type XComputedField ('Postgres pgKind) = XEnable
|
||||
type XRelay ('Postgres pgKind) = XEnable
|
||||
type XNodesAgg ('Postgres pgKind) = XEnable
|
||||
type XNestedInserts ('Postgres pgKind) = XEnable
|
||||
type XOnConflict ('Postgres pgKind) = XEnable
|
||||
|
||||
functionArgScalarType = PG.mkFunctionArgScalarType
|
||||
isComparableType = PG.isComparableType
|
||||
|
18
server/src-lib/Hasura/Backends/Postgres/Types/Insert.hs
Normal file
18
server/src-lib/Hasura/Backends/Postgres/Types/Insert.hs
Normal file
@ -0,0 +1,18 @@
|
||||
-- | This module defines the Insert-related IR types specific to Postgres.
|
||||
module Hasura.Backends.Postgres.Types.Insert
|
||||
( BackendInsert (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.Conflict (ConflictClauseP1)
|
||||
|
||||
-- | The PostgreSQL-specific data of an Insert 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 BackendInsert b v = BackendInsert
|
||||
{ _biConflictClause :: Maybe (ConflictClauseP1 b v)
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable)
|
@ -207,25 +207,6 @@ class Backend b => BackendSchema (b :: BackendType) where
|
||||
Nullability ->
|
||||
m (Parser 'Both n (ValueWithOrigin (ColumnValue b)))
|
||||
|
||||
-- | Creates a parser for the "_on_conflict" object of the given table.
|
||||
--
|
||||
-- This object is used to generate the "ON CONFLICT" SQL clause: what should be
|
||||
-- done if an insert raises a conflict? It may not always exist: it can't be
|
||||
-- created if there aren't any unique or primary keys constraints. However, if
|
||||
-- there are no columns for which the current role has update permissions, we
|
||||
-- must still accept an empty list for `update_columns`; we do this by adding a
|
||||
-- placeholder value to the enum (see 'tableUpdateColumnsEnum').
|
||||
--
|
||||
-- The default implementation elides on_conflict support.
|
||||
conflictObject ::
|
||||
MonadBuildSchema b r m n =>
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
Maybe (SelPermInfo b) ->
|
||||
UpdPermInfo b ->
|
||||
m (Maybe (Parser 'Input n (XOnConflict b, IR.ConflictClauseP1 b (UnpreparedValue b))))
|
||||
conflictObject _ _ _ _ = pure Nothing
|
||||
|
||||
-- | The "path" argument for json column fields
|
||||
jsonPathArg ::
|
||||
MonadParse n =>
|
||||
@ -260,9 +241,6 @@ class Backend b => BackendSchema (b :: BackendType) where
|
||||
-- SQL literals
|
||||
columnDefaultValue :: Column b -> SQLExpression b
|
||||
|
||||
-- Extra insert data
|
||||
getExtraInsertData :: TableInfo b -> ExtraInsertData b
|
||||
|
||||
type ComparisonExp b = OpExpG b (UnpreparedValue b)
|
||||
|
||||
-- $modelling
|
||||
|
@ -95,6 +95,7 @@ buildTableQueryFields sourceName tableName tableInfo gqlName selPerms = do
|
||||
buildTableInsertMutationFields ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
(SourceName -> TableInfo b -> Maybe (SelPermInfo b) -> Maybe (UpdPermInfo b) -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
||||
SourceName ->
|
||||
TableName b ->
|
||||
TableInfo b ->
|
||||
@ -104,6 +105,7 @@ buildTableInsertMutationFields ::
|
||||
Maybe (UpdPermInfo b) ->
|
||||
m [FieldParser n (AnnInsert b (RemoteRelationshipField UnpreparedValue) (UnpreparedValue b))]
|
||||
buildTableInsertMutationFields
|
||||
backendInsertAction
|
||||
sourceName
|
||||
tableName
|
||||
tableInfo
|
||||
@ -119,12 +121,12 @@ buildTableInsertMutationFields
|
||||
insertOneDesc = Just $ G.Description $ "insert a single row into the table: " <>> tableName
|
||||
insertName <- mkRootFieldName $ fromMaybe ($$(G.litName "insert_") <> gqlName) $ _tcrfInsert customRootFields
|
||||
insertOneName <- mkRootFieldName $ fromMaybe ($$(G.litName "insert_") <> gqlName <> $$(G.litName "_one")) $ _tcrfInsertOne customRootFields
|
||||
insert <- insertIntoTable sourceName tableInfo insertName insertDesc insPerms mSelPerms mUpdPerms
|
||||
insert <- insertIntoTable backendInsertAction sourceName tableInfo insertName insertDesc insPerms mSelPerms mUpdPerms
|
||||
-- Select permissions are required for insertOne: the selection set is the
|
||||
-- same as a select on that table, and it therefore can't be populated if the
|
||||
-- user doesn't have select permissions.
|
||||
insertOne <- for mSelPerms \selPerms ->
|
||||
insertOneIntoTable sourceName tableInfo insertOneName insertOneDesc insPerms selPerms mUpdPerms
|
||||
insertOneIntoTable backendInsertAction sourceName tableInfo insertOneName insertOneDesc insPerms selPerms mUpdPerms
|
||||
pure $ insert : maybeToList insertOne
|
||||
|
||||
-- | This function is the basic building block for update mutations. It
|
||||
|
127
server/src-lib/Hasura/GraphQL/Schema/Conflict.hs
Normal file
127
server/src-lib/Hasura/GraphQL/Schema/Conflict.hs
Normal file
@ -0,0 +1,127 @@
|
||||
{-# LANGUAGE ApplicativeDo #-}
|
||||
|
||||
-- | This module contains common schema parser building blocks pertaining to
|
||||
-- parsing @_on_conflict@ clauses.
|
||||
module Hasura.GraphQL.Schema.Conflict
|
||||
( onConflictFieldParser,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Text.Extended
|
||||
import Hasura.GraphQL.Parser
|
||||
( InputFieldsParser,
|
||||
Kind (..),
|
||||
Parser,
|
||||
UnpreparedValue (..),
|
||||
)
|
||||
import Hasura.GraphQL.Parser qualified as P
|
||||
import Hasura.GraphQL.Parser.Class
|
||||
import Hasura.GraphQL.Schema.Backend
|
||||
import Hasura.GraphQL.Schema.BoolExp
|
||||
import Hasura.GraphQL.Schema.Common
|
||||
import Hasura.GraphQL.Schema.Table
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.Insert qualified as IR
|
||||
import Hasura.RQL.Types
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
|
||||
-- | Parser for a field name @on_conflict@ of type @<tablename>_on_conflict@.
|
||||
onConflictFieldParser ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
Maybe (SelPermInfo b) ->
|
||||
Maybe (UpdPermInfo b) ->
|
||||
m (InputFieldsParser n (Maybe (IR.ConflictClauseP1 b (UnpreparedValue b))))
|
||||
onConflictFieldParser sourceName tableInfo selectPerms updatePerms = do
|
||||
o <- withJust updatePerms $ defaultConflictObject sourceName tableInfo selectPerms
|
||||
return $ mkConflictArg o
|
||||
|
||||
-- | Creates a parser for the "_on_conflict" object of the given table.
|
||||
--
|
||||
-- This object is used to generate the "ON CONFLICT" SQL clause: what should be
|
||||
-- done if an insert raises a conflict? It may not always exist: it can't be
|
||||
-- created if there aren't any unique or primary keys constraints. However, if
|
||||
-- there are no columns for which the current role has update permissions, we
|
||||
-- must still accept an empty list for `update_columns`; we do this by adding a
|
||||
-- placeholder value to the enum (see 'tableUpdateColumnsEnum').
|
||||
defaultConflictObject ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
Maybe (SelPermInfo b) ->
|
||||
UpdPermInfo b ->
|
||||
m (Maybe (Parser 'Input n (IR.ConflictClauseP1 b (UnpreparedValue b))))
|
||||
defaultConflictObject sourceName tableInfo selectPerms updatePerms = runMaybeT $ do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
columnsEnum <- lift $ tableUpdateColumnsEnum tableInfo updatePerms
|
||||
constraints <- hoistMaybe $ tciUniqueOrPrimaryKeyConstraints . _tiCoreInfo $ tableInfo
|
||||
constraintParser <- lift $ conflictConstraint constraints sourceName tableInfo
|
||||
whereExpParser <- lift $ boolExp sourceName tableInfo selectPerms
|
||||
objectName <- P.mkTypename $ tableGQLName <> $$(G.litName "_on_conflict")
|
||||
let presetColumns = partialSQLExpToUnpreparedValue <$> upiSet updatePerms
|
||||
updateFilter = fmap partialSQLExpToUnpreparedValue <$> upiFilter updatePerms
|
||||
objectDesc = G.Description $ "on conflict condition type for table " <>> tableInfoName tableInfo
|
||||
constraintName = $$(G.litName "constraint")
|
||||
columnsName = $$(G.litName "update_columns")
|
||||
whereExpName = $$(G.litName "where")
|
||||
pure $
|
||||
P.object objectName (Just objectDesc) $ do
|
||||
constraint <- IR.CTConstraint <$> P.field constraintName Nothing constraintParser
|
||||
whereExp <- P.fieldOptional whereExpName Nothing whereExpParser
|
||||
columns <-
|
||||
P.fieldWithDefault columnsName Nothing (G.VList []) (P.list columnsEnum) `P.bindFields` \cs ->
|
||||
-- this can only happen if the placeholder was used
|
||||
sequenceA cs `onNothing` parseError "erroneous column name"
|
||||
pure $
|
||||
case columns of
|
||||
[] -> IR.CP1DoNothing $ Just constraint
|
||||
_ -> IR.CP1Update constraint columns presetColumns $ BoolAnd $ updateFilter : maybeToList whereExp
|
||||
|
||||
-- | Creates a field parser for the "on_conflict" argument of insert fields.
|
||||
--
|
||||
-- The parser might not exist, as the current role might not have the
|
||||
-- appropriate permissions, but insert fields can exist regardless. This
|
||||
-- function creates a dummy 'InputFieldsParser' that always returns @Nothing@ in
|
||||
-- such a case.
|
||||
mkConflictArg ::
|
||||
MonadParse n =>
|
||||
Maybe (Parser 'Input n (IR.ConflictClauseP1 b (UnpreparedValue b))) ->
|
||||
InputFieldsParser n (Maybe (IR.ConflictClauseP1 b (UnpreparedValue b)))
|
||||
mkConflictArg conflictParser' = withJust conflictParser' $ P.fieldOptional conflictName (Just conflictDesc)
|
||||
where
|
||||
conflictName = $$(G.litName "on_conflict")
|
||||
conflictDesc = "on conflict condition"
|
||||
|
||||
-- | Constructs a Parser for the name of the constraints on a given table.
|
||||
--
|
||||
-- The TableCoreInfo of a given table contains a list of unique or primary key
|
||||
-- constraints. Given the list of such constraints, this function creates a
|
||||
-- parser for an enum type that matches it. This function makes no attempt at
|
||||
-- de-duplicating contraint names, and assumes they are correct.
|
||||
--
|
||||
-- This function can fail if a constraint has a name that cannot be translated
|
||||
-- to a GraphQL name (see hasura/graphql-engine-mono#1748).
|
||||
conflictConstraint ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
NonEmpty (Constraint b) ->
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
m (Parser 'Both n (ConstraintName b))
|
||||
conflictConstraint constraints sourceName tableInfo =
|
||||
memoizeOn 'conflictConstraint (sourceName, tableName) $ do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
constraintEnumValues <- for constraints \constraint -> do
|
||||
name <- textToName $ toTxt $ _cName constraint
|
||||
pure
|
||||
( P.Definition name (Just "unique or primary key constraint") P.EnumValueInfo,
|
||||
_cName constraint
|
||||
)
|
||||
enumName <- P.mkTypename $ tableGQLName <> $$(G.litName "_constraint")
|
||||
let enumDesc = G.Description $ "unique or primary key constraints on table " <>> tableName
|
||||
pure $ P.enum enumName (Just enumDesc) constraintEnumValues
|
||||
where
|
||||
tableName = tableInfoName tableInfo
|
@ -6,7 +6,6 @@ module Hasura.GraphQL.Schema.Mutation
|
||||
deleteFromTable,
|
||||
deleteFromTableByPk,
|
||||
mkDefaultRelationshipParser,
|
||||
defaultConflictObject,
|
||||
mutationSelectionSet,
|
||||
primaryKeysArguments,
|
||||
)
|
||||
@ -49,6 +48,7 @@ import Language.GraphQL.Draft.Syntax qualified as G
|
||||
insertIntoTable ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
(SourceName -> TableInfo b -> Maybe (SelPermInfo b) -> Maybe (UpdPermInfo b) -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
||||
SourceName ->
|
||||
-- | qualified name of the table
|
||||
TableInfo b ->
|
||||
@ -63,14 +63,14 @@ insertIntoTable ::
|
||||
-- | update permissions of the table (if any)
|
||||
Maybe (UpdPermInfo b) ->
|
||||
m (FieldParser n (IR.AnnInsert b (IR.RemoteRelationshipField UnpreparedValue) (UnpreparedValue b)))
|
||||
insertIntoTable sourceName tableInfo fieldName description insertPerms selectPerms updatePerms = do
|
||||
insertIntoTable backendInsertAction sourceName tableInfo fieldName description insertPerms selectPerms updatePerms = do
|
||||
selectionParser <- mutationSelectionSet sourceName tableInfo selectPerms
|
||||
objectParser <- tableFieldsInput sourceName tableInfo insertPerms
|
||||
conflictParser <- withJust updatePerms $ conflictObject sourceName tableInfo selectPerms
|
||||
backendInsertParser <- backendInsertAction sourceName tableInfo selectPerms updatePerms
|
||||
let argsParser = do
|
||||
conflict <- mkConflictArg conflictParser
|
||||
backendInsert <- backendInsertParser
|
||||
objects <- mkObjectsArg objectParser
|
||||
pure $ mkInsertObject objects tableInfo conflict insertPerms updatePerms
|
||||
pure $ mkInsertObject objects tableInfo backendInsert insertPerms updatePerms
|
||||
return $
|
||||
P.subselection fieldName description argsParser selectionParser
|
||||
<&> \(insertObject, output) -> IR.AnnInsert (G.unName fieldName) False insertObject (IR.MOutMultirowFields output)
|
||||
@ -89,6 +89,7 @@ insertIntoTable sourceName tableInfo fieldName description insertPerms selectPer
|
||||
insertOneIntoTable ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
(SourceName -> TableInfo b -> Maybe (SelPermInfo b) -> Maybe (UpdPermInfo b) -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
||||
-- | source of the table
|
||||
SourceName ->
|
||||
-- | table info
|
||||
@ -104,14 +105,14 @@ insertOneIntoTable ::
|
||||
-- | update permissions of the table (if any)
|
||||
Maybe (UpdPermInfo b) ->
|
||||
m (FieldParser n (IR.AnnInsert b (IR.RemoteRelationshipField UnpreparedValue) (UnpreparedValue b)))
|
||||
insertOneIntoTable sourceName tableInfo fieldName description insertPerms selectPerms updatePerms = do
|
||||
insertOneIntoTable backendInsertAction sourceName tableInfo fieldName description insertPerms selectPerms updatePerms = do
|
||||
selectionParser <- tableSelectionSet sourceName tableInfo selectPerms
|
||||
objectParser <- tableFieldsInput sourceName tableInfo insertPerms
|
||||
conflictParser <- withJust updatePerms $ conflictObject sourceName tableInfo (Just selectPerms)
|
||||
backendInsertParser <- backendInsertAction sourceName tableInfo (Just selectPerms) updatePerms
|
||||
let argsParser = do
|
||||
conflict <- mkConflictArg conflictParser
|
||||
backendInsert <- backendInsertParser
|
||||
object <- mkObjectArg objectParser
|
||||
pure $ mkInsertObject [object] tableInfo conflict insertPerms updatePerms
|
||||
pure $ mkInsertObject [object] tableInfo backendInsert insertPerms updatePerms
|
||||
pure $
|
||||
P.subselection fieldName description argsParser selectionParser
|
||||
<&> \(insertObject, output) -> IR.AnnInsert (G.unName fieldName) True insertObject (IR.MOutSinglerowObject output)
|
||||
@ -122,21 +123,6 @@ insertOneIntoTable sourceName tableInfo fieldName description insertPerms select
|
||||
(Just "the row to be inserted")
|
||||
objectParser
|
||||
|
||||
-- | Creates a field parser for the "on_conflict" argument of insert fields.
|
||||
--
|
||||
-- The parser might not exist, as the current role might not have the
|
||||
-- appropriate permissions, but insert fields can exist regardless. This
|
||||
-- function creates a dummy 'InputFieldsParser' that always returns @Nothing@ in
|
||||
-- such a case.
|
||||
mkConflictArg ::
|
||||
MonadParse n =>
|
||||
Maybe (Parser 'Input n (XOnConflict b, IR.ConflictClauseP1 b (UnpreparedValue b))) ->
|
||||
InputFieldsParser n (Maybe (XOnConflict b, IR.ConflictClauseP1 b (UnpreparedValue b)))
|
||||
mkConflictArg conflictParser = withJust conflictParser $ P.fieldOptional conflictName (Just conflictDesc)
|
||||
where
|
||||
conflictName = $$(G.litName "on_conflict")
|
||||
conflictDesc = "on conflict condition"
|
||||
|
||||
-- | Creates the parser for an input object for a row of the given table.
|
||||
--
|
||||
-- This function creates an input object type named "tablename_insert_input" in
|
||||
@ -197,11 +183,12 @@ tableFieldsInput sourceName tableInfo insertPerms =
|
||||
mkDefaultRelationshipParser ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
(SourceName -> TableInfo b -> Maybe (SelPermInfo b) -> Maybe (UpdPermInfo b) -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
||||
XNestedInserts b ->
|
||||
SourceName ->
|
||||
RelInfo b ->
|
||||
m (Maybe (InputFieldsParser n (Maybe (IR.AnnotatedInsert b (UnpreparedValue b)))))
|
||||
mkDefaultRelationshipParser xNestedInserts sourceName relationshipInfo = runMaybeT do
|
||||
mkDefaultRelationshipParser backendInsertAction xNestedInserts sourceName relationshipInfo = runMaybeT do
|
||||
let otherTableName = riRTable relationshipInfo
|
||||
relName = riName relationshipInfo
|
||||
otherTableInfo <- askTableInfo sourceName otherTableName
|
||||
@ -212,13 +199,13 @@ mkDefaultRelationshipParser xNestedInserts sourceName relationshipInfo = runMayb
|
||||
updPerms = _permUpd permissions
|
||||
lift $ case riType relationshipInfo of
|
||||
ObjRel -> do
|
||||
parser <- objectRelationshipInput sourceName otherTableInfo insPerms selPerms updPerms
|
||||
parser <- objectRelationshipInput backendInsertAction sourceName otherTableInfo insPerms selPerms updPerms
|
||||
pure $
|
||||
P.fieldOptional relFieldName Nothing (P.nullable parser) <&> \objRelIns -> do
|
||||
rel <- join objRelIns
|
||||
Just $ IR.AIObjectRelationship xNestedInserts $ IR.RelIns rel relationshipInfo
|
||||
ArrRel -> do
|
||||
parser <- arrayRelationshipInput sourceName otherTableInfo insPerms selPerms updPerms
|
||||
parser <- arrayRelationshipInput backendInsertAction sourceName otherTableInfo insPerms selPerms updPerms
|
||||
pure $
|
||||
P.fieldOptional relFieldName Nothing (P.nullable parser) <&> \arrRelIns -> do
|
||||
rel <- join arrRelIns
|
||||
@ -235,24 +222,25 @@ mkDefaultRelationshipParser xNestedInserts sourceName relationshipInfo = runMayb
|
||||
objectRelationshipInput ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
(SourceName -> TableInfo b -> Maybe (SelPermInfo b) -> Maybe (UpdPermInfo b) -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
InsPermInfo b ->
|
||||
Maybe (SelPermInfo b) ->
|
||||
Maybe (UpdPermInfo b) ->
|
||||
m (Parser 'Input n (IR.SingleObjIns b (UnpreparedValue b)))
|
||||
objectRelationshipInput sourceName tableInfo insertPerms selectPerms updatePerms =
|
||||
objectRelationshipInput backendInsertAction sourceName tableInfo insertPerms selectPerms updatePerms =
|
||||
memoizeOn 'objectRelationshipInput (sourceName, tableName) do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
objectParser <- tableFieldsInput sourceName tableInfo insertPerms
|
||||
conflictParser <- withJust updatePerms $ conflictObject sourceName tableInfo selectPerms
|
||||
backendInsertParser <- backendInsertAction sourceName tableInfo selectPerms updatePerms
|
||||
inputName <- P.mkTypename $ tableGQLName <> $$(G.litName "_obj_rel_insert_input")
|
||||
let objectName = $$(G.litName "data")
|
||||
inputDesc = G.Description $ "input type for inserting object relation for remote table " <>> tableName
|
||||
inputParser = do
|
||||
conflict <- mkConflictArg conflictParser
|
||||
backendInsert <- backendInsertParser
|
||||
object <- P.field objectName Nothing objectParser
|
||||
pure $ mkInsertObject (IR.Single object) tableInfo conflict insertPerms updatePerms
|
||||
pure $ mkInsertObject (IR.Single object) tableInfo backendInsert insertPerms updatePerms
|
||||
pure $ P.object inputName (Just inputDesc) inputParser
|
||||
where
|
||||
tableName = tableInfoName tableInfo
|
||||
@ -267,24 +255,25 @@ objectRelationshipInput sourceName tableInfo insertPerms selectPerms updatePerms
|
||||
arrayRelationshipInput ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
(SourceName -> TableInfo b -> Maybe (SelPermInfo b) -> Maybe (UpdPermInfo b) -> m (InputFieldsParser n (BackendInsert b (UnpreparedValue b)))) ->
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
InsPermInfo b ->
|
||||
Maybe (SelPermInfo b) ->
|
||||
Maybe (UpdPermInfo b) ->
|
||||
m (Parser 'Input n (IR.MultiObjIns b (UnpreparedValue b)))
|
||||
arrayRelationshipInput sourceName tableInfo insertPerms selectPerms updatePerms =
|
||||
arrayRelationshipInput backendInsertAction sourceName tableInfo insertPerms selectPerms updatePerms =
|
||||
memoizeOn 'arrayRelationshipInput (sourceName, tableName) do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
objectParser <- tableFieldsInput sourceName tableInfo insertPerms
|
||||
conflictParser <- withJust updatePerms $ conflictObject sourceName tableInfo selectPerms
|
||||
backendInsertParser <- backendInsertAction sourceName tableInfo selectPerms updatePerms
|
||||
inputName <- P.mkTypename $ tableGQLName <> $$(G.litName "_arr_rel_insert_input")
|
||||
let objectsName = $$(G.litName "data")
|
||||
inputDesc = G.Description $ "input type for inserting array relation for remote table " <>> tableName
|
||||
inputParser = do
|
||||
conflict <- mkConflictArg conflictParser
|
||||
backendInsert <- backendInsertParser
|
||||
objects <- P.field objectsName Nothing $ P.list objectParser
|
||||
pure $ mkInsertObject objects tableInfo conflict insertPerms updatePerms
|
||||
pure $ mkInsertObject objects tableInfo backendInsert insertPerms updatePerms
|
||||
pure $ P.object inputName (Just inputDesc) inputParser
|
||||
where
|
||||
tableName = tableInfoName tableInfo
|
||||
@ -295,104 +284,28 @@ mkInsertObject ::
|
||||
BackendSchema b =>
|
||||
f (IR.AnnotatedInsertRow b (UnpreparedValue b)) ->
|
||||
TableInfo b ->
|
||||
Maybe (XOnConflict b, IR.ConflictClauseP1 b (UnpreparedValue b)) ->
|
||||
BackendInsert b (UnpreparedValue b) ->
|
||||
InsPermInfo b ->
|
||||
Maybe (UpdPermInfo b) ->
|
||||
IR.AnnIns b f (UnpreparedValue b)
|
||||
mkInsertObject objects tableInfo conflictClause insertPerms updatePerms =
|
||||
mkInsertObject objects tableInfo backendInsert insertPerms updatePerms =
|
||||
IR.AnnIns
|
||||
{ _aiInsObj = objects,
|
||||
_aiTableName = table,
|
||||
_aiConflictClause = conflictClause,
|
||||
_aiCheckCond = (insertCheck, updateCheck),
|
||||
_aiTableCols = columns,
|
||||
_aiDefVals = defaultValues,
|
||||
_aiExtraInsertData = extraInsertData
|
||||
_aiBackendInsert = backendInsert
|
||||
}
|
||||
where
|
||||
table = tableInfoName tableInfo
|
||||
columns = tableColumns tableInfo
|
||||
extraInsertData = getExtraInsertData tableInfo
|
||||
insertCheck = fmap partialSQLExpToUnpreparedValue <$> ipiCheck insertPerms
|
||||
updateCheck = (fmap . fmap . fmap) partialSQLExpToUnpreparedValue $ upiCheck =<< updatePerms
|
||||
defaultValues =
|
||||
Map.union (partialSQLExpToUnpreparedValue <$> ipiSet insertPerms) $
|
||||
Map.fromList [(column, UVLiteral $ columnDefaultValue @b column) | column <- pgiColumn <$> columns]
|
||||
|
||||
-- | Creates a parser for the "_on_conflict" object of the given table.
|
||||
--
|
||||
-- This object is used to generate the "ON CONFLICT" SQL clause: what should be
|
||||
-- done if an insert raises a conflict? It may not always exist: it can't be
|
||||
-- created if there aren't any unique or primary keys constraints. However, if
|
||||
-- there are no columns for which the current role has update permissions, we
|
||||
-- must still accept an empty list for `update_columns`; we do this by adding a
|
||||
-- placeholder value to the enum (see 'tableUpdateColumnsEnum').
|
||||
defaultConflictObject ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
XOnConflict b ->
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
Maybe (SelPermInfo b) ->
|
||||
UpdPermInfo b ->
|
||||
m (Maybe (Parser 'Input n (XOnConflict b, IR.ConflictClauseP1 b (UnpreparedValue b))))
|
||||
defaultConflictObject xOnConflict sourceName tableInfo selectPerms updatePerms = runMaybeT $ do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
columnsEnum <- lift $ tableUpdateColumnsEnum tableInfo updatePerms
|
||||
constraints <- hoistMaybe $ tciUniqueOrPrimaryKeyConstraints . _tiCoreInfo $ tableInfo
|
||||
constraintParser <- lift $ conflictConstraint constraints sourceName tableInfo
|
||||
whereExpParser <- lift $ boolExp sourceName tableInfo selectPerms
|
||||
objectName <- P.mkTypename $ tableGQLName <> $$(G.litName "_on_conflict")
|
||||
let presetColumns = partialSQLExpToUnpreparedValue <$> upiSet updatePerms
|
||||
updateFilter = fmap partialSQLExpToUnpreparedValue <$> upiFilter updatePerms
|
||||
objectDesc = G.Description $ "on conflict condition type for table " <>> tableInfoName tableInfo
|
||||
constraintName = $$(G.litName "constraint")
|
||||
columnsName = $$(G.litName "update_columns")
|
||||
whereExpName = $$(G.litName "where")
|
||||
pure $
|
||||
P.object objectName (Just objectDesc) $ do
|
||||
constraint <- IR.CTConstraint <$> P.field constraintName Nothing constraintParser
|
||||
whereExp <- P.fieldOptional whereExpName Nothing whereExpParser
|
||||
columns <-
|
||||
P.fieldWithDefault columnsName Nothing (G.VList []) (P.list columnsEnum) `P.bindFields` \cs ->
|
||||
-- this can only happen if the placeholder was used
|
||||
sequenceA cs `onNothing` parseError "erroneous column name"
|
||||
pure $
|
||||
(xOnConflict,) $ case columns of
|
||||
[] -> IR.CP1DoNothing $ Just constraint
|
||||
_ -> IR.CP1Update constraint columns presetColumns $ BoolAnd $ updateFilter : maybeToList whereExp
|
||||
|
||||
-- | Constructs a Parser for the name of the constraints on a given table.
|
||||
--
|
||||
-- The TableCoreInfo of a given table contains a list of unique or primary key
|
||||
-- constraints. Given the list of such constraints, this function creates a
|
||||
-- parser for an enum type that matches it. This function makes no attempt at
|
||||
-- de-duplicating contraint names, and assumes they are correct.
|
||||
--
|
||||
-- This function can fail if a constraint has a name that cannot be translated
|
||||
-- to a GraphQL name (see hasura/graphql-engine-mono#1748).
|
||||
conflictConstraint ::
|
||||
forall b r m n.
|
||||
MonadBuildSchema b r m n =>
|
||||
NonEmpty (Constraint b) ->
|
||||
SourceName ->
|
||||
TableInfo b ->
|
||||
m (Parser 'Both n (ConstraintName b))
|
||||
conflictConstraint constraints sourceName tableInfo =
|
||||
memoizeOn 'conflictConstraint (sourceName, tableName) $ do
|
||||
tableGQLName <- getTableGQLName tableInfo
|
||||
constraintEnumValues <- for constraints \constraint -> do
|
||||
name <- textToName $ toTxt $ _cName constraint
|
||||
pure
|
||||
( P.Definition name (Just "unique or primary key constraint") P.EnumValueInfo,
|
||||
_cName constraint
|
||||
)
|
||||
enumName <- P.mkTypename $ tableGQLName <> $$(G.litName "_constraint")
|
||||
let enumDesc = G.Description $ "unique or primary key constraints on table " <>> tableName
|
||||
pure $ P.enum enumName (Just enumDesc) constraintEnumValues
|
||||
where
|
||||
tableName = tableInfoName tableInfo
|
||||
|
||||
-- delete
|
||||
|
||||
-- | Construct a root field, normally called delete_tablename, that can be used
|
||||
|
23
server/src-lib/Hasura/RQL/IR/Conflict.hs
Normal file
23
server/src-lib/Hasura/RQL/IR/Conflict.hs
Normal file
@ -0,0 +1,23 @@
|
||||
module Hasura.RQL.IR.Conflict
|
||||
( ConflictClauseP1 (..),
|
||||
ConflictTarget (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.BoolExp
|
||||
import Hasura.RQL.Types.Backend
|
||||
import Hasura.SQL.Backend
|
||||
|
||||
data ConflictTarget (b :: BackendType)
|
||||
= CTColumn [Column b]
|
||||
| CTConstraint (ConstraintName b)
|
||||
|
||||
deriving instance Backend b => Show (ConflictTarget b)
|
||||
|
||||
deriving instance Backend b => Eq (ConflictTarget b)
|
||||
|
||||
data ConflictClauseP1 (b :: BackendType) v
|
||||
= CP1DoNothing (Maybe (ConflictTarget b))
|
||||
| CP1Update (ConflictTarget b) [Column b] (PreSetColsG b v) (AnnBoolExp b v)
|
||||
deriving (Functor, Foldable, Traversable)
|
@ -37,6 +37,7 @@ import Control.Lens.TH (makePrisms)
|
||||
import Data.Kind (Type)
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.BoolExp
|
||||
import Hasura.RQL.IR.Conflict
|
||||
import Hasura.RQL.IR.Returning
|
||||
import Hasura.RQL.Types.Backend
|
||||
import Hasura.RQL.Types.Column
|
||||
@ -49,10 +50,10 @@ import Hasura.SQL.Backend
|
||||
-- whether the root mutation is a single row or not, and will distinguish
|
||||
-- between them using a boolean field.
|
||||
data AnnInsert (b :: BackendType) (r :: Type) v = AnnInsert
|
||||
{ _aiFieldName :: !Text,
|
||||
_aiIsSingle :: !Bool,
|
||||
_aiData :: !(MultiObjIns b v),
|
||||
_aiOutput :: !(MutationOutputG b r v)
|
||||
{ _aiFieldName :: Text,
|
||||
_aiIsSingle :: Bool,
|
||||
_aiData :: MultiObjIns b v,
|
||||
_aiOutput :: MutationOutputG b r v
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable)
|
||||
|
||||
@ -60,13 +61,12 @@ data AnnInsert (b :: BackendType) (r :: Type) v = AnnInsert
|
||||
-- The @f@ parameter is used to construct the container for the values to be
|
||||
-- inserted: 'Single' for a single-row insert, '[]' for a multi-row insert.
|
||||
data AnnIns (b :: BackendType) (f :: Type -> Type) (v :: Type) = AnnIns
|
||||
{ _aiInsObj :: !(f (AnnotatedInsertRow b v)),
|
||||
_aiTableName :: !(TableName b),
|
||||
_aiConflictClause :: !(Maybe (XOnConflict b, ConflictClauseP1 b v)),
|
||||
_aiCheckCond :: !(AnnBoolExp b v, Maybe (AnnBoolExp b v)),
|
||||
_aiTableCols :: ![ColumnInfo b],
|
||||
_aiDefVals :: !(PreSetColsG b v),
|
||||
_aiExtraInsertData :: !(ExtraInsertData b)
|
||||
{ _aiInsObj :: f (AnnotatedInsertRow b v),
|
||||
_aiTableName :: TableName b,
|
||||
_aiCheckCond :: (AnnBoolExp b v, Maybe (AnnBoolExp b v)),
|
||||
_aiTableCols :: [ColumnInfo b],
|
||||
_aiDefVals :: PreSetColsG b v,
|
||||
_aiBackendInsert :: BackendInsert b v
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable)
|
||||
|
||||
@ -85,9 +85,9 @@ type MultiObjIns b v = AnnIns b [] v
|
||||
-- | An insert item.
|
||||
-- The object and array relationships are not unavailable when 'XNestedInserts b = XDisable'
|
||||
data AnnotatedInsert (b :: BackendType) v
|
||||
= AIColumn !(Column b, v)
|
||||
| AIObjectRelationship !(XNestedInserts b) !(ObjRelIns b v)
|
||||
| AIArrayRelationship !(XNestedInserts b) !(ArrRelIns b v)
|
||||
= AIColumn (Column b, v)
|
||||
| AIObjectRelationship (XNestedInserts b) (ObjRelIns b v)
|
||||
| AIArrayRelationship (XNestedInserts b) (ArrRelIns b v)
|
||||
deriving (Functor, Foldable, Traversable)
|
||||
|
||||
-- | One individual row to be inserted.
|
||||
@ -99,8 +99,8 @@ type AnnotatedInsertRow b v = [AnnotatedInsert b v]
|
||||
-- @v@, but by the kind of insert has to be performed: multi-row or single row.
|
||||
-- See 'ObjRelIns' and 'ArrRelIns'.
|
||||
data RelIns (b :: BackendType) a = RelIns
|
||||
{ _riAnnIns :: !a,
|
||||
_riRelInfo :: !(RelInfo b)
|
||||
{ _riAnnIns :: a,
|
||||
_riRelInfo :: RelInfo b
|
||||
}
|
||||
deriving (Show, Eq, Functor, Foldable, Traversable)
|
||||
|
||||
@ -116,34 +116,19 @@ type ObjRelIns b v = RelIns b (SingleObjIns b v)
|
||||
-- therefore parameterized by a 'MultiObjIns'.
|
||||
type ArrRelIns b v = RelIns b (MultiObjIns b v)
|
||||
|
||||
-- Conflict resolution options
|
||||
|
||||
data ConflictTarget (b :: BackendType)
|
||||
= CTColumn ![Column b]
|
||||
| CTConstraint !(ConstraintName b)
|
||||
|
||||
deriving instance Backend b => Show (ConflictTarget b)
|
||||
|
||||
deriving instance Backend b => Eq (ConflictTarget b)
|
||||
|
||||
data ConflictClauseP1 (b :: BackendType) v
|
||||
= CP1DoNothing !(Maybe (ConflictTarget b))
|
||||
| CP1Update !(ConflictTarget b) ![Column b] !(PreSetColsG b v) (AnnBoolExp b v)
|
||||
deriving (Functor, Foldable, Traversable)
|
||||
|
||||
-- | Old-style representation used for non-recursive insertions.
|
||||
-- This is the representation used by RQL.DML, instead of the new fancy
|
||||
-- recursive one present in this file. Postgres supports both representations,
|
||||
-- and actually translates recursive queries that do not have any relationships
|
||||
-- into this representation first.
|
||||
data InsertQueryP1 (b :: BackendType) = InsertQueryP1
|
||||
{ iqp1Table :: !(TableName b),
|
||||
iqp1Cols :: ![Column b],
|
||||
iqp1Tuples :: ![[SQLExpression b]],
|
||||
iqp1Conflict :: !(Maybe (ConflictClauseP1 b (SQLExpression b))),
|
||||
iqp1CheckCond :: !(AnnBoolExpSQL b, Maybe (AnnBoolExpSQL b)),
|
||||
iqp1Output :: !(MutationOutput b),
|
||||
iqp1AllCols :: ![ColumnInfo b]
|
||||
{ iqp1Table :: TableName b,
|
||||
iqp1Cols :: [Column b],
|
||||
iqp1Tuples :: [[SQLExpression b]],
|
||||
iqp1Conflict :: Maybe (ConflictClauseP1 b (SQLExpression b)),
|
||||
iqp1CheckCond :: (AnnBoolExpSQL b, Maybe (AnnBoolExpSQL b)),
|
||||
iqp1Output :: MutationOutput b,
|
||||
iqp1AllCols :: [ColumnInfo b]
|
||||
}
|
||||
|
||||
-- Template Haskell related
|
||||
|
@ -108,7 +108,10 @@ class
|
||||
-- Intermediate Representations
|
||||
Functor (BackendUpdate b),
|
||||
Foldable (BackendUpdate b),
|
||||
Traversable (BackendUpdate b)
|
||||
Traversable (BackendUpdate b),
|
||||
Functor (BackendInsert b),
|
||||
Foldable (BackendInsert b),
|
||||
Traversable (BackendInsert b)
|
||||
) =>
|
||||
Backend (b :: BackendType)
|
||||
where
|
||||
@ -141,8 +144,11 @@ class
|
||||
|
||||
type BackendUpdate b = Const Void
|
||||
|
||||
-- | Extra backend specific context needed for insert mutations.
|
||||
type ExtraInsertData b :: Type
|
||||
-- | Intermediate Representation of Insert Mutations.
|
||||
-- The default implementation makes insert expressions uninstantiable.
|
||||
type BackendInsert b :: Type -> Type
|
||||
|
||||
type BackendInsert b = Const Void
|
||||
|
||||
-- extension types
|
||||
type XComputedField b :: Type
|
||||
@ -152,9 +158,6 @@ class
|
||||
-- | Extension to flag the availability of object and array relationships in inserts (aka nested inserts).
|
||||
type XNestedInserts b :: Type
|
||||
|
||||
-- | Extension to flag the availability of `on_conflict` input field in inserts (aka upsert feature)
|
||||
type XOnConflict b :: Type
|
||||
|
||||
-- functions on types
|
||||
functionArgScalarType :: FunctionArgType b -> ScalarType b
|
||||
isComparableType :: ScalarType b -> Bool
|
||||
|
Loading…
Reference in New Issue
Block a user