From 126178642b66dd5f5d18b4c3089cc9ab32d27e24 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Sat, 10 Feb 2024 19:05:05 +0100 Subject: [PATCH] feat: Drop support for pg 10 --- .github/workflows/test.yaml | 2 +- CHANGELOG.md | 1 + default.nix | 1 - docs/explanations/install.rst | 6 +- nix/README.md | 24 +-- nix/overlays/postgis.nix | 5 - nix/overlays/postgresql-legacy.nix | 15 -- src/PostgREST/Config/PgVersion.hs | 10 +- src/PostgREST/Query.hs | 4 +- src/PostgREST/Query/QueryBuilder.hs | 9 +- src/PostgREST/SchemaCache.hs | 44 +++-- test/spec/Feature/OpenApi/OpenApiSpec.hs | 9 +- test/spec/Feature/OptionsSpec.hs | 13 +- test/spec/Feature/Query/InsertSpec.hs | 31 ++-- test/spec/Feature/Query/QuerySpec.hs | 217 +++++++++++------------ test/spec/Feature/Query/RpcSpec.hs | 71 ++++---- test/spec/Feature/Query/UpsertSpec.hs | 98 +++++----- test/spec/Main.hs | 6 +- test/spec/fixtures/data.sql | 20 +-- test/spec/fixtures/schema.sql | 57 +++--- 20 files changed, 280 insertions(+), 363 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 96674e231..282ece310 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - pgVersion: [10, 11, 12, 13, 14, 15, 16] + pgVersion: [11, 12, 13, 14, 15, 16] name: PG ${{ matrix.pgVersion }} runs-on: ubuntu-22.04 defaults: diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf29ac04..72da9e4e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther + - #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther ## [12.2.0] - 2024-06-11 diff --git a/default.nix b/default.nix index a432b2f9d..70900caf9 100644 --- a/default.nix +++ b/default.nix @@ -55,7 +55,6 @@ let { 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-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 diff --git a/docs/explanations/install.rst b/docs/explanations/install.rst index e9378dfed..e711a9371 100644 --- a/docs/explanations/install.rst +++ b/docs/explanations/install.rst @@ -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:: - 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/. diff --git a/nix/README.md b/nix/README.md index 8d3087b95..424e18ddc 100644 --- a/nix/README.md +++ b/nix/README.md @@ -75,12 +75,12 @@ The PostgREST utilities available in `nix-shell` all have names that begin with postgrest-build postgrest-test-spec postgrest-check postgrest-watch postgrest-clean postgrest-with-all -postgrest-coverage postgrest-with-postgresql-10 -postgrest-lint postgrest-with-postgresql-11 -postgrest-run postgrest-with-postgresql-12 -postgrest-style postgrest-with-postgresql-13 -postgrest-style-check postgrest-with-postgresql-14 -postgrest-test-io +postgrest-coverage postgrest-with-postgresql-11 +postgrest-lint postgrest-with-postgresql-12 +postgrest-run postgrest-with-postgresql-13 +postgrest-style postgrest-with-postgresql-14 +postgrest-style-check postgrest-with-postgresql-15 +postgrest-test-io postgrest-with-postgresql-16 ... [nix-shell]$ @@ -99,12 +99,12 @@ $ nix-shell --arg memory true postgrest-build postgrest-test-spec postgrest-check postgrest-watch postgrest-clean postgrest-with-all -postgrest-coverage postgrest-with-postgresql-10 -postgrest-lint postgrest-with-postgresql-11 -postgrest-run postgrest-with-postgresql-12 -postgrest-style postgrest-with-postgresql-13 -postgrest-style-check postgrest-with-postgresql-14 -postgrest-test-io +postgrest-coverage postgrest-with-postgresql-11 +postgrest-lint postgrest-with-postgresql-12 +postgrest-run postgrest-with-postgresql-13 +postgrest-style postgrest-with-postgresql-14 +postgrest-style-check postgrest-with-postgresql-15 +postgrest-test-io postgrest-with-postgresql-16 postgrest-test-memory ... diff --git a/nix/overlays/postgis.nix b/nix/overlays/postgis.nix index 49bf26aef..73040da28 100644 --- a/nix/overlays/postgis.nix +++ b/nix/overlays/postgis.nix @@ -15,9 +15,4 @@ in 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); - }; - }; } diff --git a/nix/overlays/postgresql-legacy.nix b/nix/overlays/postgresql-legacy.nix index aeef1a6df..4b90d3d60 100644 --- a/nix/overlays/postgresql-legacy.nix +++ b/nix/overlays/postgresql-legacy.nix @@ -2,21 +2,6 @@ _: _: # Overlay that adds legacy versions of PostgreSQL that are supported by # 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 # https://github.com/NixOS/nixpkgs/commit/1220a4d4dd1a4590780a5e1c18d1333a121be366 # We pin its parent commit to get the last version that was available. diff --git a/src/PostgREST/Config/PgVersion.hs b/src/PostgREST/Config/PgVersion.hs index 855352f3b..73b2f81bd 100644 --- a/src/PostgREST/Config/PgVersion.hs +++ b/src/PostgREST/Config/PgVersion.hs @@ -3,8 +3,6 @@ module PostgREST.Config.PgVersion ( PgVersion(..) , minimumPgVersion - , pgVersion109 - , pgVersion110 , pgVersion112 , pgVersion114 , pgVersion120 @@ -31,13 +29,7 @@ instance Ord PgVersion where -- | Tells the minimum PostgreSQL version required by this version of PostgREST minimumPgVersion :: PgVersion -minimumPgVersion = pgVersion100 - -pgVersion100 :: PgVersion -pgVersion100 = PgVersion 100000 "10" "10" - -pgVersion109 :: PgVersion -pgVersion109 = PgVersion 100009 "10.9" "10.9" +minimumPgVersion = pgVersion110 pgVersion110 :: PgVersion pgVersion110 = PgVersion 110000 "11.0" "11.0" diff --git a/src/PostgREST/Query.hs b/src/PostgREST/Query.hs index 59f5ecf61..cf9833e16 100644 --- a/src/PostgREST/Query.hs +++ b/src/PostgREST/Query.hs @@ -175,13 +175,13 @@ actionQuery (DbCall plan@CallReadPlan{..}) conf@AppConfig{..} apiReq@ApiRequest{ failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) 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 OAFollowPriv -> do tableAccess <- SQL.statement [tSchema] (SchemaCache.accessibleTables configDbPreparedStatements) MaybeDbResult plan . Just <$> ((,,) (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)) OAIgnorePriv -> MaybeDbResult plan . Just <$> ((,,) diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index 0d51c5548..1729f0dce 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -23,8 +23,7 @@ import Data.Maybe (fromJust) import Data.Tree (Tree (..)) import PostgREST.ApiRequest.Preferences (PreferResolution (..)) -import PostgREST.Config.PgVersion (PgVersion, pgVersion110, - pgVersion130) +import PostgREST.Config.PgVersion (PgVersion, pgVersion130) import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..)) import PostgREST.SchemaCache.Relationship (Cardinality (..), Junction (..), @@ -202,9 +201,9 @@ callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar re "LATERAL " <> callIt (fmtParams prms) callIt :: SQL.Snippet -> SQL.Snippet - callIt argument | pgVer < pgVersion130 && pgVer >= pgVersion110 && returnsCompositeAlias = "(SELECT (" <> fromQi qi <> "(" <> argument <> ")).*) pgrst_call" - | returnsScalar || returnsSetOfScalar = "(SELECT " <> fromQi qi <> "(" <> argument <> ") pgrst_scalar) pgrst_call" - | otherwise = 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" + | otherwise = fromQi qi <> "(" <> argument <> ") pgrst_call" fmtParams :: [RoutineParam] -> SQL.Snippet fmtParams prms = intercalateSnippet ", " diff --git a/src/PostgREST/SchemaCache.hs b/src/PostgREST/SchemaCache.hs index a597edda6..a4aeee10d 100644 --- a/src/PostgREST/SchemaCache.hs +++ b/src/PostgREST/SchemaCache.hs @@ -47,8 +47,7 @@ import PostgREST.Config (AppConfig (..)) import PostgREST.Config.Database (TimezoneNames, pgVersionStatement, toIsolationLevel) -import PostgREST.Config.PgVersion (PgVersion, pgVersion110, - pgVersion120) +import PostgREST.Config.PgVersion (PgVersion, pgVersion120) import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName, QualifiedIdentifier (..), RelIdentifier (..), @@ -148,11 +147,11 @@ querySchemaCache AppConfig{..} = do pgVer <- SQL.statement mempty $ pgVersionStatement prepared tabs <- SQL.statement schemas $ allTables pgVer prepared keyDeps <- SQL.statement (schemas, configDbExtraSearchPath) $ allViewsKeyDependencies prepared - m2oRels <- SQL.statement mempty $ allM2OandO2ORels pgVer prepared - funcs <- SQL.statement (schemas, configDbHoistedTxSettings) $ allFunctions pgVer prepared + m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared + funcs <- SQL.statement (schemas, configDbHoistedTxSettings) $ allFunctions prepared cRels <- SQL.statement mempty $ allComputedRels 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 _ <- 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))) |] -allFunctions :: PgVersion -> Bool -> SQL.Statement ([Schema], [Text]) RoutineMap -allFunctions pgVer = SQL.Statement sql (contrazip2 (arrayParam HE.text) (arrayParam HE.text)) decodeFuncs +allFunctions :: Bool -> SQL.Statement ([Schema], [Text]) RoutineMap +allFunctions = SQL.Statement sql (contrazip2 (arrayParam HE.text) (arrayParam HE.text)) decodeFuncs 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 pgVer = SQL.Statement sql (contrazip2 (param HE.text) (arrayParam HE.text)) decodeFuncs +accessibleFuncs :: Bool -> SQL.Statement (Schema, [Text]) RoutineMap +accessibleFuncs = SQL.Statement sql (contrazip2 (param HE.text) (arrayParam HE.text)) decodeFuncs 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 pgVer = [q| +funcsSqlQuery :: SqlQuery +funcsSqlQuery = [q| -- Recursively get the base types of domains WITH base_types AS ( @@ -461,7 +460,7 @@ funcsSqlQuery pgVer = [q| WHERE setting ~ ANY($2) ) func_settings ON 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 = @@ -828,8 +827,8 @@ tablesSqlQuery pgVer = END|] -- | 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 pgVer = +allM2OandO2ORels :: Bool -> SQL.Statement () [Relationship] +allM2OandO2ORels = SQL.Statement sql HE.noParams decodeRels where -- 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 LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid WHERE traint.contype = 'f' - |] <> - (if pgVer >= pgVersion110 - then " and traint.conparentid = 0 " - else mempty) <> - "ORDER BY traint.conrelid, traint.conname" + AND traint.conparentid = 0 + ORDER BY traint.conrelid, traint.conname|] allComputedRels :: Bool -> SQL.Statement () [Relationship] allComputedRels = @@ -1132,8 +1128,8 @@ initialMediaHandlers = HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON) HM.empty -mediaHandlers :: PgVersion -> Bool -> SQL.Statement [Schema] MediaHandlerMap -mediaHandlers pgVer = +mediaHandlers :: Bool -> SQL.Statement [Schema] MediaHandlerMap +mediaHandlers = SQL.Statement sql (arrayParam HE.text) decodeMediaHandlers where sql = [q| @@ -1195,7 +1191,7 @@ mediaHandlers pgVer = join pg_namespace typ_sch on typ_sch.oid = mtype.typnamespace where 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 = diff --git a/test/spec/Feature/OpenApi/OpenApiSpec.hs b/test/spec/Feature/OpenApi/OpenApiSpec.hs index 72275a1f1..215c4b5ac 100644 --- a/test/spec/Feature/OpenApi/OpenApiSpec.hs +++ b/test/spec/Feature/OpenApi/OpenApiSpec.hs @@ -11,14 +11,12 @@ import Network.HTTP.Types import Test.Hspec hiding (pendingWith) import Test.Hspec.Wai -import PostgREST.Config.PgVersion (PgVersion, pgVersion110) - import PostgREST.Version (docsVersion) import Protolude hiding (get) import SpecHelper -spec :: PgVersion -> SpecWith ((), Application) -spec actualPgVersion = describe "OpenAPI" $ do +spec :: SpecWith ((), Application) +spec = describe "OpenAPI" $ do it "root path returns a valid openapi spec" $ do validateOpenApiResponse [("Accept", "application/openapi+json")] request methodHead "/" @@ -290,8 +288,7 @@ spec actualPgVersion = describe "OpenAPI" $ do 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" $ diff --git a/test/spec/Feature/OptionsSpec.hs b/test/spec/Feature/OptionsSpec.hs index ebe1a412e..e5aa54ab9 100644 --- a/test/spec/Feature/OptionsSpec.hs +++ b/test/spec/Feature/OptionsSpec.hs @@ -7,13 +7,11 @@ import Network.HTTP.Types import Test.Hspec import Test.Hspec.Wai -import PostgREST.Config.PgVersion (PgVersion, pgVersion110) - import Protolude import SpecHelper -spec :: PgVersion -> SpecWith ((), Application) -spec actualPgVersion = describe "Allow header" $ do +spec :: SpecWith ((), Application) +spec = describe "Allow header" $ do context "a table" $ do it "includes read/write methods for writeable table" $ do r <- request methodOptions "/items" [] "" @@ -29,12 +27,7 @@ spec actualPgVersion = describe "Allow header" $ do r <- request methodOptions "/car_models" [] "" liftIO $ simpleHeaders r `shouldSatisfy` - matchHeader "Allow" ( - if actualPgVersion >= pgVersion110 then - "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE" - else - "OPTIONS,GET,HEAD,POST,PATCH,DELETE" - ) + matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE" context "a view" $ do context "auto updatable" $ do diff --git a/test/spec/Feature/Query/InsertSpec.hs b/test/spec/Feature/Query/InsertSpec.hs index b6281b4c1..475507db1 100644 --- a/test/spec/Feature/Query/InsertSpec.hs +++ b/test/spec/Feature/Query/InsertSpec.hs @@ -11,9 +11,9 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Text.Heredoc -import PostgREST.Config.PgVersion (PgVersion, pgVersion110, - pgVersion112, pgVersion120, - pgVersion130, pgVersion140) +import PostgREST.Config.PgVersion (PgVersion, pgVersion112, + pgVersion120, pgVersion130, + pgVersion140) import Protolude hiding (get) import SpecHelper @@ -141,19 +141,18 @@ spec actualPgVersion = do , "Preference-Applied" <:> "return=headers-only"] } - when (actualPgVersion >= pgVersion110) $ - it "should not throw and return location header for partitioned tables when selecting without PK" $ - request methodPost "/car_models" - [("Prefer", "return=headers-only")] - [json|{"name":"Enzo","year":2021}|] - `shouldRespondWith` - "" - { matchStatus = 201 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021" - , "Content-Range" <:> "*/*" - , "Preference-Applied" <:> "return=headers-only"] - } + it "should not throw and return location header for partitioned tables when selecting without PK" $ + request methodPost "/car_models" + [("Prefer", "return=headers-only")] + [json|{"name":"Enzo","year":2021}|] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021" + , "Content-Range" <:> "*/*" + , "Preference-Applied" <:> "return=headers-only"] + } context "requesting no representation" $ it "should not throw and return no location header when selecting without PK" $ diff --git a/test/spec/Feature/Query/QuerySpec.hs b/test/spec/Feature/Query/QuerySpec.hs index 3b1da167b..e738dc9d7 100644 --- a/test/spec/Feature/Query/QuerySpec.hs +++ b/test/spec/Feature/Query/QuerySpec.hs @@ -8,8 +8,8 @@ import Test.Hspec hiding (pendingWith) import Test.Hspec.Wai import Test.Hspec.Wai.JSON -import PostgREST.Config.PgVersion (PgVersion, pgVersion110, - pgVersion112, pgVersion121) +import PostgREST.Config.PgVersion (PgVersion, pgVersion112, + pgVersion121) import Protolude hiding (get) import SpecHelper @@ -539,107 +539,106 @@ spec actualPgVersion = do [json|[{"id":1,"computed_overload":true}]|] { matchHeaders = [matchContentTypeJson] } - when (actualPgVersion >= pgVersion110) $ do - describe "partitioned tables embedding" $ do - 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` + describe "partitioned tables embedding" $ do + 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` + [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| - [{"name":"DeLorean","year":1981,"car_brands":{"name":"DMC"}}, - {"name":"Murcielago","year":2001,"car_brands":{"name":"Lamborghini"}}] |] + [{"name":"DeLorean","year":1981,"car_racers":[]}, + {"name":"F310-B","year":1997,"car_racers":[{"name":"Michael Schumacher"}]}] |] { 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` + it "can request a partitioned table as parent from a table" $ + get "/car_racers?select=name,car_models(name,year)&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}]}] |] + [{"name":"Alain Prost","car_models":null}, + {"name":"Michael Schumacher","car_models":{"name":"F310-B","year":1997}}] |] { 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| - [{"name":"DeLorean","year":1981,"car_racers":[]}, - {"name":"F310-B","year":1997,"car_racers":[{"name":"Michael Schumacher"}]}] |] - { matchHeaders = [matchContentTypeJson] } + it "can request partitioned 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` + [json| + [{"name":"DeLorean","year":1981,"car_model_sales":[{"date":"2021-01-14","quantity":7},{"date":"2021-01-15","quantity":9}]}, + {"name":"Murcielago","year":2001,"car_model_sales":[{"date":"2021-02-11","quantity":1},{"date":"2021-02-12","quantity":3}]}, + {"name":"Veneno","year":2013,"car_model_sales":[]}] |] + { matchHeaders = [matchContentTypeJson] } - it "can request a partitioned table as parent from a table" $ - get "/car_racers?select=name,car_models(name,year)&order=name.asc" `shouldRespondWith` - [json| - [{"name":"Alain Prost","car_models":null}, - {"name":"Michael Schumacher","car_models":{"name":"F310-B","year":1997}}] |] - { matchHeaders = [matchContentTypeJson] } + it "can request a partitioned table as parent from a partitioned table" $ do + get "/car_model_sales?date=in.(2021-01-15,2021-02-11)&select=date,quantity,car_models(name,year)&order=date.asc" `shouldRespondWith` + [json| + [{"date":"2021-01-15","quantity":9,"car_models":{"name":"DeLorean","year":1981}}, + {"date":"2021-02-11","quantity":1,"car_models":{"name":"Murcielago","year":2001}}] |] + { matchHeaders = [matchContentTypeJson] } - it "can request partitioned 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` - [json| - [{"name":"DeLorean","year":1981,"car_model_sales":[{"date":"2021-01-14","quantity":7},{"date":"2021-01-15","quantity":9}]}, - {"name":"Murcielago","year":2001,"car_model_sales":[{"date":"2021-02-11","quantity":1},{"date":"2021-02-12","quantity":3}]}, - {"name":"Veneno","year":2013,"car_model_sales":[]}] |] - { matchHeaders = [matchContentTypeJson] } + it "can request many to many relationships between partitioned tables ignoring the intermediate table partitions" $ + get "/car_models?select=name,year,car_dealers(name,city)&order=name.asc&limit=4" `shouldRespondWith` + [json| + [{"name":"DeLorean","year":1981,"car_dealers":[{"name":"Springfield Cars S.A.","city":"Springfield"}]}, + {"name":"F310-B","year":1997,"car_dealers":[]}, + {"name":"Murcielago","year":2001,"car_dealers":[{"name":"The Best Deals S.A.","city":"Franklin"}]}, + {"name":"Veneno","year":2013,"car_dealers":[]}] |] + { matchStatus = 200 + , matchHeaders = [matchContentTypeJson] + } - it "can request a partitioned table as parent from a partitioned table" $ do - get "/car_model_sales?date=in.(2021-01-15,2021-02-11)&select=date,quantity,car_models(name,year)&order=date.asc" `shouldRespondWith` - [json| - [{"date":"2021-01-15","quantity":9,"car_models":{"name":"DeLorean","year":1981}}, - {"date":"2021-02-11","quantity":1,"car_models":{"name":"Murcielago","year":2001}}] |] - { matchHeaders = [matchContentTypeJson] } + it "cannot request partitions as children from a partitioned table" $ + get "/car_models?id=in.(1,2,4)&select=id,name,car_model_sales_202101(id)&order=id.asc" `shouldRespondWith` + [json| + {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.", + "details":"Searched for a foreign key relationship between 'car_models' and 'car_model_sales_202101' in the schema 'test', but no matches were found.", + "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" $ - get "/car_models?select=name,year,car_dealers(name,city)&order=name.asc&limit=4" `shouldRespondWith` - [json| - [{"name":"DeLorean","year":1981,"car_dealers":[{"name":"Springfield Cars S.A.","city":"Springfield"}]}, - {"name":"F310-B","year":1997,"car_dealers":[]}, - {"name":"Murcielago","year":2001,"car_dealers":[{"name":"The Best Deals S.A.","city":"Franklin"}]}, - {"name":"Veneno","year":2013,"car_dealers":[]}] |] - { matchStatus = 200 - , matchHeaders = [matchContentTypeJson] - } + it "cannot request a partitioned table as parent from a partition" $ + get "/car_model_sales_202101?select=id,name,car_models(id,name)&order=id.asc" `shouldRespondWith` + [json| + {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.", + "details":"Searched for a foreign key relationship between 'car_model_sales_202101' and 'car_models' in the schema 'test', but no matches were found.", + "code":"PGRST200", + "message":"Could not find a relationship between 'car_model_sales_202101' and 'car_models' in the schema cache"} |] + { matchStatus = 400 + , matchHeaders = [matchContentTypeJson] + } - it "cannot request partitions as children from a partitioned table" $ - get "/car_models?id=in.(1,2,4)&select=id,name,car_model_sales_202101(id)&order=id.asc" `shouldRespondWith` - [json| - {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.", - "details":"Searched for a foreign key relationship between 'car_models' and 'car_model_sales_202101' in the schema 'test', but no matches were found.", - "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 "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 a partitioned table as parent from a partition" $ - get "/car_model_sales_202101?select=id,name,car_models(id,name)&order=id.asc" `shouldRespondWith` - [json| - {"hint":"Perhaps you meant 'car_model_sales' instead of 'car_model_sales_202101'.", - "details":"Searched for a foreign key relationship between 'car_model_sales_202101' and 'car_models' in the schema 'test', but no matches were found.", - "code":"PGRST200", - "message":"Could not find a relationship between 'car_model_sales_202101' and 'car_models' in the schema cache"} |] - { matchStatus = 400 - , 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] - } + 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 it "can detect fk relations through views to tables in the public schema" $ @@ -1362,22 +1361,19 @@ spec actualPgVersion = do { matchStatus = 400 , matchHeaders = [matchContentTypeJson] } - -- Before PG 11, this will fail because we need arrays of domain type values. The docs should explain data reps are - -- not supported in this case. - when (actualPgVersion >= pgVersion110) $ do - it "uses text parser for filter with 'IN' predicates" $ - get "/datarep_todos?select=id,due_at&label_color=in.(000100,01E240)" `shouldRespondWith` - [json| [ - {"id":2, "due_at": "2018-01-03T00:00:00Z"}, - {"id":3, "due_at": "2018-01-01T14:12:34.123456Z"} - ] |] - { matchHeaders = [matchContentTypeJson] } - it "uses text parser for filter with 'NOT IN' predicates" $ - get "/datarep_todos?select=id,due_at&label_color=not.in.(000000,01E240)" `shouldRespondWith` - [json| [ - {"id":2, "due_at": "2018-01-03T00:00:00Z"} - ] |] - { matchHeaders = [matchContentTypeJson] } + it "uses text parser for filter with 'IN' predicates" $ + get "/datarep_todos?select=id,due_at&label_color=in.(000100,01E240)" `shouldRespondWith` + [json| [ + {"id":2, "due_at": "2018-01-03T00:00:00Z"}, + {"id":3, "due_at": "2018-01-01T14:12:34.123456Z"} + ] |] + { matchHeaders = [matchContentTypeJson] } + it "uses text parser for filter with 'NOT IN' predicates" $ + get "/datarep_todos?select=id,due_at&label_color=not.in.(000000,01E240)" `shouldRespondWith` + [json| [ + {"id":2, "due_at": "2018-01-03T00:00:00Z"} + ] |] + { matchHeaders = [matchContentTypeJson] } 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` [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 -- 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" $ - get "/datarep_todos?select=id,name&label_color=ilike.#*100" `shouldRespondWith` ( - if actualPgVersion >= pgVersion110 then + get "/datarep_todos?select=id,name&label_color=ilike.#*100" `shouldRespondWith` [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"} |] - 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 , matchHeaders = [matchContentTypeJson] } diff --git a/test/spec/Feature/Query/RpcSpec.hs b/test/spec/Feature/Query/RpcSpec.hs index c01150bfb..abe522420 100644 --- a/test/spec/Feature/Query/RpcSpec.hs +++ b/test/spec/Feature/Query/RpcSpec.hs @@ -11,8 +11,7 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Text.Heredoc -import PostgREST.Config.PgVersion (PgVersion, pgVersion109, - pgVersion110, pgVersion112, +import PostgREST.Config.PgVersion (PgVersion, pgVersion112, pgVersion114) import Protolude hiding (get) @@ -395,15 +394,14 @@ spec actualPgVersion = ]|] { matchHeaders = [matchContentTypeJson] } - when (actualPgVersion >= pgVersion110) $ - it "can embed if rpc returns domain of table type" $ do - post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)" - [json| { "id": 1} |] - `shouldRespondWith` - [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)" - `shouldRespondWith` - [json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] + it "can embed if rpc returns domain of table type" $ do + post "/rpc/getproject_domain?select=id,name,client:clients(id),tasks(id)" + [json| { "id": 1} |] + `shouldRespondWith` + [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)" + `shouldRespondWith` + [json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] context "a proc that returns an empty rowset" $ it "returns empty json array" $ do @@ -466,12 +464,11 @@ spec actualPgVersion = it "cannot return composite type in hidden schema" $ post "/rpc/ret_point_3d" [json|{}|] `shouldRespondWith` 401 - when (actualPgVersion >= pgVersion110) $ - it "returns domain of composite type" $ - post "/rpc/ret_composite_domain" - [json|{}|] - `shouldRespondWith` - [json|{"x": 10, "y": 5}|] + it "returns domain of composite type" $ + post "/rpc/ret_composite_domain" + [json|{}|] + `shouldRespondWith` + [json|{"x": 10, "y": 5}|] it "returns single row from table" $ post "/rpc/single_article?select=id" @@ -494,26 +491,25 @@ spec actualPgVersion = `shouldRespondWith` [json|null|] - when (actualPgVersion >= pgVersion110) $ do - it "returns a record type" $ do - post "/rpc/returns_record" - "" - `shouldRespondWith` - [json|{"id":1,"name":"Windows 7","client_id":1}|] - post "/rpc/returns_record_params" - [json|{"id":1, "name": "Windows%"}|] - `shouldRespondWith` - [json|{"id":1,"name":"Windows 7","client_id":1}|] + it "returns a record type" $ do + post "/rpc/returns_record" + "" + `shouldRespondWith` + [json|{"id":1,"name":"Windows 7","client_id":1}|] + post "/rpc/returns_record_params" + [json|{"id":1, "name": "Windows%"}|] + `shouldRespondWith` + [json|{"id":1,"name":"Windows 7","client_id":1}|] - it "returns a setof record type" $ do - post "/rpc/returns_setof_record" - "" - `shouldRespondWith` - [json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|] - post "/rpc/returns_setof_record_params" - [json|{"id":1,"name":"Windows%"}|] - `shouldRespondWith` - [json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|] + it "returns a setof record type" $ do + post "/rpc/returns_setof_record" + "" + `shouldRespondWith` + [json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|] + post "/rpc/returns_setof_record_params" + [json|{"id":1,"name":"Windows%"}|] + `shouldRespondWith` + [json|[{"id":1,"name":"Windows 7","client_id":1},{"id":2,"name":"Windows 10","client_id":1}]|] context "different types when overloaded" $ do it "returns composite type" $ @@ -603,8 +599,7 @@ spec actualPgVersion = [json|"object"|] { matchHeaders = [matchContentTypeJson] } - when ((actualPgVersion >= pgVersion109 && actualPgVersion < pgVersion110) - || actualPgVersion >= pgVersion114) $ + when (actualPgVersion >= pgVersion114) $ it "parses quoted JSON arguments as JSON string (from Postgres 10.9, 11.4)" $ post "/rpc/json_argument" [json| { "arg": "{ \"key\": 3 }" } |] diff --git a/test/spec/Feature/Query/UpsertSpec.hs b/test/spec/Feature/Query/UpsertSpec.hs index 165eada3e..db08aa89b 100644 --- a/test/spec/Feature/Query/UpsertSpec.hs +++ b/test/spec/Feature/Query/UpsertSpec.hs @@ -7,13 +7,11 @@ import Test.Hspec import Test.Hspec.Wai import Test.Hspec.Wai.JSON -import PostgREST.Config.PgVersion (PgVersion, pgVersion110) - import Protolude hiding (get, put) import SpecHelper -spec :: PgVersion -> SpecWith ((), Application) -spec actualPgVersion = +spec :: SpecWith ((), Application) +spec = describe "UPSERT" $ do context "with POST" $ 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] } - when (actualPgVersion >= pgVersion110) $ - it "INSERTs and UPDATEs rows on composite pk conflict for partitioned tables" $ - request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] - [json| [ - { "name": "Murcielago", "year": 2001, "car_brand_name": null}, - { "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" } - ]|] `shouldRespondWith` [json| [ - { "name": "Murcielago", "year": 2001, "car_brand_name": null}, - { "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" } - ]|] - { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson] - } + it "INSERTs and UPDATEs rows on composite pk conflict for partitioned tables" $ + request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] + [json| [ + { "name": "Murcielago", "year": 2001, "car_brand_name": null}, + { "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" } + ]|] `shouldRespondWith` [json| [ + { "name": "Murcielago", "year": 2001, "car_brand_name": null}, + { "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" } + ]|] + { matchStatus = 201 + , matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson] + } it "succeeds when the payload has no elements" $ 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] } - when (actualPgVersion >= pgVersion110) $ - it "INSERTs and ignores rows on composite pk conflict for partitioned tables" $ - request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] - [json| [ - { "name": "Murcielago", "year": 2001, "car_brand_name": "Ferrari" }, - { "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" } - ]|] `shouldRespondWith` [json| [ - { "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" } - ]|] - { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson] - } + it "INSERTs and ignores rows on composite pk conflict for partitioned tables" $ + request methodPost "/car_models" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] + [json| [ + { "name": "Murcielago", "year": 2001, "car_brand_name": "Ferrari" }, + { "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" } + ]|] `shouldRespondWith` [json| [ + { "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" } + ]|] + { matchStatus = 201 + , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson] + } it "INSERTs and ignores rows on single unique key conflict" $ 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" } ]|] { matchStatus = 201 } - when (actualPgVersion >= pgVersion110) $ - it "succeeds on a partitioned table with composite pk" $ do - -- assert that the next request will indeed be an insert - get "/car_models?name=eq.Supra&year=eq.2021" - `shouldRespondWith` - [json|[]|] + it "succeeds on a partitioned table with composite pk" $ do + -- assert that the next request will indeed be an insert + get "/car_models?name=eq.Supra&year=eq.2021" + `shouldRespondWith` + [json|[]|] - request methodPut "/car_models?name=eq.Supra&year=eq.2021" - [("Prefer", "return=representation")] - [json| [ { "name": "Supra", "year": 2021 } ]|] - `shouldRespondWith` - [json| [ { "name": "Supra", "year": 2021, "car_brand_name": null } ]|] - { matchStatus = 201 } + request methodPut "/car_models?name=eq.Supra&year=eq.2021" + [("Prefer", "return=representation")] + [json| [ { "name": "Supra", "year": 2021 } ]|] + `shouldRespondWith` + [json| [ { "name": "Supra", "year": 2021, "car_brand_name": null } ]|] + { matchStatus = 201 } it "succeeds if the table has only PK cols and no other cols" $ do -- assert that the next request will indeed be an insert @@ -378,18 +373,17 @@ spec actualPgVersion = `shouldRespondWith` [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 - -- assert that the next request will indeed be an update - get "/car_models?name=eq.DeLorean&year=eq.1981" - `shouldRespondWith` - [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": "DMC" } ]|] + it "succeeds on a partitioned table with composite pk" $ do + -- assert that the next request will indeed be an update + get "/car_models?name=eq.DeLorean&year=eq.1981" + `shouldRespondWith` + [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": "DMC" } ]|] - request methodPut "/car_models?name=eq.DeLorean&year=eq.1981" - [("Prefer", "return=representation")] - [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|] - `shouldRespondWith` - [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|] + request methodPut "/car_models?name=eq.DeLorean&year=eq.1981" + [("Prefer", "return=representation")] + [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|] + `shouldRespondWith` + [json| [ { "name": "DeLorean", "year": 1981, "car_brand_name": null } ]|] it "succeeds if the table has only PK cols and no other cols" $ do -- assert that the next request will indeed be an update diff --git a/test/spec/Main.hs b/test/spec/Main.hs index be982d93a..fde51a4c1 100644 --- a/test/spec/Main.hs +++ b/test/spec/Main.hs @@ -142,8 +142,8 @@ main = do , ("Feature.CorsSpec" , Feature.CorsSpec.spec) , ("Feature.CustomMediaSpec" , Feature.Query.CustomMediaSpec.spec) , ("Feature.NoSuperuserSpec" , Feature.NoSuperuserSpec.spec) - , ("Feature.OpenApi.OpenApiSpec" , Feature.OpenApi.OpenApiSpec.spec actualPgVersion) - , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec actualPgVersion) + , ("Feature.OpenApi.OpenApiSpec" , Feature.OpenApi.OpenApiSpec.spec) + , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec) , ("Feature.Query.AndOrParamsSpec" , Feature.Query.AndOrParamsSpec.spec actualPgVersion) , ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec) , ("Feature.Query.DeleteSpec" , Feature.Query.DeleteSpec.spec) @@ -163,7 +163,7 @@ main = do , ("Feature.Query.SingularSpec" , Feature.Query.SingularSpec.spec) , ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.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 diff --git a/test/spec/fixtures/data.sql b/test/spec/fixtures/data.sql index b02a05bc4..aef27d1f9 100644 --- a/test/spec/fixtures/data.sql +++ b/test/spec/fixtures/data.sql @@ -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 ('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 - 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 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); diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index 2fcb81b56..3ad0505d3 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -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 *; $_$; --- domains on tables are only supported from pg 11 on -DO $do$BEGIN - IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN - CREATE DOMAIN projects_domain AS projects; +CREATE DOMAIN projects_domain AS projects; - CREATE FUNCTION getproject_domain(id int) RETURNS SETOF projects_domain - LANGUAGE sql - STABLE - AS $_$ - SELECT projects::projects_domain FROM test.projects WHERE id = $1; - $_$; - END IF; -END$do$; +CREATE FUNCTION getproject_domain(id int) RETURNS SETOF projects_domain + LANGUAGE sql + STABLE + AS $_$ + SELECT projects::projects_domain FROM test.projects WHERE id = $1; +$_$; create table images ( name text not null, @@ -1119,16 +1114,11 @@ create function test.ret_point_overloaded(x json) returns json as $$ select $1; $$ language sql; --- domains on composite types are only supported from pg 11 on -do $do$begin - if (SELECT current_setting('server_version_num')::int >= 110000) then - create domain test.composite_domain as test.point_2d; +create domain test.composite_domain as test.point_2d; - create function test.ret_composite_domain() returns test.composite_domain as $$ - select row(10, 5)::test.composite_domain; - $$ language sql; - end if; -end$do$; +create function test.ret_composite_domain() returns test.composite_domain as $$ + select row(10, 5)::test.composite_domain; +$$ language sql; 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 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 - -- 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 if (select current_setting('server_version_num')::int >= 120000) then 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. -- Procedures are not supported, yet. -do $do$begin - if (select current_setting('server_version_num')::int >= 110000) then - CREATE PROCEDURE test.unsupported_proc () - LANGUAGE SQL AS ''; - end if; -end $do$; +CREATE PROCEDURE test.unsupported_proc () +LANGUAGE SQL AS ''; CREATE FUNCTION public.dummy(int) RETURNS int LANGUAGE SQL AS $$ SELECT 1 $$;