Allow http status override through response.status guc (#1541)

Fixes https://github.com/PostgREST/postgrest/issues/1525
This commit is contained in:
Steve Chavez
2020-06-05 13:01:36 -05:00
committed by GitHub
parent 69b09e312a
commit 0f0d617951
8 changed files with 137 additions and 50 deletions
+2
View File
@@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added ### Added
- #1525, Allow http status override through response.status guc - @steve-chavez
### Fixed ### Fixed
- #1530, Fix how the PostgREST version is shown in the help text when the `.git` directory is not available - @monacoremo - #1530, Fix how the PostgREST version is shown in the help text when the `.git` directory is not available - @monacoremo
+1 -1
View File
@@ -46,7 +46,7 @@ let
EOF EOF
${withTmpDb postgresql} ${cabal-install}/bin/cabal v2-test \ ${withTmpDb postgresql} ${cabal-install}/bin/cabal v2-test -f FailOnWarn \
--test-show-detail=direct --test-show-detail=direct
cat << EOF cat << EOF
+42 -30
View File
@@ -136,17 +136,19 @@ app dbStructure proc cols conf apiRequest =
(contentType == CTTextCSV) bField pgVer (contentType == CTTextCSV) bField pgVer
explStm = createExplainStatement cq explStm = createExplainStatement cq
row <- H.statement () stm row <- H.statement () stm
let (tableTotal, queryTotal, _ , body, gucHeaders) = row let (tableTotal, queryTotal, _ , body, gucHeaders, gucStatus) = row
case gucHeaders of gucs = (,) <$> gucHeaders <*> gucStatus
Left _ -> return . errorResponseFor $ GucHeadersError case gucs of
Right ghdrs -> do Left err -> return $ errorResponseFor err
Right (ghdrs, gstatus) -> do
total <- if | plannedCount -> H.statement () explStm total <- if | plannedCount -> H.statement () explStm
| estimatedCount -> if tableTotal > (fromIntegral <$> maxRows) | estimatedCount -> if tableTotal > (fromIntegral <$> maxRows)
then do estTotal <- H.statement () explStm then do estTotal <- H.statement () explStm
pure $ if estTotal > tableTotal then estTotal else tableTotal pure $ if estTotal > tableTotal then estTotal else tableTotal
else pure tableTotal else pure tableTotal
| otherwise -> pure tableTotal | otherwise -> pure tableTotal
let (status, contentRange) = rangeStatusHeader topLevelRange queryTotal total let (rangeStatus, contentRange) = rangeStatusHeader topLevelRange queryTotal total
status = fromMaybe rangeStatus gstatus
headers = addHeadersIfNotIncluded (catMaybes [ headers = addHeadersIfNotIncluded (catMaybes [
Just $ toHeader contentType, Just contentRange, Just $ toHeader contentType, Just contentRange,
Just $ contentLocationH tName (iCanonicalQS apiRequest), profileH]) Just $ contentLocationH tName (iCanonicalQS apiRequest), profileH])
@@ -166,14 +168,16 @@ app dbStructure proc cols conf apiRequest =
(contentType == CTSingularJSON) True (contentType == CTSingularJSON) True
(contentType == CTTextCSV) (iPreferRepresentation apiRequest) pkCols pgVer (contentType == CTTextCSV) (iPreferRepresentation apiRequest) pkCols pgVer
row <- H.statement (toS $ pjRaw pJson) stm row <- H.statement (toS $ pjRaw pJson) stm
let (_, queryTotal, fields, body, gucHeaders) = row let (_, queryTotal, fields, body, gucHeaders, gucStatus) = row
case gucHeaders of gucs = (,) <$> gucHeaders <*> gucStatus
Left _ -> return . errorResponseFor $ GucHeadersError case gucs of
Right ghdrs -> do Left err -> return $ errorResponseFor err
Right (ghdrs, gstatus) -> do
let let
(ctHeaders, rBody) = if iPreferRepresentation apiRequest == Full (ctHeaders, rBody) = if iPreferRepresentation apiRequest == Full
then ([Just $ toHeader contentType, profileH], toS body) then ([Just $ toHeader contentType, profileH], toS body)
else ([], mempty) else ([], mempty)
status = fromMaybe status201 gstatus
headers = addHeadersIfNotIncluded (catMaybes ([ headers = addHeadersIfNotIncluded (catMaybes ([
if null fields if null fields
then Nothing then Nothing
@@ -188,7 +192,7 @@ app dbStructure proc cols conf apiRequest =
HT.condemn HT.condemn
return . errorResponseFor . singularityError $ queryTotal return . errorResponseFor . singularityError $ queryTotal
else else
return $ responseLBS status201 headers rBody return $ responseLBS status headers rBody
(ActionUpdate, TargetIdent (QualifiedIdentifier tSchema tName), Just pJson) -> (ActionUpdate, TargetIdent (QualifiedIdentifier tSchema tName), Just pJson) ->
case mutateSqlParts tSchema tName of case mutateSqlParts tSchema tName of
@@ -198,15 +202,17 @@ app dbStructure proc cols conf apiRequest =
(contentType == CTSingularJSON) False (contentType == CTTextCSV) (contentType == CTSingularJSON) False (contentType == CTTextCSV)
(iPreferRepresentation apiRequest) [] pgVer (iPreferRepresentation apiRequest) [] pgVer
row <- H.statement (toS $ pjRaw pJson) stm row <- H.statement (toS $ pjRaw pJson) stm
let (_, queryTotal, _, body, gucHeaders) = row let (_, queryTotal, _, body, gucHeaders, gucStatus) = row
case gucHeaders of gucs = (,) <$> gucHeaders <*> gucStatus
Left _ -> return . errorResponseFor $ GucHeadersError case gucs of
Right ghdrs -> do Left err -> return $ errorResponseFor err
Right (ghdrs, gstatus) -> do
let let
updateIsNoOp = S.null cols updateIsNoOp = S.null cols
status | queryTotal == 0 && not updateIsNoOp = status404 defStatus | queryTotal == 0 && not updateIsNoOp = status404
| iPreferRepresentation apiRequest == Full = status200 | iPreferRepresentation apiRequest == Full = status200
| otherwise = status204 | otherwise = status204
status = fromMaybe defStatus gstatus
contentRangeHeader = contentRangeH 0 (queryTotal - 1) $ if shouldCount then Just queryTotal else Nothing contentRangeHeader = contentRangeH 0 (queryTotal - 1) $ if shouldCount then Just queryTotal else Nothing
(ctHeaders, rBody) = if iPreferRepresentation apiRequest == Full (ctHeaders, rBody) = if iPreferRepresentation apiRequest == Full
then ([Just $ toHeader contentType, profileH], toS body) then ([Just $ toHeader contentType, profileH], toS body)
@@ -229,12 +235,14 @@ app dbStructure proc cols conf apiRequest =
row <- H.statement (toS $ pjRaw pJson) $ row <- H.statement (toS $ pjRaw pJson) $
createWriteStatement sq mq (contentType == CTSingularJSON) False createWriteStatement sq mq (contentType == CTSingularJSON) False
(contentType == CTTextCSV) (iPreferRepresentation apiRequest) [] pgVer (contentType == CTTextCSV) (iPreferRepresentation apiRequest) [] pgVer
let (_, queryTotal, _, body, gucHeaders) = row let (_, queryTotal, _, body, gucHeaders, gucStatus) = row
case gucHeaders of gucs = (,) <$> gucHeaders <*> gucStatus
Left _ -> return . errorResponseFor $ GucHeadersError case gucs of
Right ghdrs -> do Left err -> return $ errorResponseFor err
Right (ghdrs, gstatus) -> do
let headers = addHeadersIfNotIncluded (catMaybes [Just $ toHeader contentType, profileH]) (unwrapGucHeader <$> ghdrs) let headers = addHeadersIfNotIncluded (catMaybes [Just $ toHeader contentType, profileH]) (unwrapGucHeader <$> ghdrs)
(status, rBody) = if iPreferRepresentation apiRequest == Full then (status200, toS body) else (status204, mempty) (defStatus, rBody) = if iPreferRepresentation apiRequest == Full then (status200, toS body) else (status204, mempty)
status = fromMaybe defStatus gstatus
-- Makes sure the querystring pk matches the payload pk -- Makes sure the querystring pk matches the payload pk
-- e.g. PUT /items?id=eq.1 { "id" : 1, .. } is accepted, PUT /items?id=eq.14 { "id" : 2, .. } is rejected -- e.g. PUT /items?id=eq.1 { "id" : 1, .. } is accepted, PUT /items?id=eq.14 { "id" : 2, .. } is rejected
-- If this condition is not satisfied then nothing is inserted, check the WHERE for INSERT in QueryBuilder.hs to see how it's done -- If this condition is not satisfied then nothing is inserted, check the WHERE for INSERT in QueryBuilder.hs to see how it's done
@@ -254,12 +262,14 @@ app dbStructure proc cols conf apiRequest =
(contentType == CTTextCSV) (contentType == CTTextCSV)
(iPreferRepresentation apiRequest) [] pgVer (iPreferRepresentation apiRequest) [] pgVer
row <- H.statement mempty stm row <- H.statement mempty stm
let (_, queryTotal, _, body, gucHeaders) = row let (_, queryTotal, _, body, gucHeaders, gucStatus) = row
case gucHeaders of gucs = (,) <$> gucHeaders <*> gucStatus
Left _ -> return . errorResponseFor $ GucHeadersError case gucs of
Right ghdrs -> do Left err -> return $ errorResponseFor err
Right (ghdrs, gstatus) -> do
let let
status = if iPreferRepresentation apiRequest == Full then status200 else status204 defStatus = if iPreferRepresentation apiRequest == Full then status200 else status204
status = fromMaybe defStatus gstatus
contentRangeHeader = contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing contentRangeHeader = contentRangeH 1 0 $ if shouldCount then Just queryTotal else Nothing
(ctHeaders, rBody) = if iPreferRepresentation apiRequest == Full (ctHeaders, rBody) = if iPreferRepresentation apiRequest == Full
then ([Just $ toHeader contentType, profileH], toS body) then ([Just $ toHeader contentType, profileH], toS body)
@@ -294,11 +304,13 @@ app dbStructure proc cols conf apiRequest =
(contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects) (contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects)
bField pgVer bField pgVer
row <- H.statement (toS $ pjRaw pJson) stm row <- H.statement (toS $ pjRaw pJson) stm
let (tableTotal, queryTotal, body, gucHeaders) = row let (tableTotal, queryTotal, body, gucHeaders, gucStatus) = row
case gucHeaders of gucs = (,) <$> gucHeaders <*> gucStatus
Left _ -> return . errorResponseFor $ GucHeadersError case gucs of
Right ghdrs -> do Left err -> return $ errorResponseFor err
let (status, contentRange) = rangeStatusHeader topLevelRange queryTotal tableTotal Right (ghdrs, gstatus) -> do
let (rangeStatus, contentRange) = rangeStatusHeader topLevelRange queryTotal tableTotal
status = fromMaybe rangeStatus gstatus
headers = addHeadersIfNotIncluded headers = addHeadersIfNotIncluded
(catMaybes [Just $ toHeader contentType, Just contentRange, profileH]) (catMaybes [Just $ toHeader contentType, Just contentRange, profileH])
(unwrapGucHeader <$> ghdrs) (unwrapGucHeader <$> ghdrs)
+4
View File
@@ -221,6 +221,7 @@ checkIsFatal _ = Nothing
data SimpleError data SimpleError
= GucHeadersError = GucHeadersError
| GucStatusError
| BinaryFieldError ContentType | BinaryFieldError ContentType
| ConnectionLostError | ConnectionLostError
| PutMatchingPkError | PutMatchingPkError
@@ -233,6 +234,7 @@ data SimpleError
instance PgrstError SimpleError where instance PgrstError SimpleError where
status GucHeadersError = HT.status500 status GucHeadersError = HT.status500
status GucStatusError = HT.status500
status (BinaryFieldError _) = HT.status406 status (BinaryFieldError _) = HT.status406
status ConnectionLostError = HT.status503 status ConnectionLostError = HT.status503
status PutMatchingPkError = HT.status400 status PutMatchingPkError = HT.status400
@@ -249,6 +251,8 @@ instance PgrstError SimpleError where
instance JSON.ToJSON SimpleError where instance JSON.ToJSON SimpleError where
toJSON GucHeadersError = JSON.object [ toJSON GucHeadersError = JSON.object [
"message" .= ("response.headers guc must be a JSON array composed of objects with a single key and a string value" :: Text)] "message" .= ("response.headers guc must be a JSON array composed of objects with a single key and a string value" :: Text)]
toJSON GucStatusError = JSON.object [
"message" .= ("response.status guc must be a valid status code" :: Text)]
toJSON (BinaryFieldError ct) = JSON.object [ toJSON (BinaryFieldError ct) = JSON.object [
"message" .= ((toS (toMime ct) <> " requested but more than one column was selected") :: Text)] "message" .= ((toS (toMime ct) <> " requested but more than one column was selected") :: Text)]
toJSON ConnectionLostError = JSON.object [ toJSON ConnectionLostError = JSON.object [
+13 -2
View File
@@ -202,5 +202,16 @@ returningF qi returnings =
responseHeadersF :: PgVersion -> SqlFragment responseHeadersF :: PgVersion -> SqlFragment
responseHeadersF pgVer = responseHeadersF pgVer =
if pgVer >= pgVersion96 if pgVer >= pgVersion96
then "coalesce(nullif(current_setting('response.headers', true), ''), '[]')" :: Text -- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15 then currentSettingF "response.headers"
else "'[]'" :: Text else "null" :: Text
responseStatusF :: PgVersion -> SqlFragment
responseStatusF pgVer =
if pgVer >= pgVersion96
then currentSettingF "response.status"
else "null" :: Text
currentSettingF :: SqlFragment -> SqlFragment
currentSettingF setting =
-- nullif is used because of https://gist.github.com/steve-chavez/8d7033ea5655096903f3b52f8ed09a15
"nullif(current_setting(" <> pgFmtLit setting <> ", true), '')"
+28 -13
View File
@@ -22,9 +22,12 @@ import Data.Aeson as JSON
import qualified Data.Aeson.Lens as L import qualified Data.Aeson.Lens as L
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import Data.Maybe import Data.Maybe
import Data.Text.Read (decimal)
import qualified Hasql.Decoders as HD import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE import qualified Hasql.Encoders as HE
import qualified Hasql.Statement as H import qualified Hasql.Statement as H
import Network.HTTP.Types.Status
import PostgREST.Error
import PostgREST.Private.Common import PostgREST.Private.Common
import PostgREST.Private.QueryFragment import PostgREST.Private.QueryFragment
import PostgREST.Types import PostgREST.Types
@@ -37,7 +40,7 @@ import Text.InterpolatedString.Perl6 (qc)
is represented as a list of strings containing variable bindings like is represented as a list of strings containing variable bindings like
@"k1=eq.42"@, or the empty list if there is no location header. @"k1=eq.42"@, or the empty list if there is no location header.
-} -}
type ResultsWithCount = (Maybe Int64, Int64, [BS.ByteString], BS.ByteString, Either Text [GucHeader]) type ResultsWithCount = (Maybe Int64, Int64, [BS.ByteString], BS.ByteString, Either SimpleError [GucHeader], Either SimpleError (Maybe Status))
createWriteStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool -> createWriteStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool ->
PreferRepresentation -> [Text] -> PgVersion -> PreferRepresentation -> [Text] -> PgVersion ->
@@ -53,7 +56,8 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
pg_catalog.count(_postgrest_t) AS page_total, pg_catalog.count(_postgrest_t) AS page_total,
{locF} AS header, {locF} AS header,
{bodyF} AS body, {bodyF} AS body,
{responseHeadersF pgVer} AS response_headers {responseHeadersF pgVer} AS response_headers,
{responseStatusF pgVer} AS response_status
FROM ({selectF}) _postgrest_t |] FROM ({selectF}) _postgrest_t |]
locF = locF =
@@ -78,7 +82,7 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys
decodeStandard :: HD.Result ResultsWithCount decodeStandard :: HD.Result ResultsWithCount
decodeStandard = decodeStandard =
fromMaybe (Nothing, 0, [], mempty, Right []) <$> HD.rowMaybe standardRow fromMaybe (Nothing, 0, [], mempty, Right [], Right Nothing) <$> HD.rowMaybe standardRow
createReadStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion -> createReadStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
H.Statement () ResultsWithCount H.Statement () ResultsWithCount
@@ -94,7 +98,8 @@ createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField
pg_catalog.count(_postgrest_t) AS page_total, pg_catalog.count(_postgrest_t) AS page_total,
{noLocationF} AS header, {noLocationF} AS header,
{bodyF} AS body, {bodyF} AS body,
{responseHeadersF pgVer} AS response_headers {responseHeadersF pgVer} AS response_headers,
{responseStatusF pgVer} AS response_status
FROM ( SELECT * FROM {sourceCTEName}) _postgrest_t |] FROM ( SELECT * FROM {sourceCTEName}) _postgrest_t |]
(countCTEF, countResultF) = countF countQuery countTotal (countCTEF, countResultF) = countF countQuery countTotal
@@ -114,12 +119,14 @@ createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField
for that common type of query. for that common type of query.
-} -}
standardRow :: HD.Row ResultsWithCount standardRow :: HD.Row ResultsWithCount
standardRow = (,,,,) <$> nullableColumn HD.int8 <*> column HD.int8 standardRow = (,,,,,) <$> nullableColumn HD.int8 <*> column HD.int8
<*> column header <*> column HD.bytea <*> column decodeGucHeaders <*> column header <*> column HD.bytea
<*> (fromMaybe (Right []) <$> nullableColumn decodeGucHeaders)
<*> (fromMaybe (Right Nothing) <$> nullableColumn decodeGucStatus)
where where
header = HD.array $ HD.dimension replicateM $ element HD.bytea header = HD.array $ HD.dimension replicateM $ element HD.bytea
type ProcResults = (Maybe Int64, Int64, ByteString, Either Text [GucHeader]) type ProcResults = (Maybe Int64, Int64, ByteString, Either SimpleError [GucHeader], Either SimpleError (Maybe Status))
callProcStatement :: Bool -> SqlQuery -> SqlQuery -> SqlQuery -> Bool -> callProcStatement :: Bool -> SqlQuery -> SqlQuery -> SqlQuery -> Bool ->
Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion -> Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion ->
@@ -134,7 +141,8 @@ callProcStatement returnsScalar callProcQuery selectQuery countQuery countTotal
{countResultF} AS total_result_set, {countResultF} AS total_result_set,
pg_catalog.count(_postgrest_t) AS page_total, pg_catalog.count(_postgrest_t) AS page_total,
{bodyF} AS body, {bodyF} AS body,
{responseHeadersF pgVer} AS response_headers {responseHeadersF pgVer} AS response_headers,
{responseStatusF pgVer} AS response_status
FROM ({selectQuery}) _postgrest_t;|] FROM ({selectQuery}) _postgrest_t;|]
(countCTEF, countResultF) = countF countQuery countTotal (countCTEF, countResultF) = countF countQuery countTotal
@@ -153,10 +161,14 @@ callProcStatement returnsScalar callProcQuery selectQuery countQuery countTotal
decodeProc :: HD.Result ProcResults decodeProc :: HD.Result ProcResults
decodeProc = decodeProc =
fromMaybe (Just 0, 0, mempty, Right []) <$> HD.rowMaybe procRow fromMaybe (Just 0, 0, mempty, defGucHeaders, defGucStatus) <$> HD.rowMaybe procRow
where where
procRow = (,,,) <$> nullableColumn HD.int8 <*> column HD.int8 defGucHeaders = Right []
<*> column HD.bytea <*> column decodeGucHeaders defGucStatus = Right Nothing
procRow = (,,,,) <$> nullableColumn HD.int8 <*> column HD.int8
<*> column HD.bytea
<*> (fromMaybe defGucHeaders <$> nullableColumn decodeGucHeaders)
<*> (fromMaybe defGucStatus <$> nullableColumn decodeGucStatus)
createExplainStatement :: SqlQuery -> H.Statement () (Maybe Int64) createExplainStatement :: SqlQuery -> H.Statement () (Maybe Int64)
createExplainStatement countQuery = createExplainStatement countQuery =
@@ -179,5 +191,8 @@ createExplainStatement countQuery =
unicodeStatement :: Text -> HE.Params a -> HD.Result b -> Bool -> H.Statement a b unicodeStatement :: Text -> HE.Params a -> HD.Result b -> Bool -> H.Statement a b
unicodeStatement = H.Statement . encodeUtf8 unicodeStatement = H.Statement . encodeUtf8
decodeGucHeaders :: HD.Value (Either Text [GucHeader]) decodeGucHeaders :: HD.Value (Either SimpleError [GucHeader])
decodeGucHeaders = first toS . JSON.eitherDecode . toS <$> HD.bytea decodeGucHeaders = first (const GucHeadersError) . JSON.eitherDecode . toS <$> HD.bytea
decodeGucStatus :: HD.Value (Either SimpleError (Maybe Status))
decodeGucStatus = first (const GucStatusError) . fmap (Just . toEnum . fst) . decimal <$> HD.text
+21
View File
@@ -161,6 +161,27 @@ spec =
let respHeaders = simpleHeaders r let respHeaders = simpleHeaders r
respHeaders `shouldSatisfy` noBlankHeader respHeaders `shouldSatisfy` noBlankHeader
context "GUC status override" $ do
it "can override the status on RPC" $
get "/rpc/send_body_status_403"
`shouldRespondWith`
[json|{"message" : "invalid user or password"}|]
{ matchStatus = 403
, matchHeaders = [ matchContentTypeJson ]
}
it "can override the status through trigger" $
request methodPatch "/stuff?id=eq.1" [] [json|[{"name": "updated stuff 1"}]|]
`shouldRespondWith` 205
it "fails when setting invalid status guc" $
get "/rpc/send_bad_status"
`shouldRespondWith`
[json|{"message":"response.status guc must be a valid status code"}|]
{ matchStatus = 500
, matchHeaders = [ matchContentTypeJson ]
}
context "Use of the phraseto_tsquery function" $ do context "Use of the phraseto_tsquery function" $ do
it "finds matches" $ it "finds matches" $
get "/tsearch?text_search_vector=phfts.The%20Fat%20Cats" `shouldRespondWith` get "/tsearch?text_search_vector=phfts.The%20Fat%20Cats" `shouldRespondWith`
+24 -2
View File
@@ -1086,6 +1086,20 @@ begin
end; end;
$$ language plpgsql; $$ language plpgsql;
create or replace function test.send_body_status_403() returns pg_catalog.json as $$
begin
perform set_config('response.status', '403', true);
return json_build_object('message', 'invalid user or password');
end;
$$ language plpgsql;
create or replace function test.send_bad_status() returns pg_catalog.json as $$
begin
perform set_config('response.status', 'bad', true);
return null;
end;
$$ language plpgsql;
create or replace function test.get_projects_and_guc_headers() returns setof test.projects as $$ create or replace function test.get_projects_and_guc_headers() returns setof test.projects as $$
set local "response.headers" = '[{"X-Test": "key1=val1; someValue; key2=val2"}, {"X-Test-2": "key1=val1"}]'; set local "response.headers" = '[{"X-Test": "key1=val1; someValue; key2=val2"}, {"X-Test-2": "key1=val1"}]';
select * from test.projects; select * from test.projects;
@@ -1692,8 +1706,7 @@ create table private.stuff(
create view test.stuff as select * from private.stuff; create view test.stuff as select * from private.stuff;
create or replace function location_for_stuff() returns trigger create or replace function location_for_stuff() returns trigger as $$
as $$
begin begin
insert into private.stuff values (new.id, new.name); insert into private.stuff values (new.id, new.name);
if new.id is not null if new.id is not null
@@ -1709,6 +1722,15 @@ end
$$ language plpgsql security definer; $$ language plpgsql security definer;
create trigger location_for_stuff instead of insert on test.stuff for each row execute procedure test.location_for_stuff(); create trigger location_for_stuff instead of insert on test.stuff for each row execute procedure test.location_for_stuff();
create or replace function status_205_for_updated_stuff() returns trigger as $$
begin
update private.stuff set id = new.id, name = new.name;
perform set_config('response.status' , '205' , true);
return new;
end
$$ language plpgsql security definer;
create trigger status_205_for_updated_stuff instead of update on test.stuff for each row execute procedure test.status_205_for_updated_stuff();
create table loc_test ( create table loc_test (
id int primary key id int primary key
, c text , c text