Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4197d2f739 | ||
|
|
c10ba8e214 | ||
|
|
887948d259 | ||
|
|
c63786733a | ||
|
|
4fe696dd96 | ||
|
|
67936b343f | ||
|
|
b0e395f495 | ||
|
|
3b55a27ef3 | ||
|
|
43da81c30c |
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
|
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
|
||||||
run: postgrest-coverage
|
run: postgrest-coverage
|
||||||
- name: Upload coverage to codecov
|
- name: Upload coverage to codecov
|
||||||
uses: codecov/codecov-action@v3.1.1
|
uses: codecov/codecov-action@v3.1.3
|
||||||
with:
|
with:
|
||||||
files: ./coverage/codecov.json
|
files: ./coverage/codecov.json
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## [11.0.1] - 2023-04-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #2762, Fixes "permission denied for schema" error during schema cache load - @steve-chavez
|
||||||
|
- #2756, Fix bad error message on generated columns when using `Prefer: missing=default` - @steve-chavez
|
||||||
|
- #1139, Allow a 30 second skew for JWT validation - @steve-chavez
|
||||||
|
+ It used to be 1 second, which was too strict
|
||||||
|
|
||||||
## [11.0.0] - 2023-04-16
|
## [11.0.0] - 2023-04-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -38,11 +38,17 @@ let
|
|||||||
{
|
{
|
||||||
name = "postgrest-run";
|
name = "postgrest-run";
|
||||||
docs = "Run PostgREST after building it interactively with cabal-install";
|
docs = "Run PostgREST after building it interactively with cabal-install";
|
||||||
args = [ "ARG_LEFTOVERS([PostgREST arguments])" ];
|
args =
|
||||||
|
[
|
||||||
|
"ARG_USE_ENV([PGRST_DB_ANON_ROLE], [postgrest_test_anonymous], [PostgREST anonymous role])"
|
||||||
|
"ARG_LEFTOVERS([PostgREST arguments])"
|
||||||
|
];
|
||||||
inRootDir = true;
|
inRootDir = true;
|
||||||
withEnv = postgrest.env;
|
withEnv = postgrest.env;
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
|
export PGRST_DB_ANON_ROLE
|
||||||
|
|
||||||
exec ${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \
|
exec ${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \
|
||||||
postgrest "''${_arg_leftovers[@]}"
|
postgrest "''${_arg_leftovers[@]}"
|
||||||
'';
|
'';
|
||||||
|
|||||||
+12
-5
@@ -15,11 +15,14 @@
|
|||||||
let
|
let
|
||||||
withTmpDb =
|
withTmpDb =
|
||||||
{ name, postgresql }:
|
{ name, postgresql }:
|
||||||
let commandName = "postgrest-with-${name}"; in
|
let
|
||||||
|
commandName = "postgrest-with-${name}";
|
||||||
|
superuserRole = "postgres";
|
||||||
|
in
|
||||||
checkedShellScript
|
checkedShellScript
|
||||||
{
|
{
|
||||||
name = commandName;
|
name = commandName;
|
||||||
docs = "Run the given command in a temporary database with ${name}";
|
docs = "Run the given command in a temporary database with ${name}. If you wish to mutate the database, login with the '${superuserRole}' role.";
|
||||||
args =
|
args =
|
||||||
[
|
[
|
||||||
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/spec/fixtures/load.sql])"
|
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/spec/fixtures/load.sql])"
|
||||||
@@ -66,7 +69,8 @@ let
|
|||||||
log "Initializing database cluster..."
|
log "Initializing database cluster..."
|
||||||
# We try to make the database cluster as independent as possible from the host
|
# We try to make the database cluster as independent as possible from the host
|
||||||
# by specifying the timezone, locale and encoding.
|
# by specifying the timezone, locale and encoding.
|
||||||
PGTZ=UTC initdb --no-locale --encoding=UTF8 --nosync -U "$PGUSER" --auth=trust \
|
# initdb -U creates a superuser(man initdb)
|
||||||
|
PGTZ=UTC initdb --no-locale --encoding=UTF8 --nosync -U "${superuserRole}" --auth=trust \
|
||||||
>> "$setuplog"
|
>> "$setuplog"
|
||||||
|
|
||||||
log "Starting the database cluster..."
|
log "Starting the database cluster..."
|
||||||
@@ -82,8 +86,11 @@ let
|
|||||||
}
|
}
|
||||||
trap stop EXIT
|
trap stop EXIT
|
||||||
|
|
||||||
log "Loading fixtures..."
|
log "Creating a minimally privileged $PGUSER connection role..."
|
||||||
psql -v ON_ERROR_STOP=1 -f "$_arg_fixtures" >> "$setuplog"
|
createuser "$PGUSER" -U "${superuserRole}" --host="$tmpdir/socket" --no-createdb --no-inherit --no-superuser --no-createrole --no-replication --login
|
||||||
|
|
||||||
|
log "Loading fixtures under the ${superuserRole} role..."
|
||||||
|
psql -U "${superuserRole}" -v PGUSER="$PGUSER" -v ON_ERROR_STOP=1 -f "$_arg_fixtures" >> "$setuplog"
|
||||||
|
|
||||||
log "Done. Running command..."
|
log "Done. Running command..."
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,5 @@
|
|||||||
name: postgrest
|
name: postgrest
|
||||||
version: 11.0.0
|
version: 11.0.1
|
||||||
synopsis: REST API for any Postgres database
|
synopsis: REST API for any Postgres database
|
||||||
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
||||||
for tables, views, and functions, supporting all HTTP methods that security
|
for tables, views, and functions, supporting all HTTP methods that security
|
||||||
@@ -187,6 +187,7 @@ test-suite spec
|
|||||||
Feature.CorsSpec
|
Feature.CorsSpec
|
||||||
Feature.ExtraSearchPathSpec
|
Feature.ExtraSearchPathSpec
|
||||||
Feature.LegacyGucsSpec
|
Feature.LegacyGucsSpec
|
||||||
|
Feature.NoSuperuserSpec
|
||||||
Feature.ObservabilitySpec
|
Feature.ObservabilitySpec
|
||||||
Feature.OpenApi.DisabledOpenApiSpec
|
Feature.OpenApi.DisabledOpenApiSpec
|
||||||
Feature.OpenApi.IgnorePrivOpenApiSpec
|
Feature.OpenApi.IgnorePrivOpenApiSpec
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ parseToken AppConfig{..} token time = do
|
|||||||
liftEither . mapLeft jwtClaimsError $ JSON.toJSON <$> eitherClaims
|
liftEither . mapLeft jwtClaimsError $ JSON.toJSON <$> eitherClaims
|
||||||
where
|
where
|
||||||
validation =
|
validation =
|
||||||
JWT.defaultJWTValidationSettings audienceCheck & set JWT.allowedSkew 1
|
JWT.defaultJWTValidationSettings audienceCheck & set JWT.allowedSkew 30
|
||||||
|
|
||||||
audienceCheck :: JWT.StringOrURI -> Bool
|
audienceCheck :: JWT.StringOrURI -> Bool
|
||||||
audienceCheck = maybe (const True) (==) configJwtAudience
|
audienceCheck = maybe (const True) (==) configJwtAudience
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import Text.InterpolatedString.Perl6 (q)
|
|||||||
|
|
||||||
import PostgREST.Config.Database (pgVersionStatement)
|
import PostgREST.Config.Database (pgVersionStatement)
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||||
pgVersion110)
|
pgVersion110, pgVersion120)
|
||||||
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
|
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
|
||||||
QualifiedIdentifier (..),
|
QualifiedIdentifier (..),
|
||||||
Schema)
|
Schema)
|
||||||
@@ -506,6 +506,8 @@ tablesSqlQuery pgVer =
|
|||||||
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
|
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
|
||||||
-- we cannot use those directly as they include the following privilege filter:
|
-- we cannot use those directly as they include the following privilege filter:
|
||||||
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
|
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
|
||||||
|
-- on the "columns" CTE, left joining on pg_depend and pg_class is used to obtain the sequence name as a column default in case there are GENERATED .. AS IDENTITY,
|
||||||
|
-- generated columns are only available from pg >= 10 but the query is agnostic to versions. dep.deptype = 'i' is done because there are other 'a' dependencies on PKs
|
||||||
[q|
|
[q|
|
||||||
WITH
|
WITH
|
||||||
columns AS (
|
columns AS (
|
||||||
@@ -549,6 +551,12 @@ tablesSqlQuery pgVer =
|
|||||||
ON t.typtype = 'd' AND t.typbasetype = bt.oid
|
ON t.typtype = 'd' AND t.typbasetype = bt.oid
|
||||||
LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
|
LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
|
||||||
ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
|
ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
|
||||||
|
LEFT JOIN pg_depend dep
|
||||||
|
ON dep.refobjid = a.attrelid and dep.refobjsubid = a.attnum and dep.deptype = 'i'
|
||||||
|
LEFT JOIN pg_class seqclass
|
||||||
|
ON seqclass.oid = dep.objid
|
||||||
|
LEFT JOIN pg_namespace seqsch
|
||||||
|
ON seqsch.oid = seqclass.relnamespace
|
||||||
WHERE
|
WHERE
|
||||||
NOT pg_is_other_temp_schema(nc.oid)
|
NOT pg_is_other_temp_schema(nc.oid)
|
||||||
AND a.attnum > 0
|
AND a.attnum > 0
|
||||||
@@ -697,17 +705,19 @@ tablesSqlQuery pgVer =
|
|||||||
"ORDER BY table_schema, table_name"
|
"ORDER BY table_schema, table_name"
|
||||||
where
|
where
|
||||||
relIsPartition = if pgVer >= pgVersion100 then " AND not c.relispartition " else mempty
|
relIsPartition = if pgVer >= pgVersion100 then " AND not c.relispartition " else mempty
|
||||||
-- detect default values on columns that have GENERATED .. AS IDENTITY
|
columnDefault
|
||||||
columnDefault =
|
| pgVer >= pgVersion120 = [q|
|
||||||
if pgVer >= pgVersion100
|
CASE
|
||||||
then [q|
|
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
|
||||||
CASE
|
WHEN a.attgenerated = 's' THEN null
|
||||||
WHEN nullif(a.attidentity, '') is null
|
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
||||||
THEN pg_get_expr(ad.adbin, ad.adrelid)::text
|
END AS column_default,|]
|
||||||
ELSE format('nextval(%s)', quote_literal(pg_get_serial_sequence(a.attrelid::regclass::text, a.attname::text)))
|
| pgVer >= pgVersion100 = [q|
|
||||||
END AS column_default,|]
|
CASE
|
||||||
else "pg_get_expr(ad.adbin, ad.adrelid)::text AS column_default,"
|
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
|
||||||
|
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
||||||
|
END AS column_default,|]
|
||||||
|
| otherwise = "pg_get_expr(ad.adbin, ad.adrelid)::text as column_default,"
|
||||||
|
|
||||||
-- | Gets many-to-one relationships and one-to-one(O2O) relationships, which are a refinement of the many-to-one's
|
-- | Gets many-to-one relationships and one-to-one(O2O) relationships, which are a refinement of the many-to-one's
|
||||||
allM2OandO2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
|
allM2OandO2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
set search_path to public;
|
set search_path to public;
|
||||||
|
|
||||||
CREATE ROLE postgrest_test_anonymous;
|
CREATE ROLE postgrest_test_anonymous;
|
||||||
ALTER ROLE :USER SET pgrst.db_anon_role = 'postgrest_test_anonymous';
|
ALTER ROLE :PGUSER SET pgrst.db_anon_role = 'postgrest_test_anonymous';
|
||||||
|
|
||||||
CREATE ROLE postgrest_test_author;
|
CREATE ROLE postgrest_test_author;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ alter role postgrest_test_serializable set default_transaction_isolation = 'seri
|
|||||||
CREATE ROLE postgrest_test_repeatable_read;
|
CREATE ROLE postgrest_test_repeatable_read;
|
||||||
alter role postgrest_test_repeatable_read set default_transaction_isolation = 'REPEATABLE READ';
|
alter role postgrest_test_repeatable_read set default_transaction_isolation = 'REPEATABLE READ';
|
||||||
|
|
||||||
GRANT postgrest_test_anonymous, postgrest_test_author, postgrest_test_serializable, postgrest_test_repeatable_read TO :USER;
|
GRANT postgrest_test_anonymous, postgrest_test_author, postgrest_test_serializable, postgrest_test_repeatable_read TO :PGUSER;
|
||||||
|
|
||||||
CREATE SCHEMA v1;
|
CREATE SCHEMA v1;
|
||||||
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
|
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
CREATE ROLE postgrest_test_anonymous;
|
CREATE ROLE postgrest_test_anonymous;
|
||||||
GRANT postgrest_test_anonymous TO :USER;
|
GRANT postgrest_test_anonymous TO :PGUSER;
|
||||||
CREATE SCHEMA test;
|
CREATE SCHEMA test;
|
||||||
|
|
||||||
-- PUT+PATCH target needs one record and column to modify
|
-- PUT+PATCH target needs one record and column to modify
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
module Feature.NoSuperuserSpec where
|
||||||
|
|
||||||
|
import Network.Wai (Application)
|
||||||
|
|
||||||
|
import Network.HTTP.Types
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.Hspec.Wai
|
||||||
|
|
||||||
|
import Protolude
|
||||||
|
|
||||||
|
spec :: SpecWith ((), Application)
|
||||||
|
spec =
|
||||||
|
describe "No Superuser" $ do
|
||||||
|
it "proves that the authenticator role is not a superuser" $ do
|
||||||
|
request methodGet "/rpc/is_superuser"
|
||||||
|
mempty
|
||||||
|
""
|
||||||
|
`shouldRespondWith`
|
||||||
|
"false"
|
||||||
|
{ matchStatus = 200 }
|
||||||
@@ -13,7 +13,8 @@ import Text.Heredoc
|
|||||||
|
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||||
pgVersion110, pgVersion112,
|
pgVersion110, pgVersion112,
|
||||||
pgVersion130)
|
pgVersion120, pgVersion130,
|
||||||
|
pgVersion140)
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
@@ -496,7 +497,7 @@ spec actualPgVersion = do
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion100) $
|
when (actualPgVersion >= pgVersion100) $
|
||||||
it "inserts a default on a generated by default as identity column" $ do
|
it "inserts a default on a generated by default as identity column" $
|
||||||
request methodPost "/channels?columns=id,data,slug&select=data,slug" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
request methodPost "/channels?columns=id,data,slug&select=data,slug" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
||||||
[json| { "slug": "foo" } |]
|
[json| { "slug": "foo" } |]
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
@@ -505,6 +506,29 @@ spec actualPgVersion = do
|
|||||||
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
|
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (actualPgVersion >= pgVersion120) $
|
||||||
|
it "fails with a good error message on generated always columns" $
|
||||||
|
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
||||||
|
[json| [
|
||||||
|
{"a": "val"},
|
||||||
|
{"a": "val", "b": "val"}
|
||||||
|
]|]
|
||||||
|
`shouldRespondWith`
|
||||||
|
(if actualPgVersion < pgVersion140
|
||||||
|
then [json| {
|
||||||
|
"code": "42601",
|
||||||
|
"details": "Column \"b\" is a generated column.",
|
||||||
|
"hint": null,
|
||||||
|
"message": "cannot insert into column \"b\""
|
||||||
|
}|]
|
||||||
|
else [json| {
|
||||||
|
"code": "428C9",
|
||||||
|
"details": "Column \"b\" is a generated column.",
|
||||||
|
"hint": null,
|
||||||
|
"message": "cannot insert a non-DEFAULT value into column \"b\""
|
||||||
|
}|])
|
||||||
|
{ matchStatus = 400 }
|
||||||
|
|
||||||
it "inserts json that has duplicate keys" $ do
|
it "inserts json that has duplicate keys" $ do
|
||||||
request methodPost "/tbl_w_json" [("Prefer", "return=representation")]
|
request methodPost "/tbl_w_json" [("Prefer", "return=representation")]
|
||||||
[json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
|
[json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import qualified Feature.ConcurrentSpec
|
|||||||
import qualified Feature.CorsSpec
|
import qualified Feature.CorsSpec
|
||||||
import qualified Feature.ExtraSearchPathSpec
|
import qualified Feature.ExtraSearchPathSpec
|
||||||
import qualified Feature.LegacyGucsSpec
|
import qualified Feature.LegacyGucsSpec
|
||||||
|
import qualified Feature.NoSuperuserSpec
|
||||||
import qualified Feature.ObservabilitySpec
|
import qualified Feature.ObservabilitySpec
|
||||||
import qualified Feature.OpenApi.DisabledOpenApiSpec
|
import qualified Feature.OpenApi.DisabledOpenApiSpec
|
||||||
import qualified Feature.OpenApi.IgnorePrivOpenApiSpec
|
import qualified Feature.OpenApi.IgnorePrivOpenApiSpec
|
||||||
@@ -149,6 +150,7 @@ main = do
|
|||||||
, ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec)
|
, ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec)
|
||||||
, ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec)
|
, ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec)
|
||||||
, ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec)
|
, ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec)
|
||||||
|
, ("Feature.NoSuperuserSpec" , Feature.NoSuperuserSpec.spec)
|
||||||
]
|
]
|
||||||
|
|
||||||
hspec $ do
|
hspec $ do
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ testObservabilityCfg = baseCfg { configServerTraceHeader = Just $ mk "X-Request-
|
|||||||
|
|
||||||
analyzeTable :: Text -> IO ()
|
analyzeTable :: Text -> IO ()
|
||||||
analyzeTable tableName =
|
analyzeTable tableName =
|
||||||
void $ readProcess "psql" ["--set", "ON_ERROR_STOP=1", "-a", "-c", toS $ "ANALYZE test.\"" <> tableName <> "\""] []
|
void $ readProcess "psql" ["-U", "postgres", "--set", "ON_ERROR_STOP=1", "-a", "-c", toS $ "ANALYZE test.\"" <> tableName <> "\""] []
|
||||||
|
|
||||||
rangeHdrs :: ByteRange -> [Header]
|
rangeHdrs :: ByteRange -> [Header]
|
||||||
rangeHdrs r = [rangeUnit, (hRange, renderByteRange r)]
|
rangeHdrs r = [rangeUnit, (hRange, renderByteRange r)]
|
||||||
|
|||||||
Vendored
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
\set AUTHENTICATOR current_user
|
|
||||||
DROP ROLE IF EXISTS postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author;
|
DROP ROLE IF EXISTS postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author;
|
||||||
CREATE ROLE postgrest_test_anonymous;
|
CREATE ROLE postgrest_test_anonymous;
|
||||||
CREATE ROLE postgrest_test_default_role;
|
CREATE ROLE postgrest_test_default_role;
|
||||||
CREATE ROLE postgrest_test_author;
|
CREATE ROLE postgrest_test_author;
|
||||||
|
|
||||||
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :USER;
|
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :PGUSER;
|
||||||
|
|||||||
Vendored
+22
@@ -3122,3 +3122,25 @@ BEGIN
|
|||||||
END IF;
|
END IF;
|
||||||
END
|
END
|
||||||
$do$;
|
$do$;
|
||||||
|
|
||||||
|
CREATE FUNCTION test.is_superuser() RETURNS boolean
|
||||||
|
LANGUAGE sql
|
||||||
|
AS $$
|
||||||
|
select current_setting('is_superuser')::boolean;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
DO $do$
|
||||||
|
BEGIN
|
||||||
|
IF current_setting('server_version_num')::INT >= 120000 THEN
|
||||||
|
CREATE TABLE test.foo (
|
||||||
|
a text,
|
||||||
|
b text GENERATED ALWAYS AS (
|
||||||
|
case WHEN a = 'telegram' THEN 'im'
|
||||||
|
WHEN a = 'proton' THEN 'email'
|
||||||
|
WHEN a = 'infinity' THEN 'idea'
|
||||||
|
ELSE 'bad idea'
|
||||||
|
end) stored
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$do$;
|
||||||
|
|||||||
Reference in New Issue
Block a user