Skip to content

Commit 6eef0c8

Browse files
feat: Drop support for pg 11
PostgreSQL 11 is EOL since November 2023.
1 parent e624db8 commit 6eef0c8

File tree

10 files changed

+128
-202
lines changed

10 files changed

+128
-202
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
strategy:
7171
fail-fast: false
7272
matrix:
73-
pgVersion: [11, 12, 13, 14, 15, 16]
73+
pgVersion: [12, 13, 14, 15, 16]
7474
name: Test PG ${{ matrix.pgVersion }} (Nix)
7575
runs-on: ubuntu-latest
7676
defaults:

default.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ let
6060
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
6161
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
6262
{ name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
63-
{ name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
6463
];
6564

6665
# Dynamic derivation for PostgREST

nix/overlays/default.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
checked-shell-script = import ./checked-shell-script;
44
gitignore = import ./gitignore.nix;
55
haskell-packages = import ./haskell-packages.nix;
6-
postgis = import ./postgis.nix;
76
postgresql-libpq = import ./postgresql-libpq.nix;
87
postgresql-legacy = import ./postgresql-legacy.nix;
98
postgresql-future = import ./postgresql-future.nix;

nix/overlays/postgresql-legacy.nix

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
11
self: super:
22
# Overlay that adds legacy versions of PostgreSQL that are supported by
33
# PostgREST.
4-
{
5-
# PostgreSQL 11 was removed from Nixpkgs with
6-
# https://github.com/NixOS/nixpkgs/commit/1220a4d4dd1a4590780a5e1c18d1333a121be366.
7-
# However, postgis was updated to 3.4.0 five months before at
8-
# https://github.com/NixOS/nixpkgs/commit/dfde9c83bce9e6c2bc903dfc1bca3bf93b3f52de.
9-
# Since postgis 3.4.0 doesn't support v11 anymore, we pin the last commit with
10-
# postgis 3.3.3.
11-
postgresql_11 =
12-
let
13-
rev = "0b458fbc462ced7fc03c8b68cf1b1bd0d92af465";
14-
tarballHash = "1vapq800crshsrvypckldrzy3ss0gzbb9mxlh3yajmdg702hcvrf";
15-
pinnedPkgs =
16-
builtins.fetchTarball {
17-
url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz";
18-
sha256 = tarballHash;
19-
};
20-
in
21-
(import pinnedPkgs { }).pkgs.postgresql_11;
22-
}
4+
{ }

src/PostgREST/Config/PgVersion.hs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module PostgREST.Config.PgVersion
44
( PgVersion(..)
55
, minimumPgVersion
6-
, pgVersion120
76
, pgVersion121
87
, pgVersion130
98
, pgVersion140
@@ -26,14 +25,7 @@ instance Ord PgVersion where
2625

2726
-- | Tells the minimum PostgreSQL version required by this version of PostgREST
2827
minimumPgVersion :: PgVersion
29-
minimumPgVersion = pgVersion11
30-
31-
-- PostgreSQL 11 is EOL already, so we only allow the last
32-
-- minor release as the minimum version. Theoretically. But
33-
-- the version we are using from legacy nix is only 11.21,
34-
-- so we are happy with that.
35-
pgVersion11 :: PgVersion
36-
pgVersion11 = PgVersion 110021 "11.21"
28+
minimumPgVersion = pgVersion120
3729

3830
pgVersion120 :: PgVersion
3931
pgVersion120 = PgVersion 120000 "12.0"

src/PostgREST/SchemaCache.hs

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ import Text.InterpolatedString.Perl6 (q)
4646

4747
import PostgREST.Config (AppConfig (..))
4848
import PostgREST.Config.Database (TimezoneNames,
49-
pgVersionStatement,
5049
toIsolationLevel)
51-
import PostgREST.Config.PgVersion (PgVersion, pgVersion120)
5250
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
5351
QualifiedIdentifier (..),
5452
RelIdentifier (..),
@@ -145,8 +143,7 @@ type SqlQuery = ByteString
145143
querySchemaCache :: AppConfig -> SQL.Transaction SchemaCache
146144
querySchemaCache AppConfig{..} = do
147145
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
148-
pgVer <- SQL.statement mempty $ pgVersionStatement prepared
149-
tabs <- SQL.statement schemas $ allTables pgVer prepared
146+
tabs <- SQL.statement schemas $ allTables prepared
150147
keyDeps <- SQL.statement (schemas, configDbExtraSearchPath) $ allViewsKeyDependencies prepared
151148
m2oRels <- SQL.statement mempty $ allM2OandO2ORels prepared
152149
funcs <- SQL.statement schemas $ allFunctions prepared
@@ -602,15 +599,13 @@ addViewPrimaryKeys tabs keyDeps =
602599
-- * We need to choose a single reference for each column, otherwise we'd output too many columns in location headers etc.
603600
takeFirstPK = mapMaybe (head . snd)
604601

605-
allTables :: PgVersion -> Bool -> SQL.Statement [Schema] TablesMap
606-
allTables pgVer =
607-
SQL.Statement sql (arrayParam HE.text) decodeTables
608-
where
609-
sql = tablesSqlQuery pgVer
602+
allTables :: Bool -> SQL.Statement [Schema] TablesMap
603+
allTables =
604+
SQL.Statement tablesSqlQuery (arrayParam HE.text) decodeTables
610605

611606
-- | Gets tables with their PK cols
612-
tablesSqlQuery :: PgVersion -> SqlQuery
613-
tablesSqlQuery pgVer =
607+
tablesSqlQuery :: SqlQuery
608+
tablesSqlQuery =
614609
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
615610
-- we cannot use those directly as they include the following privilege filter:
616611
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
@@ -624,7 +619,13 @@ tablesSqlQuery pgVer =
624619
c.relname::name AS table_name,
625620
a.attname::name AS column_name,
626621
d.description AS description,
627-
|] <> columnDefault <> [q| AS column_default,
622+
-- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
623+
CASE
624+
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
625+
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
626+
WHEN a.attgenerated = 's' THEN null
627+
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
628+
END AS column_default,
628629
not (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
629630
CASE
630631
WHEN t.typtype = 'd' THEN
@@ -810,21 +811,6 @@ tablesSqlQuery pgVer =
810811
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
811812
AND not c.relispartition
812813
ORDER BY table_schema, table_name|]
813-
where
814-
columnDefault -- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
815-
| pgVer >= pgVersion120 = [q|
816-
CASE
817-
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
818-
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
819-
WHEN a.attgenerated = 's' THEN null
820-
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
821-
END|]
822-
| otherwise = [q|
823-
CASE
824-
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
825-
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
826-
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
827-
END|]
828814

829815
-- | Gets many-to-one relationships and one-to-one(O2O) relationships, which are a refinement of the many-to-one's
830816
allM2OandO2ORels :: Bool -> SQL.Statement () [Relationship]

test/spec/Feature/Query/InsertSpec.hs

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import Test.Hspec.Wai
1111
import Test.Hspec.Wai.JSON
1212
import Text.Heredoc
1313

14-
import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
15-
pgVersion130, pgVersion140)
14+
import PostgREST.Config.PgVersion (PgVersion, pgVersion130,
15+
pgVersion140)
1616

1717
import Protolude hiding (get)
1818
import SpecHelper
@@ -525,28 +525,27 @@ spec actualPgVersion = do
525525
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
526526
}
527527

