Skip to content

Commit

Permalink
feat: Drop support for pg 11
Browse files Browse the repository at this point in the history
PostgreSQL 11 is EOL since November 2023.
  • Loading branch information
wolfgangwalther committed May 5, 2024
1 parent 0ecd71f commit 7a8971c
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 218 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pgVersion: [11, 12, 13, 14, 15, 16]
pgVersion: [12, 13, 14, 15, 16]
name: PG ${{ matrix.pgVersion }}
runs-on: ubuntu-22.04
defaults:
Expand Down
2 changes: 0 additions & 2 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ let
allOverlays.postgresql-libpq
allOverlays.postgresql-legacy
allOverlays.postgresql-future
allOverlays.postgis
(allOverlays.haskell-packages { inherit compiler; })
allOverlays.slocat
];
Expand All @@ -55,7 +54,6 @@ let
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
];

# Dynamic derivation for PostgREST
Expand Down
1 change: 0 additions & 1 deletion nix/overlays/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
checked-shell-script = import ./checked-shell-script;
gitignore = import ./gitignore.nix;
haskell-packages = import ./haskell-packages.nix;
postgis = import ./postgis.nix;
postgresql-libpq = import ./postgresql-libpq.nix;
postgresql-legacy = import ./postgresql-legacy.nix;
postgresql-future = import ./postgresql-future.nix;
Expand Down
18 changes: 0 additions & 18 deletions nix/overlays/postgis.nix

This file was deleted.

17 changes: 1 addition & 16 deletions nix/overlays/postgresql-legacy.nix
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
_: _:
# Overlay that adds legacy versions of PostgreSQL that are supported by
# PostgREST.
{
# PostgreSQL 11 was removed from Nixpkgs with
# https://github.com/NixOS/nixpkgs/commit/1220a4d4dd1a4590780a5e1c18d1333a121be366
# We pin its parent commit to get the last version that was available.
postgresql_11 =
let
rev = "f5458516e42cc5cb4123cc2d93f45c240548aa18";
tarballHash = "1h03621sxfhw4z6ya74k6c2lyx3z7pvf2jcg4vs7i01yz2m6w3cv";
pinnedPkgs =
builtins.fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
sha256 = tarballHash;
};
in
(import pinnedPkgs { }).pkgs.postgresql_11;
}
{ }
10 changes: 1 addition & 9 deletions src/PostgREST/Config/PgVersion.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
module PostgREST.Config.PgVersion
( PgVersion(..)
, minimumPgVersion
, pgVersion120
, pgVersion121
, pgVersion130
, pgVersion140
Expand All @@ -27,14 +26,7 @@ instance Ord PgVersion where

-- | Tells the minimum PostgreSQL version required by this version of PostgREST
minimumPgVersion :: PgVersion
minimumPgVersion = pgVersion11

-- PostgreSQL 11 is EOL already, so we only allow the last
-- minor release as the minimum version. Theoretically. But
-- the version we are using from legacy nix is only 11.21,
-- so we are happy with that.
pgVersion11 :: PgVersion
pgVersion11 = PgVersion 110021 "11.21" "11.21"
minimumPgVersion = pgVersion120

pgVersion120 :: PgVersion
pgVersion120 = PgVersion 120000 "12.0" "12.0"
Expand Down
40 changes: 13 additions & 27 deletions src/PostgREST/SchemaCache.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ import Text.InterpolatedString.Perl6 (q)

import PostgREST.Config (AppConfig (..))
import PostgREST.Config.Database (TimezoneNames,
pgVersionStatement,
toIsolationLevel)
import PostgREST.Config.PgVersion (PgVersion, pgVersion120)
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
QualifiedIdentifier (..),
RelIdentifier (..),
Expand Down Expand Up @@ -144,8 +142,7 @@ type SqlQuery = ByteString
querySchemaCache :: AppConfig -> SQL.Transaction SchemaCache
querySchemaCache AppConfig{..} = do
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
pgVer <- SQL.statement mempty $ pgVersionStatement prepared
tabs <- SQL.statement schemas $ allTables pgVer prepared
tabs <- SQL.statement schemas $ allTables prepared
keyDeps <- SQL.statement (schemas, configDbExtraSearchPath) $ allViewsKeyDependencies prepared
m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared
funcs <- SQL.statement schemas $ allFunctions prepared
Expand Down Expand Up @@ -601,15 +598,13 @@ addViewPrimaryKeys tabs keyDeps =
-- * We need to choose a single reference for each column, otherwise we'd output too many columns in location headers etc.
takeFirstPK = mapMaybe (head . snd)

allTables :: PgVersion -> Bool -> SQL.Statement [Schema] TablesMap
allTables pgVer =
SQL.Statement sql (arrayParam HE.text) decodeTables
where
sql = tablesSqlQuery pgVer
allTables :: Bool -> SQL.Statement [Schema] TablesMap
allTables =
SQL.Statement tablesSqlQuery (arrayParam HE.text) decodeTables

-- | Gets tables with their PK cols
tablesSqlQuery :: PgVersion -> SqlQuery
tablesSqlQuery pgVer =
tablesSqlQuery :: SqlQuery
tablesSqlQuery =
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
-- we cannot use those directly as they include the following privilege filter:
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
Expand All @@ -623,7 +618,13 @@ tablesSqlQuery pgVer =
c.relname::name AS table_name,
a.attname::name AS column_name,
d.description AS description,
|] <> columnDefault <> [q| AS column_default,
-- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
CASE
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
WHEN a.attgenerated = 's' THEN null
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END AS column_default,
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
CASE
WHEN t.typtype = 'd' THEN
Expand Down Expand Up @@ -809,21 +810,6 @@ tablesSqlQuery pgVer =
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
AND not c.relispartition
ORDER BY table_schema, table_name|]
where
columnDefault -- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
| pgVer >= pgVersion120 = [q|
CASE
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
WHEN a.attgenerated = 's' THEN null
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END|]
| otherwise = [q|
CASE
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END|]

