Compare commits

..
9 Commits
Author SHA1 Message Date
Taimoor Zaeem 1f69247cc2 bump version to 14.11 2026-05-04 22:59:24 +05:00
Taimoor Zaeem e25a87bd84 fix: functions count in startup logs not counting overloaded functions
Renames the "Functions" in the logs to "RPCs". This clarifies that
we log number of callable functions and not the number of SQL objects.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-05-04 22:08:06 +05:00
Laurence IslaandTaimoor Zaeem 0502488b70 fix: call pg_notification_queue_usage() automatically when the LISTEN channel bug is detected (#4858)
There's a PostgreSQL bug that doesn't let any listener to register in the DB:
https://www.postgresql.org/message-id/flat/CAK98qZ3wZLE-RZJN_Y%2BTFjiTRPPFPBwNBpBi5K5CU8hUHkzDpw%40mail.gmail.com
The only workaround is to advance the async notification queue tail,
which can be done by executing:  "SELECT pg_notification_queue_usage();".
Before we just logged a HINT with this suggestion, but now we call that function directly
and then let the listener to automatically recover.

No automated tests were added here as it would be too complex and this is a PostgreSQL bug. But this was manually tested following the steps on https://github.com/PostgREST/postgrest/pull/4581#issuecomment-3690610592
2026-05-04 22:08:06 +05:00
Michal KleczekandTaimoor Zaeem a62b6de1d0 fix: Restore showing LISTEN pgrst in pg_stat_activity 2026-05-04 22:08:06 +05:00
Taimoor ZaeemandWolfgang Walther 9baf17aed0 fix: login with uppercase and mixed case role names
PostgREST failed when querying role settings where current
role name contained uppercase letters. This commit resolves
it by quoting the CURRENT_USER.

Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
2026-04-27 19:53:52 +00:00
renovate[bot]andWolfgang Walther d9eabb2742 chore(deps): update korthout/backport-action action to v4.5 2026-04-27 19:51:56 +00:00
renovate[bot]andWolfgang Walther 7711a86c0c chore(deps): update ubuntu docker tag to v26 2026-04-24 06:29:44 +00:00
renovate[bot]andWolfgang Walther d75a2bdce9 chore(deps): update korthout/backport-action action to v4.4.0 2026-04-21 09:05:15 +00:00
steve-chavezandWolfgang Walther dd1956557d ci: reinstate macos x86 releases
Closes https://github.com/PostgREST/postgrest/issues/4755

(cherry picked from commit b6c6bb27a8)
2026-04-19 12:19:17 +00:00
21 changed files with 102 additions and 41 deletions
+1
View File
@@ -2,4 +2,5 @@
# and made its way to us through nixpkgs.
self-hosted-runner:
labels:
- macos-15-intel
- ubuntu-24.04-arm
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
# Backport action that creates the PR with given settings
- name: Create backport PR
uses: korthout/backport-action@3c06f323a58619da1e8522229ebc8d5de2633e46 # v4.3.0
uses: korthout/backport-action@7c3f6cd5843cac11bc59a04a1b7699af93261670 # v4.5
with:
github_token: ${{ steps.app-token.outputs.token }}
pull_description: 'Backport for #${pull_number}.'
+9
View File
@@ -105,6 +105,15 @@ jobs:
artifact: postgrest-macos-aarch64
deps: brew link --force libpq
- name: MacOS x86-64
runs-on: macos-15-intel
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-macos-x86-64
deps: brew link --force libpq
- name: Windows
runs-on: windows-2022
cache: |
+3
View File
@@ -81,6 +81,9 @@ jobs:
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
-C artifacts/postgrest-macos-aarch64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-x86-64.tar.xz" \
-C artifacts/postgrest-macos-x86-64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
-C artifacts/postgrest-freebsd-x86-64 postgrest
+1
View File
@@ -156,6 +156,7 @@ jobs:
fail-fast: false
matrix:
runs-on:
- macos-15-intel # x86_64-darwin
- macos-14 # aarch64-darwin
- ubuntu-24.04 # x86_64-linux
- ubuntu-24.04-arm # aarch64-linux
+9
View File
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. From versio
## Unreleased
## [14.11] - 2026-05-04
### Fixed
- Fix login with uppercase and mixed case role names by @taimoorzaeem in #4678
- Restore Listener query shape so it can be found in `pg_stat_activity` by @mkleczek in #4857 #4859
- The LISTEN channel now automatically recovers when it stops working due to a PostgreSQL bug @laurenceisla in #3147
- Fix misleading "Functions" name on schema cache summary in startup logs by @taimoorzaeem in #4821
## [14.10] - 2026-04-16
### Added
+1 -1
View File
@@ -2,7 +2,7 @@
# The x86-64 is a single-static-binary image built via Nix, see:
# nix/tools/docker/README.md
FROM ubuntu:noble@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b AS postgrest
FROM ubuntu:resolute@sha256:5e275723f82c67e387ba9e3c24baa0abdcb268917f276a0561c97bef9450d0b4 AS postgrest
RUN apt-get update -y \
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
+1 -1
View File
@@ -39,7 +39,7 @@ For diagnostic information about the server itself, PostgREST logs to ``stderr``
06/May/2024:08:16:11 -0500: Listening for database notifications on the "pgrst" channel
06/May/2024:08:16:11 -0500: Config reloaded
06/May/2024:08:16:11 -0500: Schema cache queried in 3.8 milliseconds
06/May/2024:08:16:11 -0500: Schema cache loaded 15 Relations, 8 Relationships, 8 Functions, 0 Domain Representations, 4 Media Type Handlers
06/May/2024:08:16:11 -0500: Schema cache loaded 15 Relations, 8 Relationships, 8 RPCs, 0 Domain Representations, 4 Media Type Handlers
06/May/2024:14:11:27 -0500: Received a config reload message on the "pgrst" channel
06/May/2024:14:11:27 -0500: Config reloaded
+1 -1
View File
@@ -25,7 +25,7 @@ let
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from])"
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])"
"ARG_USE_ENV([PGUSER], [Postgrest_Test_Authenticator], [Authenticator PG role])" # user is written in mixed case to implicitly test that it is being properly quoted in schema cache queries
"ARG_USE_ENV([PGDATABASE], [postgres], [PG database name])"
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
"ARG_USE_ENV([PGTZ], [utc], [Timezone to use])"
+1 -1
View File
@@ -1,5 +1,5 @@
name: postgrest
version: 14.10
version: 14.11
synopsis: REST API for any Postgres database
description: Reads the schema of a PostgreSQL database and creates RESTful routes
for tables, views, and functions, supporting all HTTP methods that security
+3 -3
View File
@@ -101,7 +101,7 @@ queryDbSettings preConfFunc prepared =
SELECT setdatabase as database,
unnest(setconfig) as setting
FROM pg_catalog.pg_db_role_setting
WHERE setrole = CURRENT_USER::regrole::oid
WHERE setrole = quote_ident(CURRENT_USER)::regrole::oid
AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG))
),
kv_settings AS (
@@ -142,7 +142,7 @@ queryRoleSettings pgVer prepared =
select r.rolname, unnest(r.rolconfig) as setting
from pg_auth_members m
join pg_roles r on r.oid = m.roleid
where member = current_user::regrole::oid
where member = quote_ident(current_user)::regrole::oid
),
kv_settings AS (
SELECT
@@ -167,7 +167,7 @@ queryRoleSettings pgVer prepared =
|]
hasParameterPrivilege
| pgVer >= pgVersion150 = "or has_parameter_privilege(current_user::regrole::oid, ps.name, 'set')"
| pgVer >= pgVersion150 = "or has_parameter_privilege(quote_ident(current_user)::regrole::oid, ps.name, 'set')"
| otherwise = ""
processRows :: [(Text, Maybe Text, [(Text, Text)])] -> (RoleSettings, RoleIsolationLvl)
+16 -8
View File
@@ -10,8 +10,7 @@ import qualified Hasql.Connection as SQL
import qualified Hasql.Notifications as SQL
import PostgREST.AppState (AppState, getConfig)
import PostgREST.Config (AppConfig (..))
import PostgREST.Observation (Observation (..),
isDbListenerBug)
import PostgREST.Observation (Observation (..))
import PostgREST.Version (prettyVersion)
import qualified PostgREST.AppState as AppState
@@ -20,6 +19,7 @@ import qualified PostgREST.Config as Config
import Control.Arrow ((&&&))
import Data.Bitraversable (bisequence)
import Data.Either.Combinators (whenRight)
import qualified Data.Text as T
import qualified Database.PostgreSQL.LibPQ as LibPQ
import qualified Hasql.Session as SQL
import PostgREST.Config.Database (queryPgVersion)
@@ -31,12 +31,12 @@ runListener :: AppState -> IO ()
runListener appState = do
AppConfig{..} <- getConfig appState
when configDbChannelEnabled $
void . forkIO . void $ retryingListen appState
void . forkIO . void $ retryingListen appState False
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
-- | This function never returns (but can throw) and return type enforces that.
retryingListen :: AppState -> IO Void
retryingListen appState = do
retryingListen :: AppState -> Bool -> IO Void
retryingListen appState hasDbListenerBug = do
AppConfig{..} <- AppState.getConfig appState
let
dbChannel = toS configDbChannel
@@ -44,7 +44,7 @@ retryingListen appState = do
AppState.putIsListenerOn appState False
observer $ DBListenFail dbChannel (Right err)
when (isDbListenerBug err) $
observer DBListenBugHint
observer DBListenBugCallQueryFix
unless configDbPoolAutomaticRecovery $
killThread mainThreadId
@@ -55,7 +55,7 @@ retryingListen appState = do
unless (delay == maxDelay) $
AppState.putNextListenerDelay appState (delay * 2)
-- loop running the listener
retryingListen appState
retryingListen appState (isDbListenerBug err)
-- Execute the listener with with error handling
handle onError $ do
@@ -68,9 +68,10 @@ retryingListen appState = do
-- use connection
\case
Right db -> do
SQL.listen db $ SQL.toPgIdentifier dbChannel
(pqHost, pqPort) <- SQL.withLibPQConnection db $ bisequence . (LibPQ.host &&& LibPQ.port)
pgFullName <- SQL.run (queryPgVersion False) db >>= either throwIO (pure . pgvFullName)
when hasDbListenerBug $ SQL.run callNotifQueryUsage db >>= either throwIO pure
SQL.listen db $ SQL.toPgIdentifier dbChannel
AppState.putIsListenerOn appState True
@@ -106,3 +107,10 @@ retryingListen appState = do
AppState.schemaCacheLoader appState
releaseConnection = void . forkIO . handle (observer . DBListenerConnectionCleanupFail) . SQL.release
isDbListenerBug e = "could not access status of transaction" `T.isInfixOf` show e
-- Used to fix a Postgres bug in the listener, see: https://github.com/PostgREST/postgrest/issues/3147#issuecomment-3494591361
-- This query advances the async notification query tail, which solves this issue.
callNotifQueryUsage :: SQL.Session ()
callNotifQueryUsage = SQL.sql "SELECT pg_notification_queue_usage();"
+3 -7
View File
@@ -13,7 +13,6 @@ module PostgREST.Observation
, observationMessage
, ObservationHandler
, showOnSingleLine
, isDbListenerBug
) where
import qualified Data.ByteString.Lazy as LBS
@@ -48,7 +47,7 @@ data Observation
| DBListenStart (Maybe ByteString) (Maybe ByteString) Text Text -- host, port, version string, channel
| DBListenFail Text (Either SQL.ConnectionError SomeException)
| DBListenRetry Int
| DBListenBugHint -- https://github.com/PostgREST/postgrest/issues/3147
| DBListenBugCallQueryFix
| DBListenerGotSCacheMsg ByteString
| DBListenerGotConfigMsg ByteString
| DBListenerConnectionCleanupFail SomeException
@@ -122,8 +121,8 @@ observationMessage = \case
either showListenerConnError showListenerException listenErr
DBListenRetry delay ->
"Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
DBListenBugHint ->
"HINT: This is likely a bug in the notification queue, try executing the following to solve it: select pg_notification_queue_usage();"
DBListenBugCallQueryFix ->
"This is likely a PostgreSQL bug in the notification queue, executing the following to try to solve it: SELECT pg_notification_queue_usage();"
DBListenerGotSCacheMsg channel ->
"Received a schema cache reload message on the " <> show channel <> " channel"
DBListenerGotConfigMsg channel ->
@@ -188,6 +187,3 @@ observationMessage = \case
showOnSingleLine :: Char -> Text -> Text
showOnSingleLine split txt = T.intercalate " " $ T.filter (/= split) <$> T.lines txt -- the errors from hasql-notifications come intercalated with "\t\n"
isDbListenerBug :: SomeException -> Bool
isDbListenerBug e = "could not access status of transaction" `T.isInfixOf` show e
+1 -1
View File
@@ -102,7 +102,7 @@ showSummary (SchemaCache tbls rels routs reps mediaHdlrs tzs _) =
T.intercalate ", "
[ show (HM.size tbls) <> " Relations"
, show (HM.size rels) <> " Relationships"
, show (HM.size routs) <> " Functions"
, show (HM.size routs) <> " RPCs"
, show (HM.size reps) <> " Domain Representations"
, show (HM.size mediaHdlrs) <> " Media Type Handlers"
, show (S.size tzs) <> " Timezones"
+1 -1
View File
@@ -11399,7 +11399,7 @@ $$;
DROP ROLE IF EXISTS postgrest_test_anonymous;
CREATE ROLE postgrest_test_anonymous;
GRANT postgrest_test_anonymous TO :PGUSER;
GRANT postgrest_test_anonymous TO :"PGUSER";
GRANT USAGE ON SCHEMA apflora TO postgrest_test_anonymous;
GRANT USAGE ON SCHEMA fuzzysearch TO postgrest_test_anonymous;
+11 -11
View File
@@ -4,7 +4,7 @@ set check_function_bodies = false; -- to allow conditionals based on the pg vers
set search_path to public;
CREATE ROLE postgrest_test_anonymous;
ALTER ROLE :PGUSER SET pgrst.db_anon_role = 'postgrest_test_anonymous';
ALTER ROLE :"PGUSER" SET pgrst.db_anon_role = 'postgrest_test_anonymous';
CREATE ROLE postgrest_test_author;
@@ -21,14 +21,14 @@ alter role postgrest_test_w_superuser_settings set log_min_messages = 'fatal';
DO $do$BEGIN
IF (SELECT current_setting('server_version_num')::INT >= 150000) THEN
ALTER ROLE postgrest_test_w_superuser_settings SET log_min_duration_sample = 12345;
GRANT SET ON PARAMETER log_min_duration_sample to postgrest_test_authenticator;
GRANT SET ON PARAMETER log_min_duration_sample to "Postgrest_Test_Authenticator";
END IF;
END$do$;
GRANT
postgrest_test_anonymous, postgrest_test_author,
postgrest_test_serializable, postgrest_test_repeatable_read,
postgrest_test_w_superuser_settings TO :PGUSER;
postgrest_test_w_superuser_settings TO :"PGUSER";
CREATE SCHEMA v1;
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
@@ -57,7 +57,7 @@ $$ language sql;
create function change_max_rows_config(val int, notify bool default false) returns void as $_$
begin
execute format($$
alter role postgrest_test_authenticator set pgrst.db_max_rows = %L;
alter role "Postgrest_Test_Authenticator" set pgrst.db_max_rows = %L;
$$, val);
if notify then
perform pg_notify('pgrst', 'reload config');
@@ -66,13 +66,13 @@ end $_$ volatile security definer language plpgsql ;
create function reset_max_rows_config() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.db_max_rows;
alter role "Postgrest_Test_Authenticator" reset pgrst.db_max_rows;
end $_$ volatile security definer language plpgsql ;
create function change_db_schema_and_full_reload(schemas text) returns void as $_$
begin
execute format($$
alter role postgrest_test_authenticator set pgrst.db_schemas = %L;
alter role "Postgrest_Test_Authenticator" set pgrst.db_schemas = %L;
$$, schemas);
perform pg_notify('pgrst', 'reload config');
perform pg_notify('pgrst', 'reload schema');
@@ -80,14 +80,14 @@ end $_$ volatile security definer language plpgsql ;
create function v1.reset_db_schema_config() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.db_schemas;
alter role "Postgrest_Test_Authenticator" reset pgrst.db_schemas;
perform pg_notify('pgrst', 'reload config');
perform pg_notify('pgrst', 'reload schema');
end $_$ volatile security definer language plpgsql ;
create function invalid_role_claim_key_reload() returns void as $_$
begin
alter role postgrest_test_authenticator set pgrst.jwt_role_claim_key = 'test';
alter role "Postgrest_Test_Authenticator" set pgrst.jwt_role_claim_key = 'test';
perform pg_notify('pgrst', 'reload config');
end $_$ volatile security definer language plpgsql ;
@@ -100,7 +100,7 @@ $_$ language sql;
create function reset_invalid_role_claim_key() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.jwt_role_claim_key;
alter role "Postgrest_Test_Authenticator" reset pgrst.jwt_role_claim_key;
perform pg_notify('pgrst', 'reload config');
end $_$ volatile security definer language plpgsql ;
@@ -235,12 +235,12 @@ $$ language sql;
create function change_db_schemas_config() returns void as $_$
begin
alter role postgrest_test_authenticator set pgrst.db_schemas = 'test';
alter role "Postgrest_Test_Authenticator" set pgrst.db_schemas = 'test';
end $_$ volatile security definer language plpgsql;
create function reset_db_schemas_config() returns void as $_$
begin
alter role postgrest_test_authenticator reset pgrst.db_schemas;
alter role "Postgrest_Test_Authenticator" reset pgrst.db_schemas;
end $_$ volatile security definer language plpgsql ;
create function test.get_current_schema() returns text as $$
+1 -1
View File
@@ -13,7 +13,7 @@ create table replica.items as select x as id from generate_series(1, 10) x;
DROP ROLE IF EXISTS postgrest_test_anonymous;
CREATE ROLE postgrest_test_anonymous;
GRANT postgrest_test_anonymous TO :PGUSER;
GRANT postgrest_test_anonymous TO :"PGUSER";
GRANT USAGE ON SCHEMA replica TO postgrest_test_anonymous;
+34
View File
@@ -3,6 +3,7 @@
import os
import re
import signal
import subprocess
import time
import pytest
@@ -542,6 +543,39 @@ def test_admin_ready_w_channel(defaultenv):
assert response.status_code == 200
def test_listener_query_is_visible_in_pg_stat_activity(defaultenv):
"The listener connection should show the LISTEN pgrst statement in pg_stat_activity"
env = {
**defaultenv,
"PGRST_DB_CHANNEL_ENABLED": "true",
"PGAPPNAME": "listener-query-test",
}
with run(env=env):
query = """
select query
from pg_stat_activity
where application_name = 'listener-query-test'
and query = 'LISTEN "pgrst"'
limit 1;
"""
output = subprocess.check_output(
[
"psql",
"--set",
"ON_ERROR_STOP=1",
"--tuples-only",
"--no-align",
"-c",
query,
],
text=True,
).strip()
assert output == 'LISTEN "pgrst"'
def test_admin_ready_wo_channel(defaultenv):
"Should get a success response from the admin server ready endpoint when the LISTEN channel is disabled"
+2 -2
View File
@@ -1,7 +1,7 @@
CREATE ROLE postgrest_test_anonymous;
CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous TO :PGUSER;
GRANT postgrest_test_author TO :PGUSER;
GRANT postgrest_test_anonymous TO :"PGUSER";
GRANT postgrest_test_author TO :"PGUSER";
CREATE SCHEMA test;
-- PUT+PATCH target needs one record and column to modify
+1 -1
View File
@@ -2,4 +2,4 @@ DROP ROLE IF EXISTS postgrest_test_anonymous, postgrest_test_author;
CREATE ROLE postgrest_test_anonymous;
CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous, postgrest_test_author TO :PGUSER;
GRANT postgrest_test_anonymous, postgrest_test_author TO :"PGUSER";
+1 -1
View File
@@ -4,4 +4,4 @@ CREATE ROLE postgrest_test_default_role;
CREATE ROLE postgrest_test_author;
CREATE ROLE postgrest_test_superuser WITH SUPERUSER;
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author, postgrest_test_superuser TO :PGUSER;
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author, postgrest_test_superuser TO :"PGUSER";