Refactor XOnConflict and ExtraInsertData

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3055
GitOrigin-RevId: 06f7f92f0e09695f5f7bc02df457d3b96ac9f5f6
This commit is contained in:
Philip Lykke Carlsen 2021-12-09 10:05:42 +01:00 committed by hasura-bot
parent e4064c9a90
commit 2fbcd783e7
21 changed files with 291 additions and 233 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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"

View File

@ -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) ->

View File

@ -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

View File

@ -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

View 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)

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View 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)

View File

@ -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

View File

@ -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