feat: Drop support for pg 11
PostgreSQL 11 is EOL since November 2023.
This commit is contained in:
committed by
Wolfgang Walther
parent
126178642b
commit
bb96c2dc74
@@ -67,7 +67,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
pgVersion: [11, 12, 13, 14, 15, 16]
|
pgVersion: [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:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
|
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
|
||||||
- #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther
|
- #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther
|
||||||
|
- #2052, Dropped support for PostgreSQL 11 - @wolfgangwalther
|
||||||
|
|
||||||
## [12.2.0] - 2024-06-11
|
## [12.2.0] - 2024-06-11
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ let
|
|||||||
allOverlays.checked-shell-script
|
allOverlays.checked-shell-script
|
||||||
allOverlays.gitignore
|
allOverlays.gitignore
|
||||||
allOverlays.postgresql-libpq
|
allOverlays.postgresql-libpq
|
||||||
allOverlays.postgresql-legacy
|
|
||||||
allOverlays.postgresql-future
|
allOverlays.postgresql-future
|
||||||
allOverlays.postgis
|
|
||||||
(allOverlays.haskell-packages { inherit compiler; })
|
(allOverlays.haskell-packages { inherit compiler; })
|
||||||
allOverlays.slocat
|
allOverlays.slocat
|
||||||
];
|
];
|
||||||
@@ -54,7 +52,6 @@ let
|
|||||||
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||||
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||||
{ 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 ]); }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Dynamic derivation for PostgREST
|
# Dynamic derivation for PostgREST
|
||||||
|
|||||||
@@ -16,16 +16,11 @@ Supported PostgreSQL versions
|
|||||||
=============================
|
=============================
|
||||||
|
|
||||||
=============== =================================
|
=============== =================================
|
||||||
**Supported** PostgreSQL >= 11
|
**Supported** PostgreSQL >= 12
|
||||||
=============== =================================
|
=============== =================================
|
||||||
|
|
||||||
PostgREST works with all PostgreSQL versions starting from 11.
|
PostgREST works with all PostgreSQL versions still `officially supported <https://www.postgresql.org/support/versioning/>`_.
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Support for PostgreSQL version 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/.
|
|
||||||
|
|
||||||
Running PostgREST
|
Running PostgREST
|
||||||
=================
|
=================
|
||||||
|
|||||||
+12
-12
@@ -75,12 +75,12 @@ The PostgREST utilities available in `nix-shell` all have names that begin with
|
|||||||
postgrest-build postgrest-test-spec
|
postgrest-build postgrest-test-spec
|
||||||
postgrest-check postgrest-watch
|
postgrest-check postgrest-watch
|
||||||
postgrest-clean postgrest-with-all
|
postgrest-clean postgrest-with-all
|
||||||
postgrest-coverage postgrest-with-postgresql-11
|
postgrest-coverage postgrest-with-postgresql-12
|
||||||
postgrest-lint postgrest-with-postgresql-12
|
postgrest-lint postgrest-with-postgresql-13
|
||||||
postgrest-run postgrest-with-postgresql-13
|
postgrest-run postgrest-with-postgresql-14
|
||||||
postgrest-style postgrest-with-postgresql-14
|
postgrest-style postgrest-with-postgresql-15
|
||||||
postgrest-style-check postgrest-with-postgresql-15
|
postgrest-style-check postgrest-with-postgresql-16
|
||||||
postgrest-test-io postgrest-with-postgresql-16
|
postgrest-test-io
|
||||||
...
|
...
|
||||||
|
|
||||||
[nix-shell]$
|
[nix-shell]$
|
||||||
@@ -99,12 +99,12 @@ $ nix-shell --arg memory true
|
|||||||
postgrest-build postgrest-test-spec
|
postgrest-build postgrest-test-spec
|
||||||
postgrest-check postgrest-watch
|
postgrest-check postgrest-watch
|
||||||
postgrest-clean postgrest-with-all
|
postgrest-clean postgrest-with-all
|
||||||
postgrest-coverage postgrest-with-postgresql-11
|
postgrest-coverage postgrest-with-postgresql-12
|
||||||
postgrest-lint postgrest-with-postgresql-12
|
postgrest-lint postgrest-with-postgresql-13
|
||||||
postgrest-run postgrest-with-postgresql-13
|
postgrest-run postgrest-with-postgresql-14
|
||||||
postgrest-style postgrest-with-postgresql-14
|
postgrest-style postgrest-with-postgresql-15
|
||||||
postgrest-style-check postgrest-with-postgresql-15
|
postgrest-style-check postgrest-with-postgresql-16
|
||||||
postgrest-test-io postgrest-with-postgresql-16
|
postgrest-test-io
|
||||||
postgrest-test-memory
|
postgrest-test-memory
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
checked-shell-script = import ./checked-shell-script;
|
checked-shell-script = import ./checked-shell-script;
|
||||||
gitignore = import ./gitignore.nix;
|
gitignore = import ./gitignore.nix;
|
||||||
haskell-packages = import ./haskell-packages.nix;
|
haskell-packages = import ./haskell-packages.nix;
|
||||||
postgis = import ./postgis.nix;
|
|
||||||
postgresql-libpq = import ./postgresql-libpq.nix;
|
postgresql-libpq = import ./postgresql-libpq.nix;
|
||||||
postgresql-legacy = import ./postgresql-legacy.nix;
|
|
||||||
postgresql-future = import ./postgresql-future.nix;
|
postgresql-future = import ./postgresql-future.nix;
|
||||||
slocat = import ./slocat.nix;
|
slocat = import ./slocat.nix;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
final: prev:
|
|
||||||
let
|
|
||||||
postgis_3_2_3 = rec {
|
|
||||||
version = "3.2.3";
|
|
||||||
src = final.fetchurl {
|
|
||||||
url = "https://download.osgeo.org/postgis/source/postgis-${version}.tar.gz";
|
|
||||||
sha256 = "sha256-G02LXHVuWrpZ77wYM7Iu/k1lYneO7KVvpJf+susTZow=";
|
|
||||||
};
|
|
||||||
meta.broken = false;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
postgresql_11 = prev.postgresql_11.override { this = final.postgresql_11; } // {
|
|
||||||
pkgs = prev.postgresql_11.pkgs // {
|
|
||||||
postgis = prev.postgresql_11.pkgs.postgis.overrideAttrs (_: postgis_3_2_3);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
_: _:
|
|
||||||
# Overlay that adds legacy versions of PostgreSQL that are supported by
|
|
||||||
# PostgREST.
|
|
||||||
{
|
|
||||||
# PostgreSQL 11 was removed from Nixpkgs with
|
|
||||||
# https://github.com/NixOS/nixpkgs/commit/1220a4d4dd1a4590780a5e1c18d1333a121be366
|
|
||||||
# We pin its parent commit to get the last version that was available.
|
|
||||||
postgresql_11 =
|
|
||||||
let
|
|
||||||
rev = "f5458516e42cc5cb4123cc2d93f45c240548aa18";
|
|
||||||
tarballHash = "1h03621sxfhw4z6ya74k6c2lyx3z7pvf2jcg4vs7i01yz2m6w3cv";
|
|
||||||
pinnedPkgs =
|
|
||||||
builtins.fetchTarball {
|
|
||||||
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
|
|
||||||
sha256 = tarballHash;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
(import pinnedPkgs { }).pkgs.postgresql_11;
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,6 @@
|
|||||||
module PostgREST.Config.PgVersion
|
module PostgREST.Config.PgVersion
|
||||||
( PgVersion(..)
|
( PgVersion(..)
|
||||||
, minimumPgVersion
|
, minimumPgVersion
|
||||||
, pgVersion112
|
|
||||||
, pgVersion114
|
|
||||||
, pgVersion120
|
|
||||||
, pgVersion121
|
, pgVersion121
|
||||||
, pgVersion130
|
, pgVersion130
|
||||||
, pgVersion140
|
, pgVersion140
|
||||||
@@ -29,16 +26,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 = pgVersion110
|
minimumPgVersion = pgVersion120
|
||||||
|
|
||||||
pgVersion110 :: PgVersion
|
|
||||||
pgVersion110 = PgVersion 110000 "11.0" "11.0"
|
|
||||||
|
|
||||||
pgVersion112 :: PgVersion
|
|
||||||
pgVersion112 = PgVersion 110002 "11.2" "11.2"
|
|
||||||
|
|
||||||
pgVersion114 :: PgVersion
|
|
||||||
pgVersion114 = PgVersion 110004 "11.4" "11.4"
|
|
||||||
|
|
||||||
pgVersion120 :: PgVersion
|
pgVersion120 :: PgVersion
|
||||||
pgVersion120 = PgVersion 120000 "12.0" "12.0"
|
pgVersion120 = PgVersion 120000 "12.0" "12.0"
|
||||||
|
|||||||
@@ -45,9 +45,7 @@ import Text.InterpolatedString.Perl6 (q)
|
|||||||
|
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.Config.Database (TimezoneNames,
|
import PostgREST.Config.Database (TimezoneNames,
|
||||||
pgVersionStatement,
|
|
||||||
toIsolationLevel)
|
toIsolationLevel)
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion120)
|
|
||||||
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
|
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
|
||||||
QualifiedIdentifier (..),
|
QualifiedIdentifier (..),
|
||||||
RelIdentifier (..),
|
RelIdentifier (..),
|
||||||
@@ -144,8 +142,7 @@ type SqlQuery = ByteString
|
|||||||
querySchemaCache :: AppConfig -> SQL.Transaction SchemaCache
|
querySchemaCache :: AppConfig -> SQL.Transaction SchemaCache
|
||||||
querySchemaCache AppConfig{..} = do
|
querySchemaCache AppConfig{..} = do
|
||||||
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
|
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
|
||||||
pgVer <- SQL.statement mempty $ pgVersionStatement prepared
|
tabs <- SQL.statement schemas $ allTables prepared
|
||||||
tabs <- SQL.statement schemas $ allTables pgVer prepared
|
|
||||||
keyDeps <- SQL.statement (schemas, configDbExtraSearchPath) $ allViewsKeyDependencies prepared
|
keyDeps <- SQL.statement (schemas, configDbExtraSearchPath) $ allViewsKeyDependencies prepared
|
||||||
m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared
|
m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared
|
||||||
funcs <- SQL.statement (schemas, configDbHoistedTxSettings) $ allFunctions prepared
|
funcs <- SQL.statement (schemas, configDbHoistedTxSettings) $ allFunctions prepared
|
||||||
@@ -602,15 +599,13 @@ addViewPrimaryKeys tabs keyDeps =
|
|||||||
-- * We need to choose a single reference for each column, otherwise we'd output too many columns in location headers etc.
|
-- * We need to choose a single reference for each column, otherwise we'd output too many columns in location headers etc.
|
||||||
takeFirstPK = mapMaybe (head . snd)
|
takeFirstPK = mapMaybe (head . snd)
|
||||||
|
|
||||||
allTables :: PgVersion -> Bool -> SQL.Statement [Schema] TablesMap
|
allTables :: Bool -> SQL.Statement [Schema] TablesMap
|
||||||
allTables pgVer =
|
allTables =
|
||||||
SQL.Statement sql (arrayParam HE.text) decodeTables
|
SQL.Statement tablesSqlQuery (arrayParam HE.text) decodeTables
|
||||||
where
|
|
||||||
sql = tablesSqlQuery pgVer
|
|
||||||
|
|
||||||
-- | Gets tables with their PK cols
|
-- | Gets tables with their PK cols
|
||||||
tablesSqlQuery :: PgVersion -> SqlQuery
|
tablesSqlQuery :: SqlQuery
|
||||||
tablesSqlQuery pgVer =
|
tablesSqlQuery =
|
||||||
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
|
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
|
||||||
-- we cannot use those directly as they include the following privilege filter:
|
-- we cannot use those directly as they include the following privilege filter:
|
||||||
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
|
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
|
||||||
@@ -624,7 +619,13 @@ tablesSqlQuery pgVer =
|
|||||||
c.relname::name AS table_name,
|
c.relname::name AS table_name,
|
||||||
a.attname::name AS column_name,
|
a.attname::name AS column_name,
|
||||||
d.description AS description,
|
d.description AS description,
|
||||||
|] <> columnDefault <> [q| AS column_default,
|
-- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
|
||||||
|
CASE
|
||||||
|
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.attgenerated = 's' THEN null
|
||||||
|
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
||||||
|
END AS column_default,
|
||||||
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
|
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
|
||||||
CASE
|
CASE
|
||||||
WHEN t.typtype = 'd' THEN
|
WHEN t.typtype = 'd' THEN
|
||||||
@@ -810,21 +811,6 @@ tablesSqlQuery pgVer =
|
|||||||
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
||||||
AND not c.relispartition
|
AND not c.relispartition
|
||||||
ORDER BY table_schema, table_name|]
|
ORDER BY table_schema, table_name|]
|
||||||
where
|
|
||||||
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|
|
|
||||||
CASE
|
|
||||||
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.attgenerated = 's' THEN null
|
|
||||||
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
|
||||||
END|]
|
|
||||||
| otherwise = [q|
|
|
||||||
CASE
|
|
||||||
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))
|
|
||||||
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 :: Bool -> SQL.Statement () [Relationship]
|
allM2OandO2ORels :: Bool -> SQL.Statement () [Relationship]
|
||||||
|
|||||||
@@ -7,30 +7,20 @@ 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, pgVersion112)
|
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
spec :: PgVersion -> SpecWith ((), Application)
|
spec :: SpecWith ((), Application)
|
||||||
spec actualPgVersion = describe "authorization" $ do
|
spec = describe "authorization" $ do
|
||||||
let single = ("Accept","application/vnd.pgrst.object+json")
|
let single = ("Accept","application/vnd.pgrst.object+json")
|
||||||
|
|
||||||
it "denies access to tables that anonymous does not own" $
|
it "denies access to tables that anonymous does not own" $
|
||||||
get "/authors_only" `shouldRespondWith` (
|
get "/authors_only" `shouldRespondWith`
|
||||||
if actualPgVersion >= pgVersion112 then
|
[json| {
|
||||||
[json| {
|
"hint":null,
|
||||||
"hint":null,
|
"details":null,
|
||||||
"details":null,
|
"code":"42501",
|
||||||
"code":"42501",
|
"message":"permission denied for table authors_only"} |]
|
||||||
"message":"permission denied for table authors_only"} |]
|
|
||||||
else
|
|
||||||
[json| {
|
|
||||||
"hint":null,
|
|
||||||
"details":null,
|
|
||||||
"code":"42501",
|
|
||||||
"message":"permission denied for relation authors_only"} |]
|
|
||||||
)
|
|
||||||
{ matchStatus = 401
|
{ matchStatus = 401
|
||||||
, matchHeaders = ["WWW-Authenticate" <:> "Bearer"]
|
, matchHeaders = ["WWW-Authenticate" <:> "Bearer"]
|
||||||
}
|
}
|
||||||
@@ -38,20 +28,12 @@ spec actualPgVersion = describe "authorization" $ do
|
|||||||
it "denies access to tables that postgrest_test_author does not own" $
|
it "denies access to tables that postgrest_test_author does not own" $
|
||||||
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA" in
|
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA" in
|
||||||
request methodGet "/private_table" [auth] ""
|
request methodGet "/private_table" [auth] ""
|
||||||
`shouldRespondWith` (
|
`shouldRespondWith`
|
||||||
if actualPgVersion >= pgVersion112 then
|
[json| {
|
||||||
[json| {
|
"hint":null,
|
||||||
"hint":null,
|
"details":null,
|
||||||
"details":null,
|
"code":"42501",
|
||||||
"code":"42501",
|
"message":"permission denied for table private_table"} |]
|
||||||
"message":"permission denied for table private_table"} |]
|
|
||||||
else
|
|
||||||
[json| {
|
|
||||||
"hint":null,
|
|
||||||
"details":null,
|
|
||||||
"code":"42501",
|
|
||||||
"message":"permission denied for relation private_table"} |]
|
|
||||||
)
|
|
||||||
{ matchStatus = 403
|
{ matchStatus = 403
|
||||||
, matchHeaders = []
|
, matchHeaders = []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, pgVersion112)
|
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
spec :: PgVersion -> SpecWith ((), Application)
|
spec :: SpecWith ((), Application)
|
||||||
spec actualPgVersion =
|
spec =
|
||||||
describe "and/or params used for complex boolean logic" $ do
|
describe "and/or params used for complex boolean logic" $ do
|
||||||
context "used with GET" $ do
|
context "used with GET" $ do
|
||||||
context "or param" $ do
|
context "or param" $ do
|
||||||
@@ -96,17 +94,16 @@ spec actualPgVersion =
|
|||||||
get "/entities?and=(id.gte.2,arr.isdistinct.{1,2})&select=id" `shouldRespondWith`
|
get "/entities?and=(id.gte.2,arr.isdistinct.{1,2})&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 3 }, { "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
[json|[{ "id": 3 }, { "id": 4 }]|] { matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion112) $
|
it "can handle wfts (websearch_to_tsquery)" $
|
||||||
it "can handle wfts (websearch_to_tsquery)" $
|
get "/tsearch?or=(text_search_vector.plfts(german).Art,text_search_vector.plfts(french).amusant,text_search_vector.not.wfts(english).impossible)"
|
||||||
get "/tsearch?or=(text_search_vector.plfts(german).Art,text_search_vector.plfts(french).amusant,text_search_vector.not.wfts(english).impossible)"
|
`shouldRespondWith`
|
||||||
`shouldRespondWith`
|
[json|[
|
||||||
[json|[
|
{"text_search_vector": "'also':2 'fun':3 'possibl':8" },
|
||||||
{"text_search_vector": "'also':2 'fun':3 'possibl':8" },
|
{"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" },
|
||||||
{"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" },
|
{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" },
|
||||||
{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" },
|
{"text_search_vector": "'art':4 'spass':5 'unmog':7" }
|
||||||
{"text_search_vector": "'art':4 'spass':5 'unmog':7" }
|
]|]
|
||||||
]|]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "can handle cs and cd" $
|
it "can handle cs and cd" $
|
||||||
get "/entities?or=(arr.cs.{1,2,3},arr.cd.{1})&select=id" `shouldRespondWith`
|
get "/entities?or=(arr.cs.{1,2,3},arr.cd.{1})&select=id" `shouldRespondWith`
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ 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, pgVersion112,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion130,
|
||||||
pgVersion120, pgVersion130,
|
|
||||||
pgVersion140)
|
pgVersion140)
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
@@ -542,28 +541,27 @@ spec actualPgVersion = do
|
|||||||
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
|
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
|
||||||
}
|
}
|
||||||
|
|
||||||
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" $
|
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
||||||
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
[json| [
|
||||||
[json| [
|
{"a": "val"},
|
||||||
{"a": "val"},
|
{"a": "val", "b": "val"}
|
||||||
{"a": "val", "b": "val"}
|
]|]
|
||||||
]|]
|
`shouldRespondWith`
|
||||||
`shouldRespondWith`
|
(if actualPgVersion < pgVersion140
|
||||||
(if actualPgVersion < pgVersion140
|
then [json| {
|
||||||
then [json| {
|
"code": "42601",
|
||||||
"code": "42601",
|
"details": "Column \"b\" is a generated column.",
|
||||||
"details": "Column \"b\" is a generated column.",
|
"hint": null,
|
||||||
"hint": null,
|
"message": "cannot insert into column \"b\""
|
||||||
"message": "cannot insert into column \"b\""
|
}|]
|
||||||
}|]
|
else [json| {
|
||||||
else [json| {
|
"code": "428C9",
|
||||||
"code": "428C9",
|
"details": "Column \"b\" is a generated column.",
|
||||||
"details": "Column \"b\" is a generated column.",
|
"hint": null,
|
||||||
"hint": null,
|
"message": "cannot insert a non-DEFAULT value into column \"b\""
|
||||||
"message": "cannot insert a non-DEFAULT value into column \"b\""
|
}|])
|
||||||
}|])
|
{ matchStatus = 400 }
|
||||||
{ matchStatus = 400 }
|
|
||||||
|
|
||||||
it "inserts a default on a DOMAIN with default" $
|
it "inserts a default on a DOMAIN with default" $
|
||||||
request methodPost "/evil_friends?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
request methodPost "/evil_friends?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
|
||||||
@@ -710,24 +708,16 @@ spec actualPgVersion = do
|
|||||||
|
|
||||||
it "fails inserting if more columns are selected" $
|
it "fails inserting if more columns are selected" $
|
||||||
request methodPost "/limited_article_stars?select=article_id,user_id,created_at" [("Prefer", "return=representation")]
|
request methodPost "/limited_article_stars?select=article_id,user_id,created_at" [("Prefer", "return=representation")]
|
||||||
[json| {"article_id": 2, "user_id": 2} |] `shouldRespondWith` (
|
[json| {"article_id": 2, "user_id": 2} |] `shouldRespondWith`
|
||||||
if actualPgVersion >= pgVersion112 then
|
|
||||||
[json|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
|
[json|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
|
||||||
else
|
|
||||||
[json|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
|
|
||||||
)
|
|
||||||
{ matchStatus = 401
|
{ matchStatus = 401
|
||||||
, matchHeaders = []
|
, matchHeaders = []
|
||||||
}
|
}
|
||||||
|
|
||||||
it "fails inserting if select is not specified" $
|
it "fails inserting if select is not specified" $
|
||||||
request methodPost "/limited_article_stars" [("Prefer", "return=representation")]
|
request methodPost "/limited_article_stars" [("Prefer", "return=representation")]
|
||||||
[json| {"article_id": 3, "user_id": 1} |] `shouldRespondWith` (
|
[json| {"article_id": 3, "user_id": 1} |] `shouldRespondWith`
|
||||||
if actualPgVersion >= pgVersion112 then
|
|
||||||
[json|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
|
[json|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
|
||||||
else
|
|
||||||
[json|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
|
|
||||||
)
|
|
||||||
{ matchStatus = 401
|
{ matchStatus = 401
|
||||||
, matchHeaders = []
|
, matchHeaders = []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ 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, pgVersion112,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion121)
|
||||||
pgVersion121)
|
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
@@ -75,28 +74,16 @@ spec actualPgVersion = describe "json and jsonb operators" $ do
|
|||||||
-- this works fine for /rpc/unexistent requests, but for this case a 500 seems more appropriate
|
-- this works fine for /rpc/unexistent requests, but for this case a 500 seems more appropriate
|
||||||
it "fails when a double arrow ->> is followed with a single arrow ->" $ do
|
it "fails when a double arrow ->> is followed with a single arrow ->" $ do
|
||||||
get "/json_arr?select=data->>c->1"
|
get "/json_arr?select=data->>c->1"
|
||||||
`shouldRespondWith` (
|
`shouldRespondWith`
|
||||||
if actualPgVersion >= pgVersion112 then
|
|
||||||
[json|
|
[json|
|
||||||
{"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.",
|
{"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.",
|
||||||
"details":null,"code":"42883","message":"operator does not exist: text -> integer"} |]
|
"details":null,"code":"42883","message":"operator does not exist: text -> integer"} |]
|
||||||
else
|
|
||||||
[json|
|
|
||||||
{"hint":"No operator matches the given name and argument type(s). You might need to add explicit type casts.",
|
|
||||||
"details":null,"code":"42883","message":"operator does not exist: text -> integer"} |]
|
|
||||||
)
|
|
||||||
{ matchStatus = 404 , matchHeaders = [] }
|
{ matchStatus = 404 , matchHeaders = [] }
|
||||||
get "/json_arr?select=data->>c->b"
|
get "/json_arr?select=data->>c->b"
|
||||||
`shouldRespondWith` (
|
`shouldRespondWith`
|
||||||
if actualPgVersion >= pgVersion112 then
|
|
||||||
[json|
|
[json|
|
||||||
{"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.",
|
{"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.",
|
||||||
"details":null,"code":"42883","message":"operator does not exist: text -> unknown"} |]
|
"details":null,"code":"42883","message":"operator does not exist: text -> unknown"} |]
|
||||||
else
|
|
||||||
[json|
|
|
||||||
{"hint":"No operator matches the given name and argument type(s). You might need to add explicit type casts.",
|
|
||||||
"details":null,"code":"42883","message":"operator does not exist: text -> unknown"} |]
|
|
||||||
)
|
|
||||||
{ matchStatus = 404 , matchHeaders = [] }
|
{ matchStatus = 404 , matchHeaders = [] }
|
||||||
|
|
||||||
context "with array index" $ do
|
context "with array index" $ do
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import Test.Hspec hiding (pendingWith)
|
|||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
|
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion130)
|
||||||
pgVersion130)
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
@@ -34,10 +33,7 @@ spec actualPgVersion = do
|
|||||||
liftIO $ do
|
liftIO $ do
|
||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
totalCost `shouldBe`
|
totalCost `shouldBe` 15.63
|
||||||
if actualPgVersion > pgVersion120
|
|
||||||
then 15.63
|
|
||||||
else 15.69
|
|
||||||
|
|
||||||
it "outputs the total cost for a single filter on a view" $ do
|
it "outputs the total cost for a single filter on a view" $ do
|
||||||
r <- request methodGet "/projects_view?id=gt.2"
|
r <- request methodGet "/projects_view?id=gt.2"
|
||||||
@@ -50,10 +46,7 @@ spec actualPgVersion = do
|
|||||||
liftIO $ do
|
liftIO $ do
|
||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
|
||||||
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
|
||||||
totalCost `shouldBe`
|
totalCost `shouldBe` 24.28
|
||||||
if actualPgVersion > pgVersion120
|
|
||||||
then 24.28
|
|
||||||
else 32.27
|
|
||||||
|
|
||||||
it "outputs blocks info when using the buffers option" $
|
it "outputs blocks info when using the buffers option" $
|
||||||
if actualPgVersion >= pgVersion130
|
if actualPgVersion >= pgVersion130
|
||||||
@@ -77,21 +70,20 @@ spec actualPgVersion = do
|
|||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|buffers; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|buffers; charset=utf-8")
|
||||||
blocks `shouldBe` Just [aesonQQ| 1.0 |]
|
blocks `shouldBe` Just [aesonQQ| 1.0 |]
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion120) $
|
it "outputs the search path when using the settings option" $ do
|
||||||
it "outputs the search path when using the settings option" $ do
|
r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=settings") ""
|
||||||
r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=settings") ""
|
|
||||||
|
|
||||||
let searchPath = simpleBody r ^? nth 0 . key "Settings"
|
let searchPath = simpleBody r ^? nth 0 . key "Settings"
|
||||||
resHeaders = simpleHeaders r
|
resHeaders = simpleHeaders r
|
||||||
|
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=settings; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=settings; charset=utf-8")
|
||||||
searchPath `shouldBe`
|
searchPath `shouldBe`
|
||||||
Just [aesonQQ|
|
Just [aesonQQ|
|
||||||
{
|
{
|
||||||
"search_path": "\"test\""
|
"search_path": "\"test\""
|
||||||
}
|
}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion130) $
|
when (actualPgVersion >= pgVersion130) $
|
||||||
it "outputs WAL info when using the wal option" $ do
|
it "outputs WAL info when using the wal option" $ do
|
||||||
@@ -123,9 +115,7 @@ spec actualPgVersion = do
|
|||||||
liftIO $ do
|
liftIO $ do
|
||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=verbose; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=verbose; charset=utf-8")
|
||||||
aggCol `shouldBe`
|
aggCol `shouldBe`
|
||||||
if actualPgVersion >= pgVersion120
|
Just [aesonQQ| "COALESCE(json_agg(ROW(projects.id, projects.name, projects.client_id)), '[]'::json)" |]
|
||||||
then Just [aesonQQ| "COALESCE(json_agg(ROW(projects.id, projects.name, projects.client_id)), '[]'::json)" |]
|
|
||||||
else Just [aesonQQ| "COALESCE(json_agg(ROW(pgrst_source.id, pgrst_source.name, pgrst_source.client_id)), '[]'::json)" |]
|
|
||||||
|
|
||||||
it "outputs the plan for application/vnd.pgrst.object " $ do
|
it "outputs the plan for application/vnd.pgrst.object " $ do
|
||||||
r <- request methodGet "/projects_view" (acceptHdrs "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object\"; options=verbose") ""
|
r <- request methodGet "/projects_view" (acceptHdrs "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object\"; options=verbose") ""
|
||||||
@@ -136,9 +126,7 @@ spec actualPgVersion = do
|
|||||||
liftIO $ do
|
liftIO $ do
|
||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object+json\"; options=verbose; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object+json\"; options=verbose; charset=utf-8")
|
||||||
aggCol `shouldBe`
|
aggCol `shouldBe`
|
||||||
if actualPgVersion >= pgVersion120
|
Just [aesonQQ| "COALESCE((json_agg(ROW(projects.id, projects.name, projects.client_id)) -> 0), 'null'::json)" |]
|
||||||
then Just [aesonQQ| "COALESCE((json_agg(ROW(projects.id, projects.name, projects.client_id)) -> 0), 'null'::json)" |]
|
|
||||||
else Just [aesonQQ| "COALESCE((json_agg(ROW(pgrst_source.id, pgrst_source.name, pgrst_source.client_id)) -> 0), 'null'::json)" |]
|
|
||||||
|
|
||||||
describe "writes plans" $ do
|
describe "writes plans" $ do
|
||||||
it "outputs the total cost for an insert" $ do
|
it "outputs the total cost for an insert" $ do
|
||||||
@@ -452,11 +440,7 @@ spec actualPgVersion = do
|
|||||||
liftIO $ do
|
liftIO $ do
|
||||||
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.twkb\"; options=verbose; charset=utf-8")
|
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.twkb\"; options=verbose; charset=utf-8")
|
||||||
aggCol `shouldBe`
|
aggCol `shouldBe`
|
||||||
(
|
Just [aesonQQ| "twkb_agg(ROW(lines.id, lines.name, lines.geom)::lines)" |]
|
||||||
if actualPgVersion >= pgVersion120
|
|
||||||
then Just [aesonQQ| "twkb_agg(ROW(lines.id, lines.name, lines.geom)::lines)" |]
|
|
||||||
else Just [aesonQQ| "twkb_agg(ROW(pgrst_source.id, pgrst_source.name, pgrst_source.geom)::lines)" |]
|
|
||||||
)
|
|
||||||
|
|
||||||
disabledSpec :: SpecWith ((), Application)
|
disabledSpec :: SpecWith ((), Application)
|
||||||
disabledSpec =
|
disabledSpec =
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import Test.Hspec hiding (pendingWith)
|
|||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
|
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion112,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion121)
|
||||||
pgVersion121)
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
@@ -176,28 +175,27 @@ spec actualPgVersion = do
|
|||||||
[json| [ {"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" }] |]
|
[json| [ {"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" }] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion112) $ do
|
it "finds matches with websearch_to_tsquery" $
|
||||||
it "finds matches with websearch_to_tsquery" $
|
get "/tsearch?text_search_vector=wfts.The%20Fat%20Rats" `shouldRespondWith`
|
||||||
get "/tsearch?text_search_vector=wfts.The%20Fat%20Rats" `shouldRespondWith`
|
[json| [ {"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" }] |]
|
||||||
[json| [ {"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" }] |]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "can use boolean operators(and, or, -) in websearch_to_tsquery" $ do
|
it "can use boolean operators(and, or, -) in websearch_to_tsquery" $ do
|
||||||
get "/tsearch?text_search_vector=wfts.fun%20and%20possible"
|
get "/tsearch?text_search_vector=wfts.fun%20and%20possible"
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json| [ {"text_search_vector": "'also':2 'fun':3 'possibl':8"}] |]
|
[json| [ {"text_search_vector": "'also':2 'fun':3 'possibl':8"}] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
get "/tsearch?text_search_vector=wfts.impossible%20or%20possible"
|
get "/tsearch?text_search_vector=wfts.impossible%20or%20possible"
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json| [
|
[json| [
|
||||||
{"text_search_vector": "'fun':5 'imposs':9 'kind':3"},
|
{"text_search_vector": "'fun':5 'imposs':9 'kind':3"},
|
||||||
{"text_search_vector": "'also':2 'fun':3 'possibl':8"}]
|
{"text_search_vector": "'also':2 'fun':3 'possibl':8"}]
|
||||||
|]
|
|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
get "/tsearch?text_search_vector=wfts.fun%20and%20-possible"
|
get "/tsearch?text_search_vector=wfts.fun%20and%20-possible"
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json| [ {"text_search_vector": "'fun':5 'imposs':9 'kind':3"}] |]
|
[json| [ {"text_search_vector": "'fun':5 'imposs':9 'kind':3"}] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "finds matches with different dictionaries" $ do
|
it "finds matches with different dictionaries" $ do
|
||||||
get "/tsearch?text_search_vector=fts(french).amusant" `shouldRespondWith`
|
get "/tsearch?text_search_vector=fts(french).amusant" `shouldRespondWith`
|
||||||
@@ -207,11 +205,10 @@ spec actualPgVersion = do
|
|||||||
[json| [{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" }] |]
|
[json| [{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" }] |]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
when (actualPgVersion >= pgVersion112) $
|
get "/tsearch?text_search_vector=wfts(french).amusant%20impossible"
|
||||||
get "/tsearch?text_search_vector=wfts(french).amusant%20impossible"
|
`shouldRespondWith`
|
||||||
`shouldRespondWith`
|
[json| [{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" }] |]
|
||||||
[json| [{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" }] |]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "can be negated with not operator" $ do
|
it "can be negated with not operator" $ do
|
||||||
get "/tsearch?text_search_vector=not.fts.impossible%7Cfat%7Cfun" `shouldRespondWith`
|
get "/tsearch?text_search_vector=not.fts.impossible%7Cfat%7Cfun" `shouldRespondWith`
|
||||||
@@ -231,13 +228,12 @@ spec actualPgVersion = do
|
|||||||
{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4"},
|
{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4"},
|
||||||
{"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|]
|
{"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
when (actualPgVersion >= pgVersion112) $
|
get "/tsearch?text_search_vector=not.wfts(english).impossible%20or%20fat%20or%20fun"
|
||||||
get "/tsearch?text_search_vector=not.wfts(english).impossible%20or%20fat%20or%20fun"
|
`shouldRespondWith`
|
||||||
`shouldRespondWith`
|
[json| [
|
||||||
[json| [
|
{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4"},
|
||||||
{"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4"},
|
{"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|]
|
||||||
{"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
context "Use of the phraseto_tsquery function" $ do
|
context "Use of the phraseto_tsquery function" $ do
|
||||||
it "finds matches" $
|
it "finds matches" $
|
||||||
|
|||||||
@@ -11,14 +11,11 @@ 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, pgVersion112,
|
|
||||||
pgVersion114)
|
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
spec :: PgVersion -> SpecWith ((), Application)
|
spec :: SpecWith ((), Application)
|
||||||
spec actualPgVersion =
|
spec =
|
||||||
describe "remote procedure call" $ do
|
describe "remote procedure call" $ do
|
||||||
context "a proc that returns a set" $ do
|
context "a proc that returns a set" $ do
|
||||||
context "returns paginated results" $ do
|
context "returns paginated results" $ do
|
||||||
@@ -599,13 +596,12 @@ spec actualPgVersion =
|
|||||||
[json|"object"|]
|
[json|"object"|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
when (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)" $
|
post "/rpc/json_argument"
|
||||||
post "/rpc/json_argument"
|
[json| { "arg": "{ \"key\": 3 }" } |]
|
||||||
[json| { "arg": "{ \"key\": 3 }" } |]
|
`shouldRespondWith`
|
||||||
`shouldRespondWith`
|
[json|"string"|]
|
||||||
[json|"string"|]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
context "improper input" $ do
|
context "improper input" $ do
|
||||||
it "rejects unknown content type even if payload is good" $ do
|
it "rejects unknown content type even if payload is good" $ do
|
||||||
@@ -1019,10 +1015,9 @@ spec actualPgVersion =
|
|||||||
get "/rpc/get_tsearch?text_search_vector=not.fts(english).fun%7Crat" `shouldRespondWith`
|
get "/rpc/get_tsearch?text_search_vector=not.fts(english).fun%7Crat" `shouldRespondWith`
|
||||||
[json|[{"text_search_vector":"'amus':5 'fair':7 'impossibl':9 'peu':4"},{"text_search_vector":"'art':4 'spass':5 'unmog':7"}]|]
|
[json|[{"text_search_vector":"'amus':5 'fair':7 'impossibl':9 'peu':4"},{"text_search_vector":"'art':4 'spass':5 'unmog':7"}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
when (actualPgVersion >= pgVersion112) $
|
get "/rpc/get_tsearch?text_search_vector=wfts.impossible" `shouldRespondWith`
|
||||||
get "/rpc/get_tsearch?text_search_vector=wfts.impossible" `shouldRespondWith`
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
|
||||||
|
|
||||||
it "should work with the phraseto_tsquery function" $
|
it "should work with the phraseto_tsquery function" $
|
||||||
get "/rpc/get_tsearch?text_search_vector=phfts(english).impossible" `shouldRespondWith`
|
get "/rpc/get_tsearch?text_search_vector=phfts(english).impossible" `shouldRespondWith`
|
||||||
|
|||||||
+3
-3
@@ -137,14 +137,14 @@ main = do
|
|||||||
analyzeTable "child_entities"
|
analyzeTable "child_entities"
|
||||||
|
|
||||||
specs = uncurry describe <$> [
|
specs = uncurry describe <$> [
|
||||||
("Feature.Auth.AuthSpec" , Feature.Auth.AuthSpec.spec actualPgVersion)
|
("Feature.Auth.AuthSpec" , Feature.Auth.AuthSpec.spec)
|
||||||
, ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec)
|
, ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec)
|
||||||
, ("Feature.CorsSpec" , Feature.CorsSpec.spec)
|
, ("Feature.CorsSpec" , Feature.CorsSpec.spec)
|
||||||
, ("Feature.CustomMediaSpec" , Feature.Query.CustomMediaSpec.spec)
|
, ("Feature.CustomMediaSpec" , Feature.Query.CustomMediaSpec.spec)
|
||||||
, ("Feature.NoSuperuserSpec" , Feature.NoSuperuserSpec.spec)
|
, ("Feature.NoSuperuserSpec" , Feature.NoSuperuserSpec.spec)
|
||||||
, ("Feature.OpenApi.OpenApiSpec" , Feature.OpenApi.OpenApiSpec.spec)
|
, ("Feature.OpenApi.OpenApiSpec" , Feature.OpenApi.OpenApiSpec.spec)
|
||||||
, ("Feature.OptionsSpec" , Feature.OptionsSpec.spec)
|
, ("Feature.OptionsSpec" , Feature.OptionsSpec.spec)
|
||||||
, ("Feature.Query.AndOrParamsSpec" , Feature.Query.AndOrParamsSpec.spec actualPgVersion)
|
, ("Feature.Query.AndOrParamsSpec" , Feature.Query.AndOrParamsSpec.spec)
|
||||||
, ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec)
|
, ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec)
|
||||||
, ("Feature.Query.DeleteSpec" , Feature.Query.DeleteSpec.spec)
|
, ("Feature.Query.DeleteSpec" , Feature.Query.DeleteSpec.spec)
|
||||||
, ("Feature.Query.EmbedDisambiguationSpec" , Feature.Query.EmbedDisambiguationSpec.spec)
|
, ("Feature.Query.EmbedDisambiguationSpec" , Feature.Query.EmbedDisambiguationSpec.spec)
|
||||||
@@ -159,7 +159,7 @@ main = do
|
|||||||
, ("Feature.Query.QuerySpec" , Feature.Query.QuerySpec.spec actualPgVersion)
|
, ("Feature.Query.QuerySpec" , Feature.Query.QuerySpec.spec actualPgVersion)
|
||||||
, ("Feature.Query.RawOutputTypesSpec" , Feature.Query.RawOutputTypesSpec.spec)
|
, ("Feature.Query.RawOutputTypesSpec" , Feature.Query.RawOutputTypesSpec.spec)
|
||||||
, ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec)
|
, ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec)
|
||||||
, ("Feature.Query.RpcSpec" , Feature.Query.RpcSpec.spec actualPgVersion)
|
, ("Feature.Query.RpcSpec" , Feature.Query.RpcSpec.spec)
|
||||||
, ("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)
|
, ("Feature.Query.UpdateSpec" , Feature.Query.UpdateSpec.spec)
|
||||||
|
|||||||
Vendored
+10
-14
@@ -694,23 +694,19 @@ UPDATE test.car_models SET car_brand_name = 'Ferrari' WHERE name = 'F310-B';
|
|||||||
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Veneno';
|
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Veneno';
|
||||||
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Murcielago';
|
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Murcielago';
|
||||||
|
|
||||||
DO $do$BEGIN
|
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-14',7,'DeLorean',1981);
|
||||||
IF (SELECT current_setting('server_version_num')::INT >= 120000) THEN
|
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-15',9,'DeLorean',1981);
|
||||||
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-14',7,'DeLorean',1981);
|
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-11',1,'Murcielago',2001);
|
||||||
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-15',9,'DeLorean',1981);
|
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-12',3,'Murcielago',2001);
|
||||||
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-11',1,'Murcielago',2001);
|
|
||||||
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-12',3,'Murcielago',2001);
|
|
||||||
|
|
||||||
INSERT INTO test.car_racers(name) VALUES ('Alain Prost');
|
INSERT INTO test.car_racers(name) VALUES ('Alain Prost');
|
||||||
INSERT INTO test.car_racers(name, car_model_name, car_model_year) VALUES ('Michael Schumacher', 'F310-B', 1997);
|
INSERT INTO test.car_racers(name, car_model_name, car_model_year) VALUES ('Michael Schumacher', 'F310-B', 1997);
|
||||||
|
|
||||||
INSERT INTO test.car_dealers(name,city) VALUES ('Springfield Cars S.A.','Springfield');
|
INSERT INTO test.car_dealers(name,city) VALUES ('Springfield Cars S.A.','Springfield');
|
||||||
INSERT INTO test.car_dealers(name,city) VALUES ('The Best Deals S.A.','Franklin');
|
INSERT INTO test.car_dealers(name,city) VALUES ('The Best Deals S.A.','Franklin');
|
||||||
|
|
||||||
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('DeLorean',1981,'Springfield Cars S.A.','Springfield',15);
|
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('DeLorean',1981,'Springfield Cars S.A.','Springfield',15);
|
||||||
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('Murcielago',2001,'The Best Deals S.A.','Franklin',2);
|
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('Murcielago',2001,'The Best Deals S.A.','Franklin',2);
|
||||||
END IF;
|
|
||||||
END$do$;
|
|
||||||
|
|
||||||
TRUNCATE TABLE test.products CASCADE;
|
TRUNCATE TABLE test.products CASCADE;
|
||||||
INSERT INTO test.products (id, name) VALUES (1,'product-1'), (2,'product-2'), (3,'product-3');
|
INSERT INTO test.products (id, name) VALUES (1,'product-1'), (2,'product-2'), (3,'product-3');
|
||||||
|
|||||||
Vendored
+50
-61
@@ -2301,61 +2301,56 @@ create table test.car_brands (
|
|||||||
alter table test.car_models add primary key (name, year);
|
alter table test.car_models add primary key (name, year);
|
||||||
alter table test.car_models add column car_brand_name varchar(64) references test.car_brands(name);
|
alter table test.car_models add column car_brand_name varchar(64) references test.car_brands(name);
|
||||||
|
|
||||||
do $do$begin
|
create table test.car_model_sales(
|
||||||
-- foreign keys referencing partitioned tables are supported from pg v12
|
date varchar(64) not null,
|
||||||
if (select current_setting('server_version_num')::int >= 120000) then
|
quantity int not null,
|
||||||
create table test.car_model_sales(
|
car_model_name varchar(64),
|
||||||
date varchar(64) not null,
|
car_model_year int,
|
||||||
quantity int not null,
|
primary key (date, car_model_name, car_model_year),
|
||||||
car_model_name varchar(64),
|
foreign key (car_model_name, car_model_year) references test.car_models (name, year)
|
||||||
car_model_year int,
|
) partition by range (date);
|
||||||
primary key (date, car_model_name, car_model_year),
|
|
||||||
foreign key (car_model_name, car_model_year) references test.car_models (name, year)
|
|
||||||
) partition by range (date);
|
|
||||||
|
|
||||||
create table test.car_model_sales_202101 partition of test.car_model_sales
|
create table test.car_model_sales_202101 partition of test.car_model_sales
|
||||||
for values from ('2021-01-01') to ('2021-01-31');
|
for values from ('2021-01-01') to ('2021-01-31');
|
||||||
|
|
||||||
create table test.car_model_sales_default partition of test.car_model_sales
|
create table test.car_model_sales_default partition of test.car_model_sales
|
||||||
default;
|
default;
|
||||||
|
|
||||||
create table test.car_racers (
|
create table test.car_racers (
|
||||||
name varchar(64) not null primary key,
|
name varchar(64) not null primary key,
|
||||||
car_model_name varchar(64),
|
car_model_name varchar(64),
|
||||||
car_model_year int,
|
car_model_year int,
|
||||||
foreign key (car_model_name, car_model_year) references test.car_models (name, year)
|
foreign key (car_model_name, car_model_year) references test.car_models (name, year)
|
||||||
);
|
);
|
||||||
|
|
||||||
create table test.car_dealers (
|
create table test.car_dealers (
|
||||||
name varchar(64) not null,
|
name varchar(64) not null,
|
||||||
city varchar(64) not null,
|
city varchar(64) not null,
|
||||||
primary key (name, city)
|
primary key (name, city)
|
||||||
) partition by list (city);
|
) partition by list (city);
|
||||||
|
|
||||||
create table test.car_dealers_springfield partition of test.car_dealers
|
create table test.car_dealers_springfield partition of test.car_dealers
|
||||||
for values in ('Springfield');
|
for values in ('Springfield');
|
||||||
|
|
||||||
create table test.car_dealers_default partition of test.car_dealers
|
create table test.car_dealers_default partition of test.car_dealers
|
||||||
default;
|
default;
|
||||||
|
|
||||||
create table test.car_models_car_dealers (
|
create table test.car_models_car_dealers (
|
||||||
car_model_name varchar(64) not null,
|
car_model_name varchar(64) not null,
|
||||||
car_model_year int not null,
|
car_model_year int not null,
|
||||||
car_dealer_name varchar(64) not null,
|
car_dealer_name varchar(64) not null,
|
||||||
car_dealer_city varchar(64) not null,
|
car_dealer_city varchar(64) not null,
|
||||||
quantity int not null,
|
quantity int not null,
|
||||||
foreign key (car_model_name, car_model_year) references test.car_models (name, year),
|
foreign key (car_model_name, car_model_year) references test.car_models (name, year),
|
||||||
foreign key (car_dealer_name, car_dealer_city) references test.car_dealers (name, city),
|
foreign key (car_dealer_name, car_dealer_city) references test.car_dealers (name, city),
|
||||||
primary key (car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity)
|
primary key (car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity)
|
||||||
) partition by range (quantity);
|
) partition by range (quantity);
|
||||||
|
|
||||||
create table test.car_models_car_dealers_10to20 partition of test.car_models_car_dealers
|
create table test.car_models_car_dealers_10to20 partition of test.car_models_car_dealers
|
||||||
for values from (10) to (20);
|
for values from (10) to (20);
|
||||||
|
|
||||||
create table test.car_models_car_dealers_default partition of test.car_models_car_dealers
|
create table test.car_models_car_dealers_default partition of test.car_models_car_dealers
|
||||||
default;
|
default;
|
||||||
end if;
|
|
||||||
end$do$;
|
|
||||||
|
|
||||||
create or replace function test.unnamed_json_param(json) returns json as $$
|
create or replace function test.unnamed_json_param(json) returns json as $$
|
||||||
select $1;
|
select $1;
|
||||||
@@ -3261,21 +3256,15 @@ AS $$
|
|||||||
select current_setting('is_superuser')::boolean;
|
select current_setting('is_superuser')::boolean;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
DO $do$
|
CREATE TABLE test.foo (
|
||||||
BEGIN
|
a text,
|
||||||
IF current_setting('server_version_num')::INT >= 120000 THEN
|
b text GENERATED ALWAYS AS (
|
||||||
CREATE TABLE test.foo (
|
case WHEN a = 'telegram' THEN 'im'
|
||||||
a text,
|
WHEN a = 'proton' THEN 'email'
|
||||||
b text GENERATED ALWAYS AS (
|
WHEN a = 'infinity' THEN 'idea'
|
||||||
case WHEN a = 'telegram' THEN 'im'
|
ELSE 'bad idea'
|
||||||
WHEN a = 'proton' THEN 'email'
|
end) stored
|
||||||
WHEN a = 'infinity' THEN 'idea'
|
);
|
||||||
ELSE 'bad idea'
|
|
||||||
end) stored
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
$do$;
|
|
||||||
|
|
||||||
create domain devil_int as int
|
create domain devil_int as int
|
||||||
default 666;
|
default 666;
|
||||||
|
|||||||
Reference in New Issue
Block a user