diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5ef37153ecb..746aedcc220 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -70,7 +70,7 @@ jobs: strategy: fail-fast: false matrix: - pgVersion: [10, 11, 12, 13, 14, 15, 16] + pgVersion: [11, 12, 13, 14, 15, 16] name: Test PG ${{ matrix.pgVersion }} (Nix) runs-on: ubuntu-latest defaults: diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f865e1fdd..dcfadf80c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,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 ### Deprecated diff --git a/default.nix b/default.nix index 84330cd1f06..2d7aa8b067c 100644 --- a/default.nix +++ b/default.nix @@ -62,7 +62,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/nix/README.md b/nix/README.md index 8016955fd7e..2e90a005bb9 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 49bf26aef95..73040da28e6 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 b7cb19a6803..84a1248313a 100644 --- a/nix/overlays/postgresql-legacy.nix +++ b/nix/overlays/postgresql-legacy.nix @@ -2,21 +2,6 @@ self: super: # 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/App.hs b/src/PostgREST/App.hs index 2ac3f34b9b2..fb575ede82d 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -206,7 +206,7 @@ handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@A (ActionInspect headersOnly, TargetDefaultSpec tSchema) -> do (planTime', iPlan) <- withTiming $ liftEither $ Plan.inspectPlan apiReq - (txTime', oaiResult) <- withTiming $ runQuery roleIsoLvl mempty (Plan.ipTxmode iPlan) $ Query.openApiQuery sCache pgVer conf tSchema + (txTime', oaiResult) <- withTiming $ runQuery roleIsoLvl mempty (Plan.ipTxmode iPlan) $ Query.openApiQuery sCache conf tSchema (respTime', pgrst) <- withTiming $ liftEither $ Response.openApiResponse (T.decodeUtf8 prettyVersion, docsVersion) headersOnly oaiResult conf sCache iSchema iNegotiatedByProfile return $ pgrstResponse (ServerTiming jwtTime parseTime planTime' txTime' respTime') pgrst diff --git a/src/PostgREST/Config/PgVersion.hs b/src/PostgREST/Config/PgVersion.hs index 0b25f8b33f0..8bd074dab2e 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 @@ -30,13 +28,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" - -pgVersion109 :: PgVersion -pgVersion109 = PgVersion 100009 "10.9" +minimumPgVersion = pgVersion110 pgVersion110 :: PgVersion pgVersion110 = PgVersion 110000 "11.0" diff --git a/src/PostgREST/Query.hs b/src/PostgREST/Query.hs index c90cf57fa06..a1637c05e50 100644 --- a/src/PostgREST/Query.hs +++ b/src/PostgREST/Query.hs @@ -172,14 +172,14 @@ invokeQuery rout CallReadPlan{..} apiReq@ApiRequest{iPreferences=Preferences{..} failExceedsMaxAffectedPref (preferMaxAffected,preferHandling) resultSet pure resultSet -openApiQuery :: SchemaCache -> PgVersion -> AppConfig -> Schema -> DbHandler (Maybe (TablesMap, RoutineMap, Maybe Text)) -openApiQuery sCache pgVer AppConfig{..} tSchema = +openApiQuery :: SchemaCache -> AppConfig -> Schema -> DbHandler (Maybe (TablesMap, RoutineMap, Maybe Text)) +openApiQuery sCache AppConfig{..} tSchema = lift $ case configOpenApiMode of OAFollowPriv -> do tableAccess <- SQL.statement [tSchema] (SchemaCache.accessibleTables configDbPreparedStatements) Just <$> ((,,) (HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache) - <$> SQL.statement tSchema (SchemaCache.accessibleFuncs pgVer configDbPreparedStatements) + <$> SQL.statement tSchema (SchemaCache.accessibleFuncs configDbPreparedStatements) <*> SQL.statement tSchema (SchemaCache.schemaDescription configDbPreparedStatements)) OAIgnorePriv -> Just <$> ((,,) diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index 0d51c554842..1729f0dce43 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 523985b6648..d8d36d03c2b 100644 --- a/src/PostgREST/SchemaCache.hs +++ b/src/PostgREST/SchemaCache.hs @@ -48,8 +48,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 (..), @@ -149,11 +148,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 $ allFunctions pgVer prepared + m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared + funcs <- SQL.statement schemas $ 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)" (param HE.int4) HD.noResult prepared in @@ -363,18 +362,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] RoutineMap -allFunctions pgVer = SQL.Statement sql (arrayParam HE.text) decodeFuncs +allFunctions :: Bool -> SQL.Statement [Schema] RoutineMap +allFunctions = SQL.Statement sql (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 RoutineMap -accessibleFuncs pgVer = SQL.Statement sql (param HE.text) decodeFuncs +accessibleFuncs :: Bool -> SQL.Statement Schema RoutineMap +accessibleFuncs = SQL.Statement sql (param 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 ( @@ -462,7 +461,7 @@ funcsSqlQuery pgVer = [q| WHERE setting not LIKE 'default_transaction_isolation%' ) 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 1cba6cda706..0ed33f425aa 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 "/" @@ -275,8 +273,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 ebe1a412e90..e5aa54ab9e2 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 0d6c406d230..019672d3490 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 073cb482495..7ed6d9beab1 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 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 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 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 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 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 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 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 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 "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 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 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] + } 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 a8ad5bba8e4..d095f59d1f4 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 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 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}]|] 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 165eada3e52..db08aa89bb0 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|[]|] - - 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 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 } 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" } ]|] - - 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 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 } ]|] 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 8d10627d436..c1beec53f7e 100644 --- a/test/spec/Main.hs +++ b/test/spec/Main.hs @@ -136,8 +136,8 @@ main = do , ("Feature.Query.EmbedInnerJoinSpec" , Feature.Query.EmbedInnerJoinSpec.spec) , ("Feature.Query.InsertSpec" , Feature.Query.InsertSpec.spec actualPgVersion) , ("Feature.Query.JsonOperatorSpec" , Feature.Query.JsonOperatorSpec.spec actualPgVersion) - , ("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.PgSafeUpdateSpec.disabledSpec" , Feature.Query.PgSafeUpdateSpec.disabledSpec) , ("Feature.Query.PlanSpec.disabledSpec" , Feature.Query.PlanSpec.disabledSpec) , ("Feature.Query.PreferencesSpec" , Feature.Query.PreferencesSpec.spec) @@ -147,7 +147,7 @@ main = do , ("Feature.Query.SingularSpec" , Feature.Query.SingularSpec.spec) , ("Feature.Query.NullsStripSpec" , Feature.Query.NullsStripSpec.spec) , ("Feature.Query.UpdateSpec" , Feature.Query.UpdateSpec.spec) - , ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec actualPgVersion) + , ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec) , ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec) , ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec) , ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec) diff --git a/test/spec/fixtures/data.sql b/test/spec/fixtures/data.sql index b02a05bc4a6..aef27d1f98b 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); -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; +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 >= 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 115f95d7f69..ecc6a6e445c 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -1055,19 +1055,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 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 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; +$_$; create table images ( name text not null, @@ -1128,16 +1123,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); @@ -2313,17 +2303,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); -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 - ); +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; +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 -- 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( @@ -2527,12 +2514,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 $$;