Compare commits
@@ -2,4 +2,5 @@
|
|||||||
# and made its way to us through nixpkgs.
|
# and made its way to us through nixpkgs.
|
||||||
self-hosted-runner:
|
self-hosted-runner:
|
||||||
labels:
|
labels:
|
||||||
|
- macos-15-intel
|
||||||
- ubuntu-24.04-arm
|
- ubuntu-24.04-arm
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ runs:
|
|||||||
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
|
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
|
||||||
- name: Save artifact to GitHub Actions
|
- name: Save artifact to GitHub Actions
|
||||||
if: steps.find-task.outputs.task_found
|
if: steps.find-task.outputs.task_found
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.upload }}
|
name: ${{ inputs.upload }}
|
||||||
path: ${{ steps.download.outputs.artifacts }}
|
path: ${{ steps.download.outputs.artifacts }}
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||||
with:
|
with:
|
||||||
path: ${{ inputs.path }}
|
path: ${{ inputs.path }}
|
||||||
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ inputs.prefix }}-
|
${{ runner.os }}-${{ inputs.prefix }}-
|
||||||
- uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
- uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||||
with:
|
with:
|
||||||
path: ${{ inputs.path }}
|
path: ${{ inputs.path }}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
# This actions creates the github token using the postgrest app secrets
|
# This actions creates the github token using the postgrest app secrets
|
||||||
- name: Create Github App Token
|
- name: Create Github App Token
|
||||||
id: app-token
|
id: app-token
|
||||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ vars.POSTGREST_CI_APP_ID }}
|
app-id: ${{ vars.POSTGREST_CI_APP_ID }}
|
||||||
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
|
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
|
|
||||||
# Backport action that creates the PR with given settings
|
# Backport action that creates the PR with given settings
|
||||||
- name: Create backport PR
|
- name: Create backport PR
|
||||||
uses: korthout/backport-action@3c06f323a58619da1e8522229ebc8d5de2633e46 # v4.3.0
|
uses: korthout/backport-action@7c3f6cd5843cac11bc59a04a1b7699af93261670 # v4.5
|
||||||
with:
|
with:
|
||||||
github_token: ${{ steps.app-token.outputs.token }}
|
github_token: ${{ steps.app-token.outputs.token }}
|
||||||
pull_description: 'Backport for #${pull_number}.'
|
pull_description: 'Backport for #${pull_number}.'
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
- name: Build static executable
|
- name: Build static executable
|
||||||
run: nix-build -A postgrestStatic -A postgrestStatic.tests
|
run: nix-build -A postgrestStatic -A postgrestStatic.tests
|
||||||
- name: Save built executable as artifact
|
- name: Save built executable as artifact
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: postgrest-linux-static-x86-64
|
name: postgrest-linux-static-x86-64
|
||||||
path: result/bin/postgrest
|
path: result/bin/postgrest
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
|
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
|
||||||
- name: Save built Docker image as artifact
|
- name: Save built Docker image as artifact
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: postgrest-docker-x86-64
|
name: postgrest-docker-x86-64
|
||||||
path: postgrest-docker.tar.gz
|
path: postgrest-docker.tar.gz
|
||||||
@@ -105,6 +105,15 @@ jobs:
|
|||||||
artifact: postgrest-macos-aarch64
|
artifact: postgrest-macos-aarch64
|
||||||
deps: brew link --force libpq
|
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
|
- name: Windows
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
cache: |
|
cache: |
|
||||||
@@ -118,7 +127,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: haskell-actions/setup@f9150cb1d140e9a9271700670baa38991e6fa25c # v2.10.3
|
- uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
|
||||||
with:
|
with:
|
||||||
# This must match the version in stack.yaml's resolver
|
# This must match the version in stack.yaml's resolver
|
||||||
ghc-version: 9.6.7
|
ghc-version: 9.6.7
|
||||||
@@ -146,7 +155,7 @@ jobs:
|
|||||||
- name: Strip Executable
|
- name: Strip Executable
|
||||||
run: strip result/postgrest*
|
run: strip result/postgrest*
|
||||||
- name: Save built executable as artifact
|
- name: Save built executable as artifact
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.artifact }}
|
name: ${{ matrix.artifact }}
|
||||||
path: |
|
path: |
|
||||||
@@ -177,7 +186,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
- uses: haskell-actions/setup@f9150cb1d140e9a9271700670baa38991e6fa25c # v2.10.3
|
- uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
|
||||||
with:
|
with:
|
||||||
ghc-version: ${{ matrix.ghc }}
|
ghc-version: ${{ matrix.ghc }}
|
||||||
- name: Cache .cabal
|
- name: Cache .cabal
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
echo "Relevant extract from CHANGELOG.md:"
|
echo "Relevant extract from CHANGELOG.md:"
|
||||||
cat CHANGES.md
|
cat CHANGES.md
|
||||||
- name: Save CHANGES.md as artifact
|
- name: Save CHANGES.md as artifact
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: release-changes
|
name: release-changes
|
||||||
path: CHANGES.md
|
path: CHANGES.md
|
||||||
@@ -81,6 +81,9 @@ jobs:
|
|||||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
|
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
|
||||||
-C artifacts/postgrest-macos-aarch64 postgrest
|
-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" \
|
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
|
||||||
-C artifacts/postgrest-freebsd-x86-64 postgrest
|
-C artifacts/postgrest-freebsd-x86-64 postgrest
|
||||||
|
|
||||||
@@ -91,7 +94,7 @@ jobs:
|
|||||||
artifacts/postgrest-windows-x86-64/postgrest.exe
|
artifacts/postgrest-windows-x86-64/postgrest.exe
|
||||||
|
|
||||||
- name: Save release bundle
|
- name: Save release bundle
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: release-bundle
|
name: release-bundle
|
||||||
path: release-bundle
|
path: release-bundle
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
runs-on:
|
runs-on:
|
||||||
|
- macos-15-intel # x86_64-darwin
|
||||||
- macos-14 # aarch64-darwin
|
- macos-14 # aarch64-darwin
|
||||||
- ubuntu-24.04 # x86_64-linux
|
- ubuntu-24.04 # x86_64-linux
|
||||||
- ubuntu-24.04-arm # aarch64-linux
|
- ubuntu-24.04-arm # aarch64-linux
|
||||||
|
|||||||
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file. From versio
|
|||||||
|
|
||||||
## Unreleased
|
## 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
|
||||||
|
|
||||||
|
- Log when the pool is released during schema cache reload on `log-level=debug` by @mkleczek in #4668
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix unnecessary connection pool flushes during schema cache reloading by @mkleczek in #4645
|
||||||
|
|
||||||
|
## [14.9] - 2026-04-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Log host, port and pg version of listener database connection by @mkleczek in #4617 #4618
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Remove red herring warp logs on default log-level, only emit them on `log-level=debug` by @steve-chavez in #4799
|
||||||
|
|
||||||
## [14.8] - 2026-04-03
|
## [14.8] - 2026-04-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
# The x86-64 is a single-static-binary image built via Nix, see:
|
# The x86-64 is a single-static-binary image built via Nix, see:
|
||||||
# nix/tools/docker/README.md
|
# nix/tools/docker/README.md
|
||||||
|
|
||||||
FROM ubuntu:noble@sha256:186072bba1b2f436cbb91ef2567abca677337cfc786c86e107d25b7072feef0c AS postgrest
|
FROM ubuntu:resolute@sha256:5e275723f82c67e387ba9e3c24baa0abdcb268917f276a0561c97bef9450d0b4 AS postgrest
|
||||||
|
|
||||||
RUN apt-get update -y \
|
RUN apt-get update -y \
|
||||||
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
|
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ Community Tutorials
|
|||||||
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
|
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
|
||||||
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
|
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
|
||||||
|
|
||||||
* `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
* `PostgREST + Auth0: Create REST API in minutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest-1-introduction/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
||||||
|
|
||||||
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
|
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
|
||||||
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
|
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Custom Queries
|
|||||||
|
|
||||||
The PostgREST URL grammar limits the kinds of queries clients can perform. It prevents arbitrary, potentially poorly constructed and slow client queries. It's good for quality of service, but means database administrators must create custom views and functions to provide richer endpoints. The most common causes for custom endpoints are
|
The PostgREST URL grammar limits the kinds of queries clients can perform. It prevents arbitrary, potentially poorly constructed and slow client queries. It's good for quality of service, but means database administrators must create custom views and functions to provide richer endpoints. The most common causes for custom endpoints are
|
||||||
|
|
||||||
* Table unions
|
* SET operators like `UNION, INTERSECT and EXCEPT <https://www.postgresql.org/docs/current/queries-union.html>`_.
|
||||||
* More complicated joins than those provided by :ref:`resource_embedding`.
|
* More complicated joins than those provided by :ref:`resource_embedding`.
|
||||||
* Geo-spatial queries that require an argument, like "points near (lat,lon)"
|
* Geo-spatial queries that require an argument, like "points near (lat,lon)"
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ This will cause the :ref:`connection_pool` to connect to the read replica host a
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Under the hood, PostgREST forces `target_session_attrs=read-write <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS>`_ for the ``LISTEN`` session.
|
- Under the hood, PostgREST forces `target_session_attrs=read-write <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS>`_ for the ``LISTEN`` session.
|
||||||
|
So if you specify ``target_session_attrs=read-only`` as mentioned above, PostgREST will override it for the ``LISTEN``.
|
||||||
|
- ``read-only`` is only available on libpq >= 14, if you use a lower version you will get an error like ``invalid target_session_attrs value: \"read-only\"``.
|
||||||
|
|
||||||
.. _listener_automatic_recovery:
|
.. _listener_automatic_recovery:
|
||||||
|
|
||||||
|
|||||||
@@ -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: Listening for database notifications on the "pgrst" channel
|
||||||
06/May/2024:08:16:11 -0500: Config reloaded
|
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 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: Received a config reload message on the "pgrst" channel
|
||||||
06/May/2024:14:11:27 -0500: Config reloaded
|
06/May/2024:14:11:27 -0500: Config reloaded
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ let
|
|||||||
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from])"
|
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from])"
|
||||||
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
|
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
|
||||||
"ARG_LEFTOVERS([command arguments])"
|
"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([PGDATABASE], [postgres], [PG database name])"
|
||||||
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
|
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
|
||||||
"ARG_USE_ENV([PGTZ], [utc], [Timezone to use])"
|
"ARG_USE_ENV([PGTZ], [utc], [Timezone to use])"
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,5 @@
|
|||||||
name: postgrest
|
name: postgrest
|
||||||
version: 14.8
|
version: 14.11
|
||||||
synopsis: REST API for any Postgres database
|
synopsis: REST API for any Postgres database
|
||||||
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
||||||
for tables, views, and functions, supporting all HTTP methods that security
|
for tables, views, and functions, supporting all HTTP methods that security
|
||||||
@@ -304,6 +304,7 @@ test-suite observability
|
|||||||
other-modules: ObsHelper
|
other-modules: ObsHelper
|
||||||
Observation.JwtCache
|
Observation.JwtCache
|
||||||
Observation.MetricsSpec
|
Observation.MetricsSpec
|
||||||
|
Observation.SchemaCacheSpec
|
||||||
build-depends: base >= 4.9 && < 4.20
|
build-depends: base >= 4.9 && < 4.20
|
||||||
, base64-bytestring >= 1 && < 1.3
|
, base64-bytestring >= 1 && < 1.3
|
||||||
, bytestring >= 0.10.8 && < 0.13
|
, bytestring >= 0.10.8 && < 0.13
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ run appState = do
|
|||||||
onWarpException :: Maybe Wai.Request -> SomeException -> IO ()
|
onWarpException :: Maybe Wai.Request -> SomeException -> IO ()
|
||||||
onWarpException _ ex =
|
onWarpException _ ex =
|
||||||
when (shouldDisplayException ex) $
|
when (shouldDisplayException ex) $
|
||||||
observer $ WarpErrorObs $ show ex
|
observer $ WarpServerObs $ show ex
|
||||||
|
|
||||||
-- Similar to wai defaultShouldDisplayException in
|
-- Similar to wai defaultShouldDisplayException in
|
||||||
-- https://github.com/yesodweb/wai//blob/8c3882c60f6abe043889fc20c7efd3fa9747fa4a/warp/Network/Wai/Handler/Warp/Settings.hs#L251-L258
|
-- https://github.com/yesodweb/wai//blob/8c3882c60f6abe043889fc20c7efd3fa9747fa4a/warp/Network/Wai/Handler/Warp/Settings.hs#L251-L258
|
||||||
|
|||||||
@@ -219,10 +219,14 @@ usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} ses
|
|||||||
|
|
||||||
-- | Flush the connection pool so that any future use of the pool will
|
-- | Flush the connection pool so that any future use of the pool will
|
||||||
-- use connections freshly established after this call.
|
-- use connections freshly established after this call.
|
||||||
|
-- | Emits PoolFlushed observation
|
||||||
flushPool :: AppState -> IO ()
|
flushPool :: AppState -> IO ()
|
||||||
flushPool AppState{..} = SQL.release statePool
|
flushPool AppState{..} = do
|
||||||
|
SQL.release statePool
|
||||||
|
stateObserver PoolFlushed
|
||||||
|
|
||||||
-- | Destroy the pool on shutdown.
|
-- | Destroy the pool on shutdown.
|
||||||
|
-- | Differs from flushPool in not emiting PoolFlushed observation.
|
||||||
destroyPool :: AppState -> IO ()
|
destroyPool :: AppState -> IO ()
|
||||||
destroyPool AppState{..} = SQL.release statePool
|
destroyPool AppState{..} = SQL.release statePool
|
||||||
|
|
||||||
@@ -309,8 +313,6 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
|
|||||||
observer $ ConnectionRetryObs delay
|
observer $ ConnectionRetryObs delay
|
||||||
putNextListenerDelay appState delay
|
putNextListenerDelay appState delay
|
||||||
|
|
||||||
flushPool appState
|
|
||||||
|
|
||||||
(,) <$> qPgVersion <*> (qInDbConfig *> qSchemaCache)
|
(,) <$> qPgVersion <*> (qInDbConfig *> qSchemaCache)
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
@@ -359,6 +361,10 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
|
|||||||
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
|
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
|
||||||
putSCacheStatus appState SCPending
|
putSCacheStatus appState SCPending
|
||||||
putSchemaCache appState $ Just sCache
|
putSchemaCache appState $ Just sCache
|
||||||
|
-- Flush the pool after loading the schema cache to reset any stale session cache entries
|
||||||
|
-- We do it after successfully querying the schema cache (because this can fail and during retries we would flush the pool repeatedly unnecessarily)
|
||||||
|
-- and after marking sCacheStatus as pending,
|
||||||
|
flushPool appState
|
||||||
observer $ SchemaCacheQueriedObs resultTime
|
observer $ SchemaCacheQueriedObs resultTime
|
||||||
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
||||||
observer $ SchemaCacheLoadedObs t
|
observer $ SchemaCacheLoadedObs t
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ queryDbSettings preConfFunc prepared =
|
|||||||
SELECT setdatabase as database,
|
SELECT setdatabase as database,
|
||||||
unnest(setconfig) as setting
|
unnest(setconfig) as setting
|
||||||
FROM pg_catalog.pg_db_role_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))
|
AND setdatabase IN (0, (SELECT oid FROM pg_catalog.pg_database WHERE datname = CURRENT_CATALOG))
|
||||||
),
|
),
|
||||||
kv_settings AS (
|
kv_settings AS (
|
||||||
@@ -142,7 +142,7 @@ queryRoleSettings pgVer prepared =
|
|||||||
select r.rolname, unnest(r.rolconfig) as setting
|
select r.rolname, unnest(r.rolconfig) as setting
|
||||||
from pg_auth_members m
|
from pg_auth_members m
|
||||||
join pg_roles r on r.oid = m.roleid
|
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 (
|
kv_settings AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -167,7 +167,7 @@ queryRoleSettings pgVer prepared =
|
|||||||
|]
|
|]
|
||||||
|
|
||||||
hasParameterPrivilege
|
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 = ""
|
| otherwise = ""
|
||||||
|
|
||||||
processRows :: [(Text, Maybe Text, [(Text, Text)])] -> (RoleSettings, RoleIsolationLvl)
|
processRows :: [(Text, Maybe Text, [(Text, Text)])] -> (RoleSettings, RoleIsolationLvl)
|
||||||
|
|||||||
+27
-10
@@ -10,27 +10,33 @@ import qualified Hasql.Connection as SQL
|
|||||||
import qualified Hasql.Notifications as SQL
|
import qualified Hasql.Notifications as SQL
|
||||||
import PostgREST.AppState (AppState, getConfig)
|
import PostgREST.AppState (AppState, getConfig)
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.Observation (Observation (..),
|
import PostgREST.Observation (Observation (..))
|
||||||
isDbListenerBug)
|
|
||||||
import PostgREST.Version (prettyVersion)
|
import PostgREST.Version (prettyVersion)
|
||||||
|
|
||||||
import qualified PostgREST.AppState as AppState
|
import qualified PostgREST.AppState as AppState
|
||||||
import qualified PostgREST.Config as Config
|
import qualified PostgREST.Config as Config
|
||||||
|
|
||||||
import Data.Either.Combinators (whenRight)
|
import Control.Arrow ((&&&))
|
||||||
import Protolude
|
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)
|
||||||
|
import PostgREST.Config.PgVersion (pgvFullName)
|
||||||
|
import Protolude
|
||||||
|
|
||||||
-- | Starts the Listener in a thread
|
-- | Starts the Listener in a thread
|
||||||
runListener :: AppState -> IO ()
|
runListener :: AppState -> IO ()
|
||||||
runListener appState = do
|
runListener appState = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
when configDbChannelEnabled $
|
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.
|
-- | 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.
|
-- | This function never returns (but can throw) and return type enforces that.
|
||||||
retryingListen :: AppState -> IO Void
|
retryingListen :: AppState -> Bool -> IO Void
|
||||||
retryingListen appState = do
|
retryingListen appState hasDbListenerBug = do
|
||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
let
|
let
|
||||||
dbChannel = toS configDbChannel
|
dbChannel = toS configDbChannel
|
||||||
@@ -38,7 +44,7 @@ retryingListen appState = do
|
|||||||
AppState.putIsListenerOn appState False
|
AppState.putIsListenerOn appState False
|
||||||
observer $ DBListenFail dbChannel (Right err)
|
observer $ DBListenFail dbChannel (Right err)
|
||||||
when (isDbListenerBug err) $
|
when (isDbListenerBug err) $
|
||||||
observer DBListenBugHint
|
observer DBListenBugCallQueryFix
|
||||||
unless configDbPoolAutomaticRecovery $
|
unless configDbPoolAutomaticRecovery $
|
||||||
killThread mainThreadId
|
killThread mainThreadId
|
||||||
|
|
||||||
@@ -49,7 +55,7 @@ retryingListen appState = do
|
|||||||
unless (delay == maxDelay) $
|
unless (delay == maxDelay) $
|
||||||
AppState.putNextListenerDelay appState (delay * 2)
|
AppState.putNextListenerDelay appState (delay * 2)
|
||||||
-- loop running the listener
|
-- loop running the listener
|
||||||
retryingListen appState
|
retryingListen appState (isDbListenerBug err)
|
||||||
|
|
||||||
-- Execute the listener with with error handling
|
-- Execute the listener with with error handling
|
||||||
handle onError $ do
|
handle onError $ do
|
||||||
@@ -62,7 +68,11 @@ retryingListen appState = do
|
|||||||
-- use connection
|
-- use connection
|
||||||
\case
|
\case
|
||||||
Right db -> do
|
Right db -> do
|
||||||
|
(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
|
SQL.listen db $ SQL.toPgIdentifier dbChannel
|
||||||
|
|
||||||
AppState.putIsListenerOn appState True
|
AppState.putIsListenerOn appState True
|
||||||
|
|
||||||
delay <- AppState.getNextListenerDelay appState
|
delay <- AppState.getNextListenerDelay appState
|
||||||
@@ -72,7 +82,7 @@ retryingListen appState = do
|
|||||||
-- reset the delay
|
-- reset the delay
|
||||||
AppState.putNextListenerDelay appState 1
|
AppState.putNextListenerDelay appState 1
|
||||||
|
|
||||||
observer $ DBListenStart dbChannel
|
observer $ DBListenStart pqHost pqPort pgFullName dbChannel
|
||||||
|
|
||||||
-- wait for notifications
|
-- wait for notifications
|
||||||
-- this will never return, in case of an error it will throw and be caught by onError
|
-- this will never return, in case of an error it will throw and be caught by onError
|
||||||
@@ -97,3 +107,10 @@ retryingListen appState = do
|
|||||||
AppState.schemaCacheLoader appState
|
AppState.schemaCacheLoader appState
|
||||||
|
|
||||||
releaseConnection = void . forkIO . handle (observer . DBListenerConnectionCleanupFail) . SQL.release
|
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();"
|
||||||
|
|||||||
@@ -110,12 +110,18 @@ observationLogger loggerState logLevel obs = case obs of
|
|||||||
o@PoolRequestFullfilled ->
|
o@PoolRequestFullfilled ->
|
||||||
when (logLevel >= LogDebug) $ do
|
when (logLevel >= LogDebug) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
|
o@PoolFlushed ->
|
||||||
|
when (logLevel >= LogDebug) $ do
|
||||||
|
logWithZTime loggerState $ observationMessage o
|
||||||
o@JwtCacheEviction ->
|
o@JwtCacheEviction ->
|
||||||
when (logLevel >= LogDebug) $ do
|
when (logLevel >= LogDebug) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
o@(JwtCacheLookup _) ->
|
o@(JwtCacheLookup _) ->
|
||||||
when (logLevel >= LogDebug) $ do
|
when (logLevel >= LogDebug) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
|
o@(WarpServerObs _) ->
|
||||||
|
when (logLevel >= LogDebug) $ do
|
||||||
|
logWithZTime loggerState $ observationMessage o
|
||||||
o ->
|
o ->
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ module PostgREST.Observation
|
|||||||
, observationMessage
|
, observationMessage
|
||||||
, ObservationHandler
|
, ObservationHandler
|
||||||
, showOnSingleLine
|
, showOnSingleLine
|
||||||
, isDbListenerBug
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
@@ -45,10 +44,10 @@ data Observation
|
|||||||
| SchemaCacheSummaryObs Text
|
| SchemaCacheSummaryObs Text
|
||||||
| SchemaCacheLoadedObs Double
|
| SchemaCacheLoadedObs Double
|
||||||
| ConnectionRetryObs Int
|
| ConnectionRetryObs Int
|
||||||
| DBListenStart Text
|
| DBListenStart (Maybe ByteString) (Maybe ByteString) Text Text -- host, port, version string, channel
|
||||||
| DBListenFail Text (Either SQL.ConnectionError SomeException)
|
| DBListenFail Text (Either SQL.ConnectionError SomeException)
|
||||||
| DBListenRetry Int
|
| DBListenRetry Int
|
||||||
| DBListenBugHint -- https://github.com/PostgREST/postgrest/issues/3147
|
| DBListenBugCallQueryFix
|
||||||
| DBListenerGotSCacheMsg ByteString
|
| DBListenerGotSCacheMsg ByteString
|
||||||
| DBListenerGotConfigMsg ByteString
|
| DBListenerGotConfigMsg ByteString
|
||||||
| DBListenerConnectionCleanupFail SomeException
|
| DBListenerConnectionCleanupFail SomeException
|
||||||
@@ -64,10 +63,11 @@ data Observation
|
|||||||
| HasqlPoolObs SQL.Observation
|
| HasqlPoolObs SQL.Observation
|
||||||
| PoolRequest
|
| PoolRequest
|
||||||
| PoolRequestFullfilled
|
| PoolRequestFullfilled
|
||||||
|
| PoolFlushed
|
||||||
| JwtCacheLookup Bool
|
| JwtCacheLookup Bool
|
||||||
| JwtCacheEviction
|
| JwtCacheEviction
|
||||||
| TerminationUnixSignalObs Text
|
| TerminationUnixSignalObs Text
|
||||||
| WarpErrorObs Text
|
| WarpServerObs Text
|
||||||
deriving (Generic)
|
deriving (Generic)
|
||||||
|
|
||||||
data ObsFatalError = ServerAuthError | ServerPgrstBug | ServerError42P05 | ServerError08P01
|
data ObsFatalError = ServerAuthError | ServerPgrstBug | ServerError42P05 | ServerError08P01
|
||||||
@@ -114,15 +114,15 @@ observationMessage = \case
|
|||||||
"Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
|
"Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
|
||||||
QueryPgVersionError usageErr ->
|
QueryPgVersionError usageErr ->
|
||||||
"Failed to query the PostgreSQL version. " <> jsonMessage usageErr
|
"Failed to query the PostgreSQL version. " <> jsonMessage usageErr
|
||||||
DBListenStart channel -> do
|
DBListenStart host port fullName channel -> do
|
||||||
"Listening for database notifications on the " <> show channel <> " channel"
|
"Listener connected to " <> fullName <> " on " <> show (fold $ host <> fmap (":" <>) port) <> " and listening for database notifications on the " <> show channel <> " channel"
|
||||||
DBListenFail channel listenErr ->
|
DBListenFail channel listenErr ->
|
||||||
"Failed listening for database notifications on the " <> show channel <> " channel. " <>
|
"Failed listening for database notifications on the " <> show channel <> " channel. " <>
|
||||||
either showListenerConnError showListenerException listenErr
|
either showListenerConnError showListenerException listenErr
|
||||||
DBListenRetry delay ->
|
DBListenRetry delay ->
|
||||||
"Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
|
"Retrying listening for database notifications in " <> (show delay::Text) <> " seconds..."
|
||||||
DBListenBugHint ->
|
DBListenBugCallQueryFix ->
|
||||||
"HINT: This is likely a bug in the notification queue, try executing the following to solve it: select pg_notification_queue_usage();"
|
"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 ->
|
DBListenerGotSCacheMsg channel ->
|
||||||
"Received a schema cache reload message on the " <> show channel <> " channel"
|
"Received a schema cache reload message on the " <> show channel <> " channel"
|
||||||
DBListenerGotConfigMsg channel ->
|
DBListenerGotConfigMsg channel ->
|
||||||
@@ -161,14 +161,16 @@ observationMessage = \case
|
|||||||
"Trying to borrow a connection from pool"
|
"Trying to borrow a connection from pool"
|
||||||
PoolRequestFullfilled ->
|
PoolRequestFullfilled ->
|
||||||
"Borrowed a connection from the pool"
|
"Borrowed a connection from the pool"
|
||||||
|
PoolFlushed ->
|
||||||
|
"Database connection pool flushed"
|
||||||
JwtCacheLookup _ ->
|
JwtCacheLookup _ ->
|
||||||
"Looked up a JWT in JWT cache"
|
"Looked up a JWT in JWT cache"
|
||||||
JwtCacheEviction ->
|
JwtCacheEviction ->
|
||||||
"Evicted entry from JWT cache"
|
"Evicted entry from JWT cache"
|
||||||
TerminationUnixSignalObs signal ->
|
TerminationUnixSignalObs signal ->
|
||||||
"Received termination unix signal " <> signal
|
"Received termination unix signal " <> signal
|
||||||
WarpErrorObs txt ->
|
WarpServerObs txt ->
|
||||||
"Warp server error: " <> txt
|
"Warp server: " <> txt
|
||||||
where
|
where
|
||||||
showMillis :: Double -> Text
|
showMillis :: Double -> Text
|
||||||
showMillis x = toS $ showFFloat (Just 1) x ""
|
showMillis x = toS $ showFFloat (Just 1) x ""
|
||||||
@@ -185,6 +187,3 @@ observationMessage = \case
|
|||||||
|
|
||||||
showOnSingleLine :: Char -> Text -> Text
|
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"
|
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
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ showSummary (SchemaCache tbls rels routs reps mediaHdlrs tzs _) =
|
|||||||
T.intercalate ", "
|
T.intercalate ", "
|
||||||
[ show (HM.size tbls) <> " Relations"
|
[ show (HM.size tbls) <> " Relations"
|
||||||
, show (HM.size rels) <> " Relationships"
|
, show (HM.size rels) <> " Relationships"
|
||||||
, show (HM.size routs) <> " Functions"
|
, show (HM.size routs) <> " RPCs"
|
||||||
, show (HM.size reps) <> " Domain Representations"
|
, show (HM.size reps) <> " Domain Representations"
|
||||||
, show (HM.size mediaHdlrs) <> " Media Type Handlers"
|
, show (HM.size mediaHdlrs) <> " Media Type Handlers"
|
||||||
, show (S.size tzs) <> " Timezones"
|
, show (S.size tzs) <> " Timezones"
|
||||||
|
|||||||
@@ -11399,7 +11399,7 @@ $$;
|
|||||||
DROP ROLE IF EXISTS postgrest_test_anonymous;
|
DROP ROLE IF EXISTS postgrest_test_anonymous;
|
||||||
CREATE ROLE 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 apflora TO postgrest_test_anonymous;
|
||||||
GRANT USAGE ON SCHEMA fuzzysearch TO postgrest_test_anonymous;
|
GRANT USAGE ON SCHEMA fuzzysearch TO postgrest_test_anonymous;
|
||||||
|
|||||||
+11
-11
@@ -4,7 +4,7 @@ set check_function_bodies = false; -- to allow conditionals based on the pg vers
|
|||||||
set search_path to public;
|
set search_path to public;
|
||||||
|
|
||||||
CREATE ROLE postgrest_test_anonymous;
|
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;
|
CREATE ROLE postgrest_test_author;
|
||||||
|
|
||||||
@@ -21,14 +21,14 @@ alter role postgrest_test_w_superuser_settings set log_min_messages = 'fatal';
|
|||||||
DO $do$BEGIN
|
DO $do$BEGIN
|
||||||
IF (SELECT current_setting('server_version_num')::INT >= 150000) THEN
|
IF (SELECT current_setting('server_version_num')::INT >= 150000) THEN
|
||||||
ALTER ROLE postgrest_test_w_superuser_settings SET log_min_duration_sample = 12345;
|
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 IF;
|
||||||
END$do$;
|
END$do$;
|
||||||
|
|
||||||
GRANT
|
GRANT
|
||||||
postgrest_test_anonymous, postgrest_test_author,
|
postgrest_test_anonymous, postgrest_test_author,
|
||||||
postgrest_test_serializable, postgrest_test_repeatable_read,
|
postgrest_test_serializable, postgrest_test_repeatable_read,
|
||||||
postgrest_test_w_superuser_settings TO :PGUSER;
|
postgrest_test_w_superuser_settings TO :"PGUSER";
|
||||||
|
|
||||||
CREATE SCHEMA v1;
|
CREATE SCHEMA v1;
|
||||||
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
|
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 $_$
|
create function change_max_rows_config(val int, notify bool default false) returns void as $_$
|
||||||
begin
|
begin
|
||||||
execute format($$
|
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);
|
$$, val);
|
||||||
if notify then
|
if notify then
|
||||||
perform pg_notify('pgrst', 'reload config');
|
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 $_$
|
create function reset_max_rows_config() returns void as $_$
|
||||||
begin
|
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 ;
|
end $_$ volatile security definer language plpgsql ;
|
||||||
|
|
||||||
create function change_db_schema_and_full_reload(schemas text) returns void as $_$
|
create function change_db_schema_and_full_reload(schemas text) returns void as $_$
|
||||||
begin
|
begin
|
||||||
execute format($$
|
execute format($$
|
||||||
alter role postgrest_test_authenticator set pgrst.db_schemas = %L;
|
alter role "Postgrest_Test_Authenticator" set pgrst.db_schemas = %L;
|
||||||
$$, schemas);
|
$$, schemas);
|
||||||
perform pg_notify('pgrst', 'reload config');
|
perform pg_notify('pgrst', 'reload config');
|
||||||
perform pg_notify('pgrst', 'reload schema');
|
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 $_$
|
create function v1.reset_db_schema_config() returns void as $_$
|
||||||
begin
|
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 config');
|
||||||
perform pg_notify('pgrst', 'reload schema');
|
perform pg_notify('pgrst', 'reload schema');
|
||||||
end $_$ volatile security definer language plpgsql ;
|
end $_$ volatile security definer language plpgsql ;
|
||||||
|
|
||||||
create function invalid_role_claim_key_reload() returns void as $_$
|
create function invalid_role_claim_key_reload() returns void as $_$
|
||||||
begin
|
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');
|
perform pg_notify('pgrst', 'reload config');
|
||||||
end $_$ volatile security definer language plpgsql ;
|
end $_$ volatile security definer language plpgsql ;
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ $_$ language sql;
|
|||||||
|
|
||||||
create function reset_invalid_role_claim_key() returns void as $_$
|
create function reset_invalid_role_claim_key() returns void as $_$
|
||||||
begin
|
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');
|
perform pg_notify('pgrst', 'reload config');
|
||||||
end $_$ volatile security definer language plpgsql ;
|
end $_$ volatile security definer language plpgsql ;
|
||||||
|
|
||||||
@@ -235,12 +235,12 @@ $$ language sql;
|
|||||||
|
|
||||||
create function change_db_schemas_config() returns void as $_$
|
create function change_db_schemas_config() returns void as $_$
|
||||||
begin
|
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;
|
end $_$ volatile security definer language plpgsql;
|
||||||
|
|
||||||
create function reset_db_schemas_config() returns void as $_$
|
create function reset_db_schemas_config() returns void as $_$
|
||||||
begin
|
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 ;
|
end $_$ volatile security definer language plpgsql ;
|
||||||
|
|
||||||
create function test.get_current_schema() returns text as $$
|
create function test.get_current_schema() returns text as $$
|
||||||
|
|||||||
@@ -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;
|
DROP ROLE IF EXISTS postgrest_test_anonymous;
|
||||||
CREATE ROLE 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;
|
GRANT USAGE ON SCHEMA replica TO postgrest_test_anonymous;
|
||||||
|
|
||||||
|
|||||||
@@ -55,13 +55,15 @@ def test_openapi_in_big_schema(defaultenv):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_stackoverflow_is_logged(defaultenv):
|
@pytest.mark.parametrize("level", ["crit", "error", "warn", "info", "debug"])
|
||||||
"Stack overflow errors should be logged with the Warp error message"
|
def test_stackoverflow_is_logged(level, defaultenv):
|
||||||
|
"Stack overflow should be logged with the Warp message only on log-level=debug"
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
**defaultenv,
|
**defaultenv,
|
||||||
"PGRST_DB_SCHEMAS": "apflora",
|
"PGRST_DB_SCHEMAS": "apflora",
|
||||||
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous",
|
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous",
|
||||||
|
"PGRST_LOG_LEVEL": level,
|
||||||
}
|
}
|
||||||
|
|
||||||
with run(env=env, wait_max_seconds=30, no_startup_stdout=False) as postgrest:
|
with run(env=env, wait_max_seconds=30, no_startup_stdout=False) as postgrest:
|
||||||
@@ -69,9 +71,15 @@ def test_stackoverflow_is_logged(defaultenv):
|
|||||||
postgrest.session.get("/")
|
postgrest.session.get("/")
|
||||||
|
|
||||||
output = postgrest.read_stdout(nlines=10)
|
output = postgrest.read_stdout(nlines=10)
|
||||||
output.extend(postgrest.read_stdout(nlines=10))
|
for _ in range(3):
|
||||||
|
output.extend(postgrest.read_stdout(nlines=10))
|
||||||
|
|
||||||
assert any("Warp server error: stack overflow" in line for line in output)
|
found = any("Warp server: stack overflow" in line for line in output)
|
||||||
|
|
||||||
|
if level == "debug":
|
||||||
|
assert found
|
||||||
|
else:
|
||||||
|
assert not found
|
||||||
|
|
||||||
|
|
||||||
# See: https://github.com/PostgREST/postgrest/issues/3329
|
# See: https://github.com/PostgREST/postgrest/issues/3329
|
||||||
|
|||||||
+106
-46
@@ -3,6 +3,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -21,6 +22,19 @@ from postgrest import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def match_log(output, matchers):
|
||||||
|
ito = iter(output)
|
||||||
|
itm = iter(matchers)
|
||||||
|
nextMatcher = next(itm, None)
|
||||||
|
while nextMatcher is not None and (line := next(ito, None)) is not None:
|
||||||
|
if re.match(nextMatcher, line) is not None:
|
||||||
|
nextMatcher = next(itm, None)
|
||||||
|
if nextMatcher is not None:
|
||||||
|
raise AssertionError(
|
||||||
|
f"Expected log line matching {nextMatcher} not found in output"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_connect_with_dburi(dburi, defaultenv):
|
def test_connect_with_dburi(dburi, defaultenv):
|
||||||
"Connecting with db-uri instead of LIPQ* environment variables should work."
|
"Connecting with db-uri instead of LIPQ* environment variables should work."
|
||||||
defaultenv_without_libpq = {
|
defaultenv_without_libpq = {
|
||||||
@@ -529,6 +543,39 @@ def test_admin_ready_w_channel(defaultenv):
|
|||||||
assert response.status_code == 200
|
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):
|
def test_admin_ready_wo_channel(defaultenv):
|
||||||
"Should get a success response from the admin server ready endpoint when the LISTEN channel is disabled"
|
"Should get a success response from the admin server ready endpoint when the LISTEN channel is disabled"
|
||||||
|
|
||||||
@@ -679,55 +726,45 @@ def test_log_level(level, defaultenv):
|
|||||||
response = postgrest.session.get("/")
|
response = postgrest.session.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
output = sorted(postgrest.read_stdout(nlines=7))
|
output = postgrest.read_stdout(nlines=9)
|
||||||
|
|
||||||
if level == "crit":
|
if level == "crit":
|
||||||
assert len(output) == 0
|
assert len(output) == 0
|
||||||
elif level == "error":
|
elif level == "error":
|
||||||
assert re.match(
|
match_log(
|
||||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
output,
|
||||||
output[0],
|
[r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"'],
|
||||||
)
|
)
|
||||||
assert len(output) == 1
|
assert len(output) == 1
|
||||||
elif level == "warn":
|
elif level == "warn":
|
||||||
assert re.match(
|
match_log(
|
||||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
output,
|
||||||
output[0],
|
[
|
||||||
)
|
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||||
assert re.match(
|
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
||||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
],
|
||||||
output[1],
|
|
||||||
)
|
)
|
||||||
assert len(output) == 2
|
assert len(output) == 2
|
||||||
elif level == "info":
|
elif level == "info":
|
||||||
assert re.match(
|
match_log(
|
||||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
output,
|
||||||
output[0],
|
[
|
||||||
)
|
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||||
assert re.match(
|
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
||||||
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 \d+ "" "python-requests/.+"',
|
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 \d+ "" "python-requests/.+"',
|
||||||
output[1],
|
],
|
||||||
)
|
|
||||||
assert re.match(
|
|
||||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
|
||||||
output[2],
|
|
||||||
)
|
)
|
||||||
assert len(output) == 3
|
assert len(output) == 3
|
||||||
elif level == "debug":
|
elif level == "debug":
|
||||||
assert re.match(
|
match_log(
|
||||||
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
output,
|
||||||
output[0],
|
[
|
||||||
|
r'- - - \[.+\] "GET / HTTP/1.1" 500 \d+ "" "python-requests/.+"',
|
||||||
|
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
||||||
|
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 \d+ "" "python-requests/.+"',
|
||||||
|
],
|
||||||
)
|
)
|
||||||
assert re.match(
|
assert len(output) == 9
|
||||||
r'- - postgrest_test_anonymous \[.+\] "GET / HTTP/1.1" 200 \d+ "" "python-requests/.+"',
|
|
||||||
output[1],
|
|
||||||
)
|
|
||||||
assert re.match(
|
|
||||||
r'- - postgrest_test_anonymous \[.+\] "GET /unknown HTTP/1.1" 404 \d+ "" "python-requests/.+"',
|
|
||||||
output[2],
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(output) == 7
|
|
||||||
assert any("Connection" and "is available" in line for line in output)
|
assert any("Connection" and "is available" in line for line in output)
|
||||||
assert any("Connection" and "is used" in line for line in output)
|
assert any("Connection" and "is used" in line for line in output)
|
||||||
|
|
||||||
@@ -1364,16 +1401,21 @@ def test_db_error_logging_to_stderr(level, defaultenv, metapostgrest):
|
|||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
|
|
||||||
# ensure the message appears on the logs
|
# ensure the message appears on the logs
|
||||||
output = sorted(postgrest.read_stdout(nlines=6))
|
output = postgrest.read_stdout(nlines=8)
|
||||||
|
|
||||||
if level == "crit":
|
if level == "crit":
|
||||||
assert len(output) == 0
|
assert len(output) == 0
|
||||||
elif level == "debug":
|
elif level == "debug":
|
||||||
assert " 500 " in output[0]
|
match_log(
|
||||||
assert "canceling statement due to statement timeout" in output[5]
|
output,
|
||||||
|
[
|
||||||
|
r".*canceling statement due to statement timeout.*",
|
||||||
|
r".*500.*",
|
||||||
|
],
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert " 500 " in output[0]
|
assert " 500 " in output[1]
|
||||||
assert "canceling statement due to statement timeout" in output[1]
|
assert "canceling statement due to statement timeout" in output[0]
|
||||||
|
|
||||||
reset_statement_timeout(metapostgrest, role)
|
reset_statement_timeout(metapostgrest, role)
|
||||||
|
|
||||||
@@ -1567,18 +1609,17 @@ def test_log_pool_req_observation(level, defaultenv):
|
|||||||
|
|
||||||
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
|
||||||
|
|
||||||
pool_req = "Trying to borrow a connection from pool"
|
pool_req = r".*Trying to borrow a connection from pool.*"
|
||||||
pool_req_fullfill = "Borrowed a connection from the pool"
|
pool_req_fullfill = r".*Borrowed a connection from the pool.*"
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
with run(env=env) as postgrest:
|
||||||
|
|
||||||
postgrest.session.get("/authors_only", headers=headers)
|
postgrest.session.get("/authors_only", headers=headers)
|
||||||
|
|
||||||
if level == "debug":
|
if level == "debug":
|
||||||
output = postgrest.read_stdout(nlines=5)
|
output = postgrest.read_stdout(nlines=7)
|
||||||
assert pool_req in output[1]
|
assert len(output) == 7
|
||||||
assert pool_req_fullfill in output[4]
|
match_log(output, [pool_req, pool_req_fullfill])
|
||||||
assert len(output) == 5
|
|
||||||
elif level == "info":
|
elif level == "info":
|
||||||
output = postgrest.read_stdout(nlines=4)
|
output = postgrest.read_stdout(nlines=4)
|
||||||
assert len(output) == 1
|
assert len(output) == 1
|
||||||
@@ -1658,6 +1699,25 @@ def test_log_listener_connection_errors(defaultenv):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_listener_connection_start(defaultenv):
|
||||||
|
"The logs should show the listener connection start message in a single line"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_CHANNEL_ENABLED": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env, no_startup_stdout=False, wait_for_readiness=True) as postgrest:
|
||||||
|
output = postgrest.read_stdout(nlines=10)
|
||||||
|
# Check for the listener start message containing host and port
|
||||||
|
# Do not check if pg version is displayed properly as it is tricky to test it
|
||||||
|
assert any(
|
||||||
|
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
|
||||||
|
in line
|
||||||
|
for line in output
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_db_pre_config_with_pg_reserved_words(defaultenv):
|
def test_db_pre_config_with_pg_reserved_words(defaultenv):
|
||||||
"The db-pre-config should not fail unexpectedly when function name is a postgres reserved word"
|
"The db-pre-config should not fail unexpectedly when function name is a postgres reserved word"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
CREATE ROLE postgrest_test_anonymous;
|
CREATE ROLE postgrest_test_anonymous;
|
||||||
CREATE ROLE postgrest_test_author;
|
CREATE ROLE postgrest_test_author;
|
||||||
GRANT postgrest_test_anonymous TO :PGUSER;
|
GRANT postgrest_test_anonymous TO :"PGUSER";
|
||||||
GRANT postgrest_test_author TO :PGUSER;
|
GRANT postgrest_test_author TO :"PGUSER";
|
||||||
CREATE SCHEMA test;
|
CREATE SCHEMA test;
|
||||||
|
|
||||||
-- PUT+PATCH target needs one record and column to modify
|
-- PUT+PATCH target needs one record and column to modify
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import PostgREST.SchemaCache (querySchemaCache)
|
|||||||
import qualified Observation.JwtCache
|
import qualified Observation.JwtCache
|
||||||
import qualified Observation.MetricsSpec
|
import qualified Observation.MetricsSpec
|
||||||
|
|
||||||
import ObsHelper
|
import qualified Observation.SchemaCacheSpec
|
||||||
import PostgREST.Observation (Observation (HasqlPoolObs))
|
import ObsHelper
|
||||||
import Protolude hiding (toList, toS)
|
import PostgREST.Observation (Observation (HasqlPoolObs))
|
||||||
import Test.Hspec
|
import Protolude hiding (toList, toS)
|
||||||
|
import Test.Hspec
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
@@ -64,6 +65,8 @@ main = do
|
|||||||
describe "Observation.JwtCacheObs" Observation.JwtCache.spec
|
describe "Observation.JwtCacheObs" Observation.JwtCache.spec
|
||||||
before (initApp baseSchemaCache testCfg) $
|
before (initApp baseSchemaCache testCfg) $
|
||||||
describe "Feature.MetricsSpec" Observation.MetricsSpec.spec
|
describe "Feature.MetricsSpec" Observation.MetricsSpec.spec
|
||||||
|
before (initApp baseSchemaCache testCfg) $
|
||||||
|
describe "Feature.SchemaCacheSpec" Observation.SchemaCacheSpec.spec
|
||||||
|
|
||||||
where
|
where
|
||||||
loadSCache pool conf =
|
loadSCache pool conf =
|
||||||
|
|||||||
@@ -206,5 +206,5 @@ waitForObs (ObsChan orig copy) t msg f =
|
|||||||
obsDiagMessage :: Observation -> Text
|
obsDiagMessage :: Observation -> Text
|
||||||
obsDiagMessage = \case
|
obsDiagMessage = \case
|
||||||
(HasqlPoolObs o) -> show o
|
(HasqlPoolObs o) -> show o
|
||||||
o@(DBListenStart channel) -> constrName o <> show channel
|
o@(DBListenStart host port name channel) -> constrName o <> show (host, port, name, channel)
|
||||||
o -> constrName o
|
o -> constrName o
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE MonadComprehensions #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
module Observation.SchemaCacheSpec where
|
||||||
|
|
||||||
|
import Network.Wai (Application)
|
||||||
|
import ObsHelper
|
||||||
|
import qualified PostgREST.AppState as AppState
|
||||||
|
import PostgREST.Config (configDbSchemas)
|
||||||
|
import PostgREST.Observation
|
||||||
|
import Protolude
|
||||||
|
import Test.Hspec (SpecWith, describe, it)
|
||||||
|
import Test.Hspec.Wai (getState)
|
||||||
|
|
||||||
|
spec :: SpecWith (SpecState, Application)
|
||||||
|
spec = describe "Server started with metrics enabled" $ do
|
||||||
|
|
||||||
|
it "Should emit PoolFlushed, SchemaCacheQueriedObs and SchemaCacheLoadedObs when schema cache is reloaded" $ do
|
||||||
|
SpecState{specAppState = appState, specObsChan} <- getState
|
||||||
|
let waitFor = waitForObs specObsChan
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
AppState.schemaCacheLoader appState
|
||||||
|
|
||||||
|
waitFor (1 * sec) "PoolFlushed" $ \x -> [ o | o@PoolFlushed <- pure x ]
|
||||||
|
waitFor (1 * sec) "SchemaCacheQueriedObs" $ \x -> [ o | o@SchemaCacheQueriedObs{} <- pure x ]
|
||||||
|
waitFor (1 * sec) "SchemaCacheLoadedObs" $ \x -> [ o | o@SchemaCacheLoadedObs{} <- pure x ]
|
||||||
|
|
||||||
|
|
||||||
|
it "Should flush pool once when schema reloading retries" $ do
|
||||||
|
SpecState{specAppState = appState, specObsChan} <- getState
|
||||||
|
let waitFor = waitForObs specObsChan
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
AppState.getConfig appState >>= \cfg -> do
|
||||||
|
AppState.putConfig appState $ cfg { configDbSchemas = pure "bad_schema" }
|
||||||
|
AppState.schemaCacheLoader appState
|
||||||
|
|
||||||
|
waitFor (1 * sec) "SchemaCacheErrorObs" $ \x -> [ o | o@SchemaCacheErrorObs{} <- pure x ]
|
||||||
|
|
||||||
|
-- Restore configuration
|
||||||
|
AppState.putConfig appState cfg
|
||||||
|
|
||||||
|
-- Wait for 2 seconds so that retry can happen
|
||||||
|
waitFor (2 * sec) "PoolFlushed" $ \x -> [ o | o@PoolFlushed <- pure x ]
|
||||||
|
waitFor (1 * sec) "SchemaCacheQueriedObs" $ \x -> [ o | o@SchemaCacheQueriedObs{} <- pure x ]
|
||||||
|
waitFor (1 * sec) "SchemaCacheLoadedObs" $ \x -> [ o | o@SchemaCacheLoadedObs{} <- pure x ]
|
||||||
|
where
|
||||||
|
sec = 1000000
|
||||||
@@ -2,4 +2,4 @@ DROP ROLE IF EXISTS postgrest_test_anonymous, postgrest_test_author;
|
|||||||
CREATE ROLE postgrest_test_anonymous;
|
CREATE ROLE postgrest_test_anonymous;
|
||||||
CREATE ROLE postgrest_test_author;
|
CREATE ROLE postgrest_test_author;
|
||||||
|
|
||||||
GRANT postgrest_test_anonymous, postgrest_test_author TO :PGUSER;
|
GRANT postgrest_test_anonymous, postgrest_test_author TO :"PGUSER";
|
||||||
|
|||||||
Vendored
+1
-1
@@ -4,4 +4,4 @@ CREATE ROLE postgrest_test_default_role;
|
|||||||
CREATE ROLE postgrest_test_author;
|
CREATE ROLE postgrest_test_author;
|
||||||
CREATE ROLE postgrest_test_superuser WITH SUPERUSER;
|
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";
|
||||||
|
|||||||
Reference in New Issue
Block a user