feat: Drop support for pg 9.6

This commit is contained in:
Wolfgang Walther
2024-06-15 17:23:34 +02:00
committed by Wolfgang Walther
parent ec110720dc
commit daa77d17aa
20 changed files with 401 additions and 656 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
pgVersion: ["9_6", 10, 11, 12, 13, 14, 15, 16] pgVersion: [10, 11, 12, 13, 14, 15, 16]
name: PG ${{ matrix.pgVersion }} name: PG ${{ matrix.pgVersion }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
defaults: defaults:
+4
View File
@@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
### Changed
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
## [12.2.0] - 2024-06-11 ## [12.2.0] - 2024-06-11
### Added ### Added
-1
View File
@@ -56,7 +56,6 @@ let
{ name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); } { name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis p.pg_safeupdate ]); } { name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-10"; postgresql = pkgs.postgresql_10.withPackages (p: [ p.postgis p.pg_safeupdate ]); } { name = "postgresql-10"; postgresql = pkgs.postgresql_10.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-9_6"; postgresql = pkgs.postgresql_9_6.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
]; ];
# Dynamic derivation for PostgREST # Dynamic derivation for PostgREST
+3 -3
View File
@@ -16,14 +16,14 @@ Supported PostgreSQL versions
============================= =============================
=============== ================================= =============== =================================
**Supported** PostgreSQL >= 9.6 **Supported** PostgreSQL >= 10
=============== ================================= =============== =================================
PostgREST works with all PostgreSQL versions starting from 9.6. PostgREST works with all PostgreSQL versions starting from 10.
.. note:: .. note::
Support for PostgreSQL versions 9.6, 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions. Support for PostgreSQL versions 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions.
See https://www.postgresql.org/support/versioning/. See https://www.postgresql.org/support/versioning/.
+2 -2
View File
@@ -79,7 +79,7 @@ postgrest-coverage postgrest-with-postgresql-10
postgrest-lint postgrest-with-postgresql-11 postgrest-lint postgrest-with-postgresql-11
postgrest-run postgrest-with-postgresql-12 postgrest-run postgrest-with-postgresql-12
postgrest-style postgrest-with-postgresql-13 postgrest-style postgrest-with-postgresql-13
postgrest-style-check postgrest-with-postgresql-9.6 postgrest-style-check postgrest-with-postgresql-14
postgrest-test-io postgrest-test-io
... ...
@@ -103,7 +103,7 @@ postgrest-coverage postgrest-with-postgresql-10
postgrest-lint postgrest-with-postgresql-11 postgrest-lint postgrest-with-postgresql-11
postgrest-run postgrest-with-postgresql-12 postgrest-run postgrest-with-postgresql-12
postgrest-style postgrest-with-postgresql-13 postgrest-style postgrest-with-postgresql-13
postgrest-style-check postgrest-with-postgresql-9.6 postgrest-style-check postgrest-with-postgresql-14
postgrest-test-io postgrest-test-io
postgrest-test-memory postgrest-test-memory
... ...
-5
View File
@@ -20,9 +20,4 @@ in
postgis = prev.postgresql_10.pkgs.postgis.overrideAttrs (_: postgis_3_2_3); postgis = prev.postgresql_10.pkgs.postgis.overrideAttrs (_: postgis_3_2_3);
}; };
}; };
postgresql_9_6 = prev.postgresql_9_6.override { this = final.postgresql_11; } // {
pkgs = prev.postgresql_9_6.pkgs // {
postgis = prev.postgresql_9_6.pkgs.postgis.overrideAttrs (_: postgis_3_2_3);
};
};
} }
-15
View File
@@ -2,21 +2,6 @@ _: _:
# Overlay that adds legacy versions of PostgreSQL that are supported by # Overlay that adds legacy versions of PostgreSQL that are supported by
# PostgREST. # PostgREST.
{ {
# PostgreSQL 9.6 was removed from Nixpkgs with
# https://github.com/NixOS/nixpkgs/commit/757dd008b2f2926fc0f7688fa8189f930ea47521
# We pin its parent commit to get the last version that was available.
postgresql_9_6 =
let
rev = "571cbf3d1db477058303cef8754fb85a14e90eb7";
tarballHash = "0q74wn418i1bn5sssacmw8ykpmqvzr0s93sj6pbs3rf6bf134fkz";
pinnedPkgs =
builtins.fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
sha256 = tarballHash;
};
in
(import pinnedPkgs { }).pkgs.postgresql_9_6;
# PostgreSQL 10 was removed from Nixpkgs with # PostgreSQL 10 was removed from Nixpkgs with
# https://github.com/NixOS/nixpkgs/commit/aa1483114bb329fee7e1266100b8d8921ed4723f # https://github.com/NixOS/nixpkgs/commit/aa1483114bb329fee7e1266100b8d8921ed4723f
# We pin its parent commit to get the last version that was available. # We pin its parent commit to get the last version that was available.
+1 -6
View File
@@ -3,8 +3,6 @@
module PostgREST.Config.PgVersion module PostgREST.Config.PgVersion
( PgVersion(..) ( PgVersion(..)
, minimumPgVersion , minimumPgVersion
, pgVersion96
, pgVersion100
, pgVersion109 , pgVersion109
, pgVersion110 , pgVersion110
, pgVersion112 , pgVersion112
@@ -33,10 +31,7 @@ instance Ord PgVersion where
-- | Tells the minimum PostgreSQL version required by this version of PostgREST -- | Tells the minimum PostgreSQL version required by this version of PostgREST
minimumPgVersion :: PgVersion minimumPgVersion :: PgVersion
minimumPgVersion = pgVersion96 minimumPgVersion = pgVersion100
pgVersion96 :: PgVersion
pgVersion96 = PgVersion 90600 "9.6" "9.6"
pgVersion100 :: PgVersion pgVersion100 :: PgVersion
pgVersion100 = PgVersion 100000 "10" "10" pgVersion100 = PgVersion 100000 "10" "10"
+1 -1
View File
@@ -178,7 +178,7 @@ actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{
actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ pgVer sCache = actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ pgVer sCache =
lift $ case configOpenApiMode of lift $ case configOpenApiMode of
OAFollowPriv -> do OAFollowPriv -> do
tableAccess <- SQL.statement [tSchema] (SchemaCache.accessibleTables pgVer configDbPreparedStatements) tableAccess <- SQL.statement [tSchema] (SchemaCache.accessibleTables configDbPreparedStatements)
MaybeDbResult plan . Just <$> ((,,) MaybeDbResult plan . Just <$> ((,,)
(HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache) (HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache)
<$> SQL.statement (tSchema, configDbHoistedTxSettings) (SchemaCache.accessibleFuncs pgVer configDbPreparedStatements) <$> SQL.statement (tSchema, configDbHoistedTxSettings) (SchemaCache.accessibleFuncs pgVer configDbPreparedStatements)
+10 -18
View File
@@ -47,8 +47,7 @@ import PostgREST.Config (AppConfig (..))
import PostgREST.Config.Database (TimezoneNames, import PostgREST.Config.Database (TimezoneNames,
pgVersionStatement, pgVersionStatement,
toIsolationLevel) toIsolationLevel)
import PostgREST.Config.PgVersion (PgVersion, pgVersion100, import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
pgVersion110,
pgVersion120) pgVersion120)
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName, import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
QualifiedIdentifier (..), QualifiedIdentifier (..),
@@ -477,8 +476,8 @@ schemaDescription =
where where
n.nspname = $1 |] n.nspname = $1 |]
accessibleTables :: PgVersion -> Bool -> SQL.Statement [Schema] AccessSet accessibleTables :: Bool -> SQL.Statement [Schema] AccessSet
accessibleTables pgVer = accessibleTables =
SQL.Statement sql (arrayParam HE.text) decodeAccessibleIdentifiers SQL.Statement sql (arrayParam HE.text) decodeAccessibleIdentifiers
where where
sql = [q| sql = [q|
@@ -494,10 +493,9 @@ accessibleTables pgVer =
pg_has_role(c.relowner, 'USAGE') pg_has_role(c.relowner, 'USAGE')
or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER') or has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES') or has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
) |] <> )
relIsPartition <> AND not c.relispartition
"ORDER BY table_schema, table_name" ORDER BY table_schema, table_name|]
relIsPartition = if pgVer >= pgVersion100 then " AND not c.relispartition " else mempty
{- {-
Adds M2O and O2O relationships for views to tables, tables to views, and views to views. The example below is taken from the test fixtures, but the views names/colnames were modified. Adds M2O and O2O relationships for views to tables, tables to views, and views to views. The example below is taken from the test fixtures, but the views names/colnames were modified.
@@ -810,11 +808,10 @@ tablesSqlQuery pgVer =
LEFT JOIN tbl_pk_cols tpks ON n.nspname = tpks.table_schema AND c.relname = tpks.table_name LEFT JOIN tbl_pk_cols tpks ON n.nspname = tpks.table_schema AND c.relname = tpks.table_name
LEFT JOIN columns_agg cols_agg ON n.nspname = cols_agg.table_schema AND c.relname = cols_agg.table_name LEFT JOIN columns_agg cols_agg ON n.nspname = cols_agg.table_schema AND c.relname = cols_agg.table_name
WHERE c.relkind IN ('v','r','m','f','p') WHERE c.relkind IN ('v','r','m','f','p')
AND n.nspname NOT IN ('pg_catalog', 'information_schema') |] <> AND n.nspname NOT IN ('pg_catalog', 'information_schema')
relIsPartition <> AND not c.relispartition
"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
columnDefault -- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column columnDefault -- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
| pgVer >= pgVersion120 = [q| | pgVer >= pgVersion120 = [q|
CASE CASE
@@ -823,17 +820,12 @@ tablesSqlQuery pgVer =
WHEN a.attgenerated = 's' THEN null WHEN a.attgenerated = 's' THEN null
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END|] END|]
| pgVer >= pgVersion100 = [q| | otherwise = [q|
CASE CASE
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0) WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname)) WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END|] END|]
| otherwise = [q|
CASE
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END|]
-- | 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]
+17 -19
View File
@@ -11,8 +11,7 @@ import Network.HTTP.Types
import Test.Hspec hiding (pendingWith) import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai import Test.Hspec.Wai
import PostgREST.Config.PgVersion (PgVersion, pgVersion100, import PostgREST.Config.PgVersion (PgVersion, pgVersion110)
pgVersion110)
import PostgREST.Version (docsVersion) import PostgREST.Version (docsVersion)
import Protolude hiding (get) import Protolude hiding (get)
@@ -269,31 +268,30 @@ spec actualPgVersion = describe "OpenAPI" $ do
] ]
|] |]
when (actualPgVersion >= pgVersion100) $ do describe "Partitioned table" $
describe "Partitioned table" $
it "includes partitioned table properties" $ do it "includes partitioned table properties" $ do
r <- simpleBody <$> get "/" r <- simpleBody <$> get "/"
let method s = key "paths" . key "/car_models" . key s let method s = key "paths" . key "/car_models" . key s
getSummary = r ^? method "get" . key "summary" getSummary = r ^? method "get" . key "summary"
getDescription = r ^? method "get" . key "description" getDescription = r ^? method "get" . key "description"
getParameterName = r ^? method "get" . key "parameters" . nth 0 . key "$ref" getParameterName = r ^? method "get" . key "parameters" . nth 0 . key "$ref"
getParameterYear = r ^? method "get" . key "parameters" . nth 1 . key "$ref" getParameterYear = r ^? method "get" . key "parameters" . nth 1 . key "$ref"
getParameterRef = r ^? method "get" . key "parameters" . nth 2 . key "$ref" getParameterRef = r ^? method "get" . key "parameters" . nth 2 . key "$ref"
liftIO $ do liftIO $ do
getSummary `shouldBe` Just "A partitioned table" getSummary `shouldBe` Just "A partitioned table"
getDescription `shouldBe` Just "A test for partitioned tables" getDescription `shouldBe` Just "A test for partitioned tables"
getParameterName `shouldBe` Just "#/parameters/rowFilter.car_models.name" getParameterName `shouldBe` Just "#/parameters/rowFilter.car_models.name"
getParameterYear `shouldBe` Just "#/parameters/rowFilter.car_models.year" getParameterYear `shouldBe` Just "#/parameters/rowFilter.car_models.year"
when (actualPgVersion >= pgVersion110) $ when (actualPgVersion >= pgVersion110) $
getParameterRef `shouldBe` Just "#/parameters/rowFilter.car_models.car_brand_name" getParameterRef `shouldBe` Just "#/parameters/rowFilter.car_models.car_brand_name"
describe "Materialized view" $ describe "Materialized view" $
+12 -14
View File
@@ -7,8 +7,7 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import PostgREST.Config.PgVersion (PgVersion, pgVersion100, import PostgREST.Config.PgVersion (PgVersion, pgVersion110)
pgVersion110)
import Protolude import Protolude
import SpecHelper import SpecHelper
@@ -25,18 +24,17 @@ spec actualPgVersion = describe "Allow header" $ do
it "fails with 404 for an unknown table" $ it "fails with 404 for an unknown table" $
request methodOptions "/unknown" [] "" `shouldRespondWith` 404 request methodOptions "/unknown" [] "" `shouldRespondWith` 404
when (actualPgVersion >= pgVersion100) $ context "a partitioned table" $ do
context "a partitioned table" $ do it "includes read/write methods for writeable partitioned tables" $ do
it "includes read/write methods for writeable partitioned tables" $ do r <- request methodOptions "/car_models" [] ""
r <- request methodOptions "/car_models" [] "" liftIO $
liftIO $ simpleHeaders r `shouldSatisfy`
simpleHeaders r `shouldSatisfy` matchHeader "Allow" (
matchHeader "Allow" ( if actualPgVersion >= pgVersion110 then
if actualPgVersion >= pgVersion110 then "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
"OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE" else
else "OPTIONS,GET,HEAD,POST,PATCH,DELETE"
"OPTIONS,GET,HEAD,POST,PATCH,DELETE" )
)
context "a view" $ do context "a view" $ do
context "auto updatable" $ do context "auto updatable" $ do
+128 -134
View File
@@ -11,10 +11,9 @@ import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc import Text.Heredoc
import PostgREST.Config.PgVersion (PgVersion, pgVersion100, import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
pgVersion110, pgVersion112, pgVersion112, pgVersion120,
pgVersion120, pgVersion130, pgVersion130, pgVersion140)
pgVersion140)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -494,24 +493,22 @@ spec actualPgVersion = do
{"id": 205, "body": "zzz"}]|] `shouldRespondWith` 400 {"id": 205, "body": "zzz"}]|] `shouldRespondWith` 400
context "apply defaults on missing values" $ do context "apply defaults on missing values" $ do
-- inserting the array fails on pg 9.6, but the feature should work normally it "inserts table default values(field-with_sep) when json keys are undefined" $
when (actualPgVersion >= pgVersion100) $ request methodPost "/complex_items?columns=id,name,field-with_sep,arr_data" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
it "inserts table default values(field-with_sep) when json keys are undefined" $ [json|[
request methodPost "/complex_items?columns=id,name,field-with_sep,arr_data" [("Prefer", "return=representation"), ("Prefer", "missing=default")] {"id": 4, "name": "Vier"},
[json|[ {"id": 5, "name": "Funf", "arr_data": null},
{"id": 4, "name": "Vier"}, {"id": 6, "name": "Sechs", "field-with_sep": 6, "arr_data": "{1,2,3}"}
{"id": 5, "name": "Funf", "arr_data": null}, ]|]
{"id": 6, "name": "Sechs", "field-with_sep": 6, "arr_data": "{1,2,3}"} `shouldRespondWith`
]|] [json|[
`shouldRespondWith` {"id": 4, "name": "Vier", "field-with_sep": 1, "settings":null,"arr_data":null},
[json|[ {"id": 5, "name": "Funf", "field-with_sep": 1, "settings":null,"arr_data":null},
{"id": 4, "name": "Vier", "field-with_sep": 1, "settings":null,"arr_data":null}, {"id": 6, "name": "Sechs", "field-with_sep": 6, "settings":null,"arr_data":[1,2,3]}
{"id": 5, "name": "Funf", "field-with_sep": 1, "settings":null,"arr_data":null}, ]|]
{"id": 6, "name": "Sechs", "field-with_sep": 6, "settings":null,"arr_data":[1,2,3]} { matchStatus = 201
]|] , matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
{ matchStatus = 201 }
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "inserts view default values(field-with_sep) when json keys are undefined" $ it "inserts view default values(field-with_sep) when json keys are undefined" $
request methodPost "/complex_items_view?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")] request methodPost "/complex_items_view?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
@@ -537,15 +534,14 @@ spec actualPgVersion = do
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"] , matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
} }
when (actualPgVersion >= pgVersion100) $ it "inserts a default on a generated by default as identity column" $
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` [json| [{"data":{"foo": "bar"},"slug":"foo"}] |] -- id 1 was inserted here, we don't get it for idempotence in the tests
[json| [{"data":{"foo": "bar"},"slug":"foo"}] |] -- id 1 was inserted here, we don't get it for idempotence in the tests { matchStatus = 201
{ matchStatus = 201 , matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"] }
}
when (actualPgVersion >= pgVersion120) $ when (actualPgVersion >= pgVersion120) $
it "fails with a good error message on generated always columns" $ it "fails with a good error message on generated always columns" $
@@ -798,115 +794,113 @@ spec actualPgVersion = do
} }
-- Data representations for payload parsing requires Postgres 10 or above. describe "Data representations" $ do
when (actualPgVersion >= pgVersion100) $ do context "on regular table" $ do
describe "Data representations" $ do it "parses values in POST body" $
context "on regular table" $ do -- we don't check that the parsing is correct here, just that it's happening. If it doesn't happen we'll get a
it "parses values in POST body" $ -- an "invalid input syntax for type integer:" error.
-- we don't check that the parsing is correct here, just that it's happening. If it doesn't happen we'll get a request methodPost "/datarep_todos" [("Prefer", "return=headers-only")]
-- an "invalid input syntax for type integer:" error. [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |]
request methodPost "/datarep_todos" [("Prefer", "return=headers-only")] `shouldRespondWith`
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |] ""
`shouldRespondWith` { matchStatus = 201
"" , matchHeaders = [ matchHeaderAbsent hContentType
{ matchStatus = 201 , "Location" <:> "/datarep_todos?id=eq.5"
, matchHeaders = [ matchHeaderAbsent hContentType , "Content-Range" <:> "*/*"
, "Location" <:> "/datarep_todos?id=eq.5" , "Preference-Applied" <:> "return=headers-only"]
, "Content-Range" <:> "*/*" }
, "Preference-Applied" <:> "return=headers-only"]
}
it "parses values in POST body and formats individually selected values in return=representation" $ it "parses values in POST body and formats individually selected values in return=representation" $
request methodPost "/datarep_todos?select=id,label_color" [("Prefer", "return=representation")] request methodPost "/datarep_todos?select=id,label_color" [("Prefer", "return=representation")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":5, "label_color": "#001100"}] |] [json| [{"id":5, "label_color": "#001100"}] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "*/*"] "Content-Range" <:> "*/*"]
} }
it "parses values in POST body and formats values in return=representation" $ it "parses values in POST body and formats values in return=representation" $
request methodPost "/datarep_todos" [("Prefer", "return=representation")] request methodPost "/datarep_todos" [("Prefer", "return=representation")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00", "icon_image": "3q2+7w", "created_at":-15, "budget": "-100000000000000.13"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00", "icon_image": "3q2+7w", "created_at":-15, "budget": "-100000000000000.13"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":5,"name": "party", "label_color": "#001100", "due_at":"2018-01-03T11:00:00Z", "icon_image": "3q2+7w==", "created_at":-15, "budget": "-100000000000000.13"}] |] [json| [{"id":5,"name": "party", "label_color": "#001100", "due_at":"2018-01-03T11:00:00Z", "icon_image": "3q2+7w==", "created_at":-15, "budget": "-100000000000000.13"}] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "*/*"] "Content-Range" <:> "*/*"]
} }
context "with ?columns parameter" $ do context "with ?columns parameter" $ do
it "ignores json keys not included in ?columns; parses only the ones specified" $ it "ignores json keys not included in ?columns; parses only the ones specified" $
request methodPost "/datarep_todos?columns=id,label_color&select=id,name,label_color,due_at" [("Prefer", "return=representation")] request methodPost "/datarep_todos?columns=id,label_color&select=id,name,label_color,due_at" [("Prefer", "return=representation")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "invalid but should be ignored"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "invalid but should be ignored"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":5, "name":null, "label_color": "#001100", "due_at": "2018-01-01T00:00:00Z"}] |] [json| [{"id":5, "name":null, "label_color": "#001100", "due_at": "2018-01-01T00:00:00Z"}] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "*/*"] "Content-Range" <:> "*/*"]
} }
it "fails without parsing anything if at least one specified column doesn't exist" $ it "fails without parsing anything if at least one specified column doesn't exist" $
request methodPost "/datarep_todos?columns=id,label_color,helicopters&select=id,name,label_color,due_at" [("Prefer", "return=representation")] request methodPost "/datarep_todos?columns=id,label_color,helicopters&select=id,name,label_color,due_at" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00+00", "smth": "here", "label_color": "invalid", "fake_id": 13} |] [json| {"due_at": "2019-01-03T11:00:00+00", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith` `shouldRespondWith`
[json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos' in the schema cache"} |] [json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos' in the schema cache"} |]
{ matchStatus = 400 { matchStatus = 400
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"] , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
} }
context "on updatable view" $ do context "on updatable view" $ do
it "parses values in POST body" $ it "parses values in POST body" $
-- we don't check that the parsing is correct here, just that it's happening. If it doesn't happen we'll get a -- we don't check that the parsing is correct here, just that it's happening. If it doesn't happen we'll get a
-- an "invalid input syntax for type integer:" error. -- an "invalid input syntax for type integer:" error.
request methodPost "/datarep_todos_computed" [("Prefer", "return=headers-only")] request methodPost "/datarep_todos_computed" [("Prefer", "return=headers-only")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |]
`shouldRespondWith` `shouldRespondWith`
"" ""
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType , matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/datarep_todos_computed?id=eq.5" , "Location" <:> "/datarep_todos_computed?id=eq.5"
, "Content-Range" <:> "*/*" , "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"] , "Preference-Applied" <:> "return=headers-only"]
} }
it "parses values in POST body and formats individually selected values in return=representation" $ it "parses values in POST body and formats individually selected values in return=representation" $
request methodPost "/datarep_todos_computed?select=id,label_color" [("Prefer", "return=representation")] request methodPost "/datarep_todos_computed?select=id,label_color" [("Prefer", "return=representation")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":5, "label_color": "#001100"}] |] [json| [{"id":5, "label_color": "#001100"}] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "*/*"] "Content-Range" <:> "*/*"]
} }
it "parses values in POST body and formats values in return=representation" $ it "parses values in POST body and formats values in return=representation" $
request methodPost "/datarep_todos_computed" [("Prefer", "return=representation")] request methodPost "/datarep_todos_computed" [("Prefer", "return=representation")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "2018-01-03T11:00:00+00"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":5,"name": "party", "label_color": "#001100", "due_at":"2018-01-03T11:00:00Z", "dark_color":"#000880"}] |] [json| [{"id":5,"name": "party", "label_color": "#001100", "due_at":"2018-01-03T11:00:00Z", "dark_color":"#000880"}] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "*/*"] "Content-Range" <:> "*/*"]
} }
context "on updatable views with ?columns parameter" $ do context "on updatable views with ?columns parameter" $ do
it "ignores json keys not included in ?columns; parses only the ones specified" $ it "ignores json keys not included in ?columns; parses only the ones specified" $
request methodPost "/datarep_todos_computed?columns=id,label_color&select=id,name,label_color,due_at" [("Prefer", "return=representation")] request methodPost "/datarep_todos_computed?columns=id,label_color&select=id,name,label_color,due_at" [("Prefer", "return=representation")]
[json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "invalid but should be ignored"} |] [json| {"id":5, "name": "party", "label_color": "#001100", "due_at": "invalid but should be ignored"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":5, "name":null, "label_color": "#001100", "due_at": "2018-01-01T00:00:00Z"}] |] [json| [{"id":5, "name":null, "label_color": "#001100", "due_at": "2018-01-01T00:00:00Z"}] |]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "*/*"] "Content-Range" <:> "*/*"]
} }
it "fails without parsing anything if at least one specified column doesn't exist" $ it "fails without parsing anything if at least one specified column doesn't exist" $
request methodPost "/datarep_todos_computed?columns=id,label_color,helicopters&select=id,name,label_color,due_at" [("Prefer", "return=representation")] request methodPost "/datarep_todos_computed?columns=id,label_color,helicopters&select=id,name,label_color,due_at" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00+00", "smth": "here", "label_color": "invalid", "fake_id": 13} |] [json| {"due_at": "2019-01-03T11:00:00+00", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith` `shouldRespondWith`
[json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos_computed' in the schema cache"} |] [json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos_computed' in the schema cache"} |]
{ matchStatus = 400 { matchStatus = 400
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"] , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
} }
+31 -66
View File
@@ -7,13 +7,11 @@ import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import PostgREST.Config.PgVersion (PgVersion, pgVersion100)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
spec :: PgVersion -> SpecWith ((), Application) spec :: SpecWith ((), Application)
spec actualPgVersion = describe "PostGIS features" $ spec = describe "PostGIS features" $
context "GeoJSON output" $ do context "GeoJSON output" $ do
it "works for a table that has a geometry column" $ it "works for a table that has a geometry column" $
request methodGet "/shops" request methodGet "/shops"
@@ -56,59 +54,32 @@ spec actualPgVersion = describe "PostGIS features" $
it "works with resource embedding" $ it "works with resource embedding" $
request methodGet "/shops?select=*,shop_bles(*)&id=eq.1" request methodGet "/shops?select=*,shop_bles(*)&id=eq.1"
[("Accept", "application/geo+json")] "" `shouldRespondWith` [("Accept", "application/geo+json")] "" `shouldRespondWith`
(if actualPgVersion >= pgVersion100 [json| {
then [json| { "type": "FeatureCollection",
"type": "FeatureCollection", "features": [
"features": [ {
{ "type": "Feature",
"type": "Feature", "geometry": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" },
"geometry": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" }, "properties": {
"properties": { "address": "1369 Cambridge St", "id": 1,
"address": "1369 Cambridge St", "id": 1, "shop_bles": [
"shop_bles": [ { "id": 1, "name": "Beacon-1", "shop_id": 1 ,
{ "id": 1, "name": "Beacon-1", "shop_id": 1 , "coords": { "coordinates": [ -71.10044, 42.373695 ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Point" },
"coords": { "coordinates": [ -71.10044, 42.373695 ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Point" }, "range_area": {
"range_area": { "coordinates": [ [ [ -71.10045254230499, 42.37387083326593 ], [ -71.10048070549963, 42.37377126199953 ], [ -71.10039688646793, 42.37375838212269 ], [ -71.10037006437777, 42.37385844878863 ], [ -71.10045254230499, 42.37387083326593 ] ] ],
"coordinates": [ [ [ -71.10045254230499, 42.37387083326593 ], [ -71.10048070549963, 42.37377126199953 ], [ -71.10039688646793, 42.37375838212269 ], [ -71.10037006437777, 42.37385844878863 ], [ -71.10045254230499, 42.37387083326593 ] ] ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Polygon" }
"crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Polygon" } },
}, { "coords": { "coordinates": [ -71.10044, 42.373695 ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Point" },
{ "coords": { "coordinates": [ -71.10044, 42.373695 ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Point" }, "id": 2, "name": "Beacon-2", "shop_id": 1,
"id": 2, "name": "Beacon-2", "shop_id": 1, "range_area": {
"range_area": { "coordinates": [ [ [ -71.10034391283989, 42.37385299961788 ], [ -71.10036939382553, 42.373756895982865 ], [ -71.1002916097641, 42.373745997623224 ], [ -71.1002641171217, 42.37384408279195 ], [ -71.10034391283989, 42.37385299961788 ] ] ],
"coordinates": [ [ [ -71.10034391283989, 42.37385299961788 ], [ -71.10036939382553, 42.373756895982865 ], [ -71.1002916097641, 42.373745997623224 ], [ -71.1002641171217, 42.37384408279195 ], [ -71.10034391283989, 42.37385299961788 ] ] ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Polygon" }
"crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Polygon" } }
} ]
]
}
} }
] }
}|] ]
else [json| { }|]
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" },
"properties": {
"address": "1369 Cambridge St", "id": 1,
"shop_bles": [
{ "id": 1, "name": "Beacon-1", "shop_id": 1 ,
"coords": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" },
"range_area": {
"coordinates": [ [ [ -71.10045254230499, 42.37387083326593 ], [ -71.10048070549963, 42.37377126199953 ], [ -71.10039688646793, 42.37375838212269 ], [ -71.10037006437777, 42.37385844878863 ], [ -71.10045254230499, 42.37387083326593 ] ] ],
"type": "Polygon" }
},
{ "coords": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" },
"id": 2, "name": "Beacon-2", "shop_id": 1,
"range_area": {
"coordinates": [ [ [ -71.10034391283989, 42.37385299961788 ], [ -71.10036939382553, 42.373756895982865 ], [ -71.1002916097641, 42.373745997623224 ], [ -71.1002641171217, 42.37384408279195 ], [ -71.10034391283989, 42.37385299961788 ] ] ],
"type": "Polygon" }
}
]
}
}
]
}|])
{ matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] }
it "works with RPC" $ it "works with RPC" $
@@ -219,14 +190,8 @@ spec actualPgVersion = describe "PostGIS features" $
it "gets the geojson geometry object with the regular application/json output" $ it "gets the geojson geometry object with the regular application/json output" $
request methodGet "/shops?id=eq.1" [] "" `shouldRespondWith` request methodGet "/shops?id=eq.1" [] "" `shouldRespondWith`
(if actualPgVersion >= pgVersion100 [json|[{
then [json|[{ "id":1,"address":"1369 Cambridge St",
"id":1,"address":"1369 Cambridge St", "shop_geom":{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-71.10044,42.373695]}
"shop_geom":{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-71.10044,42.373695]} }]|]
}]|]
else [json|[{
"address": "1369 Cambridge St",
"id": 1,
"shop_geom": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" }
}]|])
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
+71 -120
View File
@@ -11,9 +11,9 @@ import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc import Text.Heredoc
import PostgREST.Config.PgVersion (PgVersion, pgVersion100, import PostgREST.Config.PgVersion (PgVersion, pgVersion109,
pgVersion109, pgVersion110, pgVersion110, pgVersion112,
pgVersion112, pgVersion114) pgVersion114)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -530,66 +530,34 @@ spec actualPgVersion =
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
context "proc argument types" $ do context "proc argument types" $ do
-- different syntax for array needed for pg<10 it "accepts a variety of arguments (Postgres >= 10)" $
when (actualPgVersion < pgVersion100) $ post "/rpc/varied_arguments"
it "accepts a variety of arguments (Postgres < 10)" $ [json| {
post "/rpc/varied_arguments" "double": 3.1,
[json| { "varchar": "hello",
"double": 3.1, "boolean": true,
"varchar": "hello", "date": "20190101",
"boolean": true, "money": 0,
"date": "20190101", "enum": "foo",
"money": 0, "arr": ["a", "b", "c"],
"enum": "foo", "integer": 43,
"arr": "{a,b,c}", "json": {"some key": "some value"},
"integer": 43, "jsonb": {"another key": [1, 2, "3"]}
"json": {"some key": "some value"}, } |]
"jsonb": {"another key": [1, 2, "3"]} `shouldRespondWith`
} |] [json| {
`shouldRespondWith` "double": 3.1,
[json| { "varchar": "hello",
"double": 3.1, "boolean": true,
"varchar": "hello", "date": "2019-01-01",
"boolean": true, "money": "$0.00",
"date": "2019-01-01", "enum": "foo",
"money": "$0.00", "arr": ["a", "b", "c"],
"enum": "foo", "integer": 43,
"arr": ["a", "b", "c"], "json": {"some key": "some value"},
"integer": 43, "jsonb": {"another key": [1, 2, "3"]}
"json": {"some key": "some value"}, } |]
"jsonb": {"another key": [1, 2, "3"]} { matchHeaders = [matchContentTypeJson] }
} |]
{ matchHeaders = [matchContentTypeJson] }
when (actualPgVersion >= pgVersion100) $
it "accepts a variety of arguments (Postgres >= 10)" $
post "/rpc/varied_arguments"
[json| {
"double": 3.1,
"varchar": "hello",
"boolean": true,
"date": "20190101",
"money": 0,
"enum": "foo",
"arr": ["a", "b", "c"],
"integer": 43,
"json": {"some key": "some value"},
"jsonb": {"another key": [1, 2, "3"]}
} |]
`shouldRespondWith`
[json| {
"double": 3.1,
"varchar": "hello",
"boolean": true,
"date": "2019-01-01",
"money": "$0.00",
"enum": "foo",
"arr": ["a", "b", "c"],
"integer": 43,
"json": {"some key": "some value"},
"jsonb": {"another key": [1, 2, "3"]}
} |]
{ matchHeaders = [matchContentTypeJson] }
it "accepts a variety of arguments with GET" $ it "accepts a variety of arguments with GET" $
-- without JSON / JSONB here, because passing those via query string is useless - they just become a "json string" all the time -- without JSON / JSONB here, because passing those via query string is useless - they just become a "json string" all the time
@@ -635,14 +603,6 @@ spec actualPgVersion =
[json|"object"|] [json|"object"|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
when (actualPgVersion < pgVersion100) $
it "parses quoted JSON arguments as JSON (Postgres < 10)" $
post "/rpc/json_argument"
[json| { "arg": "{ \"key\": 3 }" } |]
`shouldRespondWith`
[json|"object"|]
{ matchHeaders = [matchContentTypeJson] }
when ((actualPgVersion >= pgVersion109 && actualPgVersion < pgVersion110) when ((actualPgVersion >= pgVersion109 && actualPgVersion < pgVersion110)
|| actualPgVersion >= pgVersion114) $ || actualPgVersion >= pgVersion114) $
it "parses quoted JSON arguments as JSON string (from Postgres 10.9, 11.4)" $ it "parses quoted JSON arguments as JSON string (from Postgres 10.9, 11.4)" $
@@ -765,68 +725,59 @@ spec actualPgVersion =
[json|[{"a": "A", "b": "B"}]|] [json|[{"a": "A", "b": "B"}]|]
context "procs with VARIADIC params" $ do context "procs with VARIADIC params" $ do
when (actualPgVersion < pgVersion100) $ it "works with POST (Postgres >= 10)" $
it "works with POST (Postgres < 10)" $ post "/rpc/variadic_param"
post "/rpc/variadic_param" [json| { "v": ["hi", "hello", "there"] } |]
[json| { "v": "{hi,hello,there}" } |] `shouldRespondWith`
[json|["hi", "hello", "there"]|]
context "works with GET and repeated params" $ do
it "n=0 (through DEFAULT)" $
get "/rpc/variadic_param"
`shouldRespondWith` `shouldRespondWith`
[json|["hi", "hello", "there"]|] [json|[]|]
when (actualPgVersion >= pgVersion100) $ do it "n=1" $
it "works with POST (Postgres >= 10)" $ get "/rpc/variadic_param?v=hi"
post "/rpc/variadic_param"
[json| { "v": ["hi", "hello", "there"] } |]
`shouldRespondWith` `shouldRespondWith`
[json|["hi", "hello", "there"]|] [json|["hi"]|]
context "works with GET and repeated params" $ do it "n>1" $
it "n=0 (through DEFAULT)" $ get "/rpc/variadic_param?v=hi&v=there"
get "/rpc/variadic_param" `shouldRespondWith`
`shouldRespondWith` [json|["hi", "there"]|]
[json|[]|]
it "n=1" $ context "works with POST and repeated params from html form" $ do
get "/rpc/variadic_param?v=hi" it "n=0 (through DEFAULT)" $
`shouldRespondWith` request methodPost "/rpc/variadic_param"
[json|["hi"]|] [("Content-Type", "application/x-www-form-urlencoded")]
""
`shouldRespondWith`
[json|[]|]
it "n>1" $ it "n=1" $
get "/rpc/variadic_param?v=hi&v=there" request methodPost "/rpc/variadic_param"
`shouldRespondWith` [("Content-Type", "application/x-www-form-urlencoded")]
[json|["hi", "there"]|] "v=hi"
`shouldRespondWith`
[json|["hi"]|]
context "works with POST and repeated params from html form" $ do it "n>1" $
it "n=0 (through DEFAULT)" $ request methodPost "/rpc/variadic_param"
request methodPost "/rpc/variadic_param" [("Content-Type", "application/x-www-form-urlencoded")]
[("Content-Type", "application/x-www-form-urlencoded")] "v=hi&v=there"
"" `shouldRespondWith`
`shouldRespondWith` [json|["hi", "there"]|]
[json|[]|]
it "n=1" $
request methodPost "/rpc/variadic_param"
[("Content-Type", "application/x-www-form-urlencoded")]
"v=hi"
`shouldRespondWith`
[json|["hi"]|]
it "n>1" $
request methodPost "/rpc/variadic_param"
[("Content-Type", "application/x-www-form-urlencoded")]
"v=hi&v=there"
`shouldRespondWith`
[json|["hi", "there"]|]
it "returns last value for repeated params without VARIADIC" $ it "returns last value for repeated params without VARIADIC" $
get "/rpc/sayhello?name=ignored&name=world" get "/rpc/sayhello?name=ignored&name=world"
`shouldRespondWith` `shouldRespondWith`
[json|"Hello, world"|] [json|"Hello, world"|]
when (actualPgVersion >= pgVersion100) $ it "returns last value for repeated non-variadic params in function with other VARIADIC arguments" $
it "returns last value for repeated non-variadic params in function with other VARIADIC arguments" $ get "/rpc/sayhello_variadic?name=ignored&name=world&v=unused"
get "/rpc/sayhello_variadic?name=ignored&name=world&v=unused" `shouldRespondWith`
`shouldRespondWith` [json|"Hello, world"|]
[json|"Hello, world"|]
it "can handle procs with args that have a DEFAULT value" $ do it "can handle procs with args that have a DEFAULT value" $ do
get "/rpc/many_inout_params?num=1&str=two" get "/rpc/many_inout_params?num=1&str=two"
+87 -193
View File
@@ -7,14 +7,11 @@ import Network.HTTP.Types
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import PostgREST.Config.PgVersion (PgVersion, pgVersion100)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
spec :: PgVersion -> SpecWith ((), Application) spec :: SpecWith ((), Application)
spec actualPgVersion = do spec = do
describe "Patching record" $ do describe "Patching record" $ do
context "to unknown uri" $ context "to unknown uri" $
it "indicates no table found by returning 404" $ it "indicates no table found by returning 404" $
@@ -623,196 +620,93 @@ spec actualPgVersion = do
} }
-- Data representations for payload parsing requires Postgres 10 or above. -- Data representations for payload parsing requires Postgres 10 or above.
when (actualPgVersion >= pgVersion100) $ do describe "Data representations" $ do
describe "Data representations" $ do context "for a single row" $ do
context "for a single row" $ do it "parses values in payload" $
it "parses values in payload" $ request methodPatch "/datarep_todos_computed?id=eq.2" [("Prefer", "return=headers-only")]
request methodPatch "/datarep_todos?id=eq.2" [("Prefer", "return=headers-only")] [json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |] `shouldRespondWith`
`shouldRespondWith` ""
"" { matchStatus = 204
{ matchStatus = 204 , matchHeaders = [ matchHeaderAbsent hContentType
, matchHeaders = [ matchHeaderAbsent hContentType , "Content-Range" <:> "0-0/*" ]
, "Content-Range" <:> "0-0/*"] }
}
it "parses values in payload and formats individually selected values in return=representation" $ it "parses values in payload and formats individually selected values in return=representation" $
request methodPatch "/datarep_todos?id=eq.2&select=id,label_color" [("Prefer", "return=representation")] request methodPatch "/datarep_todos_computed?id=eq.2&select=id,label_color" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |] [json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":2, "label_color": "#221100"}] |] [json| [{"id":2, "label_color": "#221100"}] |]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8" , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*" , "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"] , "Preference-Applied" <:> "return=representation"]
} }
it "parses values in payload and formats values in return=representation" $ it "parses values in payload and formats values in return=representation" $
request methodPatch "/datarep_todos?id=eq.2" [("Prefer", "return=representation")] request methodPatch "/datarep_todos_computed?id=eq.2" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:20Z", "icon_image": "3q2+7w"} |] [json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:20Z"} |]
`shouldRespondWith` `shouldRespondWith`
[json| [{"id":2,"name":"Essay","label_color":"#221100","due_at":"2019-01-03T11:00:20Z","icon_image":"3q2+7w==","created_at":1513213350,"budget":"100000000000000.13"}] |] [json| [{"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:20Z"}] |]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8" , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*" , "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"] , "Preference-Applied" <:> "return=representation"]
} }
context "for multiple rows" $ do
it "parses values in payload and formats individually selected values in return=representation" $
request methodPatch "/datarep_todos_computed?id=lt.4&select=id,name,label_color,dark_color" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith`
[json| [
{"id":1, "name": "Report", "label_color": "#221100", "dark_color":"#110880"},
{"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880"},
{"id":3, "name": "Algebra", "label_color": "#221100", "dark_color":"#110880"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats star mixed selected values in return=representation" $ it "parses values in payload and formats values in return=representation" $
request methodPatch "/datarep_todos?id=eq.2&select=due_at,*" [("Prefer", "return=representation")] request methodPatch "/datarep_todos_computed?id=lt.4" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z", "created_at": 0} |] [json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith` `shouldRespondWith`
-- end up with due_at twice here but that's unrelated to data reps [json| [
[json| [{"due_at":"2019-01-03T11:00:00Z","id":2,"name":"Essay","label_color":"#221100","due_at":"2019-01-03T11:00:00Z","icon_image":null,"created_at":0,"budget":"100000000000000.13"}] |] {"id":1, "name": "Report", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"},
{ matchStatus = 200 {"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"},
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8" {"id":3, "name": "Algebra", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"}
, "Content-Range" <:> "0-0/*" ] |]
, "Preference-Applied" <:> "return=representation"] { matchStatus = 200
} , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
context "for multiple rows" $ do , "Content-Range" <:> "0-2/*"
it "parses values in payload and formats individually selected values in return=representation" $ , "Preference-Applied" <:> "return=representation"]
request methodPatch "/datarep_todos?id=lt.4&select=id,name,label_color" [("Prefer", "return=representation")] }
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |] context "with ?columns parameter" $ do
`shouldRespondWith` it "ignores json keys not included in ?columns; parses only the ones specified" $
[json| [ request methodPatch "/datarep_todos_computed?id=eq.2&columns=due_at" [("Prefer", "return=representation")]
{"id":1, "name": "Report", "label_color": "#221100"}, [json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
{"id":2, "name": "Essay", "label_color": "#221100"}, `shouldRespondWith`
{"id":3, "name": "Algebra", "label_color": "#221100"} [json| [
] |] {"id":2, "name": "Essay", "label_color": "#000100", "dark_color": "#000080", "due_at":"2019-01-03T11:00:00Z"}
{ matchStatus = 200 ] |]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8" { matchStatus = 200
, "Content-Range" <:> "0-2/*" , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Preference-Applied" <:> "return=representation"] , "Content-Range" <:> "0-0/*"
} , "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $ it "fails if at least one specified column doesn't exist" $
request methodPatch "/datarep_todos?id=lt.4" [("Prefer", "return=representation")] request methodPatch "/datarep_todos_computed?id=eq.2&columns=label_color,helicopters" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z", "icon_image": "3q2+7w="} |] [json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith` `shouldRespondWith`
[json| [ [json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos_computed' in the schema cache"} |]
{"id":1,"name":"Report","label_color":"#221100","due_at":"2019-01-03T11:00:00Z","icon_image":"3q2+7w==","created_at":1513213350,"budget":"12.50"}, { matchStatus = 400
{"id":2,"name":"Essay","label_color":"#221100","due_at":"2019-01-03T11:00:00Z","icon_image":"3q2+7w==","created_at":1513213350,"budget":"100000000000000.13"}, , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
{"id":3,"name":"Algebra","label_color":"#221100","due_at":"2019-01-03T11:00:00Z","icon_image":"3q2+7w==","created_at":1513213350,"budget":"0.00"} }
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
context "with ?columns parameter" $ do
it "ignores json keys not included in ?columns; parses only the ones specified" $
request methodPatch "/datarep_todos?id=eq.2&columns=due_at" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith`
[json| [
{"id":2,"name":"Essay","label_color":"#000100","due_at":"2019-01-03T11:00:00Z","icon_image":null,"created_at":1513213350,"budget":"100000000000000.13"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "fails if at least one specified column doesn't exist" $ it "ignores json keys and gives 200 if no record updated" $
request methodPatch "/datarep_todos?id=eq.2&columns=label_color,helicopters" [("Prefer", "return=representation")] request methodPatch "/datarep_todos_computed?id=eq.2001&columns=label_color" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |] [json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith` `shouldRespondWith` 200
[json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos' in the schema cache"} |]
{ matchStatus = 400
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
it "ignores json keys and gives 200 if no record updated" $
request methodPatch "/datarep_todos?id=eq.2001&columns=label_color" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith` 200
context "on a view" $ do
context "for a single row" $ do
it "parses values in payload" $
request methodPatch "/datarep_todos_computed?id=eq.2" [("Prefer", "return=headers-only")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "0-0/*" ]
}
it "parses values in payload and formats individually selected values in return=representation" $
request methodPatch "/datarep_todos_computed?id=eq.2&select=id,label_color" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith`
[json| [{"id":2, "label_color": "#221100"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $
request methodPatch "/datarep_todos_computed?id=eq.2" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:20Z"} |]
`shouldRespondWith`
[json| [{"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:20Z"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
context "for multiple rows" $ do
it "parses values in payload and formats individually selected values in return=representation" $
request methodPatch "/datarep_todos_computed?id=lt.4&select=id,name,label_color,dark_color" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith`
[json| [
{"id":1, "name": "Report", "label_color": "#221100", "dark_color":"#110880"},
{"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880"},
{"id":3, "name": "Algebra", "label_color": "#221100", "dark_color":"#110880"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $
request methodPatch "/datarep_todos_computed?id=lt.4" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00Z"} |]
`shouldRespondWith`
[json| [
{"id":1, "name": "Report", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"},
{"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"},
{"id":3, "name": "Algebra", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
context "with ?columns parameter" $ do
it "ignores json keys not included in ?columns; parses only the ones specified" $
request methodPatch "/datarep_todos_computed?id=eq.2&columns=due_at" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith`
[json| [
{"id":2, "name": "Essay", "label_color": "#000100", "dark_color": "#000080", "due_at":"2019-01-03T11:00:00Z"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "fails if at least one specified column doesn't exist" $
request methodPatch "/datarep_todos_computed?id=eq.2&columns=label_color,helicopters" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith`
[json| {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'helicopters' column of 'datarep_todos_computed' in the schema cache"} |]
{ matchStatus = 400
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
it "ignores json keys and gives 200 if no record updated" $
request methodPatch "/datarep_todos_computed?id=eq.2001&columns=label_color" [("Prefer", "return=representation")]
[json| {"due_at": "2019-01-03T11:00:00Z", "smth": "here", "label_color": "invalid", "fake_id": 13} |]
`shouldRespondWith` 200
+2 -2
View File
@@ -162,7 +162,7 @@ main = do
, ("Feature.Query.RpcSpec" , Feature.Query.RpcSpec.spec actualPgVersion) , ("Feature.Query.RpcSpec" , Feature.Query.RpcSpec.spec actualPgVersion)
, ("Feature.Query.SingularSpec" , Feature.Query.SingularSpec.spec) , ("Feature.Query.SingularSpec" , Feature.Query.SingularSpec.spec)
, ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec) , ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec)
, ("Feature.Query.UpdateSpec" , Feature.Query.UpdateSpec.spec actualPgVersion) , ("Feature.Query.UpdateSpec" , Feature.Query.UpdateSpec.spec)
, ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec actualPgVersion) , ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec actualPgVersion)
] ]
@@ -228,7 +228,7 @@ main = do
-- this test runs with an extra search path -- this test runs with an extra search path
parallel $ before extraSearchPathApp $ do parallel $ before extraSearchPathApp $ do
describe "Feature.ExtraSearchPathSpec" Feature.ExtraSearchPathSpec.spec describe "Feature.ExtraSearchPathSpec" Feature.ExtraSearchPathSpec.spec
describe "Feature.Query.PostGISSpec" $ Feature.Query.PostGISSpec.spec actualPgVersion describe "Feature.Query.PostGISSpec" Feature.Query.PostGISSpec.spec
-- this test runs with a root spec function override -- this test runs with a root spec function override
parallel $ before rootSpecApp $ parallel $ before rootSpecApp $
+5 -7
View File
@@ -680,14 +680,12 @@ INSERT INTO private.films (id, title) VALUES (12,'douze commandements'), (2001,'
TRUNCATE TABLE private.personnages CASCADE; TRUNCATE TABLE private.personnages CASCADE;
INSERT INTO private.personnages (film_id, role_id, character) VALUES (12,1,'méchant'), (2001,2,'astronaute'); INSERT INTO private.personnages (film_id, role_id, character) VALUES (12,1,'méchant'), (2001,2,'astronaute');
DO $do$BEGIN INSERT INTO test.car_models(name, year) VALUES ('DeLorean',1981);
IF (SELECT current_setting('server_version_num')::INT >= 100000) THEN INSERT INTO test.car_models(name, year) VALUES ('F310-B',1997);
INSERT INTO test.car_models(name, year) VALUES ('DeLorean',1981); INSERT INTO test.car_models(name, year) VALUES ('Veneno',2013);
INSERT INTO test.car_models(name, year) VALUES ('F310-B',1997); INSERT INTO test.car_models(name, year) VALUES ('Murcielago',2001);
INSERT INTO test.car_models(name, year) VALUES ('Veneno',2013);
INSERT INTO test.car_models(name, year) VALUES ('Murcielago',2001);
END IF;
DO $do$BEGIN
IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN
INSERT INTO test.car_brands(name) VALUES ('DMC'); INSERT INTO test.car_brands(name) VALUES ('DMC');
INSERT INTO test.car_brands(name) VALUES ('Ferrari'); INSERT INTO test.car_brands(name) VALUES ('Ferrari');
+1 -7
View File
@@ -41,13 +41,7 @@ GRANT USAGE ON SEQUENCE
, leak_id_seq , leak_id_seq
TO postgrest_test_anonymous; TO postgrest_test_anonymous;
DO $do$ GRANT USAGE ON SEQUENCE channels_id_seq TO postgrest_test_anonymous;
BEGIN
IF current_setting('server_version_num')::INT >= 100000 THEN
GRANT USAGE ON SEQUENCE channels_id_seq TO postgrest_test_anonymous;
END IF;
END
$do$;
-- Privileges for non anonymous users -- Privileges for non anonymous users
GRANT USAGE ON SCHEMA test TO postgrest_test_author; GRANT USAGE ON SCHEMA test TO postgrest_test_author;
+25 -42
View File
@@ -2289,26 +2289,22 @@ create table private.rollen (
); );
-- Tables used for testing embedding between partitioned tables -- Tables used for testing embedding between partitioned tables
create table test.car_models(
name varchar(64) not null,
year int not null
) partition by list (year);
do $do$begin comment on table test.car_models is
-- partitioned tables using the PARTITION syntax are supported from pg v10 $$A partitioned table
if (select current_setting('server_version_num')::int >= 100000) then
create table test.car_models(
name varchar(64) not null,
year int not null
) partition by list (year);
comment on table test.car_models is
$$A partitioned table
A test for partitioned tables$$; A test for partitioned tables$$;
create table test.car_models_2021 partition of test.car_models create table test.car_models_2021 partition of test.car_models
for values in (2021); for values in (2021);
create table test.car_models_default partition of test.car_models create table test.car_models_default partition of test.car_models
for values in (1981,1997,2001,2013); for values in (1981,1997,2001,2013);
end if;
do $do$begin
-- primary keys for partitioned tables are supported from pg v11 -- primary keys for partitioned tables are supported from pg v11
if (select current_setting('server_version_num')::int >= 110000) then if (select current_setting('server_version_num')::int >= 110000) then
create table test.car_brands ( create table test.car_brands (
@@ -2492,22 +2488,15 @@ BEGIN
END$$; END$$;
-- This view is not used in any requests but just parsed by the pfkSourceColumns query. -- This view is not used in any requests but just parsed by the pfkSourceColumns query.
-- XMLTABLE is only supported from PG 10 on CREATE VIEW test.xml AS
DO $do$ SELECT *
BEGIN FROM (SELECT ''::xml AS data) _,
IF current_setting('server_version_num')::INT >= 100000 THEN XMLTABLE(
CREATE VIEW test.xml AS ''
SELECT * PASSING data
FROM (SELECT ''::xml AS data) _, COLUMNS id int PATH '@id',
XMLTABLE( premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'
'' );
PASSING data
COLUMNS id int PATH '@id',
premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified'
);
END IF;
END
$do$;
-- https://github.com/PostgREST/postgrest/issues/1543 -- https://github.com/PostgREST/postgrest/issues/1543
CREATE TYPE complex AS ( CREATE TYPE complex AS (
@@ -3277,17 +3266,11 @@ create table test.tbl_w_json(
data json data json
); );
DO $do$ CREATE TABLE test.channels (
BEGIN id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
IF current_setting('server_version_num')::INT >= 100000 THEN data jsonb DEFAULT '{"foo": "bar"}',
CREATE TABLE test.channels ( slug text
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, );
data jsonb DEFAULT '{"foo": "bar"}',
slug text
);
END IF;
END
$do$;
CREATE FUNCTION test.is_superuser() RETURNS boolean CREATE FUNCTION test.is_superuser() RETURNS boolean
LANGUAGE sql LANGUAGE sql