From 9ed8f717a76c427d69efdc8fa55396356d9034be Mon Sep 17 00:00:00 2001 From: Phil Freeman Date: Wed, 15 Jan 2020 20:53:28 -0800 Subject: [PATCH] remove hdb_views for inserts (#3598) * WIP: Remove hdb_views for inserts * Show failing row in check constraint error * Revert "Show failing row in check constraint error" This reverts commit dd2cac29d0dbef350695e25e4fb27b9401ff9006. * Use the better query plan * Simplify things * fix cli test * Update downgrading.rst * remove 1.1 asset for cli --- .../graphql/manual/deployment/downgrading.rst | 6 + server/graphql-engine.cabal | 1 - .../src-lib/Hasura/GraphQL/Resolve/Insert.hs | 60 ++++---- .../src-lib/Hasura/GraphQL/Resolve/Types.hs | 4 +- server/src-lib/Hasura/GraphQL/Schema.hs | 13 +- server/src-lib/Hasura/RQL/DDL/Permission.hs | 136 ++---------------- .../Hasura/RQL/DDL/Permission/Internal.hs | 23 +-- .../Hasura/RQL/DDL/Permission/Triggers.hs | 34 ----- .../Hasura/RQL/DDL/Schema/Cache/Permission.hs | 17 +-- server/src-lib/Hasura/RQL/DML/Count.hs | 2 +- server/src-lib/Hasura/RQL/DML/Insert.hs | 114 +++++++-------- server/src-lib/Hasura/RQL/DML/Internal.hs | 2 +- .../src-lib/Hasura/RQL/DML/Select/Internal.hs | 2 +- .../src-lib/Hasura/RQL/Types/SchemaCache.hs | 1 - server/src-lib/Hasura/SQL/DML.hs | 29 ++-- server/src-lib/Hasura/SQL/Rewrite.hs | 2 +- server/src-lib/Hasura/SQL/Types.hs | 4 - .../src-lib/Hasura/Server/Migrate/Version.hs | 2 +- server/src-lib/Hasura/Server/Query.hs | 8 +- server/src-rsr/initialise.sql | 7 + server/src-rsr/insert_trigger.sql.shakespeare | 34 ----- server/src-rsr/migrations/28_to_29.sql | 2 +- server/src-rsr/migrations/29_to_30.sql | 6 + 23 files changed, 161 insertions(+), 348 deletions(-) delete mode 100644 server/src-lib/Hasura/RQL/DDL/Permission/Triggers.hs delete mode 100644 server/src-rsr/insert_trigger.sql.shakespeare create mode 100644 server/src-rsr/migrations/29_to_30.sql diff --git a/docs/graphql/manual/deployment/downgrading.rst b/docs/graphql/manual/deployment/downgrading.rst index 7bf76cf2632..7afd98f8ca3 100644 --- a/docs/graphql/manual/deployment/downgrading.rst +++ b/docs/graphql/manual/deployment/downgrading.rst @@ -103,6 +103,12 @@ You can downgrade the catalogue from a particular version to its previous versio :backlinks: none :depth: 1 :local: + +From 30 to 29 +""""""""""""" +.. code-block:: plpgsql + + DROP FUNCTION hdb_catalog.check_violation(); From 27 to 26 """"""""""""" diff --git a/server/graphql-engine.cabal b/server/graphql-engine.cabal index e44a44b8501..b3e008187aa 100644 --- a/server/graphql-engine.cabal +++ b/server/graphql-engine.cabal @@ -280,7 +280,6 @@ library , Hasura.RQL.DDL.Relationship , Hasura.RQL.DDL.Deps , Hasura.RQL.DDL.Permission.Internal - , Hasura.RQL.DDL.Permission.Triggers , Hasura.RQL.DDL.Permission , Hasura.RQL.DDL.Relationship.Rename , Hasura.RQL.DDL.Relationship.Types diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs b/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs index e389cb01bd8..d1024f3b68c 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/Insert.hs @@ -29,7 +29,10 @@ import Hasura.GraphQL.Resolve.Mutation import Hasura.GraphQL.Resolve.Select import Hasura.GraphQL.Validate.Field import Hasura.GraphQL.Validate.Types -import Hasura.RQL.DML.Internal (convPartialSQLExp, dmlTxErrorHandler, +import Hasura.RQL.DML.Insert (insertCheckExpr) +import Hasura.RQL.DML.Internal (convPartialSQLExp, + convAnnBoolExpPartialSQL, + dmlTxErrorHandler, sessVarFromCurrentSetting) import Hasura.RQL.DML.Mutation import Hasura.RQL.GBoolExp (toSQLBoolExp) @@ -47,7 +50,7 @@ data AnnIns a = AnnIns { _aiInsObj :: !a , _aiConflictClause :: !(Maybe RI.ConflictClauseP1) - , _aiView :: !QualifiedTable + , _aiCheckCond :: AnnBoolExpPartialSQL , _aiTableCols :: ![PGColumnInfo] , _aiDefVals :: !(Map.HashMap PGCol S.SQLExp) } deriving (Show, Eq, Functor, Foldable, Traversable) @@ -131,7 +134,7 @@ traverseInsObj rim allColMap (gName, annVal) defVal@(AnnInsObj cols objRels arrR throw500 $ "relation " <> relName <<> " not found" let rTable = riRTable relInfo - InsCtx rtView rtColMap rtDefVals rtRelInfoMap rtUpdPerm <- getInsCtx rTable + InsCtx rtColMap checkCond rtDefVals rtRelInfoMap rtUpdPerm <- getInsCtx rTable let rtCols = Map.elems rtColMap rtDefValsRes <- mapM (convPartialSQLExp sessVarFromCurrentSetting) rtDefVals @@ -140,7 +143,7 @@ traverseInsObj rim allColMap (gName, annVal) defVal@(AnnInsObj cols objRels arrR dataObj <- asObject dataVal annDataObj <- mkAnnInsObj rtRelInfoMap rtColMap dataObj ccM <- forM onConflictM $ parseOnConflict rTable rtUpdPerm rtColMap - let singleObjIns = AnnIns annDataObj ccM rtView rtCols rtDefValsRes + let singleObjIns = AnnIns annDataObj ccM checkCond rtCols rtDefValsRes objRelIns = RelIns singleObjIns relInfo return (AnnInsObj cols (objRelIns:objRels) arrRels) @@ -151,8 +154,7 @@ traverseInsObj rim allColMap (gName, annVal) defVal@(AnnInsObj cols objRels arrR dataObj <- asObject arrDataVal mkAnnInsObj rtRelInfoMap rtColMap dataObj ccM <- forM onConflictM $ parseOnConflict rTable rtUpdPerm rtColMap - let multiObjIns = AnnIns annDataObjs ccM rtView - rtCols rtDefValsRes + let multiObjIns = AnnIns annDataObjs ccM checkCond rtCols rtDefValsRes arrRelIns = RelIns multiObjIns relInfo return (AnnInsObj cols objRels (arrRelIns:arrRels)) -- if array relation insert input data has empty objects @@ -224,20 +226,26 @@ mkInsertQ -> [PGColWithValue] -> Map.HashMap PGCol S.SQLExp -> RoleName - -> m (CTEExp, Maybe RI.ConflictCtx) -mkInsertQ vn onConflictM insCols defVals role = do + -> AnnBoolExpSQL + -> m CTEExp +mkInsertQ tn onConflictM insCols defVals role checkCond = do (givenCols, args) <- flip runStateT Seq.Empty $ toSQLExps insCols let sqlConflict = RI.toSQLConflict <$> onConflictM sqlExps = mkSQLRow defVals givenCols valueExp = S.ValuesExp [S.TupleExp sqlExps] tableCols = Map.keys defVals sqlInsert = - S.SQLInsert vn tableCols valueExp sqlConflict $ Just S.returningStar - adminIns = return (CTEExp (S.CTEInsert sqlInsert) args, Nothing) + S.SQLInsert tn tableCols valueExp sqlConflict + . Just + $ S.RetExp + [ S.selectStar + , insertCheckExpr (toSQLBoolExp (S.QualTable tn) checkCond) + ] + + adminIns = return (CTEExp (S.CTEInsert sqlInsert) args) nonAdminInsert = do - ccM <- mapM RI.extractConflictCtx onConflictM - let cteIns = S.CTEInsert sqlInsert{S.siConflict=Nothing} - return (CTEExp cteIns args, ccM) + let cteIns = S.CTEInsert sqlInsert + return (CTEExp cteIns args) bool nonAdminInsert adminIns $ isAdmin role @@ -400,10 +408,11 @@ insertObj strfyNum role tn singleObjIns addCols = do finalInsCols = cols <> objRelDeterminedCols <> addCols -- prepare insert query as with expression - (CTEExp cte insPArgs, ccM) <- - mkInsertQ vn onConflictM finalInsCols defVals role + checkExpr <- convAnnBoolExpPartialSQL sessVarFromCurrentSetting checkCond + + CTEExp cte insPArgs <- + mkInsertQ tn onConflictM finalInsCols defVals role checkExpr - RI.setConflictCtx ccM MutateResp affRows colVals <- mutateAndFetchCols tn allCols (cte, insPArgs) strfyNum colValM <- asSingleObject colVals cteExp <- mkSelCTE tn allCols colValM @@ -413,7 +422,7 @@ insertObj strfyNum role tn singleObjIns addCols = do return (totAffRows, cteExp) where - AnnIns annObj onConflictM vn allCols defVals = singleObjIns + AnnIns annObj onConflictM checkCond allCols defVals = singleObjIns AnnInsObj cols objRels arrRels = annObj arrRelDepCols = flip getColInfos allCols $ @@ -445,7 +454,7 @@ insertMultipleObjects insertMultipleObjects strfyNum role tn multiObjIns addCols mutFlds errP = bool withoutRelsInsert withRelsInsert anyRelsToInsert where - AnnIns insObjs onConflictM vn tableColInfos defVals = multiObjIns + AnnIns insObjs onConflictM checkCond tableColInfos defVals = multiObjIns singleObjInserts = multiToSingles multiObjIns insCols = map _aioColumns insObjs allInsObjRels = concatMap _aioObjRels insObjs @@ -465,10 +474,13 @@ insertMultipleObjects strfyNum role tn multiObjIns addCols mutFlds errP = (sqlRows, prepArgs) <- flip runStateT Seq.Empty $ do rowsWithCol <- mapM toSQLExps withAddCols return $ map (mkSQLRow defVals) rowsWithCol - - let insQP1 = RI.InsertQueryP1 tn vn tableCols sqlRows onConflictM mutFlds tableColInfos + + checkExpr <- convAnnBoolExpPartialSQL sessVarFromCurrentSetting checkCond + + let insQP1 = RI.InsertQueryP1 tn tableCols sqlRows onConflictM + (Just checkExpr) mutFlds tableColInfos p1 = (insQP1, prepArgs) - bool (RI.nonAdminInsert strfyNum p1) (RI.insertP2 strfyNum p1) $ isAdmin role + RI.insertP2 strfyNum p1 -- insert each object with relations withRelsInsert = withErrPath $ do @@ -513,14 +525,14 @@ convertInsert role tn fld = prefixErrPath fld $ do (withEmptyObjs mutFldsRes) $ null annVals where withNonEmptyObjs annVals mutFlds = do - InsCtx vn tableColMap defValMap relInfoMap updPerm <- getInsCtx tn + InsCtx tableColMap checkCond defValMap relInfoMap updPerm <- getInsCtx tn annObjs <- mapM asObject annVals annInsObjs <- forM annObjs $ mkAnnInsObj relInfoMap tableColMap conflictClauseM <- forM onConflictM $ parseOnConflict tn updPerm tableColMap defValMapRes <- mapM (convPartialSQLExp sessVarFromCurrentSetting) defValMap - let multiObjIns = AnnIns annInsObjs conflictClauseM - vn tableCols defValMapRes + let multiObjIns = AnnIns annInsObjs conflictClauseM checkCond + tableCols defValMapRes tableCols = Map.elems tableColMap strfyNum <- stringifyNum <$> asks getter return $ prefixErrPath fld $ insertMultipleObjects strfyNum role tn diff --git a/server/src-lib/Hasura/GraphQL/Resolve/Types.hs b/server/src-lib/Hasura/GraphQL/Resolve/Types.hs index 1d0d14ec9bc..07ba9f4d5e2 100644 --- a/server/src-lib/Hasura/GraphQL/Resolve/Types.hs +++ b/server/src-lib/Hasura/GraphQL/Resolve/Types.hs @@ -179,8 +179,8 @@ data UpdPermForIns data InsCtx = InsCtx - { icView :: !QualifiedTable - , icAllCols :: !PGColGNameMap + { icAllCols :: !PGColGNameMap + , icCheck :: !AnnBoolExpPartialSQL , icSet :: !PreSetColsPartial , icRelations :: !RelationInfoMap , icUpdPerm :: !(Maybe UpdPermForIns) diff --git a/server/src-lib/Hasura/GraphQL/Schema.hs b/server/src-lib/Hasura/GraphQL/Schema.hs index 09f1b3e1c0e..099eaf28f39 100644 --- a/server/src-lib/Hasura/GraphQL/Schema.hs +++ b/server/src-lib/Hasura/GraphQL/Schema.hs @@ -509,13 +509,13 @@ mkInsCtx role tableCache fields insPermInfo updPermM = do isInsertable insPermM viewInfoM && isValidRel relName remoteTable let relInfoMap = Map.fromList $ catMaybes relTupsM - return $ InsCtx iView gNamePGColMap setCols relInfoMap updPermForIns + return $ InsCtx gNamePGColMap checkCond setCols relInfoMap updPermForIns where gNamePGColMap = mkPGColGNameMap allCols allCols = getCols fields rels = getValidRels fields - iView = ipiView insPermInfo setCols = ipiSet insPermInfo + checkCond = ipiCheck insPermInfo updPermForIns = mkUpdPermForIns <$> updPermM mkUpdPermForIns upi = UpdPermForIns (toList $ upiCols upi) (upiFilter upi) (upiSet upi) @@ -525,11 +525,10 @@ mkInsCtx role tableCache fields insPermInfo updPermM = do mkAdminInsCtx :: MonadError QErr m - => QualifiedTable - -> TableCache + => TableCache -> FieldInfoMap FieldInfo -> m InsCtx -mkAdminInsCtx tn tc fields = do +mkAdminInsCtx tc fields = do relTupsM <- forM rels $ \relInfo -> do let remoteTable = riRTable relInfo relName = riName relInfo @@ -541,7 +540,7 @@ mkAdminInsCtx tn tc fields = do let relInfoMap = Map.fromList $ catMaybes relTupsM updPerm = UpdPermForIns updCols noFilter Map.empty - return $ InsCtx tn colGNameMap Map.empty relInfoMap (Just updPerm) + return $ InsCtx colGNameMap noFilter Map.empty relInfoMap (Just updPerm) where allCols = getCols fields colGNameMap = mkPGColGNameMap allCols @@ -667,7 +666,7 @@ mkGCtxMapTable tableCache funcCache tabInfo = do m <- flip Map.traverseWithKey rolePerms $ mkGCtxRole tableCache tn descM fields primaryKey validConstraints tabFuncs viewInfo customConfig - adminInsCtx <- mkAdminInsCtx tn tableCache fields + adminInsCtx <- mkAdminInsCtx tableCache fields adminSelFlds <- mkAdminSelFlds fields tableCache let adminCtx = mkGCtxRole' tn descM (Just (cols, icRelations adminInsCtx)) (Just (True, adminSelFlds)) (Just cols) (Just ()) diff --git a/server/src-lib/Hasura/RQL/DDL/Permission.hs b/server/src-lib/Hasura/RQL/DDL/Permission.hs index 4b26a912320..b6e3e9abfaf 100644 --- a/server/src-lib/Hasura/RQL/DDL/Permission.hs +++ b/server/src-lib/Hasura/RQL/DDL/Permission.hs @@ -7,37 +7,26 @@ module Hasura.RQL.DDL.Permission , InsPerm(..) , InsPermDef , CreateInsPerm - , clearInsInfra - , buildInsInfra , buildInsPermInfo - , DropInsPerm - , dropInsPermP2 , SelPerm(..) , SelPermDef , CreateSelPerm , buildSelPermInfo - , DropSelPerm - , dropSelPermP2 , UpdPerm(..) , UpdPermDef , CreateUpdPerm , buildUpdPermInfo - , DropUpdPerm - , dropUpdPermP2 , DelPerm(..) , DelPermDef , CreateDelPerm , buildDelPermInfo - , DropDelPerm - , dropDelPermP2 , IsPerm(..) , addPermP2 - , dropView , DropPerm , runDropPerm @@ -51,21 +40,17 @@ import Hasura.EncJSON import Hasura.Incremental (Cacheable) import Hasura.Prelude import Hasura.RQL.DDL.Permission.Internal -import Hasura.RQL.DDL.Permission.Triggers import Hasura.RQL.DML.Internal hiding (askPermInfo) -import Hasura.RQL.GBoolExp import Hasura.RQL.Types import Hasura.SQL.Types import qualified Database.PG.Query as Q -import qualified Hasura.SQL.DML as S import Data.Aeson import Data.Aeson.Casing import Data.Aeson.TH import Language.Haskell.TH.Syntax (Lift) -import qualified Crypto.Hash as CH import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS import qualified Data.Text as T @@ -83,31 +68,6 @@ $(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''InsPerm) type InsPermDef = PermDef InsPerm type CreateInsPerm = CreatePerm InsPerm -buildViewName :: QualifiedTable -> RoleName -> PermType -> QualifiedTable -buildViewName qt rn pt = QualifiedObject hdbViewsSchema tableName - where - -- Generate a unique hash for view name from role name, permission type and qualified table. - -- See Note [Postgres identifier length limitations]. - -- Black2b_224 generates 56 character hash. See Note [Blake2b faster than SHA-256]. - -- Refer https://github.com/hasura/graphql-engine/issues/3444. - tableName = TableName $ T.pack $ show hash - hash :: CH.Digest CH.Blake2b_224 = - CH.hash $ txtToBs $ roleNameToTxt rn <> "__" <> T.pack (show pt) <> "__" <> qualObjectToText qt - -buildView :: QualifiedTable -> QualifiedTable -> Q.Query -buildView tn vn = - Q.fromBuilder $ mconcat - [ "CREATE VIEW " <> toSQL vn - , " AS SELECT * FROM " <> toSQL tn - ] - -dropView :: QualifiedTable -> Q.Tx () -dropView vn = - Q.unitQ dropViewS () False - where - dropViewS = Q.fromBuilder $ - "DROP VIEW IF EXISTS " <> toSQL vn - procSetObj :: (QErrM m) => QualifiedTable @@ -136,7 +96,7 @@ buildInsPermInfo -> FieldInfoMap FieldInfo -> PermDef InsPerm -> m (WithDeps InsPermInfo) -buildInsPermInfo tn fieldInfoMap (PermDef rn (InsPerm chk set mCols) _) = +buildInsPermInfo tn fieldInfoMap (PermDef _rn (InsPerm chk set mCols) _) = withPathK "permission" $ do (be, beDeps) <- withPathK "check" $ procBoolExp tn fieldInfoMap chk (setColsSQL, setHdrs, setColDeps) <- procSetObj tn fieldInfoMap set @@ -147,56 +107,19 @@ buildInsPermInfo tn fieldInfoMap (PermDef rn (InsPerm chk set mCols) _) = insColDeps = map (mkColDep DRUntyped tn) insCols deps = mkParentDep tn : beDeps ++ setColDeps ++ insColDeps insColsWithoutPresets = insCols \\ HM.keys setColsSQL - return (InsPermInfo (HS.fromList insColsWithoutPresets) vn be setColsSQL reqHdrs, deps) + return (InsPermInfo (HS.fromList insColsWithoutPresets) be setColsSQL reqHdrs, deps) where - vn = buildViewName tn rn PTInsert allCols = map pgiColumn $ getCols fieldInfoMap insCols = fromMaybe allCols $ convColSpec fieldInfoMap <$> mCols -buildInsInfra :: QualifiedTable -> InsPermInfo -> Q.TxE QErr () -buildInsInfra tn (InsPermInfo _ vn be _ _) = do - resolvedBoolExp <- {-# SCC "buildInsInfra/convAnnBoolExpPartialSQL" #-} convAnnBoolExpPartialSQL sessVarFromCurrentSetting be - let trigFnQ = {-# SCC "buildInsInfra/buildInsTrigFn" #-} buildInsTrigFn vn tn $ toSQLBoolExp (S.QualVar "NEW") resolvedBoolExp - {-# SCC "buildInsInfra/execute" #-} Q.catchE defaultTxErrorHandler $ do - -- Create the view - dropView vn - Q.unitQ (buildView tn vn) () False - -- Inject defaults on the view - Q.discardQ (injectDefaults vn tn) () False - -- Construct a trigger function - Q.unitQ trigFnQ () False - -- Add trigger for check expression - Q.unitQ (buildInsTrig vn) () False - -clearInsInfra :: QualifiedTable -> Q.TxE QErr () -clearInsInfra vn = - Q.catchE defaultTxErrorHandler $ do - dropView vn - Q.unitQ (dropInsTrigFn vn) () False - -type DropInsPerm = DropPerm InsPerm - -dropInsPermP2 :: (MonadTx m) => DropInsPerm -> QualifiedTable -> m () -dropInsPermP2 = dropPermP2 - type instance PermInfo InsPerm = InsPermInfo instance IsPerm InsPerm where - type DropPermP1Res InsPerm = QualifiedTable - permAccessor = PAInsert buildPermInfo = buildInsPermInfo - addPermP2Setup qt _ = liftTx . buildInsInfra qt - - buildDropPermP1Res dp = - ipiView <$> dropPermP1 dp - - dropPermP2Setup _ vn = - liftTx $ clearInsInfra vn - -- Select constraint data SelPerm = SelPerm @@ -263,29 +186,16 @@ buildSelPermInfo tn fieldInfoMap sp = withPathK "permission" $ do type SelPermDef = PermDef SelPerm type CreateSelPerm = CreatePerm SelPerm -type DropSelPerm = DropPerm SelPerm type instance PermInfo SelPerm = SelPermInfo -dropSelPermP2 :: (MonadTx m) => DropSelPerm -> m () -dropSelPermP2 dp = dropPermP2 dp () - instance IsPerm SelPerm where - type DropPermP1Res SelPerm = () - permAccessor = PASelect buildPermInfo tn fieldInfoMap (PermDef _ a _) = buildSelPermInfo tn fieldInfoMap a - buildDropPermP1Res = - void . dropPermP1 - - addPermP2Setup _ _ _ = return () - - dropPermP2Setup _ _ = return () - -- Update constraint data UpdPerm = UpdPerm @@ -330,27 +240,13 @@ buildUpdPermInfo tn fieldInfoMap (UpdPerm colSpec set fltr) = do type instance PermInfo UpdPerm = UpdPermInfo -type DropUpdPerm = DropPerm UpdPerm - -dropUpdPermP2 :: (MonadTx m) => DropUpdPerm -> m () -dropUpdPermP2 dp = dropPermP2 dp () - instance IsPerm UpdPerm where - type DropPermP1Res UpdPerm = () - permAccessor = PAUpdate buildPermInfo tn fieldInfoMap (PermDef _ a _) = buildUpdPermInfo tn fieldInfoMap a - addPermP2Setup _ _ _ = return () - - buildDropPermP1Res = - void . dropPermP1 - - dropPermP2Setup _ _ = return () - -- Delete permission data DelPerm = DelPerm { dcFilter :: !BoolExp } @@ -374,29 +270,15 @@ buildDelPermInfo tn fieldInfoMap (DelPerm fltr) = do depHeaders = getDependentHeaders fltr return (DelPermInfo tn be depHeaders, deps) -type DropDelPerm = DropPerm DelPerm - -dropDelPermP2 :: (MonadTx m) => DropDelPerm -> m () -dropDelPermP2 dp = dropPermP2 dp () - type instance PermInfo DelPerm = DelPermInfo instance IsPerm DelPerm where - type DropPermP1Res DelPerm = () - permAccessor = PADelete buildPermInfo tn fieldInfoMap (PermDef _ a _) = buildDelPermInfo tn fieldInfoMap a - addPermP2Setup _ _ _ = return () - - buildDropPermP1Res = - void . dropPermP1 - - dropPermP2Setup _ _ = return () - data SetPermComment = SetPermComment { apTable :: !QualifiedTable @@ -443,13 +325,13 @@ setPermCommentTx (SetPermComment (QualifiedObject sn tn) rn pt comment) = AND perm_type = $5 |] (comment, sn, tn, rn, permTypeToCode pt) True -purgePerm :: (MonadTx m) => QualifiedTable -> RoleName -> PermType -> m () -purgePerm qt rn pt = - case pt of - PTInsert -> dropInsPermP2 dp $ buildViewName qt rn PTInsert - PTSelect -> dropSelPermP2 dp - PTUpdate -> dropUpdPermP2 dp - PTDelete -> dropDelPermP2 dp +purgePerm :: MonadTx m => QualifiedTable -> RoleName -> PermType -> m () +purgePerm qt rn pt = + case pt of + PTInsert -> dropPermP2 @InsPerm dp + PTSelect -> dropPermP2 @SelPerm dp + PTUpdate -> dropPermP2 @UpdPerm dp + PTDelete -> dropPermP2 @DelPerm dp where dp :: DropPerm a dp = DropPerm qt rn diff --git a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs index 10b4d25a875..1b5d195de2c 100644 --- a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs +++ b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs @@ -239,8 +239,6 @@ type family PermInfo a = r | r -> a class (ToJSON a) => IsPerm a where - type DropPermP1Res a - permAccessor :: PermAccessor (PermInfo a) @@ -251,16 +249,6 @@ class (ToJSON a) => IsPerm a where -> PermDef a -> m (WithDeps (PermInfo a)) - addPermP2Setup - :: (MonadTx m) => QualifiedTable -> PermDef a -> PermInfo a -> m () - - buildDropPermP1Res - :: (QErrM m, CacheRM m, UserInfoM m) - => DropPerm a - -> m (DropPermP1Res a) - - dropPermP2Setup :: (MonadTx m) => DropPerm a -> DropPermP1Res a -> m () - getPermAcc1 :: PermDef a -> PermAccessor (PermInfo a) getPermAcc1 _ = permAccessor @@ -268,7 +256,7 @@ class (ToJSON a) => IsPerm a where getPermAcc2 :: DropPerm a -> PermAccessor (PermInfo a) getPermAcc2 _ = permAccessor - + addPermP2 :: (IsPerm a, MonadTx m, HasSystemDefined m) => QualifiedTable -> PermDef a -> m () addPermP2 tn pd = do let pt = permAccToType $ getPermAcc1 pd @@ -291,9 +279,8 @@ dropPermP1 dp@(DropPerm tn rn) = do tabInfo <- askTabInfo tn askPermInfo tabInfo rn $ getPermAcc2 dp -dropPermP2 :: (MonadTx m, IsPerm a) => DropPerm a -> DropPermP1Res a -> m () -dropPermP2 dp@(DropPerm tn rn) p1Res = do - dropPermP2Setup dp p1Res +dropPermP2 :: forall a m. (MonadTx m, IsPerm a) => DropPerm a -> m () +dropPermP2 dp@(DropPerm tn rn) = do liftTx $ dropPermFromCatalog tn rn pt where pa = getPermAcc2 dp @@ -303,7 +290,7 @@ runDropPerm :: (IsPerm a, UserInfoM m, CacheRWM m, MonadTx m) => DropPerm a -> m EncJSON runDropPerm defn = do - permInfo <- buildDropPermP1Res defn - dropPermP2 defn permInfo + dropPermP1 defn + dropPermP2 defn withNewInconsistentObjsCheck buildSchemaCache return successMsg diff --git a/server/src-lib/Hasura/RQL/DDL/Permission/Triggers.hs b/server/src-lib/Hasura/RQL/DDL/Permission/Triggers.hs deleted file mode 100644 index 63d2be11c34..00000000000 --- a/server/src-lib/Hasura/RQL/DDL/Permission/Triggers.hs +++ /dev/null @@ -1,34 +0,0 @@ -module Hasura.RQL.DDL.Permission.Triggers - ( buildInsTrig - , dropInsTrigFn - , buildInsTrigFn - ) where - -import Hasura.Prelude -import Hasura.SQL.Types - -import qualified Database.PG.Query as Q -import qualified Hasura.SQL.DML as S - -import qualified Data.Text.Lazy as TL -import qualified Text.Shakespeare.Text as ST - -buildInsTrig :: QualifiedTable -> Q.Query -buildInsTrig qt@(QualifiedObject _ tn) = - Q.fromBuilder $ mconcat - [ "CREATE TRIGGER " <> toSQL tn - , " INSTEAD OF INSERT ON " <> toSQL qt - , " FOR EACH ROW EXECUTE PROCEDURE " - , toSQL qt <> "();" - ] - -dropInsTrigFn :: QualifiedTable -> Q.Query -dropInsTrigFn fn = - Q.fromBuilder $ "DROP FUNCTION " <> toSQL fn <> "()" - -buildInsTrigFn :: QualifiedTable -> QualifiedTable -> S.BoolExp -> Q.Query -buildInsTrigFn fn tn be = Q.fromText . TL.toStrict $ - let functionName = toSQLTxt fn - tableName = toSQLTxt tn - checkExpression = toSQLTxt be - in $(ST.stextFile "src-rsr/insert_trigger.sql.shakespeare") diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs index e8b8c5c6e68..0d546f2da0e 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs @@ -25,7 +25,7 @@ import Hasura.SQL.Types buildTablePermissions :: ( ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr - , ArrowWriter (Seq CollectedInfo) arr, MonadTx m, MonadReader BuildReason m ) + , ArrowWriter (Seq CollectedInfo) arr, MonadTx m ) => ( Inc.Dependency TableCoreCache , QualifiedTable , FieldInfoMap FieldInfo @@ -80,8 +80,9 @@ withPermission f = proc (e, (permission, s)) -> do buildPermission :: ( ArrowChoice arr, Inc.ArrowCache m arr - , ArrowWriter (Seq CollectedInfo) arr, MonadTx m, MonadReader BuildReason m - , Inc.Cacheable a, IsPerm a, FromJSON a, Inc.Cacheable (PermInfo a) ) + , ArrowWriter (Seq CollectedInfo) arr + , MonadTx m, IsPerm a, FromJSON a + ) => ( Inc.Dependency TableCoreCache , QualifiedTable , FieldInfoMap FieldInfo @@ -98,16 +99,6 @@ buildPermission = Inc.cache proc (tableCache, tableName, tableFields, permission (info, dependencies) <- liftEitherA <<< Inc.bindDepend -< runExceptT $ runTableCoreCacheRT (buildPermInfo tableName tableFields permDef) tableCache tellA -< Seq.fromList dependencies - rebuildViewsIfNeeded -< (tableName, permDef, info) returnA -< info) |) permission) |) >-> (\info -> join info >- returnA) - -rebuildViewsIfNeeded - :: ( Inc.ArrowCache m arr, MonadTx m, MonadReader BuildReason m - , Inc.Cacheable a, IsPerm a, Inc.Cacheable (PermInfo a) ) - => (QualifiedTable, PermDef a, PermInfo a) `arr` () -rebuildViewsIfNeeded = Inc.cache $ arrM \(tableName, permDef, info) -> do - buildReason <- ask - when (buildReason == CatalogUpdate) $ - addPermP2Setup tableName permDef info diff --git a/server/src-lib/Hasura/RQL/DML/Count.hs b/server/src-lib/Hasura/RQL/DML/Count.hs index 0c720e5d14d..a9ed3705735 100644 --- a/server/src-lib/Hasura/RQL/DML/Count.hs +++ b/server/src-lib/Hasura/RQL/DML/Count.hs @@ -56,7 +56,7 @@ mkSQLCount (CountQueryP1 tn (permFltr, mWc) mDistCols) = , S.selExtr = extrs } Nothing -> S.mkSelect - { S.selExtr = [S.Extractor S.SEStar Nothing] } + { S.selExtr = [S.Extractor (S.SEStar Nothing) Nothing] } -- SELECT count(*) FROM (SELECT DISTINCT c1, .. cn FROM .. WHERE ..) r; -- SELECT count(*) FROM (SELECT * FROM .. WHERE ..) r; diff --git a/server/src-lib/Hasura/RQL/DML/Insert.hs b/server/src-lib/Hasura/RQL/DML/Insert.hs index 65c729d0b66..e4f1446bc73 100644 --- a/server/src-lib/Hasura/RQL/DML/Insert.hs +++ b/server/src-lib/Hasura/RQL/DML/Insert.hs @@ -3,7 +3,6 @@ module Hasura.RQL.DML.Insert where import Data.Aeson.Types import Instances.TH.Lift () -import qualified Data.Aeson.Extended as J import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS import qualified Data.Sequence as DS @@ -33,22 +32,31 @@ data ConflictClauseP1 data InsertQueryP1 = InsertQueryP1 - { iqp1Table :: !QualifiedTable - , iqp1View :: !QualifiedTable - , iqp1Cols :: ![PGCol] - , iqp1Tuples :: ![[S.SQLExp]] - , iqp1Conflict :: !(Maybe ConflictClauseP1) - , iqp1MutFlds :: !MutFlds - , iqp1AllCols :: ![PGColumnInfo] + { iqp1Table :: !QualifiedTable + , iqp1Cols :: ![PGCol] + , iqp1Tuples :: ![[S.SQLExp]] + , iqp1Conflict :: !(Maybe ConflictClauseP1) + , iqp1CheckCond :: !(Maybe AnnBoolExpSQL) + , iqp1MutFlds :: !MutFlds + , iqp1AllCols :: ![PGColumnInfo] } deriving (Show, Eq) mkInsertCTE :: InsertQueryP1 -> S.CTE -mkInsertCTE (InsertQueryP1 _ vn cols vals c _ _) = - S.CTEInsert insert +mkInsertCTE (InsertQueryP1 tn cols vals c checkCond _ _) = + S.CTEInsert insert where tupVals = S.ValuesExp $ map S.TupleExp vals insert = - S.SQLInsert vn cols tupVals (toSQLConflict <$> c) $ Just S.returningStar + S.SQLInsert tn cols tupVals (toSQLConflict <$> c) + . Just + . S.RetExp + $ maybe + [S.selectStar] + (\e -> + [ S.selectStar + , insertCheckExpr (toSQLBoolExp (S.QualTable tn) e) + ]) + checkCond toSQLConflict :: ConflictClauseP1 -> S.SQLConflict toSQLConflict conflict = case conflict of @@ -199,7 +207,6 @@ convInsertQuery objsParser sessVarBldr prepFn (InsertQuery tableName val oC mRet map pgiColumn $ getCols fieldInfoMap allCols = getCols fieldInfoMap insCols = HM.keys defInsVals - insView = ipiView insPerm resolvedPreSet <- mapM (convPartialSQLExp sessVarBldr) setInsVals @@ -208,16 +215,17 @@ convInsertQuery objsParser sessVarBldr prepFn (InsertQuery tableName val oC mRet let sqlExps = map snd insTuples inpCols = HS.toList $ HS.fromList $ concatMap fst insTuples + checkExpr <- convAnnBoolExpPartialSQL sessVarFromCurrentSetting (ipiCheck insPerm) + conflictClause <- withPathK "on_conflict" $ forM oC $ \c -> do roleName <- askCurRole unless (isTabUpdatable roleName tableInfo) $ throw400 PermissionDenied $ "upsert is not allowed for role " <> roleName <<> " since update permissions are not defined" buildConflictClause sessVarBldr tableInfo inpCols c - - return $ InsertQueryP1 tableName insView insCols sqlExps - conflictClause mutFlds allCols - + + return $ InsertQueryP1 tableName insCols sqlExps + conflictClause (Just checkExpr) mutFlds allCols where selNecessaryMsg = "; \"returning\" can only be used if the role has " @@ -241,53 +249,38 @@ convInsQ = insertP2 :: Bool -> (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON insertP2 strfyNum (u, p) = - runMutation $ Mutation (iqp1Table u) (insertCTE, p) + runMutation + $ Mutation (iqp1Table u) (insertCTE, p) (iqp1MutFlds u) (iqp1AllCols u) strfyNum where insertCTE = mkInsertCTE u -data ConflictCtx - = CCUpdate !ConstraintName ![PGCol] !PreSetCols !S.BoolExp - | CCDoNothing !(Maybe ConstraintName) - deriving (Show, Eq) - -nonAdminInsert :: Bool -> (InsertQueryP1, DS.Seq Q.PrepArg) -> Q.TxE QErr EncJSON -nonAdminInsert strfyNum (insQueryP1, args) = do - conflictCtxM <- mapM extractConflictCtx conflictClauseP1 - setConflictCtx conflictCtxM - insertP2 strfyNum (withoutConflictClause, args) - where - withoutConflictClause = insQueryP1{iqp1Conflict=Nothing} - conflictClauseP1 = iqp1Conflict insQueryP1 - -extractConflictCtx :: (MonadError QErr m) => ConflictClauseP1 -> m ConflictCtx -extractConflictCtx cp = - case cp of - (CP1DoNothing mConflictTar) -> do - mConstraintName <- mapM extractConstraintName mConflictTar - return $ CCDoNothing mConstraintName - (CP1Update conflictTar inpCols preSet filtr) -> do - constraintName <- extractConstraintName conflictTar - return $ CCUpdate constraintName inpCols preSet filtr - where - extractConstraintName (CTConstraint cn) = return cn - extractConstraintName _ = throw400 NotSupported - "\"constraint_on\" not supported for non admin insert. use \"constraint\" instead" - -setConflictCtx :: Maybe ConflictCtx -> Q.TxE QErr () -setConflictCtx conflictCtxM = do - let t = maybe "null" conflictCtxToJSON conflictCtxM - setVal = toSQL $ S.SELit t - setVar = "SET LOCAL hasura.conflict_clause = " - q = Q.fromBuilder $ setVar <> setVal - Q.unitQE defaultTxErrorHandler q () False - where - conflictCtxToJSON (CCDoNothing constrM) = - J.encodeToStrictText $ InsertTxConflictCtx CAIgnore constrM Nothing - conflictCtxToJSON (CCUpdate constr updCols preSet filtr) = - J.encodeToStrictText $ InsertTxConflictCtx CAUpdate (Just constr) $ - Just $ toSQLTxt (S.buildUpsertSetExp updCols preSet) - <> " " <> toSQLTxt (S.WhereFrag filtr) +-- | Create an expression which will fail with a check constraint violation error +-- if the condition is not met on any of the inserted rows. +-- +-- The resulting SQL will look something like this: +-- +-- > INSERT INTO +-- > ... +-- > RETURNING +-- > *, +-- > CASE WHEN {cond} +-- > THEN NULL +-- > ELSE hdb_catalog.check_violation('insert check constraint failed') +-- > END +insertCheckExpr + :: S.BoolExp + -> S.Extractor +insertCheckExpr condExpr = + S.Extractor + (S.SECond condExpr S.SENull + (S.SEFunction + (S.FunctionExp + (QualifiedObject (SchemaName "hdb_catalog") (FunctionName "check_violation")) + (S.FunctionArgs [S.SELit "insert check constraint failed"] mempty) + Nothing) + )) + Nothing runInsert :: (QErrM m, UserInfoM m, CacheRM m, MonadTx m, HasSQLGenCtx m) @@ -295,6 +288,5 @@ runInsert -> m EncJSON runInsert q = do res <- convInsQ q - role <- userRole <$> askUserInfo strfyNum <- stringifyNum <$> askSQLGenCtx - liftTx $ bool (nonAdminInsert strfyNum res) (insertP2 strfyNum res) $ isAdmin role + liftTx $ insertP2 strfyNum res diff --git a/server/src-lib/Hasura/RQL/DML/Internal.hs b/server/src-lib/Hasura/RQL/DML/Internal.hs index a5900e5115a..3015d58635b 100644 --- a/server/src-lib/Hasura/RQL/DML/Internal.hs +++ b/server/src-lib/Hasura/RQL/DML/Internal.hs @@ -38,7 +38,7 @@ mkAdminRolePermInfo ti = getComputedFieldInfos fields tn = _tciName ti - i = InsPermInfo (HS.fromList pgCols) tn annBoolExpTrue M.empty [] + i = InsPermInfo (HS.fromList pgCols) annBoolExpTrue M.empty [] s = SelPermInfo (HS.fromList pgCols) (HS.fromList scalarComputedFields) tn annBoolExpTrue Nothing True [] u = UpdPermInfo (HS.fromList pgCols) tn annBoolExpTrue M.empty [] diff --git a/server/src-lib/Hasura/RQL/DML/Select/Internal.hs b/server/src-lib/Hasura/RQL/DML/Select/Internal.hs index 14243f2a8a1..a5503b8ec0e 100644 --- a/server/src-lib/Hasura/RQL/DML/Select/Internal.hs +++ b/server/src-lib/Hasura/RQL/DML/Select/Internal.hs @@ -705,7 +705,7 @@ baseNodeToSel joinCond baseNode = = baseNode -- this is the table which is aliased as "pfx.base" baseSel = S.mkSelect - { S.selExtr = [S.Extractor S.SEStar Nothing] + { S.selExtr = [S.Extractor (S.SEStar Nothing) Nothing] , S.selFrom = Just $ S.FromExp [fromItem] , S.selWhere = Just $ injectJoinCond joinCond whr } diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs index 001e02455fc..4266a058f7a 100644 --- a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs +++ b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs @@ -208,7 +208,6 @@ isPGColInfo _ = False data InsPermInfo = InsPermInfo { ipiCols :: !(HS.HashSet PGCol) - , ipiView :: !QualifiedTable , ipiCheck :: !AnnBoolExpPartialSQL , ipiSet :: !PreSetColsPartial , ipiRequiredHeaders :: ![T.Text] diff --git a/server/src-lib/Hasura/SQL/DML.hs b/server/src-lib/Hasura/SQL/DML.hs index f888ff672df..01314b6785a 100644 --- a/server/src-lib/Hasura/SQL/DML.hs +++ b/server/src-lib/Hasura/SQL/DML.hs @@ -283,7 +283,8 @@ data SQLExp | SELit !T.Text | SEUnsafe !T.Text | SESelect !Select - | SEStar + | SEStar !(Maybe Qual) + -- ^ all fields (@*@) or all fields from relation (@iden.*@) | SEIden !Iden -- iden and row identifier are distinguished for easier rewrite rules | SERowIden !Iden @@ -336,8 +337,10 @@ instance ToSQL SQLExp where TB.text t toSQL (SESelect se) = paren $ toSQL se - toSQL SEStar = + toSQL (SEStar Nothing) = TB.char '*' + toSQL (SEStar (Just qual)) = + mconcat [toSQL qual, TB.char '.', TB.char '*'] toSQL (SEIden iden) = toSQL iden toSQL (SERowIden iden) = @@ -725,7 +728,10 @@ newtype RetExp = RetExp [Extractor] deriving (Show, Eq) selectStar :: Extractor -selectStar = Extractor SEStar Nothing +selectStar = Extractor (SEStar Nothing) Nothing + +selectStar' :: Qual -> Extractor +selectStar' q = Extractor (SEStar (Just q)) Nothing returningStar :: RetExp returningStar = RetExp [selectStar] @@ -804,15 +810,14 @@ data SQLInsert = SQLInsert instance ToSQL SQLInsert where toSQL si = - let insConflict = maybe "" toSQL - in "INSERT INTO" - <-> toSQL (siTable si) - <-> "(" - <-> (", " <+> siCols si) - <-> ")" - <-> toSQL (siValues si) - <-> insConflict (siConflict si) - <-> toSQL (siRet si) + "INSERT INTO" + <-> toSQL (siTable si) + <-> "(" + <-> (", " <+> siCols si) + <-> ")" + <-> toSQL (siValues si) + <-> maybe "" toSQL (siConflict si) + <-> toSQL (siRet si) data CTE = CTESelect !Select diff --git a/server/src-lib/Hasura/SQL/Rewrite.hs b/server/src-lib/Hasura/SQL/Rewrite.hs index 1eb1a43b514..eebe38d54e2 100644 --- a/server/src-lib/Hasura/SQL/Rewrite.hs +++ b/server/src-lib/Hasura/SQL/Rewrite.hs @@ -161,7 +161,7 @@ uSqlExp = restoringIdens . \case S.SELit t -> return $ S.SELit t S.SEUnsafe t -> return $ S.SEUnsafe t S.SESelect s -> S.SESelect <$> uSelect s - S.SEStar -> return S.SEStar + S.SEStar qual -> S.SEStar <$> traverse uQual qual -- this is for row expressions -- todo: check if this is always okay S.SEIden iden -> return $ S.SEIden iden diff --git a/server/src-lib/Hasura/SQL/Types.hs b/server/src-lib/Hasura/SQL/Types.hs index 24f72d63eb1..763a894fb40 100644 --- a/server/src-lib/Hasura/SQL/Types.hs +++ b/server/src-lib/Hasura/SQL/Types.hs @@ -40,7 +40,6 @@ module Hasura.SQL.Types , SchemaName(..) , publicSchema - , hdbViewsSchema , TableName(..) , FunctionName(..) @@ -239,9 +238,6 @@ newtype SchemaName publicSchema :: SchemaName publicSchema = SchemaName "public" -hdbViewsSchema :: SchemaName -hdbViewsSchema = SchemaName "hdb_views" - instance IsIden SchemaName where toIden (SchemaName t) = Iden t diff --git a/server/src-lib/Hasura/Server/Migrate/Version.hs b/server/src-lib/Hasura/Server/Migrate/Version.hs index 2bc527161e2..6968137765f 100644 --- a/server/src-lib/Hasura/Server/Migrate/Version.hs +++ b/server/src-lib/Hasura/Server/Migrate/Version.hs @@ -12,7 +12,7 @@ import Hasura.Prelude import qualified Data.Text as T latestCatalogVersion :: Integer -latestCatalogVersion = 29 +latestCatalogVersion = 30 latestCatalogVersionString :: T.Text latestCatalogVersionString = T.pack $ show latestCatalogVersion diff --git a/server/src-lib/Hasura/Server/Query.hs b/server/src-lib/Hasura/Server/Query.hs index a640734097c..033f54aad13 100644 --- a/server/src-lib/Hasura/Server/Query.hs +++ b/server/src-lib/Hasura/Server/Query.hs @@ -61,10 +61,10 @@ data RQLQueryV1 | RQCreateUpdatePermission !CreateUpdPerm | RQCreateDeletePermission !CreateDelPerm - | RQDropInsertPermission !DropInsPerm - | RQDropSelectPermission !DropSelPerm - | RQDropUpdatePermission !DropUpdPerm - | RQDropDeletePermission !DropDelPerm + | RQDropInsertPermission !(DropPerm InsPerm) + | RQDropSelectPermission !(DropPerm SelPerm) + | RQDropUpdatePermission !(DropPerm UpdPerm) + | RQDropDeletePermission !(DropPerm DelPerm) | RQSetPermissionComment !SetPermComment | RQGetInconsistentMetadata !GetInconsistentMetadata diff --git a/server/src-rsr/initialise.sql b/server/src-rsr/initialise.sql index 940fea21745..23dd2ea8a0e 100644 --- a/server/src-rsr/initialise.sql +++ b/server/src-rsr/initialise.sql @@ -656,3 +656,10 @@ CREATE VIEW hdb_catalog.hdb_computed_field_function AS END AS function_schema FROM hdb_catalog.hdb_computed_field ); + +CREATE OR REPLACE FUNCTION hdb_catalog.check_violation(msg text) RETURNS bool AS +$$ + BEGIN + RAISE check_violation USING message=msg; + END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/server/src-rsr/insert_trigger.sql.shakespeare b/server/src-rsr/insert_trigger.sql.shakespeare deleted file mode 100644 index 1e8ce7c8e52..00000000000 --- a/server/src-rsr/insert_trigger.sql.shakespeare +++ /dev/null @@ -1,34 +0,0 @@ -CREATE OR REPLACE FUNCTION #{functionName}() RETURNS trigger LANGUAGE plpgsql AS $$ - DECLARE r #{tableName}%ROWTYPE; - DECLARE conflict_clause jsonb; - DECLARE action text; - DECLARE constraint_name text; - DECLARE set_expression text; - BEGIN - conflict_clause = current_setting('hasura.conflict_clause')::jsonb; - IF (#{checkExpression}) THEN - CASE - WHEN conflict_clause = 'null'::jsonb THEN INSERT INTO #{tableName} VALUES (NEW.*) RETURNING * INTO r; - ELSE - action = conflict_clause ->> 'action'; - constraint_name = quote_ident(conflict_clause ->> 'constraint'); - set_expression = conflict_clause ->> 'set_expression'; - IF action is NOT NULL THEN - CASE - WHEN action = 'ignore'::text AND constraint_name IS NULL THEN - INSERT INTO #{tableName} VALUES (NEW.*) ON CONFLICT DO NOTHING RETURNING * INTO r; - WHEN action = 'ignore'::text AND constraint_name is NOT NULL THEN - EXECUTE 'INSERT INTO #{tableName} VALUES ($1.*) ON CONFLICT ON CONSTRAINT ' || constraint_name || - ' DO NOTHING RETURNING *' INTO r USING NEW; - ELSE - EXECUTE 'INSERT INTO #{tableName} VALUES ($1.*) ON CONFLICT ON CONSTRAINT ' || constraint_name || - ' DO UPDATE ' || set_expression || ' RETURNING *' INTO r USING NEW; - END CASE; - ELSE - RAISE internal_error using message = 'action is not found'; RETURN NULL; - END IF; - END CASE; - IF r IS NULL THEN RETURN null; ELSE RETURN r; END IF; - ELSE RAISE check_violation using message = 'insert check constraint failed'; RETURN NULL; - END IF; - END $$; diff --git a/server/src-rsr/migrations/28_to_29.sql b/server/src-rsr/migrations/28_to_29.sql index 3b7d4f44964..3ea28e21be2 100644 --- a/server/src-rsr/migrations/28_to_29.sql +++ b/server/src-rsr/migrations/28_to_29.sql @@ -128,4 +128,4 @@ CREATE VIEW hdb_catalog.hdb_table_info_agg AS ) foreign_key_constraints ON true -- all these identify table-like things - WHERE "table".relkind IN ('r', 't', 'v', 'm', 'f', 'p'); + WHERE "table".relkind IN ('r', 't', 'v', 'm', 'f', 'p'); \ No newline at end of file diff --git a/server/src-rsr/migrations/29_to_30.sql b/server/src-rsr/migrations/29_to_30.sql new file mode 100644 index 00000000000..4de238a7ef0 --- /dev/null +++ b/server/src-rsr/migrations/29_to_30.sql @@ -0,0 +1,6 @@ +CREATE OR REPLACE FUNCTION hdb_catalog.check_violation(msg text) RETURNS bool AS +$$ + BEGIN + RAISE check_violation USING message=msg; + END; +$$ LANGUAGE plpgsql; \ No newline at end of file