528-
when (actualPgVersion >= pgVersion120) $
529-
it "fails with a good error message on generated always columns" $
530-
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
531-
[json| [
532-
{"a": "val"},
533-
{"a": "val", "b": "val"}
534-
]|]
535-
`shouldRespondWith`
536-
(if actualPgVersion < pgVersion140
537-
then [json| {
538-
"code": "42601",
539-
"details": "Column \"b\" is a generated column.",
540-
"hint": null,
541-
"message": "cannot insert into column \"b\""
542-
}|]
543-
else [json| {
544-
"code": "428C9",
545-
"details": "Column \"b\" is a generated column.",
546-
"hint": null,
547-
"message": "cannot insert a non-DEFAULT value into column \"b\""
548-
}|])
549-
{ matchStatus = 400 }
528+
it "fails with a good error message on generated always columns" $
529+
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
530+
[json| [
531+
{"a": "val"},
532+
{"a": "val", "b": "val"}
533+
]|]
534+
`shouldRespondWith`
535+
(if actualPgVersion < pgVersion140
536+
then [json| {
537+
"code": "42601",
538+
"details": "Column \"b\" is a generated column.",
539+
"hint": null,
540+
"message": "cannot insert into column \"b\""
541+
}|]
542+
else [json| {
543+
"code": "428C9",
544+
"details": "Column \"b\" is a generated column.",
545+
"hint": null,
546+
"message": "cannot insert a non-DEFAULT value into column \"b\""
547+
}|])
548+
{ matchStatus = 400 }
550549

