feat: Drop support for pg 10

This commit is contained in:
Wolfgang Walther
2024-06-15 17:23:34 +02:00
committed by Wolfgang Walther
parent daa77d17aa
commit 126178642b
20 changed files with 280 additions and 363 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
pgVersion: [10, 11, 12, 13, 14, 15, 16] pgVersion: [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:
+1
View File
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Changed ### Changed
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther - #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
- #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther
## [12.2.0] - 2024-06-11 ## [12.2.0] - 2024-06-11
-1
View File
@@ -55,7 +55,6 @@ let
{ 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 ]); } { 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 ]); }
]; ];
# Dynamic derivation for PostgREST # Dynamic derivation for PostgREST
+3 -3
View File
@@ -16,14 +16,14 @@ Supported PostgreSQL versions
============================= =============================
=============== ================================= =============== =================================
**Supported** PostgreSQL >= 10 **Supported** PostgreSQL >= 11
=============== ================================= =============== =================================
PostgREST works with all PostgreSQL versions starting from 10. PostgREST works with all PostgreSQL versions starting from 11.
.. note:: .. note::
Support for PostgreSQL versions 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions. 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/. See https://www.postgresql.org/support/versioning/.
+12 -12
View File
@@ -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-10 postgrest-coverage postgrest-with-postgresql-11
postgrest-lint postgrest-with-postgresql-11 postgrest-lint postgrest-with-postgresql-12
postgrest-run postgrest-with-postgresql-12 postgrest-run postgrest-with-postgresql-13
postgrest-style postgrest-with-postgresql-13 postgrest-style postgrest-with-postgresql-14
postgrest-style-check postgrest-with-postgresql-14 postgrest-style-check postgrest-with-postgresql-15
postgrest-test-io postgrest-test-io postgrest-with-postgresql-16
... ...
[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-10 postgrest-coverage postgrest-with-postgresql-11
postgrest-lint postgrest-with-postgresql-11 postgrest-lint postgrest-with-postgresql-12
postgrest-run postgrest-with-postgresql-12 postgrest-run postgrest-with-postgresql-13
postgrest-style postgrest-with-postgresql-13 postgrest-style postgrest-with-postgresql-14
postgrest-style-check postgrest-with-postgresql-14 postgrest-style-check postgrest-with-postgresql-15
postgrest-test-io postgrest-test-io postgrest-with-postgresql-16
postgrest-test-memory postgrest-test-memory
... ...
-5
View File
@@ -15,9 +15,4 @@ in
postgis = prev.postgresql_11.pkgs.postgis.overrideAttrs (_: postgis_3_2_3); postgis = prev.postgresql_11.pkgs.postgis.overrideAttrs (_: postgis_3_2_3);
}; };
}; };
postgresql_10 = prev.postgresql_10.override { this = final.postgresql_11; } // {
pkgs = prev.postgresql_10.pkgs // {
postgis = prev.postgresql_10.pkgs.postgis.overrideAttrs (_: postgis_3_2_3);
};
};
} }
-15
View File
@@ -2,21 +2,6 @@ _: _:
# Overlay that adds legacy versions of PostgreSQL that are supported by # Overlay that adds legacy versions of PostgreSQL that are supported by
# PostgREST. # PostgREST.
{ {
# PostgreSQL 10 was removed from Nixpkgs with
# https://github.com/NixOS/nixpkgs/commit/aa1483114bb329fee7e1266100b8d8921ed4723f
# We pin its parent commit to get the last version that was available.
postgresql_10 =
let
rev = "79661ba7e2fb96ebefbb537458a5bbae9dc5bd1a";
tarballHash = "0rn796pfn4sg90ai9fdnwmr10a2s835p1arazzgz46h6s5cxvq97";
pinnedPkgs =
builtins.fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
sha256 = tarballHash;
};
in
(import pinnedPkgs { }).pkgs.postgresql_10;
# PostgreSQL 11 was removed from Nixpkgs with # PostgreSQL 11 was removed from Nixpkgs with
# https://github.com/NixOS/nixpkgs/commit/1220a4d4dd1a4590780a5e1c18d1333a121be366 # https://github.com/NixOS/nixpkgs/commit/1220a4d4dd1a4590780a5e1c18d1333a121be366
# We pin its parent commit to get the last version that was available. # We pin its parent commit to get the last version that was available.
+1 -9
View File
@@ -3,8 +3,6 @@
module PostgREST.Config.PgVersion module PostgREST.Config.PgVersion
( PgVersion(..) ( PgVersion(..)
, minimumPgVersion , minimumPgVersion
, pgVersion109
, pgVersion110
, pgVersion112 , pgVersion112
, pgVersion114 , pgVersion114
, pgVersion120 , pgVersion120
@@ -31,13 +29,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 = pgVersion100 minimumPgVersion = pgVersion110
pgVersion100 :: PgVersion
pgVersion100 = PgVersion 100000 "10" "10"
pgVersion109 :: PgVersion
pgVersion109 = PgVersion 100009 "10.9" "10.9"
pgVersion110 :: PgVersion pgVersion110 :: PgVersion
pgVersion110 = PgVersion 110000 "11.0" "11.0" pgVersion110 = PgVersion 110000 "11.0" "11.0"
+2 -2
View File
@@ -175,13 +175,13 @@ actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{
failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) resultSet failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) resultSet
pure $ DbCallResult plan resultSet pure $ DbCallResult plan resultSet
actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ pgVer sCache = actionQuery (MaybeDb plan@InspectPlan{ipSchema=tSchema}) AppConfig{..} _ _ sCache =
lift $ case configOpenApiMode of lift $ case configOpenApiMode of
OAFollowPriv -> do OAFollowPriv -> do
tableAccess <- SQL.statement [tSchema] (SchemaCache.accessibleTables 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 configDbPreparedStatements)
<*> SQL.statement tSchema (SchemaCache.schemaDescription configDbPreparedStatements)) <*> SQL.statement tSchema (SchemaCache.schemaDescription configDbPreparedStatements))
OAIgnorePriv -> OAIgnorePriv ->
MaybeDbResult plan . Just <$> ((,,) MaybeDbResult plan . Just <$> ((,,)
+4 -5
View File
@@ -23,8 +23,7 @@ import Data.Maybe (fromJust)
import Data.Tree (Tree (..)) import Data.Tree (Tree (..))
import PostgREST.ApiRequest.Preferences (PreferResolution (..)) import PostgREST.ApiRequest.Preferences (PreferResolution (..))
import PostgREST.Config.PgVersion (PgVersion, pgVersion110, import PostgREST.Config.PgVersion (PgVersion, pgVersion130)
pgVersion130)
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.SchemaCache.Relationship (Cardinality (..), import PostgREST.SchemaCache.Relationship (Cardinality (..),
Junction (..), Junction (..),
@@ -202,9 +201,9 @@ callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar re
"LATERAL " <> callIt (fmtParams prms) "LATERAL " <> callIt (fmtParams prms)
callIt :: SQL.Snippet -> SQL.Snippet callIt :: SQL.Snippet -> SQL.Snippet
callIt argument | pgVer < pgVersion130 && pgVer >= pgVersion110 && returnsCompositeAlias = "(SELECT (" <> fromQi qi <> "(" <> argument <> ")).*) pgrst_call" callIt argument | pgVer < pgVersion130 && returnsCompositeAlias = "(SELECT (" <> fromQi qi <> "(" <> argument <> ")).*) pgrst_call"
| returnsScalar || returnsSetOfScalar = "(SELECT " <> fromQi qi <> "(" <> argument <> ") pgrst_scalar) pgrst_call" | returnsScalar || returnsSetOfScalar = "(SELECT " <> fromQi qi <> "(" <> argument <> ") pgrst_scalar) pgrst_call"
| otherwise = fromQi qi <> "(" <> argument <> ") pgrst_call" | otherwise = fromQi qi <> "(" <> argument <> ") pgrst_call"
fmtParams :: [RoutineParam] -> SQL.Snippet fmtParams :: [RoutineParam] -> SQL.Snippet
fmtParams prms = intercalateSnippet ", " fmtParams prms = intercalateSnippet ", "
+20 -24
View File
@@ -47,8 +47,7 @@ import PostgREST.Config (AppConfig (..))
import PostgREST.Config.Database (TimezoneNames, import PostgREST.Config.Database (TimezoneNames,
pgVersionStatement, pgVersionStatement,
toIsolationLevel) toIsolationLevel)
import PostgREST.Config.PgVersion (PgVersion, pgVersion110, import PostgREST.Config.PgVersion (PgVersion, pgVersion120)
pgVersion120)
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName, import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
QualifiedIdentifier (..), QualifiedIdentifier (..),
RelIdentifier (..), RelIdentifier (..),
@@ -148,11 +147,11 @@ querySchemaCache AppConfig{..} = do
pgVer <- SQL.statement mempty $ pgVersionStatement prepared pgVer <- SQL.statement mempty $ pgVersionStatement prepared
tabs <- SQL.statement schemas $ allTables pgVer 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 pgVer prepared m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared
funcs <- SQL.statement (schemas, configDbHoistedTxSettings) $ allFunctions pgVer prepared funcs <- SQL.statement (schemas, configDbHoistedTxSettings) $ allFunctions prepared
cRels <- SQL.statement mempty $ allComputedRels prepared cRels <- SQL.statement mempty $ allComputedRels prepared
reps <- SQL.statement schemas $ dataRepresentations prepared reps <- SQL.statement schemas $ dataRepresentations prepared
mHdlers <- SQL.statement schemas $ mediaHandlers pgVer prepared mHdlers <- SQL.statement schemas $ mediaHandlers prepared
tzones <- SQL.statement mempty $ timezones prepared tzones <- SQL.statement mempty $ timezones prepared
_ <- _ <-
let sleepCall = SQL.Statement "select pg_sleep($1 / 1000.0)" (param HE.int4) HD.noResult prepared in let sleepCall = SQL.Statement "select pg_sleep($1 / 1000.0)" (param HE.int4) HD.noResult prepared in
@@ -362,18 +361,18 @@ dataRepresentations = SQL.Statement sql (arrayParam HE.text) decodeRepresentatio
OR (dst_t.typtype = 'd' AND c.castsource IN ('json'::regtype::oid , 'text'::regtype::oid))) OR (dst_t.typtype = 'd' AND c.castsource IN ('json'::regtype::oid , 'text'::regtype::oid)))
|] |]
allFunctions :: PgVersion -> Bool -> SQL.Statement ([Schema], [Text]) RoutineMap allFunctions :: Bool -> SQL.Statement ([Schema], [Text]) RoutineMap
allFunctions pgVer = SQL.Statement sql (contrazip2 (arrayParam HE.text) (arrayParam HE.text)) decodeFuncs allFunctions = SQL.Statement sql (contrazip2 (arrayParam HE.text) (arrayParam HE.text)) decodeFuncs
where where
sql = funcsSqlQuery pgVer <> " AND pn.nspname = ANY($1)" sql = funcsSqlQuery <> " AND pn.nspname = ANY($1)"
accessibleFuncs :: PgVersion -> Bool -> SQL.Statement (Schema, [Text]) RoutineMap accessibleFuncs :: Bool -> SQL.Statement (Schema, [Text]) RoutineMap
accessibleFuncs pgVer = SQL.Statement sql (contrazip2 (param HE.text) (arrayParam HE.text)) decodeFuncs accessibleFuncs = SQL.Statement sql (contrazip2 (param HE.text) (arrayParam HE.text)) decodeFuncs
where where
sql = funcsSqlQuery pgVer <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')" sql = funcsSqlQuery <> " AND pn.nspname = $1 AND has_function_privilege(p.oid, 'execute')"
funcsSqlQuery :: PgVersion -> SqlQuery funcsSqlQuery :: SqlQuery
funcsSqlQuery pgVer = [q| funcsSqlQuery = [q|
-- Recursively get the base types of domains -- Recursively get the base types of domains
WITH WITH
base_types AS ( base_types AS (
@@ -461,7 +460,7 @@ funcsSqlQuery pgVer = [q|
WHERE setting ~ ANY($2) WHERE setting ~ ANY($2)
) func_settings ON TRUE ) func_settings ON TRUE
WHERE t.oid <> 'trigger'::regtype AND COALESCE(a.callable, true) WHERE t.oid <> 'trigger'::regtype AND COALESCE(a.callable, true)
|] <> (if pgVer >= pgVersion110 then "AND prokind = 'f'" else "AND NOT (proisagg OR proiswindow)") AND prokind = 'f'|]
schemaDescription :: Bool -> SQL.Statement Schema (Maybe Text) schemaDescription :: Bool -> SQL.Statement Schema (Maybe Text)
schemaDescription = schemaDescription =
@@ -828,8 +827,8 @@ tablesSqlQuery pgVer =
END|] 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 :: Bool -> SQL.Statement () [Relationship]
allM2OandO2ORels pgVer = allM2OandO2ORels =
SQL.Statement sql HE.noParams decodeRels SQL.Statement sql HE.noParams decodeRels
where where
-- We use jsonb_agg for comparing the uniques/pks instead of array_agg to avoid the ERROR: cannot accumulate arrays of different dimensionality -- We use jsonb_agg for comparing the uniques/pks instead of array_agg to avoid the ERROR: cannot accumulate arrays of different dimensionality
@@ -875,11 +874,8 @@ allM2OandO2ORels pgVer =
JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace
LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid
WHERE traint.contype = 'f' WHERE traint.contype = 'f'
|] <> AND traint.conparentid = 0
(if pgVer >= pgVersion110 ORDER BY traint.conrelid, traint.conname|]
then " and traint.conparentid = 0 "
else mempty) <>
"ORDER BY traint.conrelid, traint.conname"
allComputedRels :: Bool -> SQL.Statement () [Relationship] allComputedRels :: Bool -> SQL.Statement () [Relationship]
allComputedRels = allComputedRels =
@@ -1132,8 +1128,8 @@ initialMediaHandlers =
HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON) HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON)
HM.empty HM.empty
mediaHandlers :: PgVersion -> Bool -> SQL.Statement [Schema] MediaHandlerMap mediaHandlers :: Bool -> SQL.Statement [Schema] MediaHandlerMap
mediaHandlers pgVer = mediaHandlers =
SQL.Statement sql (arrayParam HE.text) decodeMediaHandlers SQL.Statement sql (arrayParam HE.text) decodeMediaHandlers
where where
sql = [q| sql = [q|
@@ -1195,7 +1191,7 @@ mediaHandlers pgVer =
join pg_namespace typ_sch on typ_sch.oid = mtype.typnamespace join pg_namespace typ_sch on typ_sch.oid = mtype.typnamespace
where where
pro_sch.nspname = ANY($1) and NOT proretset pro_sch.nspname = ANY($1) and NOT proretset
|] <> (if pgVer >= pgVersion110 then " AND prokind = 'f'" else " AND NOT (proisagg OR proiswindow)") and prokind = 'f'|]
decodeMediaHandlers :: HD.Result MediaHandlerMap decodeMediaHandlers :: HD.Result MediaHandlerMap
decodeMediaHandlers = decodeMediaHandlers =
+3 -6
View File
@@ -11,14 +11,12 @@ 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, pgVersion110)
import PostgREST.Version (docsVersion) import PostgREST.Version (docsVersion)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
spec :: PgVersion -> SpecWith ((), Application) spec :: SpecWith ((), Application)
spec actualPgVersion = describe "OpenAPI" $ do spec = describe "OpenAPI" $ do
it "root path returns a valid openapi spec" $ do it "root path returns a valid openapi spec" $ do
validateOpenApiResponse [("Accept", "application/openapi+json")] validateOpenApiResponse [("Accept", "application/openapi+json")]
request methodHead "/" request methodHead "/"
@@ -290,8 +288,7 @@ spec actualPgVersion = describe "OpenAPI" $ do
getParameterYear `shouldBe` Just "#/parameters/rowFilter.car_models.year" getParameterYear `shouldBe` Just "#/parameters/rowFilter.car_models.year"
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" $
+3 -10
View File
@@ -7,13 +7,11 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import PostgREST.Config.PgVersion (PgVersion, pgVersion110)
import Protolude import Protolude
import SpecHelper import SpecHelper
spec :: PgVersion -> SpecWith ((), Application) spec :: SpecWith ((), Application)
spec actualPgVersion = describe "Allow header" $ do spec = describe "Allow header" $ do
context "a table" $ do context "a table" $ do
it "includes read/write methods for writeable table" $ do it "includes read/write methods for writeable table" $ do
r <- request methodOptions "/items" [] "" r <- request methodOptions "/items" [] ""
@@ -29,12 +27,7 @@ spec actualPgVersion = describe "Allow header" $ do
r <- request methodOptions "/car_models" [] "" r <- request methodOptions "/car_models" [] ""
liftIO $ liftIO $
simpleHeaders r `shouldSatisfy` simpleHeaders r `shouldSatisfy`
matchHeader "Allow" ( matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
if actualPgVersion >= pgVersion110 then
"OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
else
"OPTIONS,GET,HEAD,POST,PATCH,DELETE"
)
context "a view" $ do context "a view" $ do
context "auto updatable" $ do context "auto updatable" $ do
+15 -16
View File
@@ -11,9 +11,9 @@ import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc import Text.Heredoc
import PostgREST.Config.PgVersion (PgVersion, pgVersion110, import PostgREST.Config.PgVersion (PgVersion, pgVersion112,
pgVersion112, pgVersion120, pgVersion120, pgVersion130,
pgVersion130, pgVersion140) pgVersion140)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -141,19 +141,18 @@ spec actualPgVersion = do
, "Preference-Applied" <:> "return=headers-only"] , "Preference-Applied" <:> "return=headers-only"]
} }
when (actualPgVersion >= pgVersion110) $ it "should not throw and return location header for partitioned tables when selecting without PK" $
it "should not throw and return location header for partitioned tables when selecting without PK" $ request methodPost "/car_models"
request methodPost "/car_models" [("Prefer", "return=headers-only")]
[("Prefer", "return=headers-only")] [json|{"name":"Enzo","year":2021}|]
[json|{"name":"Enzo","year":2021}|] `shouldRespondWith`
`shouldRespondWith` ""
"" { matchStatus = 201
{ matchStatus = 201 , matchHeaders = [ matchHeaderAbsent hContentType
, matchHeaders = [ matchHeaderAbsent hContentType , "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021"
, "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021" , "Content-Range" <:> "*/*"
, "Content-Range" <:> "*/*" , "Preference-Applied" <:> "return=headers-only"]
, "Preference-Applied" <:> "return=headers-only"] }
}
context "requesting no representation" $ context "requesting no representation" $
it "should not throw and return no location header when selecting without PK" $ it "should not throw and return no location header when selecting without PK" $
+104 -113
View File
@@ -8,8 +8,8 @@ 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, pgVersion110, import PostgREST.Config.PgVersion (PgVersion, pgVersion112,
pgVersion112, pgVersion121) pgVersion121)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -539,107 +539,106 @@ spec actualPgVersion = do
[json|[{"id":1,"computed_overload":true}]|] [json|[{"id":1,"computed_overload":true}]|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
when (actualPgVersion >= pgVersion110) $ do describe "partitioned tables embedding" $ do
describe "partitioned tables embedding" $ do it "can request a table as parent from a partitioned table" $
it "can request a table as parent from a partitioned table" $ get "/car_models?name=in.(DeLorean,Murcielago)&select=name,year,car_brands(name)&order=name.asc" `shouldRespondWith`
get "/car_models?name=in.(DeLorean,Murcielago)&select=name,year,car_brands(name)&order=name.asc" `shouldRespondWith` [json|
[{"name":"DeLorean","year":1981,"car_brands":{"name":"DMC"}},
{"name":"Murcielago","year":2001,"car_brands":{"name":"Lamborghini"}}] |]
{ matchHeaders = [matchContentTypeJson] }
it "can request partitioned tables as children from a table" $
get "/car_brands?select=name,car_models(name,year)&order=name.asc&car_models.order=name.asc" `shouldRespondWith`
[json|
[{"name":"DMC","car_models":[{"name":"DeLorean","year":1981}]},
{"name":"Ferrari","car_models":[{"name":"F310-B","year":1997}]},
{"name":"Lamborghini","car_models":[{"name":"Murcielago","year":2001},{"name":"Veneno","year":2013}]}] |]
{ matchHeaders = [matchContentTypeJson] }
when (actualPgVersion >= pgVersion121) $ do
it "can request tables as children from a partitioned table" $
get "/car_models?name=in.(DeLorean,F310-B)&select=name,year,car_racers(name)&order=name.asc" `shouldRespondWith`
[json| [json|
[{"name":"DeLorean","year":1981,"car_brands":{"name":"DMC"}}, [{"name":"DeLorean","year":1981,"car_racers":[]},
{"name":"Murcielago","year":2001,"car_brands":{"name":"Lamborghini"}}] |] {"name":"F310-B","year":1997,"car_racers":[{"name":"Michael Schumacher"}]}] |]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
it "can request partitioned tables as children from a table" $ it "can request a partitioned table as parent from a table" $
get "/car_brands?select=name,car_models(name,year)&order=name.asc&car_models.order=name.asc" `shouldRespondWith` get "/car_racers?select=name,car_models(name,year)&order=name.asc" `shouldRespondWith`
[json| [json|
[{"name":"DMC","car_models":[{"name":"DeLorean","year":1981}]}, [{"name":"Alain Prost","car_models":null},
{"name":"Ferrari","car_models":[{"name":"F310-B","year":1997}]}, {"name":"Michael Schumacher","car_models":{"name":"F310-B","year":1997}}] |]
{"name":"Lamborghini","car_models":[{"name":"Murcielago","year":2001},{"name":"Veneno","year":2013}]}] |]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
when (actualPgVersion >= pgVersion121) $ do it "can request partitioned tables as children from a partitioned table" $
it "can request tables as children from a partitioned table" $ get "/car_models?name=in.(DeLorean,Murcielago,Veneno)&select=name,year,car_model_sales(date,quantity)&order=name.asc" `shouldRespondWith`
get "/car_models?name=in.(DeLorean,F310-B)&select=name,year,car_racers(name)&order=name.asc" `shouldRespondWith` [json|
[json| [{"name":"DeLorean","year":1981,"car_model_sales":[{"date":"2021-01-14","quantity":7},{"date":"2021-01-15","quantity":9}]},
[{"name":"DeLorean","year":1981,"car_racers":[]}, {"name":"Murcielago","year":2001,"car_model_sales":[{"date":"2021-02-11","quantity":1},{"date":"2021-02-12","quantity":3}]},
{"name":"F310-B","year":1997,"car_racers":[{"name":"Michael Schumacher"}]}] |] {"name":"Veneno","year":2013,"car_model_sales":[]}] |]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
it "can request a partitioned table as parent from a table" $ it "can request a partitioned table as parent from a partitioned table" $ do
get "/car_racers?select=name,car_models(name,year)&order=name.asc" `shouldRespondWith` get "/car_model_sales?date=in.(2021-01-15,2021-02-11)&select=date,quantity,car_models(name,year)&order=date.asc" `shouldRespondWith`
[json| [json|
[{"name":"Alain Prost","car_models":null}, [{"date":"2021-01-15","quantity":9,"car_models":{"name":"DeLorean","year":1981}},
{"name":"Michael Schumacher","car_models":{"name":"F310-B","year":1997}}] |] {"date":"2021-02-11","quantity":1,"car_models":{"name":"Murcielago","year":2001}}] |]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
it "can request partitioned tables as children from a partitioned table" $ it "can request many to many relationships between partitioned tables ignoring the intermediate table partitions" $
get "/car_models?name=in.(DeLorean,Murcielago,Veneno)&select=name,year,car_model_sales(date,quantity)&order=name.asc" `shouldRespondWith` get "/car_models?select=name,year,car_dealers(name,city)&order=name.asc&limit=4" `shouldRespondWith`
[json| [json|
[{"name":"DeLorean","year":1981,"car_model_sales":[{"date":"2021-01-14","quantity":7},{"date":"2021-01-15","quantity":9}]}, [{"name":"DeLorean","year":1981,"car_dealers":[{"name":"Springfield Cars S.A.","city":"Springfield"}]},
{"name":"Murcielago","year":2001,"car_model_sales":[{"date":"2021-02-11","quantity":1},{"date":"2021-02-12","quantity":3}]}, {"name":"F310-B","year":1997,"car_dealers":[]},
{"name":"Veneno","year":2013,"car_model_sales":[]}] |] {"name":"Murcielago","year":2001,"car_dealers":[{"name":"The Best Deals S.A.","city":"Franklin"}]},
{ matchHeaders = [matchContentTypeJson] } {"name":"Veneno","year":2013,"car_dealers":[]}] |]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson]
}
it "can request a partitioned table as parent from a partitioned table" $ do it "cannot request partitions as children from a partitioned table" $
get "/car_model_sales?date=in.(2021-01-15,2021-02-11)&select=date,quantity,car_models(name,year)&order=date.asc" `shouldRespondWith` get "/car_models?id=in.(1,2,4)&select=id,name,car_model_sales_202101(id)&order=id.asc" `shouldRespondWith`
[json| [json|
[{"date":"2021-01-15","quantity":9,"car_models":{"name":"DeLorean","year":1981}}, {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.",
{"date":"2021-02-11","quantity":1,"car_models":{"name":"Murcielago","year":2001}}] |] "details":"Searched for a foreign key relationship between 'car_models' and 'car_model_sales_202101' in the schema 'test', but no matches were found.",
{ matchHeaders = [matchContentTypeJson] } "code":"PGRST200",
"message":"Could not find a relationship between 'car_models' and 'car_model_sales_202101' in the schema cache"} |]
{ matchStatus = 400
, matchHeaders = [matchContentTypeJson]
}
it "can request many to many relationships between partitioned tables ignoring the intermediate table partitions" $ it "cannot request a partitioned table as parent from a partition" $
get "/car_models?select=name,year,car_dealers(name,city)&order=name.asc&limit=4" `shouldRespondWith` get "/car_model_sales_202101?select=id,name,car_models(id,name)&order=id.asc" `shouldRespondWith`
[json| [json|
[{"name":"DeLorean","year":1981,"car_dealers":[{"name":"Springfield Cars S.A.","city":"Springfield"}]}, {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.",
{"name":"F310-B","year":1997,"car_dealers":[]}, "details":"Searched for a foreign key relationship between 'car_model_sales_202101' and 'car_models' in the schema 'test', but no matches were found.",
{"name":"Murcielago","year":2001,"car_dealers":[{"name":"The Best Deals S.A.","city":"Franklin"}]}, "code":"PGRST200",
{"name":"Veneno","year":2013,"car_dealers":[]}] |] "message":"Could not find a relationship between 'car_model_sales_202101' and 'car_models' in the schema cache"} |]
{ matchStatus = 200 { matchStatus = 400
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }
it "cannot request partitions as children from a partitioned table" $ it "cannot request a partition as parent from a partitioned table" $
get "/car_models?id=in.(1,2,4)&select=id,name,car_model_sales_202101(id)&order=id.asc" `shouldRespondWith` get "/car_model_sales?id=in.(1,3,4)&select=id,name,car_models_default(id,name)&order=id.asc" `shouldRespondWith`
[json| [json|
{"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.", {"hint":"Perhaps you meant 'car_models' instead of 'car_models_default'.",
"details":"Searched for a foreign key relationship between 'car_models' and 'car_model_sales_202101' in the schema 'test', but no matches were found.", "details":"Searched for a foreign key relationship between 'car_model_sales' and 'car_models_default' in the schema 'test', but no matches were found.",
"code":"PGRST200", "code":"PGRST200",
"message":"Could not find a relationship between 'car_models' and 'car_model_sales_202101' in the schema cache"} |] "message":"Could not find a relationship between 'car_model_sales' and 'car_models_default' in the schema cache"} |]
{ matchStatus = 400 { matchStatus = 400
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }
it "cannot request a partitioned table as parent from a partition" $ it "cannot request partitioned tables as children from a partition" $
get "/car_model_sales_202101?select=id,name,car_models(id,name)&order=id.asc" `shouldRespondWith` get "/car_models_default?select=id,name,car_model_sales(id,name)&order=id.asc" `shouldRespondWith`
[json| [json|
{"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.", {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_models_default'.",
"details":"Searched for a foreign key relationship between 'car_model_sales_202101' and 'car_models' in the schema 'test', but no matches were found.", "details":"Searched for a foreign key relationship between 'car_models_default' and 'car_model_sales' in the schema 'test', but no matches were found.",
"code":"PGRST200", "code":"PGRST200",
"message":"Could not find a relationship between 'car_model_sales_202101' and 'car_models' in the schema cache"} |] "message":"Could not find a relationship between 'car_models_default' and 'car_model_sales' in the schema cache"} |]
{ matchStatus = 400 { matchStatus = 400
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }
it "cannot request a partition as parent from a partitioned table" $
get "/car_model_sales?id=in.(1,3,4)&select=id,name,car_models_default(id,name)&order=id.asc" `shouldRespondWith`
[json|
{"hint":"Perhaps you meant 'car_models' instead of 'car_models_default'.",
"details":"Searched for a foreign key relationship between 'car_model_sales' and 'car_models_default' in the schema 'test', but no matches were found.",
"code":"PGRST200",
"message":"Could not find a relationship between 'car_model_sales' and 'car_models_default' in the schema cache"} |]
{ matchStatus = 400
, matchHeaders = [matchContentTypeJson]
}
it "cannot request partitioned tables as children from a partition" $
get "/car_models_default?select=id,name,car_model_sales(id,name)&order=id.asc" `shouldRespondWith`
[json|
{"hint":"Perhaps you meant 'car_model_sales' instead of 'car_models_default'.",
"details":"Searched for a foreign key relationship between 'car_models_default' and 'car_model_sales' in the schema 'test', but no matches were found.",
"code":"PGRST200",
"message":"Could not find a relationship between 'car_models_default' and 'car_model_sales' in the schema cache"} |]
{ matchStatus = 400
, matchHeaders = [matchContentTypeJson]
}
describe "view embedding" $ do describe "view embedding" $ do
it "can detect fk relations through views to tables in the public schema" $ it "can detect fk relations through views to tables in the public schema" $
@@ -1362,22 +1361,19 @@ spec actualPgVersion = do
{ matchStatus = 400 { matchStatus = 400
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }
-- Before PG 11, this will fail because we need arrays of domain type values. The docs should explain data reps are it "uses text parser for filter with 'IN' predicates" $
-- not supported in this case. get "/datarep_todos?select=id,due_at&label_color=in.(000100,01E240)" `shouldRespondWith`
when (actualPgVersion >= pgVersion110) $ do [json| [
it "uses text parser for filter with 'IN' predicates" $ {"id":2, "due_at": "2018-01-03T00:00:00Z"},
get "/datarep_todos?select=id,due_at&label_color=in.(000100,01E240)" `shouldRespondWith` {"id":3, "due_at": "2018-01-01T14:12:34.123456Z"}
[json| [ ] |]
{"id":2, "due_at": "2018-01-03T00:00:00Z"}, { matchHeaders = [matchContentTypeJson] }
{"id":3, "due_at": "2018-01-01T14:12:34.123456Z"} it "uses text parser for filter with 'NOT IN' predicates" $
] |] get "/datarep_todos?select=id,due_at&label_color=not.in.(000000,01E240)" `shouldRespondWith`
{ matchHeaders = [matchContentTypeJson] } [json| [
it "uses text parser for filter with 'NOT IN' predicates" $ {"id":2, "due_at": "2018-01-03T00:00:00Z"}
get "/datarep_todos?select=id,due_at&label_color=not.in.(000000,01E240)" `shouldRespondWith` ] |]
[json| [ { matchHeaders = [matchContentTypeJson] }
{"id":2, "due_at": "2018-01-03T00:00:00Z"}
] |]
{ matchHeaders = [matchContentTypeJson] }
it "uses text parser on value for filter across relations" $ it "uses text parser on value for filter across relations" $
get "/datarep_next_two_todos?select=id,name,datarep_todos!datarep_next_two_todos_first_item_id_fkey(label_color,due_at)&datarep_todos.label_color=neq.000100" `shouldRespondWith` get "/datarep_next_two_todos?select=id,name,datarep_todos!datarep_next_two_todos_first_item_id_fkey(label_color,due_at)&datarep_todos.label_color=neq.000100" `shouldRespondWith`
[json| [{"id":1,"name":"school related","datarep_todos":null},{"id":2,"name":"do these first","datarep_todos":{"label_color":"#000000","due_at":"2018-01-02T00:00:00Z"}}] |] [json| [{"id":1,"name":"school related","datarep_todos":null},{"id":2,"name":"do these first","datarep_todos":{"label_color":"#000000","due_at":"2018-01-02T00:00:00Z"}}] |]
@@ -1385,15 +1381,10 @@ spec actualPgVersion = do
-- This is not supported by data reps (would be hard to make it work with high performance). So the test just -- This is not supported by data reps (would be hard to make it work with high performance). So the test just
-- verifies we don't panic or add inappropriate SQL to the filters. -- verifies we don't panic or add inappropriate SQL to the filters.
it "fails safely on user trying to use ilike operator on data reps column" $ it "fails safely on user trying to use ilike operator on data reps column" $
get "/datarep_todos?select=id,name&label_color=ilike.#*100" `shouldRespondWith` ( get "/datarep_todos?select=id,name&label_color=ilike.#*100" `shouldRespondWith`
if actualPgVersion >= pgVersion110 then
[json| [json|
{"code":"42883","details":null,"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.","message":"operator does not exist: public.color ~~* unknown"} {"code":"42883","details":null,"hint":"No operator matches the given name and argument types. You might need to add explicit type casts.","message":"operator does not exist: public.color ~~* unknown"}
|] |]
else
[json|
{"code":"42883","details":null,"hint":"No operator matches the given name and argument type(s). You might need to add explicit type casts.","message":"operator does not exist: public.color ~~* unknown"}
|])
{ matchStatus = 404 { matchStatus = 404
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }
+33 -38
View File
@@ -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, pgVersion109, import PostgREST.Config.PgVersion (PgVersion, pgVersion112,
pgVersion110, pgVersion112,
pgVersion114) pgVersion114)
import Protolude hiding (get) import Protolude hiding (get)
@@ -395,15 +394,14 @@ spec actualPgVersion =
]|] ]|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
when (actualPgVersion >= pgVersion110) $ it "can embed if rpc returns domain of table type" $ do
it "can embed if rpc returns domain of table type" $ do post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)"
post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)" [json| { "id": 1} |]
[json| { "id": 1} |] `shouldRespondWith`
`shouldRespondWith` [json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|]
[json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] get "/rpc/getproject_domain?id=1&select=id,name,client:clients(id),tasks(id)"
get "/rpc/getproject_domain?id=1&select=id,name,client:clients(id),tasks(id)" `shouldRespondWith`
`shouldRespondWith` [json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|]
[json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|]
context "a proc that returns an empty rowset" $ context "a proc that returns an empty rowset" $
it "returns empty json array" $ do it "returns empty json array" $ do
@@ -466,12 +464,11 @@ spec actualPgVersion =
it "cannot return composite type in hidden schema" $ it "cannot return composite type in hidden schema" $
post "/rpc/ret_point_3d" [json|{}|] `shouldRespondWith` 401 post "/rpc/ret_point_3d" [json|{}|] `shouldRespondWith` 401
when (actualPgVersion >= pgVersion110) $ it "returns domain of composite type" $
it "returns domain of composite type" $ post "/rpc/ret_composite_domain"
post "/rpc/ret_composite_domain" [json|{}|]
[json|{}|] `shouldRespondWith`
`shouldRespondWith` [json|{"x": 10, "y": 5}|]
[json|{"x": 10, "y": 5}|]
it "returns single row from table" $ it "returns single row from table" $
post "/rpc/single_article?select=id" post "/rpc/single_article?select=id"
@@ -494,26 +491,25 @@ spec actualPgVersion =
`shouldRespondWith` `shouldRespondWith`
[json|null|] [json|null|]
when (actualPgVersion >= pgVersion110) $ do it "returns a record type" $ do
it "returns a record type" $ do post "/rpc/returns_record"
post "/rpc/returns_record" ""
"" `shouldRespondWith`
`shouldRespondWith` [json|{"id":1,"name":"Windows 7","client_id":1}|]
[json|{"id":1,"name":"Windows 7","client_id":1}|] post "/rpc/returns_record_params"
post "/rpc/returns_record_params" [json|{"id":1, "name": "Windows%"}|]
[json|{"id":1, "name": "Windows%"}|] `shouldRespondWith`
`shouldRespondWith` [json|{"id":1,"name":"Windows 7","client_id":1}|]
[json|{"id":1,"name":"Windows 7","client_id":1}|]
it "returns a setof record type" $ do it "returns a setof record type" $ do
post "/rpc/returns_setof_record" post "/rpc/returns_setof_record"
"" ""
`shouldRespondWith` `shouldRespondWith`
[json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|] [json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|]
post "/rpc/returns_setof_record_params" post "/rpc/returns_setof_record_params"
[json|{"id":1,"name":"Windows%"}|] [json|{"id":1,"name":"Windows%"}|]
`shouldRespondWith` `shouldRespondWith`
[json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|] [json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|]
context "different types when overloaded" $ do context "different types when overloaded" $ do
it "returns composite type" $ it "returns composite type" $
@@ -603,8 +599,7 @@ spec actualPgVersion =
[json|"object"|] [json|"object"|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
when ((actualPgVersion >= pgVersion109 && actualPgVersion < pgVersion110) when (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)" $
post "/rpc/json_argument" post "/rpc/json_argument"
[json| { "arg": "{ \"key\": 3 }" } |] [json| { "arg": "{ \"key\": 3 }" } |]
+46 -52
View File
@@ -7,13 +7,11 @@ import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import PostgREST.Config.PgVersion (PgVersion, pgVersion110)
import Protolude hiding (get, put) import Protolude hiding (get, put)
import SpecHelper import SpecHelper
spec :: PgVersion -> SpecWith ((), Application) spec :: SpecWith ((), Application)
spec actualPgVersion = spec =
describe "UPSERT" $ do describe "UPSERT" $ do
context "with POST" $ do context "with POST" $ do
context "when Prefer: resolution=merge-duplicates is specified" $ do context "when Prefer: resolution=merge-duplicates is specified" $ do
@@ -60,19 +58,18 @@ spec actualPgVersion =
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson] , matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
} }
when (actualPgVersion >= pgVersion110) $ it "INSERTs and UPDATEs rows on composite pk conflict for partitioned tables" $
it "INSERTs and UPDATEs rows on composite pk conflict for partitioned tables" $ request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] [json| [
[json| [ { "name": "Murcielago", "year": 2001, "car_brand_name": null},
{ "name": "Murcielago", "year": 2001, "car_brand_name": null}, { "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" }
{ "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" } ]|] `shouldRespondWith` [json| [
]|] `shouldRespondWith` [json| [ { "name": "Murcielago", "year": 2001, "car_brand_name": null},
{ "name": "Murcielago", "year": 2001, "car_brand_name": null}, { "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" }
{ "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" } ]|]
]|] { matchStatus = 201
{ matchStatus = 201 , matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson] }
}
it "succeeds when the payload has no elements" $ it "succeeds when the payload has no elements" $
request methodPost "/articles" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] request methodPost "/articles" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
@@ -131,18 +128,17 @@ spec actualPgVersion =
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson] , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson]
} }
when (actualPgVersion >= pgVersion110) $ it "INSERTs and ignores rows on composite pk conflict for partitioned tables" $
it "INSERTs and ignores rows on composite pk conflict for partitioned tables" $ request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")]
request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] [json| [
[json| [ { "name": "Murcielago", "year": 2001, "car_brand_name": "Ferrari" },
{ "name": "Murcielago", "year": 2001, "car_brand_name": "Ferrari" }, { "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" }
{ "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" } ]|] `shouldRespondWith` [json| [
]|] `shouldRespondWith` [json| [ { "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" }
{ "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" } ]|]
]|] { matchStatus = 201
{ matchStatus = 201 , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson] }
}
it "INSERTs and ignores rows on single unique key conflict" $ it "INSERTs and ignores rows on single unique key conflict" $
request methodPost "/single_unique?on_conflict=unique_key" request methodPost "/single_unique?on_conflict=unique_key"
@@ -312,19 +308,18 @@ spec actualPgVersion =
[json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|]
{ matchStatus = 201 } { matchStatus = 201 }
when (actualPgVersion >= pgVersion110) $ it "succeeds on a partitioned table with composite pk" $ do
it "succeeds on a partitioned table with composite pk" $ do -- assert that the next request will indeed be an insert
-- assert that the next request will indeed be an insert get "/car_models?name=eq.Supra&year=eq.2021"
get "/car_models?name=eq.Supra&year=eq.2021" `shouldRespondWith`
`shouldRespondWith` [json|[]|]
[json|[]|]
request methodPut "/car_models?name=eq.Supra&year=eq.2021" request methodPut "/car_models?name=eq.Supra&year=eq.2021"
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
[json| [ { "name": "Supra", "year": 2021 } ]|] [json| [ { "name": "Supra", "year": 2021 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json| [ { "name": "Supra", "year": 2021, "car_brand_name": null } ]|] [json| [ { "name": "Supra", "year": 2021, "car_brand_name": null } ]|]
{ matchStatus = 201 } { matchStatus = 201 }
it "succeeds if the table has only PK cols and no other cols" $ do it "succeeds if the table has only PK cols and no other cols" $ do
-- assert that the next request will indeed be an insert -- assert that the next request will indeed be an insert
@@ -378,18 +373,17 @@ spec actualPgVersion =
`shouldRespondWith` `shouldRespondWith`
[json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "$60,000.00", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "$60,000.00", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|]
when (actualPgVersion >= pgVersion110) $ it "succeeds on a partitioned table with composite pk" $ do
it "succeeds on a partitioned table with composite pk" $ do -- assert that the next request will indeed be an update
-- assert that the next request will indeed be an update get "/car_models?name=eq.DeLorean&year=eq.1981"
get "/car_models?name=eq.DeLorean&year=eq.1981" `shouldRespondWith`
`shouldRespondWith` [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": "DMC" } ]|]
[json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": "DMC" } ]|]
request methodPut "/car_models?name=eq.DeLorean&year=eq.1981" request methodPut "/car_models?name=eq.DeLorean&year=eq.1981"
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
[json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|] [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|]
`shouldRespondWith` `shouldRespondWith`
[json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|] [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|]
it "succeeds if the table has only PK cols and no other cols" $ do it "succeeds if the table has only PK cols and no other cols" $ do
-- assert that the next request will indeed be an update -- assert that the next request will indeed be an update
+3 -3
View File
@@ -142,8 +142,8 @@ main = do
, ("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 actualPgVersion) , ("Feature.OpenApi.OpenApiSpec" , Feature.OpenApi.OpenApiSpec.spec)
, ("Feature.OptionsSpec" , Feature.OptionsSpec.spec actualPgVersion) , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec)
, ("Feature.Query.AndOrParamsSpec" , Feature.Query.AndOrParamsSpec.spec actualPgVersion) , ("Feature.Query.AndOrParamsSpec" , Feature.Query.AndOrParamsSpec.spec actualPgVersion)
, ("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)
@@ -163,7 +163,7 @@ main = do
, ("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)
, ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec actualPgVersion) , ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec)
] ]
hspec $ do hspec $ do
+9 -11
View File
@@ -685,18 +685,16 @@ INSERT INTO test.car_models(name, year) VALUES ('F310-B',1997);
INSERT INTO test.car_models(name, year) VALUES ('Veneno',2013); INSERT INTO test.car_models(name, year) VALUES ('Veneno',2013);
INSERT INTO test.car_models(name, year) VALUES ('Murcielago',2001); INSERT INTO test.car_models(name, year) VALUES ('Murcielago',2001);
INSERT INTO test.car_brands(name) VALUES ('DMC');
INSERT INTO test.car_brands(name) VALUES ('Ferrari');
INSERT INTO test.car_brands(name) VALUES ('Lamborghini');
UPDATE test.car_models SET car_brand_name = 'DMC' WHERE name = 'DeLorean';
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 = 'Murcielago';
DO $do$BEGIN DO $do$BEGIN
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 ('Ferrari');
INSERT INTO test.car_brands(name) VALUES ('Lamborghini');
UPDATE test.car_models SET car_brand_name = 'DMC' WHERE name = 'DeLorean';
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 = 'Murcielago';
END IF;
IF (SELECT current_setting('server_version_num')::INT >= 120000) THEN 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-14',7,'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-01-15',9,'DeLorean',1981); INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-15',9,'DeLorean',1981);
+20 -37
View File
@@ -1046,19 +1046,14 @@ CREATE FUNCTION setprojects(id_l int, id_h int, name text) RETURNS SETOF project
update test.projects set name = $3 WHERE id >= $1 AND id <= $2 returning *; update test.projects set name = $3 WHERE id >= $1 AND id <= $2 returning *;
$_$; $_$;
-- domains on tables are only supported from pg 11 on CREATE DOMAIN projects_domain AS projects;
DO $do$BEGIN
IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN
CREATE DOMAIN projects_domain AS projects;
CREATE FUNCTION getproject_domain(id int) RETURNS SETOF projects_domain CREATE FUNCTION getproject_domain(id int) RETURNS SETOF projects_domain
LANGUAGE sql LANGUAGE sql
STABLE STABLE
AS $_$ AS $_$
SELECT projects::projects_domain FROM test.projects WHERE id = $1; SELECT projects::projects_domain FROM test.projects WHERE id = $1;
$_$; $_$;
END IF;
END$do$;
create table images ( create table images (
name text not null, name text not null,
@@ -1119,16 +1114,11 @@ create function test.ret_point_overloaded(x json) returns json as $$
select $1; select $1;
$$ language sql; $$ language sql;
-- domains on composite types are only supported from pg 11 on create domain test.composite_domain as test.point_2d;
do $do$begin
if (SELECT current_setting('server_version_num')::int >= 110000) then
create domain test.composite_domain as test.point_2d;
create function test.ret_composite_domain() returns test.composite_domain as $$ create function test.ret_composite_domain() returns test.composite_domain as $$
select row(10, 5)::test.composite_domain; select row(10, 5)::test.composite_domain;
$$ language sql; $$ language sql;
end if;
end$do$;
create type private.point_3d as (x integer, y integer, z integer); create type private.point_3d as (x integer, y integer, z integer);
@@ -2304,17 +2294,14 @@ create table test.car_models_2021 partition of test.car_models
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);
create table test.car_brands (
name varchar(64) primary key
);
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);
do $do$begin do $do$begin
-- primary keys for partitioned tables are supported from pg v11
if (select current_setting('server_version_num')::int >= 110000) then
create table test.car_brands (
name varchar(64) primary key
);
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);
end if;
-- foreign keys referencing partitioned tables are supported from pg v12 -- foreign keys referencing partitioned tables are supported from pg v12
if (select current_setting('server_version_num')::int >= 120000) then if (select current_setting('server_version_num')::int >= 120000) then
create table test.car_model_sales( create table test.car_model_sales(
@@ -2518,12 +2505,8 @@ create table test.arrays (
-- This procedure is to confirm that procedures don't show up in the OpenAPI output right now. -- This procedure is to confirm that procedures don't show up in the OpenAPI output right now.
-- Procedures are not supported, yet. -- Procedures are not supported, yet.
do $do$begin CREATE PROCEDURE test.unsupported_proc ()
if (select current_setting('server_version_num')::int >= 110000) then LANGUAGE SQL AS '';
CREATE PROCEDURE test.unsupported_proc ()
LANGUAGE SQL AS '';
end if;
end $do$;
CREATE FUNCTION public.dummy(int) RETURNS int CREATE FUNCTION public.dummy(int) RETURNS int
LANGUAGE SQL AS $$ SELECT 1 $$; LANGUAGE SQL AS $$ SELECT 1 $$;