feat: Drop support for pg 9.6
This commit is contained in:
committed by
Wolfgang Walther
parent
ec110720dc
commit
daa77d17aa
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -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);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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" $
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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] }
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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 $
|
||||||
|
|||||||
Vendored
+5
-7
@@ -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');
|
||||||
|
|||||||
Vendored
+1
-7
@@ -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;
|
||||||
|
|||||||
Vendored
+25
-42
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user