551550
it "inserts a default on a DOMAIN with default" $
552551
request methodPost "/evil_friends?columns=id,name" [("Prefer", "return=representation"), ("Prefer", "missing=default")]

test/spec/Feature/Query/PlanSpec.hs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import Test.Hspec hiding (pendingWith)
1515
import Test.Hspec.Wai
1616
import Test.Hspec.Wai.JSON
1717

18-
import PostgREST.Config.PgVersion (PgVersion, pgVersion120,
19-
pgVersion130)
18+
import PostgREST.Config.PgVersion (PgVersion, pgVersion130)
2019
import Protolude hiding (get)
2120
import SpecHelper
2221

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

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

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

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

84-
let searchPath = simpleBody r ^? nth 0 . key "Settings"
85-
resHeaders = simpleHeaders r
76+
let searchPath = simpleBody r ^? nth 0 . key "Settings"
77+
resHeaders = simpleHeaders r
8678

87-
liftIO $ do
88-
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=settings; charset=utf-8")
89-
searchPath `shouldBe`
90-
Just [aesonQQ|
91-
{
92-
"search_path": "\"test\""
93-
}
94-
|]
79+
liftIO $ do
80+
resHeaders `shouldSatisfy` elem ("Content-Type", "application/vnd.pgrst.plan+json; for=\"application/json\"; options=settings; charset=utf-8")
81+
searchPath `shouldBe`
82+
Just [aesonQQ|
83+
{
84+
"search_path": "\"test\""
85+
}
86+
|]
9587

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

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

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

461445
disabledSpec :: SpecWith ((), Application)
462446
disabledSpec =

test/spec/fixtures/data.sql

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -694,23 +694,19 @@ UPDATE test.car_models SET car_brand_name = 'Ferrari' WHERE name = 'F310-B';
694694
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Veneno';
695695
UPDATE test.car_models SET car_brand_name = 'Lamborghini' WHERE name = 'Murcielago';
696696
697-
DO $do$BEGIN
698-
IF (SELECT current_setting('server_version_num')::INT >= 120000) THEN
699-
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-14',7,'DeLorean',1981);
700-
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-15',9,'DeLorean',1981);
701-
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-11',1,'Murcielago',2001);
702-
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-12',3,'Murcielago',2001);
703-
704-
INSERT INTO test.car_racers(name) VALUES ('Alain Prost');
705-
INSERT INTO test.car_racers(name, car_model_name, car_model_year) VALUES ('Michael Schumacher', 'F310-B', 1997);
706-
707-
INSERT INTO test.car_dealers(name,city) VALUES ('Springfield Cars S.A.','Springfield');
708-
INSERT INTO test.car_dealers(name,city) VALUES ('The Best Deals S.A.','Franklin');
709-
710-
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);
711-
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);
712-
END IF;
713-
END$do$;
697+
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-14',7,'DeLorean',1981);
698+
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-01-15',9,'DeLorean',1981);
699+
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-11',1,'Murcielago',2001);
700+
INSERT INTO test.car_model_sales(date, quantity, car_model_name, car_model_year) VALUES ('2021-02-12',3,'Murcielago',2001);
701+
702+
INSERT INTO test.car_racers(name) VALUES ('Alain Prost');
703+
INSERT INTO test.car_racers(name, car_model_name, car_model_year) VALUES ('Michael Schumacher', 'F310-B', 1997);
704+
705+
INSERT INTO test.car_dealers(name,city) VALUES ('Springfield Cars S.A.','Springfield');
706+
INSERT INTO test.car_dealers(name,city) VALUES ('The Best Deals S.A.','Franklin');
707+
708+
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);
709+
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);
714710
715711
TRUNCATE TABLE test.products CASCADE;
716712
INSERT INTO test.products (id, name) VALUES (1,'product-1'), (2,'product-2'), (3,'product-3');

0 commit comments

Comments
 (0)