2022-02-08 12:24:34 +03:00
|
|
|
-- | Postgres Translate Insert
|
|
|
|
--
|
|
|
|
-- Translates IR inserts to Postgres-specific SQL INSERT statements.
|
2020-10-29 19:58:13 +03:00
|
|
|
module Hasura.Backends.Postgres.Translate.Insert
|
2021-09-24 01:56:37 +03:00
|
|
|
( mkInsertCTE,
|
|
|
|
toSQLConflict,
|
|
|
|
insertCheckConstraint,
|
|
|
|
insertOrUpdateCheckExpr,
|
|
|
|
)
|
|
|
|
where
|
2020-10-29 19:58:13 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
|
|
|
import Hasura.Backends.Postgres.SQL.Types
|
|
|
|
import Hasura.Backends.Postgres.Translate.BoolExp
|
|
|
|
import Hasura.Backends.Postgres.Translate.Returning
|
|
|
|
import Hasura.Prelude
|
|
|
|
import Hasura.RQL.IR.Insert
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Backend
|
2023-04-24 21:35:48 +03:00
|
|
|
import Hasura.RQL.Types.BackendType
|
2020-10-29 19:58:13 +03:00
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
mkInsertCTE ::
|
|
|
|
Backend ('Postgres pgKind) =>
|
|
|
|
InsertQueryP1 ('Postgres pgKind) ->
|
2022-07-18 12:44:17 +03:00
|
|
|
S.TopLevelCTE
|
2020-10-29 19:58:13 +03:00
|
|
|
mkInsertCTE (InsertQueryP1 tn cols vals conflict (insCheck, updCheck) _ _) =
|
2021-09-24 01:56:37 +03:00
|
|
|
S.CTEInsert insert
|
2020-10-29 19:58:13 +03:00
|
|
|
where
|
|
|
|
tupVals = S.ValuesExp $ map S.TupleExp vals
|
|
|
|
insert =
|
|
|
|
S.SQLInsert tn cols tupVals (toSQLConflict tn <$> conflict)
|
|
|
|
. Just
|
|
|
|
. S.RetExp
|
2021-09-24 01:56:37 +03:00
|
|
|
$ [ S.selectStar,
|
|
|
|
insertOrUpdateCheckExpr
|
|
|
|
tn
|
|
|
|
conflict
|
|
|
|
(toSQLBool insCheck)
|
|
|
|
(fmap toSQLBool updCheck)
|
2020-10-29 19:58:13 +03:00
|
|
|
]
|
|
|
|
toSQLBool = toSQLBoolExp $ S.QualTable tn
|
|
|
|
|
2021-09-24 01:56:37 +03:00
|
|
|
toSQLConflict ::
|
|
|
|
Backend ('Postgres pgKind) =>
|
|
|
|
QualifiedTable ->
|
2021-12-20 14:15:51 +03:00
|
|
|
OnConflictClause ('Postgres pgKind) S.SQLExp ->
|
2021-09-24 01:56:37 +03:00
|
|
|
S.SQLConflict
|
2020-10-29 19:58:13 +03:00
|
|
|
toSQLConflict tableName = \case
|
2021-12-20 14:15:51 +03:00
|
|
|
OCCDoNothing ct -> S.DoNothing $ toSQLCT <$> ct
|
|
|
|
OCCUpdate OnConflictClauseData {..} ->
|
2021-09-24 01:56:37 +03:00
|
|
|
S.Update
|
2021-12-20 14:15:51 +03:00
|
|
|
(toSQLCT cp1udConflictTarget)
|
|
|
|
(S.buildUpsertSetExp cp1udAffectedColumns cp1udValues)
|
2022-11-02 23:53:23 +03:00
|
|
|
$ Just
|
|
|
|
$ S.WhereFrag
|
|
|
|
$ toSQLBoolExp (S.QualTable tableName) cp1udFilter
|
2020-10-29 19:58:13 +03:00
|
|
|
where
|
|
|
|
toSQLCT ct = case ct of
|
|
|
|
CTColumn pgCols -> S.SQLColumn pgCols
|
|
|
|
CTConstraint cn -> S.SQLConstraint cn
|
|
|
|
|
2020-11-12 12:25:48 +03:00
|
|
|
-- | Annotates the check constraint expression with @boolean@
|
|
|
|
-- (<check-condition>)::boolean
|
|
|
|
insertCheckConstraint :: S.BoolExp -> S.SQLExp
|
|
|
|
insertCheckConstraint boolExp =
|
|
|
|
S.SETyAnn (S.SEBool boolExp) S.boolTypeAnn
|
2020-10-29 19:58:13 +03:00
|
|
|
|
|
|
|
-- | When inserting data, we might need to also enforce the update
|
|
|
|
-- check condition, because we might fall back to an update via an
|
|
|
|
-- @ON CONFLICT@ clause.
|
|
|
|
--
|
|
|
|
-- We generate something which looks like
|
|
|
|
--
|
|
|
|
-- > INSERT INTO
|
|
|
|
-- > ...
|
|
|
|
-- > ON CONFLICT DO UPDATE SET
|
|
|
|
-- > ...
|
|
|
|
-- > RETURNING
|
|
|
|
-- > *,
|
|
|
|
-- > CASE WHEN xmax = 0
|
2020-11-12 12:25:48 +03:00
|
|
|
-- > THEN {insert_cond}
|
|
|
|
-- > ELSE {update_cond}
|
2020-10-29 19:58:13 +03:00
|
|
|
-- > END
|
2020-11-12 12:25:48 +03:00
|
|
|
-- > AS "check__constraint"
|
2020-10-29 19:58:13 +03:00
|
|
|
--
|
|
|
|
-- See @https://stackoverflow.com/q/34762732@ for more information on the use of
|
|
|
|
-- the @xmax@ system column.
|
2021-09-24 01:56:37 +03:00
|
|
|
insertOrUpdateCheckExpr ::
|
|
|
|
QualifiedTable ->
|
2021-12-20 14:15:51 +03:00
|
|
|
Maybe (OnConflictClause ('Postgres pgKind) S.SQLExp) ->
|
2021-09-24 01:56:37 +03:00
|
|
|
S.BoolExp ->
|
|
|
|
Maybe S.BoolExp ->
|
|
|
|
S.Extractor
|
2020-10-29 19:58:13 +03:00
|
|
|
insertOrUpdateCheckExpr qt (Just _conflict) insCheck (Just updCheck) =
|
2020-11-12 12:25:48 +03:00
|
|
|
asCheckErrorExtractor $
|
2021-09-24 01:56:37 +03:00
|
|
|
S.SECond
|
|
|
|
( S.BECompare
|
|
|
|
S.SEQ
|
|
|
|
(S.SEQIdentifier (S.QIdentifier (S.mkQual qt) (Identifier "xmax")))
|
|
|
|
(S.SEUnsafe "0")
|
|
|
|
)
|
|
|
|
(insertCheckConstraint insCheck)
|
|
|
|
(insertCheckConstraint updCheck)
|
2020-10-29 19:58:13 +03:00
|
|
|
insertOrUpdateCheckExpr _ _ insCheck _ =
|
|
|
|
-- If we won't generate an ON CONFLICT clause, there is no point
|
|
|
|
-- in testing xmax. In particular, views don't provide the xmax
|
|
|
|
-- system column, but we don't provide ON CONFLICT for views,
|
|
|
|
-- even if they are auto-updatable, so we can fortunately avoid
|
|
|
|
-- having to test the non-existent xmax value.
|
|
|
|
--
|
|
|
|
-- Alternatively, if there is no update check constraint, we should
|
|
|
|
-- use the insert check constraint, for backwards compatibility.
|
2020-11-12 12:25:48 +03:00
|
|
|
asCheckErrorExtractor $ insertCheckConstraint insCheck
|