-- | Gets many-to-one relationships and one-to-one(O2O) relationships, which are a refinement of the many-to-one's
allM2OandO2ORels :: Bool -> SQL.Statement () [Relationship]
Expand Down
47 changes: 23 additions & 24 deletions test/spec/Feature/Query/InsertSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Text.Heredoc

import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
pgVersion130, pgVersion140)
import PostgREST.Config.PgVersion (PgVersion, pgVersion130,
pgVersion140)

import Protolude hiding (get)
import SpecHelper
Expand Down Expand Up @@ -541,28 +541,27 @@ spec actualPgVersion = do
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}

when (actualPgVersion >= pgVersion120) $
it "fails with a good error message on generated always columns" $
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
[json| [
{"a": "val"},
{"a": "val", "b": "val"}
]|]
`shouldRespondWith`
(if actualPgVersion < pgVersion140
then [json| {
"code": "42601",
"details": "Column \"b\" is a generated column.",
"hint": null,
"message": "cannot insert into column \"b\""
}|]
else [json| {
"code": "428C9",
"details": "Column \"b\" is a generated column.",
"hint": null,
"message": "cannot insert a non-DEFAULT value into column \"b\""
}|])
{ matchStatus = 400 }
it "fails with a good error message on generated always columns" $
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
[json| [
{"a": "val"},
{"a": "val", "b": "val"}
]|]
`shouldRespondWith`
(if actualPgVersion < pgVersion140
then [json| {
"code": "42601",
"details": "Column \"b\" is a generated column.",
"hint": null,
"message": "cannot insert into column \"b\""
}|]
else [json| {
"code": "428C9",
"details": "Column \"b\" is a generated column.",
"hint": null,
"message": "cannot insert a non-DEFAULT value into column \"b\""
}|])
{ matchStatus = 400 }

it "inserts a default on a DOMAIN with default" $
request methodPost "/evil_friends?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
Expand Down
52 changes: 18 additions & 34 deletions test/spec/Feature/Query/PlanSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON

import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
pgVersion130)
import PostgREST.Config.PgVersion (PgVersion, pgVersion130)
import Protolude hiding (get)
import SpecHelper

Expand All @@ -34,10 +33,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
totalCost `shouldBe`
if actualPgVersion > pgVersion120
then 15.63
else 15.69
totalCost `shouldBe` 15.63

it "outputs the total cost for a single filter on a view" $ do
r <- request methodGet "/projects_view?id=gt.2"
Expand All @@ -50,10 +46,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; charset=utf-8")
resStatus `shouldBe` Status { statusCode = 200, statusMessage="OK" }
totalCost `shouldBe`
if actualPgVersion > pgVersion120
then 24.28
else 32.27
totalCost `shouldBe` 24.28

it "outputs blocks info when using the buffers option" $
if actualPgVersion >= pgVersion130
Expand All @@ -77,21 +70,20 @@ spec actualPgVersion = do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=analyze|buffers; charset=utf-8")
blocks `shouldBe` Just [aesonQQ| 1.0 |]

when (actualPgVersion >= pgVersion120) $
it "outputs the search path when using the settings option" $ do
r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=settings") ""
it "outputs the search path when using the settings option" $ do
r <- request methodGet "/projects" (acceptHdrs "application/vnd.pgrst.plan+json; options=settings") ""

let searchPath = simpleBody r ^? nth 0 . key "Settings"
resHeaders = simpleHeaders r
let searchPath = simpleBody r ^? nth 0 . key "Settings"
resHeaders = simpleHeaders r

liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=settings; charset=utf-8")
searchPath `shouldBe`
Just [aesonQQ|
{
"search_path": "\"test\""
}
|]
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=settings; charset=utf-8")
searchPath `shouldBe`
Just [aesonQQ|
{
"search_path": "\"test\""
}
|]

when (actualPgVersion >= pgVersion130) $
it "outputs WAL info when using the wal option" $ do
Expand Down Expand Up @@ -123,9 +115,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=verbose; charset=utf-8")
aggCol `shouldBe`
if actualPgVersion >= pgVersion120
then Just [aesonQQ| "COALESCE(json_agg(ROW(projects.id, projects.name, projects.client_id)), '[]'::json)" |]
else Just [aesonQQ| "COALESCE(json_agg(ROW(pgrst_source.id, pgrst_source.name, pgrst_source.client_id)), '[]'::json)" |]
Just [aesonQQ| "COALESCE(json_agg(ROW(projects.id, projects.name, projects.client_id)), '[]'::json)" |]

it "outputs the plan for application/vnd.pgrst.object " $ do
r <- request methodGet "/projects_view" (acceptHdrs "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object\"; options=verbose") ""
Expand All @@ -136,9 +126,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.pgrst.object+json\"; options=verbose; charset=utf-8")
aggCol `shouldBe`
if actualPgVersion >= pgVersion120
then Just [aesonQQ| "COALESCE((json_agg(ROW(projects.id, projects.name, projects.client_id)) -> 0), 'null'::json)" |]
else Just [aesonQQ| "COALESCE((json_agg(ROW(pgrst_source.id, pgrst_source.name, pgrst_source.client_id)) -> 0), 'null'::json)" |]
Just [aesonQQ| "COALESCE((json_agg(ROW(projects.id, projects.name, projects.client_id)) -> 0), 'null'::json)" |]

describe "writes plans" $ do
it "outputs the total cost for an insert" $ do
Expand Down Expand Up @@ -452,11 +440,7 @@ spec actualPgVersion = do
liftIO $ do
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/vnd.twkb\"; options=verbose; charset=utf-8")
aggCol `shouldBe`
(
if actualPgVersion >= pgVersion120
then Just [aesonQQ| "twkb_agg(ROW(lines.id, lines.name, lines.geom)::lines)" |]
else Just [aesonQQ| "twkb_agg(ROW(pgrst_source.id, pgrst_source.name, pgrst_source.geom)::lines)" |]
)
Just [aesonQQ| "twkb_agg(ROW(lines.id, lines.name, lines.geom)::lines)" |]

disabledSpec :: SpecWith ((), Application)
disabledSpec =
Expand Down
30 changes: 13 additions & 17 deletions test/spec/fixtures/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -694,23 +694,19 @@ UPDATE test.car_models SET car_brand_name = 'Ferrari' WHERE name = 'F310-B';
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Veneno';
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = '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);
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-11',1,'Murcielago',2001);
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-12',3,'Murcielago',2001);
INSERT INTO test.car_racers(name) VALUES ('Alain Prost');
INSERT INTO test.car_racers(name, car_model_name, car_model_year) VALUES ('Michael Schumacher', 'F310-B', 1997);
INSERT INTO test.car_dealers(name,city) VALUES ('Springfield Cars S.A.','Springfield');
INSERT INTO test.car_dealers(name,city) VALUES ('The Best Deals S.A.','Franklin');
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('DeLorean',1981,'Springfield Cars S.A.','Springfield',15);
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('Murcielago',2001,'The Best Deals S.A.','Franklin',2);
END IF;
END$do$;
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-14',7,'DeLorean',1981);
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-15',9,'DeLorean',1981);
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-11',1,'Murcielago',2001);
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-12',3,'Murcielago',2001);
INSERT INTO test.car_racers(name) VALUES ('Alain Prost');
INSERT INTO test.car_racers(name, car_model_name, car_model_year) VALUES ('Michael Schumacher', 'F310-B', 1997);
INSERT INTO test.car_dealers(name,city) VALUES ('Springfield Cars S.A.','Springfield');
INSERT INTO test.car_dealers(name,city) VALUES ('The Best Deals S.A.','Franklin');
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('DeLorean',1981,'Springfield Cars S.A.','Springfield',15);
INSERT INTO test.car_models_car_dealers(car_model_name, car_model_year, car_dealer_name, car_dealer_city, quantity) VALUES ('Murcielago',2001,'The Best Deals S.A.','Franklin',2);
TRUNCATE TABLE test.products CASCADE;
INSERT INTO test.products (id, name) VALUES (1,'product-1'), (2,'product-2'), (3,'product-3');
Expand Down
Loading

0 comments on commit 7a8971c

Please sign in to comment.