Compare commits

...
83 Commits
Author SHA1 Message Date
renovate[bot]andWolfgang Walther 4a58fd463b chore(deps): update actions/checkout action to v7.0.1 2026-07-23 20:11:40 +00:00
renovate[bot]andWolfgang Walther a03e008960 chore(deps): update all dependencies 2026-06-19 19:07:35 +00:00
renovate[bot]andWolfgang Walther 8c77da4ab2 chore(deps): update all dependencies to v6.0.3 2026-06-03 07:40:19 +00:00
renovate[bot]andWolfgang Walther c2043df96c chore(deps): update cachix/cachix-action digest to 5f2d7c5 2026-05-12 13:10:53 +00:00
Wolfgang Walther bebe2fbc97 docs: fix broken link 2026-04-15 09:30:25 +02:00
renovate[bot]andWolfgang Walther 63f3c7e687 chore(deps): update cachix/cachix-action action to v17 2026-03-18 21:19:00 +00:00
Wolfgang Walther 3764a68d05 ci: remove unused Dockerfile
This was used by CI, which is disabled on this branch.

Removing to prevent useless auto-updates by renovate.
2026-03-17 11:08:14 +01:00
renovate[bot]andWolfgang Walther 0866b83732 chore(deps): update all dependencies 2026-03-13 14:02:38 +00:00
Wolfgang Walther b8951ea79f ci: remove unused actions
These trigger dependency updates otherwise, which we don't need here.
2026-03-13 14:59:35 +01:00
Wolfgang Walther 7ce205286e docs: fix prometheus text format link
Reported by linkcheck.
2026-03-11 09:45:53 +01:00
dshukertjrandWolfgang Walther 63031a1726 docs: Update the Supabase logo to a correct one 2026-02-19 10:53:31 +01:00
Wolfgang Walther 79126ced77 docs: fix links
datrium.com doesn't exist anymore, while euronodes.com seems to only
fail SSL in CI.
2026-01-28 09:57:45 +01:00
renovate[bot]andWolfgang Walther 8987582428 chore(deps): update actions/upload-artifact action to v6 2025-12-13 17:12:38 +00:00
renovate[bot]andWolfgang Walther 5ff6c10a2d chore(deps): update actions/cache action to v5.0.1 2025-12-13 11:31:26 +00:00
renovate[bot]andWolfgang Walther b65358933a chore(deps): update actions/cache action to v5 2025-12-12 08:29:26 +00:00
renovate[bot]andWolfgang Walther 32e1b1ccbd chore(deps): update actions/checkout action to v6 2025-12-03 13:52:43 +00:00
renovate[bot]andWolfgang Walther 966129f29b chore(deps): update actions/checkout action to v5.0.1 2025-11-18 09:38:33 +00:00
renovate[bot]andWolfgang Walther 163e6b31e0 chore(deps): update ubuntu:noble docker digest to c35e29c 2025-11-15 13:52:18 +00:00
renovate[bot]andWolfgang Walther 4470346049 chore(deps): update actions/upload-artifact action to v5 2025-10-25 08:12:49 +00:00
renovate[bot]andWolfgang Walther 38bdb55d06 chore(deps): update ubuntu:noble docker digest to 66460d5 2025-10-13 10:21:37 +00:00
renovate[bot]andWolfgang Walther 1295552a66 chore(deps): update ubuntu:noble docker digest to 59a458b 2025-10-10 07:33:53 +00:00
renovate[bot]andWolfgang Walther 0a30718f51 chore(deps): update ubuntu:noble docker digest to 728785b 2025-10-03 13:34:20 +00:00
renovate[bot]andWolfgang Walther fee33d4cdf chore(deps): update ubuntu:noble docker digest to fdb6c9c 2025-10-02 10:04:05 +00:00
renovate[bot]andWolfgang Walther 88e57f7849 chore(deps): update nixbuild/nix-quick-install-action action to v34 2025-09-25 08:22:29 +00:00
renovate[bot]andWolfgang Walther 22e54d52bf chore(deps): update actions/cache action to v4.3.0 2025-09-25 08:22:04 +00:00
renovate[bot]andWolfgang Walther d07aba9745 chore(deps): update ubuntu:noble docker digest to 353675e 2025-09-17 10:32:26 +00:00
renovate[bot]andWolfgang Walther e0aa59163c chore(deps): update ubuntu:noble docker digest to 590e57a 2025-09-16 09:01:42 +00:00
renovate[bot]andWolfgang Walther f66f8a15cd chore(deps): update ubuntu:noble docker digest to 9cbed75 2025-09-03 13:53:30 +00:00
renovate[bot]andWolfgang Walther b970c78a43 chore(deps): update ubuntu:noble docker digest to f3b7f1b 2025-09-02 08:16:47 +00:00
renovate[bot]andWolfgang Walther d13a39655d chore(deps): update nixbuild/nix-quick-install-action action to v33 2025-08-25 13:55:39 +00:00
renovate[bot]andWolfgang Walther 2e341ac825 chore(deps): update ubuntu:noble docker digest to 7c06e91 2025-08-13 07:19:13 +00:00
renovate[bot]andWolfgang Walther fcb163198d chore(deps): update actions/checkout action to v5 2025-08-11 15:31:59 +00:00
renovate[bot]andWolfgang Walther 68cea0750b chore(deps): update actions/cache action to v4.2.4 2025-08-07 14:24:02 +00:00
renovate[bot]andWolfgang Walther c5a984b874 chore(deps): update ubuntu:noble docker digest to a08e551 2025-07-19 12:16:50 +00:00
Wolfgang Walther 930d0ae6d7 docs: adjust some redirects
Those were reported in the weekly linkcheck.
2025-07-16 11:04:42 +02:00
renovate[bot]andWolfgang Walther 830edaeedf chore(deps): update ubuntu:noble docker digest to c4570d2 2025-07-16 09:00:05 +00:00
renovate[bot]andWolfgang Walther e925665df6 chore(deps): update ubuntu:noble docker digest to e356c06 2025-07-16 08:51:14 +00:00
renovate[bot]andWolfgang Walther 9625b39873 chore(deps): update ubuntu:noble docker digest to 440dcf6 2025-07-09 07:08:50 +00:00
steve-chavezandWolfgang Walther 5bcdeba526 docs: clarify health checks empty response body 2025-07-08 20:18:39 +02:00
Wolfgang Walther 8f8efbaa12 ci: disable all CI except docs
IO tests fail randomly and we don't need to run everything if we only
ever update the docs on this branch from now on.
2025-07-05 22:18:49 +02:00
Taimoor ZaeemandWolfgang Walther 9675ab239b docs: horizontal filtering on table-valued functions 2025-07-05 21:10:05 +02:00
Wolfgang Walther 4a16dd924b ci: fix loadtest job on stable branches
Previously, the loadtest would always compare against main and the
latest tag. This meant a loadtest running on the v13 release branch,
would compare to a *future* version of both. This is not useful, and
also not supported by `postgrest-loadtest-against`, which recently
introduced a change on `main`, that now breaks the tests on the back
branches.

With this change, the loadtest will always run against the current
target branch of a PR, so against the v13 branch for a PR against v13,
for example. Also, it will compare against the latest released tag *for
that branch*.

Thus, when running this for v12, it will test against the v12 branch and
the v12.2.12 tag.
2025-06-26 10:51:12 +00:00
Wolfgang Walther 17fcc5f47b ci: update Nix to 2.29.1
Related:
https://discourse.nixos.org/t/security-advisory-privilege-escalations-in-nix-lix-and-guix/66017
2025-06-25 12:59:01 +02:00
Thilo HohltandWolfgang Walther 38f1b06122 Update ecosystem.rst
The repository has been moved from a dedicated organisation to my personal profile, so this old link will no longer work after 90 days.
2025-05-31 13:50:17 +02:00
steve-chavezandWolfgang Walther 0031ec3ed0 docs: remove Greenplum integration
They're not really a sponsor, so it's not fair to include them.
2025-05-30 14:36:37 +02:00
Wolfgang Walther ec2f2e7f33 docs: apply redirects
Those redirect, so we might as well hardcode the them.
2025-05-28 09:16:36 +02:00
Wolfgang Walther 7538e4640f docs: remove outdated "in production" links
Moat has been bought by Oracle. The advertising subpage redirects to
oracle.com, but pointing at that would be a bit misleading.

No need to keep failing links either.
2025-05-28 09:16:11 +02:00
steve-chavez cd3cf9ed97 bump version to 12.2.12 2025-05-01 20:24:30 -05:00
steve-chavez 1f28efa9bd fix: don't enable admin server /config by default
This now requires setting `admin-server-config-enabled`.
2025-05-01 20:24:08 -05:00
steve-chavez 36eb72c2a0 bump version to 12.2.11 2025-04-21 17:08:00 -05:00
Taimoor ZaeemandSteve Chavez 38c596800a fix: regression with parameter charset=utf-8 in mediatype 2025-04-21 17:06:26 -05:00
steve-chavez a7f9181462 bump version to 12.2.10 2025-04-18 21:28:00 -05:00
Michal KleczekandSteve Chavez f68d5944e6 fix: purge JWT cache asynchronously in a separate thread
Otherwise performance was reduced unnecessarily.
2025-04-18 21:27:36 -05:00
Wolfgang Walther b454f29b2c bump version to 12.2.9 2025-04-16 20:39:19 +02:00
Taimoor ZaeemandWolfgang Walther a3cc762f17 fix: valid JWTs after jwt-secret is changed in a config reload 2025-04-16 18:26:42 +00:00
Taimoor ZaeemandWolfgang Walther 66cd4ca596 fix: regression that replaces an unknown media type with */* (#4013) 2025-04-14 18:00:21 +02:00
Taimoor ZaeemandWolfgang Walther 513704dc2b fix: parsing of the for parameter of plan media type (#4005) 2025-04-12 15:37:37 +00:00
Thilo HohltandWolfgang Walther 026d84a093 docs: add archtika to example apps section on ecosystem page (#4007) 2025-04-11 14:34:56 +02:00
Taimoor ZaeemandWolfgang Walther 106f193a56 docs: add note that ordering of columns is not enforced (#3999) 2025-04-10 14:44:03 +02:00
steve-chavezandWolfgang Walther 79fc43ec39 docs: redirect from broken #bulk-insert-default 2025-04-04 18:24:26 +02:00
Taimoor ZaeemandWolfgang Walther b187f09574 docs: mention that updates also supports specifying columns and missing pref 2025-04-04 18:24:25 +02:00
Taimoor ZaeemandWolfgang Walther 4f1dc72e40 docs: explain missing preference header 2025-04-04 18:24:23 +02:00
renovate[bot]andWolfgang Walther d70d5cb19d chore(deps): update peter-evans/dockerhub-description action to v4.0.2 2025-04-03 12:19:48 +00:00
renovate[bot]andWolfgang Walther 6e851cce59 chore(deps): update peter-evans/dockerhub-description action to v4.0.1 2025-04-01 20:25:14 +00:00
renovate[bot]andWolfgang Walther accf46d2ee chore(deps): update all dependencies 2025-03-27 19:51:24 +00:00
renovate[bot]andWolfgang Walther 852f833d56 chore(deps): update all dependencies 2025-03-27 19:32:19 +00:00
Wolfgang Walther 48e6267dde docs: Remove broken link 2025-03-26 17:00:31 +01:00
Wolfgang Walther ed86bbf3de docs: remove broken link 2025-03-14 23:21:41 +01:00
Wolfgang Walther 3ba4290ef0 chore: Fix style check 2025-02-22 17:01:27 +01:00
Wolfgang Walther 0be047a52f docs: Fix outdated links 2025-02-22 16:10:50 +01:00
Wolfgang Walther d2d3367046 ci: Fix cirrus FreeBSD builds
Apparently Cirrus removed the 14-1 image. When I firsted looked into
this some days ago, when the job started failing, the docs were not
updated, yet - so it wasn't clear. Now the docs mention freebsd-14-2
explicitly...
2025-02-22 14:22:29 +01:00
Laurence IslaandWolfgang Walther ba6f985dac docs: make the aggregate functions docs less verbose 2025-02-22 13:19:10 +01:00
steve-chavezandWolfgang Walther 7cb007567b docs: reduce verbosity of aggregate functions 2025-02-22 13:19:10 +01:00
Taimoor ZaeemandWolfgang Walther acb72b7c67 docs: add missing jwt claims and clock skew (#3908) 2025-02-22 13:19:10 +01:00
steve-chavezandWolfgang Walther 8649d27709 docs: correct package for installation under Nix 2025-02-22 13:19:10 +01:00
steve-chavez 4a3936f71c bump version to 12.2.8 2025-02-10 13:25:22 -05:00
M. Taimoor Zaeemandsteve-chavez 9b6b5d06ea fix: log 503 client error to stderr 2025-02-10 13:24:52 -05:00
Wolfgang Walther ec83d08be6 bump version to 12.2.7 2025-02-03 18:38:41 +01:00
Diogo BiazusandWolfgang Walther c026d0752f fix: Fix regression for schema cache reloading via NOTIFY on Windows
Upstream accidentally removed the fix, which was introduced for #2524. Fixed again.

(cherry picked from commit b285f5fba6)
2025-02-03 18:35:24 +01:00
steve-chavez dc96cdb0c3 bump version to 12.2.6 2025-01-29 15:22:22 -05:00
Taimoor Zaeemandsteve-chavez 89be285e6a fix: jwt cache is not purged (#3801) 2025-01-29 15:14:05 -05:00
renovate[bot]andWolfgang Walther c6b4fca160 chore(deps): update codecov/codecov-action action to v5.3.1 2025-01-25 12:01:21 +01:00
renovate[bot]andWolfgang Walther 31cccd3264 chore(deps): update codecov/codecov-action action to v5.3.0 2025-01-25 11:12:46 +01:00
62 changed files with 625 additions and 1072 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
freebsd_instance:
image_family: freebsd-14-1
image_family: freebsd-14-2
build_task:
# Don't change this name without adjusting .github/workflows/build.yaml
@@ -1,119 +0,0 @@
name: Artifact from Cirrus
description: Waits for a specific Cirrus CI run to complete, then downloads the artifact and uploads it to the current workflow. This will silently succeed if Cirrus CI did not schedule a task within 2 minutes.
inputs:
download:
description: Name of Artifact to download from Cirrus CI
required: true
task:
description: Name of Cirrus Task
required: true
token:
description: GitHub Token
required: true
upload:
description: Name of Artifact to upload on GitHub Actions
required: true
runs:
using: composite
steps:
- shell: bash
run: echo "GH_TOKEN=${{ inputs.token }}" >> "$GITHUB_ENV"
- name: Wait for Check Suite to be created
id: check-suite
env:
# GITHUB_SHA does weird things for pull request, so we roll our own:
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
shell: bash
run: |
get_check_runs_url() {
gh api "repos/{owner}/{repo}/commits/${COMMIT}/check-suites" \
| jq -r '.check_suites[] | select(.app.slug == "cirrus-ci") | .check_runs_url'
}
for _ in $(seq 1 12); do
check_runs_url="$(get_check_runs_url)"
if [ -z "$check_runs_url" ]; then
echo "Cirrus CI task has not started, yet. Waiting..."
sleep 10
else
echo "check_runs_url=$check_runs_url" >> "$GITHUB_OUTPUT"
exit 0
fi
done
>&2 echo "Cirrus CI check suite not found. Is Cirrus CI enabled for this repo?"
- name: Find task by name
id: find-task
if: steps.check-suite.outputs.check_runs_url
shell: bash
run: |
get_number_of_tasks() {
gh api "${{ steps.check-suite.outputs.check_runs_url }}" \
| jq -r '.check_runs | map(select(.name == "${{ inputs.task }}")) | length'
}
tasks="$(get_number_of_tasks)"
case "$tasks" in
0)
echo "Task not found, assuming it's skipped intentionally..."
exit 0
;;
1)
echo "task_found=1" >> "$GITHUB_OUTPUT"
exit 0
;;
*)
>&2 echo "More than 1 task with the same name found. Don't know what to do..."
exit 1
;;
esac
- name: Wait for Cirrus CI to complete task
if: steps.find-task.outputs.task_found
shell: bash
run: |
get_conclusion() {
gh api "${{ steps.check-suite.outputs.check_runs_url }}" \
| jq -r '.check_runs[] | select(.name == "${{ inputs.task }}" and .status == "completed") | .conclusion'
}
while true; do
conclusion="$(get_conclusion)"
if [ -z "$conclusion" ]; then
echo "Cirrus CI task has not completed, yet. Waiting..."
sleep 30
else
if [ "$conclusion" == "success" ]; then
break
else
exit 1
fi
fi
done
- name: Download artifact from Cirrus CI
if: steps.find-task.outputs.task_found
id: download
shell: bash
run: |
get_external_id() {
gh api "${{ steps.check-suite.outputs.check_runs_url }}" \
| jq -er '.check_runs[] | select(.name == "${{ inputs.task }}") | .external_id'
}
archive="$(mktemp)"
artifacts="$(mktemp -d)"
until curl --no-progress-meter --fail -o "${archive}" \
"https://api.cirrus-ci.com/v1/artifact/task/$(get_external_id)/${{ inputs.download }}.zip"
do
# This happens when a tag is pushed on the same commit. In this case the
# job is immediately marked as "completed" for us, so we end up here after a few
# seconds - but the actual Cirrus CI task is still running and didn't produce its artifact, yet.
echo "Artifact not found on Cirrus CI, yet. Waiting..."
sleep 30
done
unzip "${archive}" -d "${artifacts}"
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
- name: Save artifact to GitHub Actions
if: steps.find-task.outputs.task_found
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: ${{ inputs.upload }}
path: ${{ steps.download.outputs.artifacts }}
if-no-files-found: error
-35
View File
@@ -1,35 +0,0 @@
name: Cache on main
description: Stores caches on main and release branches only, but restores them on all branches.
inputs:
path:
description: Path(s) to cache
required: true
save-prs:
description: Whether to additionally store the cache in a pull request, too. Should only be used for very small caches.
type: boolean
prefix:
description: Cache key prefix to be used in both primary key and restore-keys.
required: true
suffix:
description: Cache key suffix to be used only in primary key.
required: true
runs:
using: composite
steps:
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
restore-keys: |
${{ runner.os }}-${{ inputs.prefix }}-
- uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
restore-keys: |
${{ runner.os }}-${{ inputs.prefix }}-
+2 -2
View File
@@ -11,12 +11,12 @@ inputs:
runs:
using: composite
steps:
- uses: nixbuild/nix-quick-install-action@25aff27c252e0c8cdda3264805f7b6bcd92c8718 # v29
- uses: nixbuild/nix-quick-install-action@9f63be77f412a248c9d9a65a4c82cf066cdf8f0c # v35
with:
nix_conf: |-
always-allow-substitutes = true
max-jobs = auto
- uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15
- uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: postgrest
authToken: ${{ inputs.authToken }}
-201
View File
@@ -1,201 +0,0 @@
name: Build
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/build.yaml
- .github/actions/**
- .github/scripts/**
- .github/*
- '*.nix'
- nix/**
- .cirrus.yml
- cabal.project*
- postgrest.cabal
- stack.yaml*
- '**.hs'
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: build-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
static:
name: Nix - Linux x86-64 static
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build static executable
run: nix-build -A postgrestStatic
- name: Save built executable as artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: postgrest-linux-static-x86-64
path: result/bin/postgrest
if-no-files-found: error
- name: Build Docker image
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
- name: Save built Docker image as artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: postgrest-docker-x86-64
path: postgrest-docker.tar.gz
if-no-files-found: error
macos:
name: Nix - MacOS
runs-on: macos-14
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Install gnu sed
run: brew install gnu-sed
- name: Build everything
run: |
# The --dry-run will give us a list of derivations to download from cachix and
# derivations to build. We only take those that would have to be built and then build
# those explicitly. This has the advantage that pure verification will not include
# a download anymore, making it much faster. If something needs to be built, only
# the dependencies required to do so will be downloaded, but not everything.
nix-build --dry-run 2>&1 \
| gsed -e '1,/derivations will be built:$/d' -e '/paths will be fetched/Q' \
| xargs nix-build
stack:
strategy:
fail-fast: false
matrix:
include:
- name: Linux aarch64
runs-on: ubuntu-24.04-arm
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-ubuntu-aarch64
deps: sudo apt-get update && sudo apt-get install libpq-dev
- name: MacOS
runs-on: macos-14
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-macos-aarch64
deps: brew link --force libpq
- name: Windows
runs-on: windows-2022
cache: |
C:\sr\pantry
C:\sr\snapshots
C:\sr\stack.sqlite3
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
artifact: postgrest-windows-x86-64
name: Stack - ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: haskell-actions/setup@bbd90a29996ac33b1c644a42206e312fc0379748 # v2.7.9
with:
# This must match the version in stack.yaml's resolver
ghc-version: 9.6.5
enable-stack: true
stack-no-global: true
stack-setup-ghc: true
- name: Cache ~/.stack
uses: ./.github/actions/cache-on-main
with:
path: ${{ matrix.cache }}
prefix: stack
suffix: ${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
- name: Cache .stack-work
uses: ./.github/actions/cache-on-main
with:
path: .stack-work
save-prs: true
prefix: stack-work-${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
suffix: ${{ hashFiles('main/**/*.hs', 'src/**/*.hs') }}
- name: Install dependencies
if: matrix.deps
run: ${{ matrix.deps }}
- name: Build with Stack
run: stack build --lock-file error-on-write --local-bin-path result --copy-bins
- name: Strip Executable
run: strip result/postgrest*
- name: Save built executable as artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: ${{ matrix.artifact }}
path: |
result/postgrest
result/postgrest.exe
if-no-files-found: error
freebsd:
name: Stack - FreeBSD from CirrusCI
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: ./.github/actions/artifact-from-cirrus
with:
token: ${{ github.token }}
task: Build FreeBSD (Stack)
download: bin
upload: postgrest-freebsd-x86-64
cabal:
strategy:
matrix:
ghc: ['9.6.4', '9.8.2']
fail-fast: false
name: Cabal - Linux x86-64 - GHC ${{ matrix.ghc }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: haskell-actions/setup@bbd90a29996ac33b1c644a42206e312fc0379748 # v2.7.9
with:
ghc-version: ${{ matrix.ghc }}
- name: Cache .cabal
uses: ./.github/actions/cache-on-main
with:
path: |
~/.cabal/packages
~/.cabal/store
prefix: cabal-${{ matrix.ghc }}
suffix: ${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
- name: Cache dist-newstyle
uses: ./.github/actions/cache-on-main
with:
path: dist-newstyle
save-prs: true
prefix: cabal-${{ matrix.ghc }}-dist-newstyle-${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
suffix: ${{ hashFiles('**/*.hs') }}
- name: Install dependencies
run: cabal build --only-dependencies --enable-tests --enable-benchmarks
- name: Build
run: cabal build --enable-tests --enable-benchmarks all
-32
View File
@@ -1,32 +0,0 @@
name: Check
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: style-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint-style:
name: Lint & Style
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: style.lint.bin style.styleCheck.bin
- name: Run linter (check locally with `nix-shell --run postgrest-lint`)
run: postgrest-lint
- name: Run style check (auto-format with `nix-shell --run postgrest-style`)
run: postgrest-style-check
-70
View File
@@ -1,70 +0,0 @@
name: CI
on:
push:
branches:
- main
- v[0-9]+
jobs:
check:
name: Check
uses: ./.github/workflows/check.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
docs:
name: Docs
uses: ./.github/workflows/docs.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
test:
name: Test
uses: ./.github/workflows/test.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build:
name: Build
uses: ./.github/workflows/build.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
tag:
name: Tag
concurrency:
# Never tag outdated commits on the main branch by skipping superseded commits
group: ci-tag-${{ (github.ref == 'refs/heads/main' && github.ref) || github.run_id }}
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
cancel-in-progress: false
if: vars.RELEASE_ENABLED
runs-on: ubuntu-24.04
needs:
- docs
- test
- build
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
- name: Tag latest commit
run: |
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
if [[ "$cabal_version" == *.*.* ]]; then
git fetch --tags
if [ -z "$(git tag --list "v$cabal_version")" ]; then
git tag "v$cabal_version"
git push origin "v$cabal_version"
fi
else
git tag -f "devel"
git push -f origin "devel"
fi
+7 -7
View File
@@ -1,10 +1,10 @@
name: Docs
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
push:
branches:
- main
- v[0-9]+
pull_request:
branches:
- main
@@ -27,7 +27,7 @@ jobs:
name: Build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -41,7 +41,7 @@ jobs:
name: Spellcheck
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
@@ -58,7 +58,7 @@ jobs:
if: github.base_ref == 'main'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
-202
View File
@@ -1,202 +0,0 @@
name: Release
on:
push:
tags:
- devel
- v*
concurrency:
# Terminate all previous runs of the same workflow for the same tag.
group: release-${{ github.ref }}
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
cancel-in-progress: false
jobs:
build:
name: Build
uses: ./.github/workflows/build.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
prepare:
name: Prepare
runs-on: ubuntu-24.04
needs:
- build
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Check the version to be released
run: |
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
if [ "${GITHUB_REF_NAME}" != "devel" ] && [ "${GITHUB_REF_NAME}" != "v$cabal_version" ]; then
echo "Tagged version ($GITHUB_REF_NAME) does not match the one in postgrest.cabal (v$cabal_version). Aborting release..."
exit 1
fi
- name: Identify changes from CHANGELOG.md
run: |
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
echo "Getting unreleased changes..."
sed -n "1,/## Unreleased/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
else
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
echo "Propper release, getting changes for version $version ..."
sed -n "1,/## \[$version\]/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
fi
echo "Relevant extract from CHANGELOG.md:"
cat CHANGES.md
- name: Save CHANGES.md as artifact
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: release-changes
path: CHANGES.md
if-no-files-found: error
github:
name: GitHub
permissions:
contents: write
runs-on: ubuntu-24.04
needs:
- prepare
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download all artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
path: artifacts
- name: Create release bundle with archives for all builds
run: |
find artifacts -type f -iname postgrest -exec chmod +x {} \;
mkdir -p release-bundle
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x86-64.tar.xz" \
-C artifacts/postgrest-linux-static-x86-64 postgrest
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}-freebsd-x86-64.tar.xz" \
-C artifacts/postgrest-freebsd-x86-64 postgrest
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
-C artifacts/postgrest-ubuntu-aarch64 postgrest
zip --junk-paths "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x86-64.zip" \
artifacts/postgrest-windows-x86-64/postgrest.exe
- name: Save release bundle
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: release-bundle
path: release-bundle
if-no-files-found: error
- name: Publish release on GitHub
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Releasing version ${GITHUB_REF_NAME} on GitHub..."
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
# To replace the existing release, we must first delete the old assets,
# then modify the release, then add the new assets.
gh release view devel --json assets \
| jq -r '.assets[] | .name' \
| xargs -rn1 \
gh release delete-asset -y devel
gh release edit devel \
-t devel \
--verify-tag \
-F artifacts/release-changes/CHANGES.md \
--prerelease
gh release upload --clobber devel release-bundle/*
else
gh release create "${GITHUB_REF_NAME}" \
-t "${GITHUB_REF_NAME}" \
--verify-tag \
-F artifacts/release-changes/CHANGES.md \
release-bundle/*
fi
docker:
name: Docker Hub
runs-on: ubuntu-24.04-arm
needs:
- prepare
if: |
vars.DOCKER_REPO && vars.DOCKER_USER
env:
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download x86-64 Docker image
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: postgrest-docker-x86-64
- name: Download aarch64 binary
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: postgrest-ubuntu-aarch64
- uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Build aarch64 Docker image
run: |
# This only pushes the image via digest, not a tag. This will not appear
# in the image list on Docker Hub, yet. It will be later added to the main
# tag's manifest.
docker buildx build \
-t "$DOCKER_REPO/postgrest" \
--platform linux/arm64 \
--output push-by-digest=true,type=image,push=true \
--metadata-file metadata.json \
.
echo "SHA256_ARM=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_ENV"
- name: Publish images on Docker Hub
run: |
docker load -i postgrest-docker.tar.gz
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
docker buildx imagetools create --append \
-t "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
"$DOCKER_REPO/postgrest@$SHA256_ARM"
# Only tag 'latest' for full releases
if [ "${GITHUB_REF_NAME}" != "devel" ]; then
echo "Pushing to 'latest' tag for full release of ${GITHUB_REF_NAME} ..."
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
docker push "$DOCKER_REPO"/postgrest:latest
docker buildx imagetools create --append \
-t "$DOCKER_REPO/postgrest:latest" \
"$DOCKER_REPO/postgrest@$SHA256_ARM"
else
echo "Skipping push to 'latest' tag for pre-release..."
fi
docker-description:
name: Docker Hub Description
runs-on: ubuntu-24.04
if: |
vars.DOCKER_REPO && vars.DOCKER_USER &&
github.ref == 'refs/tags/devel'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
repository: ${{ vars.DOCKER_REPO }}/postgrest
short-description: ${{ github.event.repository.description }}
readme-filepath: ./docker-hub-readme.md
-132
View File
@@ -1,132 +0,0 @@
name: Test
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
CODECOV_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/test.yaml
- .github/workflows/report.yaml
- .github/actions/setup-nix/**
- default.nix
- nix/**
- .stylish-haskell.yaml
- cabal.project
- postgrest.cabal
- '**.hs'
- test/**
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: test-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
coverage:
name: Coverage
runs-on: ubuntu-24.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.coverage.bin tests.testDoctests.bin tests.testSpecIdempotence.bin
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
run: postgrest-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
with:
files: ./coverage/codecov.json
token: ${{ secrets.CODECOV_TOKEN }}
- name: Run doctests
if: always()
run: postgrest-test-doctests
- name: Check the spec tests for idempotence
if: always()
run: postgrest-test-spec-idempotence
postgres:
strategy:
fail-fast: false
matrix:
pgVersion: ["9_6", 10, 11, 12, 13, 14, 15, 16]
name: PG ${{ matrix.pgVersion }}
runs-on: ubuntu-24.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.testSpec.bin tests.testIO.bin tests.testBigSchema.bin withTools.postgresql-${{ matrix.pgVersion }}.bin
- name: Run spec tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
- name: Run IO tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io -vv
- name: Run IO tests on a big schema
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
memory:
name: Memory
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: memory.test.bin
- name: Run memory tests
run: postgrest-test-memory
loadtest:
name: Loadtest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: loadtest.loadtestAgainst.bin loadtest.report.bin
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
id: get-latest-tag
with:
prefix: v
- name: Run loadtest
run: |
postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }}
postgrest-loadtest-report >> "$GITHUB_STEP_SUMMARY"
+1 -1
View File
@@ -32,7 +32,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.png">
<img width="296px" src="static/supabase.svg">
</a>
</td>
<td align="center" valign="middle">
+44
View File
@@ -5,6 +5,50 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
## [12.2.12] - 2025-05-01
### Fixed
- #3956, Fix exposing admin server `/config` by default - @steve-chavez
+ The above endpoint is now disabled unless the `admin-server-config-enabled` config is set to `true`
## [12.2.11] - 2025-04-21
### Fixed
- #4030, Fix regression with parameter `charset=utf-8` in mediatype - @taimoorzaeem
## [12.2.10] - 2025-04-18
### Fixed
- #3889, Fix: JWT cache purging on every request decreases performance - @mkleczek
## [12.2.9] - 2025-04-16
### Fixed
- #3498, Fix incorrect parsing of the `for` parameter of the `application/vnd.pgrst.plan` media type - @taimoorzaeem
- #4014, Fix JWT cache allows old tokens after the jwt-secret is changed in a config reload - @taimoorzaeem
## [12.2.8] - 2025-02-10
### Fixed
- #3841, Log `503` client error to stderr - @taimoorzaeem
## [12.2.7] - 2025-02-03
### Fixed
- #2524, Fix schema reloading notice on windows - @diogob
## [12.2.6] - 2025-01-29
### Fixed
- #3788, Fix jwt cache does not remove expired entries - @taimoorzaeem
## [12.2.5] - 2025-01-20
### Fixed
-21
View File
@@ -1,21 +0,0 @@
# PostgREST Docker Hub image for aarch64.
# The x86-64 is a single-static-binary image built via Nix, see:
# nix/tools/docker/README.md
FROM ubuntu:noble@sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab AS postgrest
RUN apt-get update -y \
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY postgrest /usr/bin/postgrest
RUN chmod +x /usr/bin/postgrest
EXPOSE 3000
USER 1000
# Use the array form to avoid running the command using bash, which does not handle `SIGTERM` properly.
# See https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop
CMD ["postgrest"]
+1 -1
View File
@@ -41,7 +41,7 @@ API than you are likely to write from scratch.
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.png">
<img width="296px" src="static/supabase.svg">
</a>
</td>
<td align="center" valign="middle">
+1 -1
View File
@@ -1 +1 @@
index-state: hackage.haskell.org 2024-05-17T23:41:49Z
index-state: hackage.haskell.org 2025-02-01T14:59:33Z
+1 -1
View File
@@ -38,7 +38,7 @@ write from scratch.
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.png">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.svg">
</a>
</td>
<td align="center" valign="middle">
+10 -4
View File
@@ -292,12 +292,18 @@ def setup(app):
app.add_css_file("css/custom.css")
# taken from https://github.com/sphinx-doc/sphinx/blob/82dad44e5bd3776ecb6fd8ded656bc8151d0e63d/sphinx/util/requests.py#L42
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0"
user_agent = (
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
)
linkcheck_ignore = [
r"https://www.patreon.com/postgrest",
r"https://blog.frankel.ch/poor-man-api",
# Odd SSL error
r"https://www.dripdepot.com",
r"https://www.euronodes.com",
# New GitHub UI delays comment load, so anchor fails
r"https://github.com/.*#issuecomment",
# Random 500 Internal Server Error
r"https://jwt.io",
]
# sphinx-tabs configuration
+3 -5
View File
@@ -6,10 +6,7 @@ Community Tutorials
* `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.
* `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 + PostGIS API tutorial in 5 minutes <https://gis-ops.com/postgrest-postgis-api-tutorial-geospatial-api-in-5-minutes/>`_ -
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`functions` interface.
* `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/>`_ -
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
@@ -37,6 +34,7 @@ Templates
Example Apps
------------
* `archtika <https://github.com/thiloho/archtika>`_ - selfhosted CMS
* `delibrium-postgrest <https://gitlab.com/delibrium/delibrium-postgrest/>`_ - example school API and front-end in Vue.js
* `ETH-transactions-storage <https://github.com/Adamant-im/ETH-transactions-storage>`_ - indexer for Ethereum to get transaction list by ETH address
* `general <https://github.com/PierreRochard/general>`_ - example auth back-end
@@ -90,7 +88,7 @@ Client-Side Libraries
* `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go
* `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript
* `postgrest-kt <https://github.com/supabase-community/postgrest-kt>`_ - Kotlin
* `postgrest-py <https://github.com/supabase-community/postgrest-py>`_ - Python
* `postgrest-py <https://github.com/supabase/postgrest-py>`_ - Python
* `postgrest-rs <https://github.com/supabase-community/postgrest-rs>`_ - Rust
* `postgrest-swift <https://github.com/supabase-community/postgrest-swift>`_ - Swift
* `redux-postgrest <https://github.com/andytango/redux-postgrest>`_ - TypeScript/JS, client integrated with (React) Redux.
+1 -1
View File
@@ -15,7 +15,7 @@ Roles for Each Web User
PostgREST can accommodate either viewpoint. If you treat a role as a single user then the :ref:`jwt_impersonation` does most of what you need. When an authenticated user makes a request PostgREST will switch into the database role for that user, which in addition to restricting queries, is available to SQL through the :code:`current_user` variable.
You can use row-level security to flexibly restrict visibility and access for the current user. Here is an `example <https://www.2ndquadrant.com/en/blog/application-users-vs-row-level-security/>`_ from Tomas Vondra, a chat table storing messages sent between users. Users can insert rows into it to send messages to other users, and query it to see messages sent to them by other users.
You can use row-level security to flexibly restrict visibility and access for the current user. Here is an `example <https://www.enterprisedb.com:443/blog/application-users-vs-row-level-security>`_ from Tomas Vondra, a chat table storing messages sent between users. Users can insert rows into it to send messages to other users, and query it to see messages sent to them by other users.
.. code-block:: postgres
+5 -12
View File
@@ -39,17 +39,17 @@ Sponsors
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
.. image:: ../static/gnuhost.png
:target: https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest
:target: https://euronodes.com/?utm_source=sponsor&utm_campaign=postgrest
.. container:: img-dark
.. image:: ../static/neon-dark.jpg
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
.. container:: img-light
.. image:: ../static/neon.jpg
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
|
@@ -65,12 +65,12 @@ Sponsors
.. container:: img-dark
.. image:: ../static/supabase-dark.png
.. image:: ../static/supabase-dark.svg
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. container:: img-light
.. image:: ../static/supabase.png
.. image:: ../static/supabase.svg
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. image:: ../static/tembo.png
@@ -209,21 +209,14 @@ In Production
Here are some companies that use PostgREST in production.
* `Catarse <https://www.catarse.me>`_
* `Datrium <https://www.datrium.com>`_
* `Drip Depot <https://www.dripdepot.com>`_
* `Image-charts <https://www.image-charts.com>`_
* `Moat <https://www.oracle.com/advertising/>`_
* `Netwo <https://www.netwo.io>`_
* `Nimbus <https://www.nimbusfacility.com/sg/home>`_
- See how Nimbus uses PostgREST in `Paul Copplestone's blog post <https://paul.copplest.one/blog/nimbus-tech-2019-04.html>`_.
* `OpenBooking <https://openbooking.ch>`_
* `Redsmin <https://www.redsmin.com>`_
* `Supabase <https://supabase.com>`_
.. Failing links
* `eGull <http://www.egull.co>`_
* `MotionDynamic - Fast highly dynamic video generation at scale <https://motiondynamic.tech>`_
Testimonials
------------
-6
View File
@@ -1,6 +0,0 @@
Greenplum
#########
`Greenplum <https://greenplum.org/>`_ has been reported to work by adding ``LOGIN`` to the :ref:`anonymous and user roles <roles>`.
For more details, see https://github.com/PostgREST/postgrest/issues/2021.
+2 -4
View File
@@ -12,10 +12,10 @@ booleans
BOM
Bytea
Cardano
casted
cd
centric
CLI
CMS
coercible
conf
Cloudflare
@@ -43,7 +43,6 @@ Github
Google
grantor
GraphQL
Greenplum
gte
GUC
Haskell
@@ -146,8 +145,6 @@ SHA
signup
SIGUSR
sl
spreaded
Spreaded
SQL
sql
SQLSTATE
@@ -171,6 +168,7 @@ unikernel
unix
updatable
unfulfillable
unselected
Untyped
UPSERT
Upsert
+6 -2
View File
@@ -12,7 +12,7 @@ Health Check
You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
Two endpoints ``live`` and ``ready`` will then be available.
Two endpoints ``live`` and ``ready`` will then be available. Both these endpoints reply with a status code and empty response body.
.. important::
@@ -55,10 +55,12 @@ Metrics
Provides :ref:`metrics`.
.. _runtime_config:
Runtime Configuration
=====================
Provides a ``config`` endpoint that returns the runtime :ref:`configuration`.
Provides a ``config`` endpoint that returns the runtime :ref:`configuration`. This requires setting :ref:`admin-server-config-enabled`.
.. code-block:: bash
@@ -72,6 +74,8 @@ Provides a ``config`` endpoint that returns the runtime :ref:`configuration`.
db-channel-enabled = false
...
.. _runtime_schema_cache:
Runtime Schema Cache
====================
+68 -103
View File
@@ -3,20 +3,20 @@
Aggregate Functions
###################
Aggregate functions allow you to summarize data by performing calculations across groups of rows. For instance, if you have an ``orders`` table that has an ``amount`` column, you could use an aggregate function to get the sum of the ``amount`` column, either for all rows, or for each group of rows that share specific values, for instance all rows that share the same ``order_date``.
PostgREST supports the following aggregate functions: ``avg()``, ``count()``, ``max()``, ``min()``, and ``sum()``.
Please refer to the `section on aggregate functions in the PostgreSQL documentation <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ for a detailed explanation of these functions.
.. note::
Aggregate functions are *disabled* by default in PostgREST, as without appropriate safeguards, aggregate functions can create performance problems. See :ref:`db-aggregates-enabled` for further details.
Aggregate functions are *disabled* by default in PostgREST, because they can create performance problems without appropriate safeguards.
See :ref:`db-aggregates-enabled` for further details.
PostgREST supports the following aggregate functions: ``avg()``, ``count()``, ``max()``, ``min()``, and ``sum()``. Please refer to the `section on aggregate functions in the PostgreSQL documentation <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ for a detailed explanation of these functions.
To use an aggregate function, you append the function to a value in the ``select`` parameter, like so:
To use an aggregate function, append it to a column in the ``select`` parameter, like so:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum()"
With the above query, PostgREST will return a single row with a single column named ``sum`` that contains the sum of all the values in the ``amount`` column:
This will return a ``sum`` of all the values of the ``amount`` column in a single row:
.. code-block:: json
@@ -26,15 +26,29 @@ With the above query, PostgREST will return a single row with a single column na
}
]
You can use multiple aggregate functions by just adding more columns with aggregate functions to the ``select`` parameter.
You can ``select`` multiple aggregate functions at the same time (you may need to :ref:`rename them <renaming_columns>` to disambiguate).
To group by other columns, you simply add those columns to the ``select`` parameter. For instance:
.. code-block:: bash
curl "http://localhost:3000/orders?select=total_amount:amount.sum(),avg_amount:amount.avg(),total_quantity:quantity.sum()"
.. note::
Aggregate functions work alongside other PostgREST features, like :ref:`h_filter`, :ref:`json_columns`, and :ref:`ordering`.
However they are not compatible with :ref:`domain_reps` for the moment.
Additionally, PostgreSQL's ``HAVING`` clause and ordering by aggregated columns are not yet supported.
Automatic ``GROUP BY``
======================
In SQL, a ``GROUP BY`` clause is required to aggregate the selected columns.
However, PostgREST handles grouping automatically if the columns are already present in the ``select`` parameter.
For instance:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
This will return a row for each unique value in the ``order_date`` column, with the sum and average of the ``amount`` column for all rows that share the same ``order_date``:
This will get the sum and average of the amounts grouped by each unique value in the ``order_date`` column:
.. code-block:: json
@@ -51,67 +65,55 @@ This will return a row for each unique value in the ``order_date`` column, with
}
]
.. note::
Aggregate functions work alongside other PostgREST features, like :ref:`h_filter`, :ref:`json_columns`, and :ref:`ordering`. Please note at this time aggregate functions are not compatible with :ref:`domain_reps`. Additionally, PostgreSQL's ``HAVING`` clause and ordering by aggregated columns are not yet supported.
The Case of ``count()``
===========================
The ``count()`` Aggregate
=========================
.. note::
Before the addition of aggregate functions, it was possible to count by adding ``count`` (without parentheses) to the ``select`` parameter. While this is still supported, it may be deprecated in the future, and thus use of this legacy feature is **not recommended.** Please use ``count()`` (with parentheses) instead.
Before the addition of aggregate functions, it was possible to count by adding ``count`` (without parentheses) to the ``select`` parameter.
While this is still supported, it may be deprecated in the future, and thus use of this legacy feature is **not recommended**.
Please use ``count()`` (with parentheses) instead.
``count()`` is treated specially, as it can be used without an associated column. Take for example the following query:
``count()`` is a special case because it can be used with or without an aggregated column. For example:
.. code-block:: bash
curl "http://localhost:3000/orders?select=count(),order_date"
This would return a row for each unique value in the ``order_date`` column, with the count of all rows that share the same ``order_date``:
curl "http://localhost:3000/orders?select=count(),observation_count:observation.count(),order_date"
.. code-block:: json
[
{
"count": 4,
"observation_count": 2,
"order_date": "2023-01-01"
},
{
"count": 2,
"observation_count": 1,
"order_date": "2023-01-02"
}
]
When ``count()`` is used with an associated column, its behavior is slightly different: It will return the count of all values that are not ``NULL``. This is due to how PostgreSQL itself implements the ``count()`` function.
Renaming and Casting
====================
Renaming Aggregates
-------------------
Just like with other columns, you can rename aggregated columns too. See :ref:`renaming_columns` for details.
Renaming columns is especially helpful in the context of aggregate functions, as by default a column with an aggregate function applied will take on the name of the applied aggregate function. You may want to provide a more semantically meaningful name or prevent collisions when using multiple aggregate functions of the same type.
Note that there is a difference between the result of ``count()`` and ``observation.count()``.
The former counts the whole row, while the latter counts the non ``NULL`` values of the ``observation`` column (both grouped by ``order_date``).
This is due to how PostgreSQL itself implements the ``count()`` function.
Casting Aggregates
------------------
==================
When applying an aggregate function to a column, you are able to cast both the value of the input to the aggregate function *and* the value of the output from the aggregate function. In both cases, the syntax works as described in :ref:`casting_columns`, with the only difference being the placement of the cast.
It is :ref:`possible to cast <casting_columns>` the aggregated column or the aggregate itself, or both at the same time.
Casting the Value of the Input
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Casting the Aggregated Column
-----------------------------
For instance, imagine that the ``orders`` table has a JSON column, ``order_details``, and this column contains a JSON object that has a key, ``tax_amount``. Let's say you want to get the sum of the tax amount for every order. You can use the ``->`` or ``->>`` operators to extract the value with this key (see :ref:`json_columns`), but these operators will return values of the types JSON and ``text`` respectively, and neither of these types can be used with ``sum()``.
Therefore, you will need to first cast the input value to a type that is compatible with ``sum()`` (e.g. ``numeric``). Casting the input value is done in exactly the same way as casting any other value:
For example, let's say that ``orders`` has an ``order_details`` :ref:`JSON column <json_columns>` with a ``tax_amount`` key.
We cannot sum ``tax_amount`` directly because using ``->`` or ``->>`` will return the data in ``json`` or ``text`` format.
So we need to cast it to a compatible type (e.g. ``numeric``) right before the aggregate function:
.. code-block:: bash
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
With this, you will receive the sum of the casted ``tax_amount`` value:
.. code-block:: json
[
@@ -120,17 +122,15 @@ With this, you will receive the sum of the casted ``tax_amount`` value:
}
]
Casting the Value of the Output
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Casting the Aggregate
---------------------
Now let's return to an example involving the ``amount`` column of the ``orders`` table. Imagine that we want to get the rounded average of the ``amount`` column. One way to do this is to use the ``avg()`` aggregate function and then to cast the output value of the function to ``int``. To cast the value of the output of the function, we simply place the cast *after* the aggregate function:
For instance, if we wanted to round the average of the ``amount`` column, we could do so by casting ``avg()`` to an ``int``:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.avg()::int"
You will then receive the rounded average as the result:
.. code-block:: json
[
@@ -139,27 +139,22 @@ You will then receive the rounded average as the result:
}
]
Of course, you can use both input and output casts at the same time, if you so desire.
Aggregates and Resource Embedding
=================================
You can group an aggregate function by an :ref:`embedded resource <resource_embedding>` and also use the aggregates inside them.
Using Aggregate Functions with Resource Embedding
=================================================
Grouping by an Embedded Resource
--------------------------------
Aggregate functions can be used in conjunction with :ref:`resource_embedding`. You can use embedded resources as grouping columns, use aggregate functions within the context of an embedded resource, or use columns from a spreaded resource as grouping columns or as inputs to aggregate functions.
Using Embedded Resources as Grouping Columns
--------------------------------------------
Using an embedded resource as a grouping column allows you to use data from an association to group the results of an aggregation.
For example, imagine that the ``orders`` table from the examples above is related to a ``customers`` table. If you want to get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, you can include the customer name, using the standard :ref:`resource_embedding` syntax, and perform a sum on the ``amount`` column.
Similar to grouping by columns, aggregate functions can also be grouped by embedded resources.
For example, let's say that the ``orders`` table is related to a ``customers`` table.
To get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, we would do the following:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
You will then get the summed amount, along with the embedded customer resource:
.. code-block:: json
[
@@ -177,15 +172,16 @@ You will then get the summed amount, along with the embedded customer resource:
}
]
.. note::
The previous example uses a has-one association to demonstrate this functionality, but you may also use has-many associations as grouping columns, although there are few obvious use cases for this.
The previous example uses a "to-one" relationship, but this can be done on "to-many" relationships as well (although there are few obvious use cases).
Using Aggregate Functions Within the Context of an Embedded Resource
--------------------------------------------------------------------
This also works in a similar way for :ref:`spread embedded resources <spread_embed>`.
For example, ``select=amount.sum(),...customers(name)`` would sum the ``amount`` grouped by the ``name`` column.
When embedding a resource, you can apply aggregate functions to columns from the associated resource to perform aggregations within the context of an embedded resource.
Using Aggregates Inside Embedded Resources
------------------------------------------
Continuing with the example relationship between ``orders`` and ``customers`` from the previous section, imagine that you want to fetch the ``name``, ``city``, and ``state`` for each customer, along with the sum of amount of the customer's orders, grouped by the order date. This can be done in the following way:
Using the relationship from the previous example, let's take all the ``customers`` and embed their ``orders``.
If we also want to get the total ``amount`` grouped by the ``order_date`` of the ``orders``, we would do the following:
.. code-block:: bash
@@ -226,51 +222,20 @@ Continuing with the example relationship between ``orders`` and ``customers`` fr
}
]
In this example, the ``amount`` column is summed and grouped by the ``order_date`` *within* the context of the embedded resource. That is, the ``name``, ``city``, and ``state`` from the ``customers`` table have no bearing on the aggregation performed in the context of the ``orders`` association; instead, each aggregation can be seen as being performed independently on just the orders belonging to a particular customer, using only the data from the embedded resource for both grouping and aggregation.
Note that the aggregate is done within the embedded resource ``orders``.
It is not affected by any of the columns from the top-level relationship ``customers``.
Using Columns from a Spreaded Resource
--------------------------------------
Using Aggregates in Spreads
~~~~~~~~~~~~~~~~~~~~~~~~~~~
When you :ref:`spread an embedded resource <spread_embed>`, the columns from the spreaded resource are treated as if they were columns of the top-level resource, both when using them as grouping columns and when applying aggregate functions to them.
Grouping with Columns from a Spreaded Resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For instance, assume you want to sum the ``amount`` column from the ``orders`` table, using the ``city`` and ``state`` columns from the ``customers`` table as grouping columns. To achieve this, you may select these two columns from the ``customers`` table and spread them; they will then be used as grouping columns:
All the aggregates inside a :ref:`spread embedded resource <spread_embed>` will be hoisted to the top-level relationship.
In other words, it will behave as if the aggregate was done in the top-level relationship itself. For example:
.. code-block:: bash
curl "http://localhost:3000/orders?select=amount.sum(),...customers(city,state)
curl "http://localhost:3000/orders?select=order_date,...customers(subscription_date.max(),subscription_date.min())
The result will be the same as if ``city`` and ``state`` were columns from the ``orders`` table:
.. code-block:: json
[
{
"sum": 2000.29,
"city": "New York",
"state": "NY"
},
{
"sum": 9241.21,
"city": "Los Angeles",
"state": "CA"
}
]
Aggregate Functions with Columns from a Spreaded Resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now imagine that the ``customers`` table has a ``joined_date`` column that represents the date that the customer joined. You want to get both the most recent and the oldest ``joined_date`` for customers that placed an order on every distinct order date. This can be expressed as follows:
.. code-block:: bash
curl "http://localhost:3000/orders?select=order_date,...customers(joined_date.max(),joined_date.min())
As columns from a spreaded resource are treated as if they were columns from the top-level resource, the ``max()`` and ``min()`` are applied *within* the context of the top-level, rather than within the context of the embedded resource, as in the previous section.
The result will be the same as if the aggregations were applied to columns from the top-level:
This will take the ``max`` and ``min`` subscription date of every customer and group it by the ``order_date`` column:
.. code-block:: json
+17
View File
@@ -298,6 +298,23 @@ Let's get its :ref:`explain_plan` when calling it with filters applied:
Notice there's no "Function Scan" node in the plan, which tells us it has been inlined.
Horizontal Filtering
~~~~~~~~~~~~~~~~~~~~
Table-valued functions support horizontal filtering on selected and unselected columns.
For example, the following RPC with filter on unselected column returns:
.. code-block:: bash
curl "http://localhost:3000/rpc/getallprojects?select=id,client_id&name=like.OSX"
.. code-block:: json
[
{ "id": 4, "client_id": 2 }
]
.. _scalar_functions:
Scalar functions
+42 -1
View File
@@ -12,7 +12,7 @@ The following preferences are supported.
- ``Prefer: return``. See :ref:`prefer_return`.
- ``Prefer: count``. See :ref:`prefer_count`.
- ``Prefer: resolution``. See :ref:`prefer_resolution`.
- ``Prefer: missing``. See :ref:`bulk_insert_default`.
- ``Prefer: missing``. See :ref:`prefer_missing`.
- ``Prefer: max-affected``, See :ref:`prefer_max_affected`.
- ``Prefer: tx``. See :ref:`prefer_tx`.
- ``Prefer: params``. See :ref:`prefer_params`.
@@ -197,6 +197,47 @@ The ``tx`` preference can be set to specify if the :ref:`transaction <transactio
{"id": 35, "name": "Project X"}
.. _prefer_missing:
Missing
=======
When doing ``POST`` and ``PATCH`` requests, any missing columns in the payload will be inserted as ``null`` value by default. To use the ``DEFAULT`` column value instead, use the ``Prefer: missing=default`` header.
Having:
.. code-block:: postgres
create table foo (
id bigint generated by default as identity primary key
, bar text
, baz int default 100
);
A request:
.. code-block:: bash
curl "http://localhost:3000/foo?columns=id,bar,baz" \
-H "Content-Type: application/json" \
-H "Prefer: missing=default, return=representation" \
-d @- << EOF
[
{ "bar": "val1" },
{ "bar": "val2", "baz": 15 }
]
EOF
Will result in:
.. code-block:: json
[
{ "id": 1, "bar": "val1", "baz": 100 },
{ "id": 2, "bar": "val2", "baz": 15 }
]
.. _prefer_max_affected:
Max Affected
@@ -16,6 +16,32 @@ Use the Accept request header to specify the acceptable format (or formats) for
curl "http://localhost:3000/people" \
-H "Accept: application/json"
.. note::
The ordering of columns in the response isn't guaranteed to align with the order specified in the ``select`` clause. For example, with resource embedding:
.. code-block:: bash
http://localhost:3000/films?select=directors(last_name,id),title
We may get:
.. code-block:: bash
[
{
"title": "title",
"directors": {
"id": 5,
"last_name": "name"
}
}
]
This is in line with the `JSON schema spec <https://json-schema.org/draft/2020-12/json-schema-core#name-instance-data-model>`_:
*"object: An unordered set of properties mapping a string to an instance"*
.. _builtin_media:
Builtin Media Type Handlers
+9 -41
View File
@@ -525,45 +525,6 @@ To bulk insert JSON post an array of objects having all-matching keys
]
EOF
.. _bulk_insert_default:
Bulk Insert with Default Values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Any missing columns in the payload will be inserted as ``null`` values. To use the ``DEFAULT`` column value instead, use the ``Prefer: missing=default`` header.
Having:
.. code-block:: postgres
create table foo (
id bigint generated by default as identity primary key
, bar text
, baz int default 100
);
A request:
.. code-block:: bash
curl "http://localhost:3000/foo?columns=id,bar,baz" \
-H "Content-Type: application/json" \
-H "Prefer: missing=default, return=representation" \
-d @- << EOF
[
{ "bar": "val1" },
{ "bar": "val2", "baz": 15 }
]
EOF
Will result in:
.. code-block:: json
[
{ "id": 1, "bar": "val1", "baz": 100 },
{ "id": 2, "bar": "val2", "baz": 15 }
]
.. _specify_columns:
@@ -606,7 +567,13 @@ To update a row or rows in a table, use the PATCH verb. Use :ref:`h_filter` to s
-X PATCH -H "Content-Type: application/json" \
-d '{ "category": "child" }'
Updates also support :ref:`prefer_return`, :ref:`resource_embedding` and :ref:`v_filter`.
Updates also support:
- :ref:`prefer_return`
- :ref:`resource_embedding`
- :ref:`v_filter`
- :ref:`Missing Preference <prefer_missing>`
- :ref:`specify_columns`
.. warning::
@@ -637,7 +604,7 @@ You can make an upsert with :code:`POST` and the :code:`Prefer: resolution=merge
By default, upsert operates based on the primary key columns, so you must specify all of them.
You can also choose to ignore the duplicates with :code:`Prefer: resolution=ignore-duplicates`.
Upsert works best when the primary key is natural (e.g. ``sku``).
However, it can work with surrogate primary keys (e.g. ``id serial primary key``), if you also do a :ref:`bulk_insert_default`:
However, it can work with surrogate primary keys (e.g. ``id serial primary key``), if you also do a :ref:`bulk_insert` with :ref:`prefer_missing`:
.. code-block:: bash
@@ -755,6 +722,7 @@ Using ``offset`` to target a different subset of rows is also possible.
'#estimated-count': 'pagination_count.html#estimated-count',
'#prefer-return-headers-only': 'preferences.html#headers-only',
'#prefer-return-representation': 'preferences.html#full',
'#bulk-insert-default': 'preferences.html#prefer-missing',
};
let willRedirectTo = redirects[hash];
+9 -1
View File
@@ -159,7 +159,15 @@ You can specify the literal value as we saw earlier, or reference a filename to
JWT Claims Validation
~~~~~~~~~~~~~~~~~~~~~
PostgREST honors the :code:`exp` claim for token expiration, rejecting expired tokens.
PostgREST honors the following `JWT claims <https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4>`_:
- ``exp`` Expiration Time
- ``iat`` Issued At
- ``nbf`` Not Before
- ``aud`` Audience, see :ref:`jwt-aud`
.. note::
PostgREST allows for a 30-second clock skew when validating the ``exp`` and ``iat`` claims. In other words, it gives an extra 30 seconds before the token is rejected if there is a slight discrepancy in the timestamps.
JWT Security
~~~~~~~~~~~~
+25 -1
View File
@@ -161,6 +161,30 @@ admin-server-port
Specifies the port for the :ref:`admin_server`.
.. _admin-server-config-enabled:
admin-server-config-enabled
---------------------------
.. danger::
The ``/config`` endpoint contains sensitive information, don't enable this if you're exposing the Admin Server publicly.
To safely enable this you can use a proxy like :ref:`nginx` to:
- Ensure ``/config`` are only available to local networks.
- Only expose ``/live`` and ``/ready`` to public networks.
=============== =================================
**Type** Boolean
**Default** False
**Reloadable** N
**Environment** PGRST_ADMIN_SERVER_CONFIG_ENABLED
**In-Database** `n/a`
=============== =================================
Enables the admin server :ref:`runtime_config` and :ref:`runtime_schema_cache` endpoints.
.. _app.settings.*:
app.settings.*
@@ -855,7 +879,7 @@ server-timing-enabled
**In-Database** pgrst.server_timing_enabled
=============== =================================
Enables the `Server-Timing <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing>`_ header.
Enables the `Server-Timing <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing>`_ header.
See :ref:`server-timing_header`.
.. _server-unix-socket:
+2 -2
View File
@@ -90,7 +90,7 @@ Restart the database and watch the log file in real-time to understand how HTTP
Metrics
=======
The ``metrics`` endpoint on the :ref:`admin_server` endpoint provides metrics in `Prometheus text format <https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format>`_.
The ``metrics`` endpoint on the :ref:`admin_server` endpoint provides metrics in `Prometheus text format <https://prometheus.io/docs/instrumenting/exposition_formats/#prometheus-text-format>`_.
.. code-block:: bash
@@ -209,7 +209,7 @@ You can enable tracing HTTP requests by setting :ref:`server-trace-header`. Spec
Server-Timing Header
--------------------
You can enable the `Server-Timing <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing>`_ header by setting :ref:`server-timing-enabled` on.
You can enable the `Server-Timing <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing>`_ header by setting :ref:`server-timing-enabled` on.
This header communicates metrics of the different phases in the request-response cycle.
.. code-block:: bash
+1 -1
View File
@@ -34,7 +34,7 @@
.. code:: bash
nix-env -i haskellPackages.postgrest
nix-env -i postgrest
.. group-tab:: Windows
+2 -2
View File
@@ -74,8 +74,8 @@ let
hasql-notifications = lib.dontCheck (prev.callHackageDirect
{
pkg = "hasql-notifications";
ver = "0.2.2.0";
sha256 = "sha256-73OQ9/su2qvO7HavF3xuuNWLXSXyB9reBUQDaHys06I=";
ver = "0.2.2.2";
sha256 = "sha256-myKwlug7OgTa/qP6mHfCD+5Q8IhM17JvpJBfSo+M01k=";
}
{ }
);
+2 -2
View File
@@ -1,5 +1,5 @@
name: postgrest
version: 12.2.5
version: 12.2.12
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
@@ -110,7 +110,7 @@ library
, gitrev >= 1.2 && < 1.4
, hasql >= 1.6.1.1 && < 1.7
, hasql-dynamic-statements >= 0.3.1 && < 0.4
, hasql-notifications >= 0.2.2.0 && < 0.3
, hasql-notifications >= 0.2.2.2 && < 0.2.3
, hasql-pool >= 1.0.1 && < 1.1
, hasql-transaction >= 1.0.1 && < 1.1
, heredoc >= 0.2 && < 0.3
+5 -2
View File
@@ -56,8 +56,11 @@ admin appState req respond = do
in
respond $ Wai.responseLBS status [] mempty
["config"] -> do
config <- AppState.getConfig appState
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
config@Config.AppConfig{configAdminServerConfigEnabled} <- AppState.getConfig appState
if configAdminServerConfigEnabled then
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
else
respond $ Wai.responseLBS HTTP.status404 [] mempty
["schema_cache"] -> do
sCache <- AppState.getSchemaCache appState
respond $ Wai.responseLBS HTTP.status200 [] (maybe mempty JSON.encode sCache)
+40 -14
View File
@@ -5,6 +5,7 @@
module PostgREST.AppState
( AppState
, AuthResult(..)
, JwtCacheState(..)
, destroy
, getConfig
, getSchemaCache
@@ -13,7 +14,7 @@ module PostgREST.AppState
, getNextDelay
, getNextListenerDelay
, getTime
, getJwtCache
, getJwtCacheState
, getSocketREST
, getSocketAdmin
, init
@@ -83,6 +84,12 @@ data AuthResult = AuthResult
, authRole :: BS.ByteString
}
-- | JWT Cache and IO action that triggers purging old entries from the cache
data JwtCacheState = JwtCacheState
{ jwtCache :: C.Cache ByteString AuthResult
, purgeCache :: IO ()
}
data AppState = AppState
-- | Database connection pool
{ statePool :: SQL.Pool
@@ -107,7 +114,7 @@ data AppState = AppState
-- | Keeps track of the next delay for the listener
, stateNextListenerDelay :: IORef Int
-- | JWT Cache
, jwtCache :: C.Cache ByteString AuthResult
, jwtCacheState :: JwtCacheState
-- | Network socket for REST API
, stateSocketREST :: NS.Socket
-- | Network socket for the admin UI
@@ -139,6 +146,16 @@ init conf@AppConfig{configLogLevel, configDbPoolSize} = do
initWithPool :: AppSockets -> SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO AppState
initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
cache <- C.newCache Nothing
-- purgeExpired has O(n^2) complexity
-- so we wrap it in debounce to make sure it:
-- 1) is executed asynchronously
-- 2) only a single purge operation is running at a time
debounce <- mkDebounce defaultDebounceSettings
-- debounceFreq is set to default 1 second
{ debounceAction = C.purgeExpired cache
, debounceEdge = leadingEdge
}
appState <- AppState pool
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
@@ -151,7 +168,7 @@ initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
<*> myThreadId
<*> newIORef 0
<*> newIORef 1
<*> C.newCache Nothing
<*> pure (JwtCacheState cache debounce)
<*> pure sock
<*> pure adminSock
<*> pure observer
@@ -265,8 +282,9 @@ usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} ses
SQL.ServerError{} ->
when (Error.status (Error.PgError False err) >= HTTP.status500) $
observer $ QueryErrorCodeHighObs err
SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _)) ->
pure ()
err@(SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _))) ->
-- An error on the client-side, usually indicates problems wth connection
observer $ QueryErrorCodeHighObs err
)
return res
@@ -313,8 +331,8 @@ putConfig = atomicWriteIORef . stateConf
getTime :: AppState -> IO UTCTime
getTime = stateGetTime
getJwtCache :: AppState -> C.Cache ByteString AuthResult
getJwtCache = jwtCache
getJwtCacheState :: AppState -> JwtCacheState
getJwtCacheState = jwtCacheState
getSocketREST :: AppState -> NS.Socket
getSocketREST = stateSocketREST
@@ -438,12 +456,12 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
-- | Reads the in-db config and reads the config file again
-- | We don't retry reading the in-db config after it fails immediately, because it could have user errors. We just report the error and continue.
readInDbConfig :: Bool -> AppState -> IO ()
readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
AppConfig{..} <- getConfig appState
readInDbConfig startingUp appState@AppState{stateObserver=observer, jwtCacheState=JwtCacheState{jwtCache}} = do
conf <- getConfig appState
pgVer <- getPgVersion appState
dbSettings <-
if configDbConfig then do
qDbSettings <- usePool appState (queryDbSettings (dumpQi <$> configDbPreConfig) configDbPreparedStatements)
if configDbConfig conf then do
qDbSettings <- usePool appState (queryDbSettings (dumpQi <$> configDbPreConfig conf) (configDbPreparedStatements conf))
case qDbSettings of
Left e -> do
observer $ ConfigReadErrorObs e
@@ -452,8 +470,8 @@ readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
else
pure mempty
(roleSettings, roleIsolationLvl) <-
if configDbConfig then do
rSettings <- usePool appState (queryRoleSettings pgVer configDbPreparedStatements)
if configDbConfig conf then do
rSettings <- usePool appState (queryRoleSettings pgVer (configDbPreparedStatements conf))
case rSettings of
Left e -> do
observer $ QueryRoleSettingsErrorObs e
@@ -461,7 +479,7 @@ readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
Right x -> pure x
else
pure mempty
readAppConfig dbSettings configFilePath (Just configDbUri) roleSettings roleIsolationLvl >>= \case
readAppConfig dbSettings (configFilePath conf) (Just $ configDbUri conf) roleSettings roleIsolationLvl >>= \case
Left err ->
if startingUp then
panic err -- die on invalid config if the program is starting up
@@ -469,6 +487,14 @@ readInDbConfig startingUp appState@AppState{stateObserver=observer} = do
observer $ ConfigInvalidObs err
Right newConf -> do
putConfig appState newConf
-- After the config has reloaded, jwt-secret might have changed, so
-- if it has changed, it is important to invalidate the jwt cache
-- entries, because they were cached using the old secret
if configJwtSecret conf == configJwtSecret newConf then
pass
else
C.purge jwtCache -- atomic O(1) operation
if startingUp then
pass
else
+29 -4
View File
@@ -44,8 +44,9 @@ import System.Clock (TimeSpec (..))
import System.IO.Unsafe (unsafePerformIO)
import System.TimeIt (timeItT)
import PostgREST.AppState (AppState, AuthResult (..), getConfig,
getJwtCache, getTime)
import PostgREST.AppState (AppState, AuthResult (..),
JwtCacheState (..), getConfig,
getJwtCacheState, getTime)
import PostgREST.Config (AppConfig (..), JSPath, JSPathExp (..))
import PostgREST.Error (Error (..))
@@ -131,11 +132,35 @@ middleware appState app req respond = do
-- | Used to retrieve and insert JWT to JWT Cache
getJWTFromCache :: AppState -> ByteString -> Int -> IO (Either Error AuthResult) -> UTCTime -> IO (Either Error AuthResult)
getJWTFromCache appState token maxLifetime parseJwt utc = do
checkCache <- C.lookup (getJwtCache appState) token
let JwtCacheState{..} = getJwtCacheState appState
checkCache <- C.lookup jwtCache token
authResult <- maybe parseJwt (pure . Right) checkCache
case (authResult,checkCache) of
(Right res, Nothing) -> C.insert' (getJwtCache appState) (getTimeSpec res maxLifetime utc) token res
-- From comment:
-- https://github.com/PostgREST/postgrest/pull/3801#discussion_r1857987914
--
-- We purge expired cache entries on a cache miss
-- The reasoning is that:
--
-- 1. We expect it to be rare (otherwise there is no point of the cache)
-- 2. It makes sure the cache is not growing (as inserting new entries
-- does garbage collection)
-- 3. Since this is time expiration based cache there is no real risk of
-- starvation - sooner or later we are going to have a cache miss.
(Right res, Nothing) -> do -- cache miss
let timeSpec = getTimeSpec res maxLifetime utc
-- insert new cache entry
C.insert' jwtCache timeSpec token res
-- Execute IO action to purge the cache
-- It is assumed this action returns immidiately
-- so that request processing is not blocked.
purgeCache
_ -> pure ()
return authResult
+3
View File
@@ -128,6 +128,9 @@ exampleConfigFile =
[str|## Admin server used for checks. It's disabled by default unless a port is specified.
|# admin-server-port = 3001
|
|## Whether to enable the /config endpoint of the admin server
|# admin-server-config-enabled = false
|
|## The database role to use when no client authentication is provided
|# db-anon-role = "anon"
|
+3
View File
@@ -110,6 +110,7 @@ data AppConfig = AppConfig
, configServerUnixSocket :: Maybe FilePath
, configServerUnixSocketMode :: FileMode
, configAdminServerPort :: Maybe Int
, configAdminServerConfigEnabled :: Bool
, configRoleSettings :: RoleSettings
, configRoleIsoLvl :: RoleIsolationLvl
, configInternalSCSleep :: Maybe Int32
@@ -180,6 +181,7 @@ toText conf =
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
,("server-unix-socket-mode", q . T.pack . showSocketMode)
,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
,("admin-server-config-enabled", T.toLower . show . configAdminServerConfigEnabled)
]
-- quote all app.settings
@@ -286,6 +288,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
<*> (fmap T.unpack <$> optString "server-unix-socket")
<*> parseSocketFileMode "server-unix-socket-mode"
<*> optInt "admin-server-port"
<*> (fromMaybe False <$> optBool "admin-server-config-enabled")
<*> pure roleSettings
<*> pure roleIsolationLvl
<*> optInt "internal-schema-cache-sleep"
+51 -28
View File
@@ -1,7 +1,7 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
module PostgREST.MediaType
( MediaType(..)
, MTVndPlanOption (..)
@@ -11,17 +11,16 @@ module PostgREST.MediaType
, decodeMediaType
) where
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS
import qualified Data.Text as T
import qualified Text.ParserCombinators.Parsec as P
import Data.Map (fromList, (!?))
import Data.Text.Encoding (decodeLatin1)
import Network.HTTP.Types.Header (Header, hContentType)
import Data.Map (fromList, (!?))
import qualified Data.Text as T (break, drop, dropWhile,
dropWhileEnd, null, splitOn,
toLower)
import Data.Text.Encoding (decodeLatin1)
import Protolude
import Protolude
-- | Enumeration of currently supported media types
data MediaType
@@ -104,6 +103,9 @@ toMimePlanFormat PlanText = "text"
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
-- MTVndPlan MTApplicationJSON PlanText []
--
-- >>> decodeMediaType "application/vnd.pgrst.plan ; for=\"text/xml\" ; options=analyze"
-- MTVndPlan MTTextXML PlanText [PlanAnalyze]
--
-- >>> decodeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
-- MTVndPlan MTTextCSV PlanJSON []
--
@@ -150,7 +152,10 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
("*","*",_) -> MTAny
_ -> MTOther mt'
where
(mainType, subType, params') = tokenizeMediaType mt'
mediaTypeOrError = P.parse tokenizeMediaType "parsec: tokenizeMediaType failed" $ T.unpack mt'
(mainType, subType, params') = case mediaTypeOrError of
Right mt'' -> mt''
Left _ -> (mt',"",[])
params = fromList $ map (first T.toLower) params' -- normalize parameter names to lowercase, per RFC 7321
getPlan fmt = MTVndPlan mtFor fmt $
[PlanAnalyze | inOpts "analyze" ] ++
@@ -166,21 +171,39 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
checkArrayNullStrip = if strippedNulls then MTVndArrayJSONStrip else MTApplicationJSON
-- | Split a Media Type string into components
-- >>> tokenizeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
-- ("application","vnd.pgrst.plan+json",[("for","text/csv")])
-- >>> tokenizeMediaType "*/*"
-- ("*","*",[])
-- >>> tokenizeMediaType "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
-- ("application","vnd.pgrst.plan",[("wat","application/json"),("text/csv\"","")])
tokenizeMediaType :: Text -> (Text, Text, [(Text, Text)])
tokenizeMediaType t = (mainType, subType, params)
where
(mainType, rest) = T.break (== '/') t
(subType, restParams) = T.break (== ';') $ T.drop 1 rest
params =
let rp = T.drop 1 restParams
in if T.null rp then [] else map param $ T.splitOn ";" rp -- FIXME: breaks if there's a ';' in a quoted value
param p =
let (k, v) = T.break (== '=') p
in (k, dropAround (== '"') $ T.drop 1 v) -- FIXME: doesn't unescape quotes in values
dropAround p = T.dropWhile p . T.dropWhileEnd p
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+json;for=\"text/csv\""
-- Right ("application","vnd.pgrst.plan+json",[("for","text/csv")])
--
-- >>> P.parse tokenizeMediaType "" "*/*"
-- Right ("*","*",[])
--
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
-- Right ("application","vnd.pgrst.plan",[("wat","application/json;text/csv")])
--
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+text; for=\"text/xml\"; options=analyze|verbose|settings|buffers|wal"
-- Right ("application","vnd.pgrst.plan+text",[("for","text/xml"),("options","analyze|verbose|settings|buffers|wal")])
-- TODO: Improve mediatype parser as per RFC 2045 https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
tokenizeMediaType :: P.Parser (Text, Text, [(Text, Text)])
tokenizeMediaType = do
mainType <- P.many1 (P.alphaNum <|> P.oneOf ".*")
P.char '/'
subType <- P.many1 (P.alphaNum <|> P.oneOf ".*+-")
params <- P.many pSemicolonSeparatedKeyVals
P.optional $ P.try $ P.spaces *> P.char ';' -- ending semicolon, discard input after that because it has already failed or we have hit EOF
return (T.pack mainType, T.pack subType, params)
where
pSemicolonSeparatedKeyVals :: P.Parser (Text, Text)
pSemicolonSeparatedKeyVals = P.try $ P.spaces *> P.char ';' *> P.spaces *> pKeyVal
where
pKeyVal :: P.Parser (Text, Text)
pKeyVal = do
key <- P.many1 (P.alphaNum <|> P.oneOf "-")
P.spaces
P.char '='
P.spaces
val <- P.try pQuoted <|> P.try pUnQuoted
return (T.pack key, T.pack val)
where
pUnQuoted = P.many1 (P.alphaNum <|> P.oneOf "|-")
pQuoted = P.char '\"' *> P.manyTill P.anyChar (P.char '\"')
+1 -1
View File
@@ -19,6 +19,6 @@ nix:
extra-deps:
- configurator-pg-0.2.10
- fuzzyset-0.2.4
- hasql-notifications-0.2.2.0
- hasql-notifications-0.2.2.2
- hasql-pool-1.0.1
- postgresql-libpq-0.10.1.0
+3 -3
View File
@@ -19,12 +19,12 @@ packages:
original:
hackage: fuzzyset-0.2.4
- completed:
hackage: hasql-notifications-0.2.2.0@sha256:a4e591ef3f06647b056567d3b66948c4a85371f05deb5434edb6ce190f7c845d,2021
hackage: hasql-notifications-0.2.2.2@sha256:d1d6bc0d3ee5e418fc12ea023b78739e0decba6c34e2b43bec55b89e18bd4412,2025
pantry-tree:
sha256: bd7192a5e82ef6dbac711c3433408a0330c8db1cd3482be1ccd4fbd0a63bc2f6
sha256: 83a9cbb179b1efd0b2acd6509583c7afcdbe63469ab033d8581d48d675a80b44
size: 452
original:
hackage: hasql-notifications-0.2.2.0
hackage: hasql-notifications-0.2.2.2
- completed:
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
pantry-tree:
+1 -1
View File
@@ -11,5 +11,5 @@ nix:
extra-deps:
- fuzzyset-0.2.4
- hasql-notifications-0.2.2.0
- hasql-notifications-0.2.2.2
- hasql-pool-1.0.1
+3 -3
View File
@@ -12,12 +12,12 @@ packages:
original:
hackage: fuzzyset-0.2.4
- completed:
hackage: hasql-notifications-0.2.2.0@sha256:a4e591ef3f06647b056567d3b66948c4a85371f05deb5434edb6ce190f7c845d,2021
hackage: hasql-notifications-0.2.2.2@sha256:d1d6bc0d3ee5e418fc12ea023b78739e0decba6c34e2b43bec55b89e18bd4412,2025
pantry-tree:
sha256: bd7192a5e82ef6dbac711c3433408a0330c8db1cd3482be1ccd4fbd0a63bc2f6
sha256: 83a9cbb179b1efd0b2acd6509583c7afcdbe63469ab033d8581d48d675a80b44
size: 452
original:
hackage: hasql-notifications-0.2.2.0
hackage: hasql-notifications-0.2.2.2
- completed:
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
pantry-tree:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg width="581" height="113" viewBox="0 0 581 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M151.397 66.7608C151.996 72.3621 157.091 81.9642 171.877 81.9642C184.764 81.9642 190.959 73.7624 190.959 65.7607C190.959 58.559 186.063 52.6577 176.373 50.6571L169.379 49.1569C166.682 48.6568 164.884 47.1565 164.884 44.7559C164.884 41.9552 167.681 39.8549 171.178 39.8549C176.772 39.8549 178.87 43.5556 179.27 46.4564L190.359 43.9558C189.76 38.6546 185.064 29.7527 171.078 29.7527C160.488 29.7527 152.696 37.0543 152.696 45.8561C152.696 52.7576 156.991 58.4591 166.482 60.5594L172.976 62.0598C176.772 62.8599 178.271 64.6605 178.271 66.8609C178.271 69.4615 176.173 71.762 171.777 71.762C165.983 71.762 163.085 68.1611 162.786 64.2602L151.397 66.7608Z" fill="white"/>
<path d="M233.421 80.4639H246.109C245.909 78.7635 245.609 75.3628 245.609 71.5618V31.2529H232.321V59.8592C232.321 65.5606 228.925 69.5614 223.031 69.5614C216.837 69.5614 214.039 65.1604 214.039 59.6592V31.2529H200.752V62.3599C200.752 73.0622 207.545 81.7642 219.434 81.7642C224.628 81.7642 230.325 79.7638 233.022 75.1627C233.022 77.1631 233.221 79.4636 233.421 80.4639Z" fill="white"/>
<path d="M273.076 99.4682V75.663C275.473 78.9636 280.469 81.6644 287.263 81.6644C301.149 81.6644 310.439 70.6617 310.439 55.7584C310.439 41.1553 302.148 30.1528 287.762 30.1528C280.37 30.1528 274.875 33.4534 272.677 37.2544V31.253H259.79V99.4682H273.076ZM297.352 55.8585C297.352 64.6606 291.958 69.7616 285.164 69.7616C278.372 69.7616 272.877 64.5605 272.877 55.8585C272.877 47.1566 278.372 42.0554 285.164 42.0554C291.958 42.0554 297.352 47.1566 297.352 55.8585Z" fill="white"/>
<path d="M317.964 67.0609C317.964 74.7627 324.357 81.8643 334.848 81.8643C342.139 81.8643 346.835 78.4634 349.332 74.5625C349.332 76.463 349.532 79.1635 349.832 80.4639H362.02C361.72 78.7635 361.422 75.2627 361.422 72.6622V48.4567C361.422 38.5545 355.627 29.7527 340.043 29.7527C326.855 29.7527 319.761 38.2544 318.963 45.9562L330.751 48.4567C331.151 44.1558 334.348 40.455 340.141 40.455C345.737 40.455 348.434 43.3556 348.434 46.8564C348.434 48.5568 347.536 49.9572 344.738 50.3572L332.65 52.1576C324.458 53.3579 317.964 58.2589 317.964 67.0609ZM337.644 71.962C333.349 71.962 331.25 69.1614 331.25 66.2608C331.25 62.4599 333.947 60.5594 337.345 60.0594L348.434 58.359V60.5594C348.434 69.2615 343.239 71.962 337.644 71.962Z" fill="white"/>
<path d="M387.703 80.4641V74.4627C390.299 78.6637 395.494 81.6644 402.288 81.6644C416.276 81.6644 425.467 70.5618 425.467 55.6585C425.467 41.0552 417.174 29.9528 402.788 29.9528C395.494 29.9528 390.1 33.1535 387.902 36.6541V8.04785H374.815V80.4641H387.703ZM412.178 55.7584C412.178 64.7605 406.784 69.7616 399.99 69.7616C393.297 69.7616 387.703 64.6606 387.703 55.7584C387.703 46.7564 393.297 41.8554 399.99 41.8554C406.784 41.8554 412.178 46.7564 412.178 55.7584Z" fill="white"/>
<path d="M432.99 67.0609C432.99 74.7627 439.383 81.8643 449.873 81.8643C457.165 81.8643 461.862 78.4634 464.358 74.5625C464.358 76.463 464.559 79.1635 464.858 80.4639H477.046C476.748 78.7635 476.448 75.2627 476.448 72.6622V48.4567C476.448 38.5545 470.653 29.7527 455.068 29.7527C441.881 29.7527 434.788 38.2544 433.989 45.9562L445.776 48.4567C446.177 44.1558 449.374 40.455 455.167 40.455C460.763 40.455 463.46 43.3556 463.46 46.8564C463.46 48.5568 462.561 49.9572 459.763 50.3572L447.676 52.1576C439.484 53.3579 432.99 58.2589 432.99 67.0609ZM452.671 71.962C448.375 71.962 446.276 69.1614 446.276 66.2608C446.276 62.4599 448.973 60.5594 452.371 60.0594L463.46 58.359V60.5594C463.46 69.2615 458.265 71.962 452.671 71.962Z" fill="white"/>
<path d="M485.645 66.7608C486.243 72.3621 491.339 81.9642 506.124 81.9642C519.012 81.9642 525.205 73.7624 525.205 65.7607C525.205 58.559 520.311 52.6577 510.62 50.6571L503.626 49.1569C500.929 48.6568 499.132 47.1565 499.132 44.7559C499.132 41.9552 501.928 39.8549 505.425 39.8549C511.021 39.8549 513.118 43.5556 513.519 46.4564L524.607 43.9558C524.007 38.6546 519.312 29.7527 505.326 29.7527C494.735 29.7527 486.944 37.0543 486.944 45.8561C486.944 52.7576 491.238 58.4591 500.73 60.5594L507.224 62.0598C511.021 62.8599 512.519 64.6605 512.519 66.8609C512.519 69.4615 510.421 71.762 506.025 71.762C500.23 71.762 497.334 68.1611 497.034 64.2602L485.645 66.7608Z" fill="white"/>
<path d="M545.385 50.2571C545.685 45.7562 549.482 40.5549 556.375 40.5549C563.967 40.5549 567.165 45.3561 567.365 50.2571H545.385ZM568.664 63.0601C567.065 67.4609 563.668 70.5617 557.474 70.5617C550.88 70.5617 545.385 65.8606 545.087 59.3593H580.252C580.252 59.159 580.451 57.1587 580.451 55.2582C580.451 39.4547 571.361 29.7527 556.175 29.7527C543.588 29.7527 531.998 39.9548 531.998 55.6584C531.998 72.262 543.886 81.9642 557.374 81.9642C569.462 81.9642 577.255 74.8626 579.753 66.3607L568.664 63.0601Z" fill="white"/>
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint0_linear)"/>
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint1_linear)" fill-opacity="0.2"/>
<path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/>
<defs>
<linearGradient id="paint0_linear" x1="53.9738" y1="54.974" x2="94.1635" y2="71.8295" gradientUnits="userSpaceOnUse">
<stop stop-color="#249361"/>
<stop offset="1" stop-color="#3ECF8E"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="36.1558" y1="30.578" x2="54.4844" y2="65.0806" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg width="581" height="113" viewBox="0 0 581 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M151.397 66.7608C151.996 72.3621 157.091 81.9642 171.877 81.9642C184.764 81.9642 190.959 73.7624 190.959 65.7607C190.959 58.559 186.063 52.6577 176.373 50.6571L169.379 49.1569C166.682 48.6568 164.884 47.1565 164.884 44.7559C164.884 41.9552 167.681 39.8549 171.178 39.8549C176.772 39.8549 178.87 43.5556 179.27 46.4564L190.359 43.9558C189.76 38.6546 185.064 29.7527 171.078 29.7527C160.488 29.7527 152.696 37.0543 152.696 45.8561C152.696 52.7576 156.991 58.4591 166.482 60.5594L172.976 62.0598C176.772 62.8599 178.271 64.6605 178.271 66.8609C178.271 69.4615 176.173 71.762 171.777 71.762C165.983 71.762 163.085 68.1611 162.786 64.2602L151.397 66.7608Z" fill="#1F1F1F"/>
<path d="M233.421 80.4639H246.109C245.909 78.7635 245.609 75.3628 245.609 71.5618V31.2529H232.321V59.8592C232.321 65.5606 228.925 69.5614 223.031 69.5614C216.837 69.5614 214.039 65.1604 214.039 59.6592V31.2529H200.752V62.3599C200.752 73.0622 207.545 81.7642 219.434 81.7642C224.628 81.7642 230.325 79.7638 233.022 75.1627C233.022 77.1631 233.221 79.4636 233.421 80.4639Z" fill="#1F1F1F"/>
<path d="M273.076 99.4682V75.663C275.473 78.9636 280.469 81.6644 287.263 81.6644C301.149 81.6644 310.439 70.6617 310.439 55.7584C310.439 41.1553 302.148 30.1528 287.762 30.1528C280.37 30.1528 274.875 33.4534 272.677 37.2544V31.253H259.79V99.4682H273.076ZM297.352 55.8585C297.352 64.6606 291.958 69.7616 285.164 69.7616C278.372 69.7616 272.877 64.5605 272.877 55.8585C272.877 47.1566 278.372 42.0554 285.164 42.0554C291.958 42.0554 297.352 47.1566 297.352 55.8585Z" fill="#1F1F1F"/>
<path d="M317.964 67.0609C317.964 74.7627 324.357 81.8643 334.848 81.8643C342.139 81.8643 346.835 78.4634 349.332 74.5625C349.332 76.463 349.532 79.1635 349.832 80.4639H362.02C361.72 78.7635 361.422 75.2627 361.422 72.6622V48.4567C361.422 38.5545 355.627 29.7527 340.043 29.7527C326.855 29.7527 319.761 38.2544 318.963 45.9562L330.751 48.4567C331.151 44.1558 334.348 40.455 340.141 40.455C345.737 40.455 348.434 43.3556 348.434 46.8564C348.434 48.5568 347.536 49.9572 344.738 50.3572L332.65 52.1576C324.458 53.3579 317.964 58.2589 317.964 67.0609ZM337.644 71.962C333.349 71.962 331.25 69.1614 331.25 66.2608C331.25 62.4599 333.947 60.5594 337.345 60.0594L348.434 58.359V60.5594C348.434 69.2615 343.239 71.962 337.644 71.962Z" fill="#1F1F1F"/>
<path d="M387.703 80.4641V74.4627C390.299 78.6637 395.494 81.6644 402.288 81.6644C416.276 81.6644 425.467 70.5618 425.467 55.6585C425.467 41.0552 417.174 29.9528 402.788 29.9528C395.494 29.9528 390.1 33.1535 387.902 36.6541V8.04785H374.815V80.4641H387.703ZM412.178 55.7584C412.178 64.7605 406.784 69.7616 399.99 69.7616C393.297 69.7616 387.703 64.6606 387.703 55.7584C387.703 46.7564 393.297 41.8554 399.99 41.8554C406.784 41.8554 412.178 46.7564 412.178 55.7584Z" fill="#1F1F1F"/>
<path d="M432.99 67.0609C432.99 74.7627 439.383 81.8643 449.873 81.8643C457.165 81.8643 461.862 78.4634 464.358 74.5625C464.358 76.463 464.559 79.1635 464.858 80.4639H477.046C476.748 78.7635 476.448 75.2627 476.448 72.6622V48.4567C476.448 38.5545 470.653 29.7527 455.068 29.7527C441.881 29.7527 434.788 38.2544 433.989 45.9562L445.776 48.4567C446.177 44.1558 449.374 40.455 455.167 40.455C460.763 40.455 463.46 43.3556 463.46 46.8564C463.46 48.5568 462.561 49.9572 459.763 50.3572L447.676 52.1576C439.484 53.3579 432.99 58.2589 432.99 67.0609ZM452.671 71.962C448.375 71.962 446.276 69.1614 446.276 66.2608C446.276 62.4599 448.973 60.5594 452.371 60.0594L463.46 58.359V60.5594C463.46 69.2615 458.265 71.962 452.671 71.962Z" fill="#1F1F1F"/>
<path d="M485.645 66.7608C486.243 72.3621 491.339 81.9642 506.124 81.9642C519.012 81.9642 525.205 73.7624 525.205 65.7607C525.205 58.559 520.311 52.6577 510.62 50.6571L503.626 49.1569C500.929 48.6568 499.132 47.1565 499.132 44.7559C499.132 41.9552 501.928 39.8549 505.425 39.8549C511.021 39.8549 513.118 43.5556 513.519 46.4564L524.607 43.9558C524.007 38.6546 519.312 29.7527 505.326 29.7527C494.735 29.7527 486.944 37.0543 486.944 45.8561C486.944 52.7576 491.238 58.4591 500.73 60.5594L507.224 62.0598C511.021 62.8599 512.519 64.6605 512.519 66.8609C512.519 69.4615 510.421 71.762 506.025 71.762C500.23 71.762 497.334 68.1611 497.034 64.2602L485.645 66.7608Z" fill="#1F1F1F"/>
<path d="M545.385 50.2571C545.685 45.7562 549.482 40.5549 556.375 40.5549C563.967 40.5549 567.165 45.3561 567.365 50.2571H545.385ZM568.664 63.0601C567.065 67.4609 563.668 70.5617 557.474 70.5617C550.88 70.5617 545.385 65.8606 545.087 59.3593H580.252C580.252 59.159 580.451 57.1587 580.451 55.2582C580.451 39.4547 571.361 29.7527 556.175 29.7527C543.588 29.7527 531.998 39.9548 531.998 55.6584C531.998 72.262 543.886 81.9642 557.374 81.9642C569.462 81.9642 577.255 74.8626 579.753 66.3607L568.664 63.0601Z" fill="#1F1F1F"/>
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint0_linear)"/>
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint1_linear)" fill-opacity="0.2"/>
<path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/>
<defs>
<linearGradient id="paint0_linear" x1="53.9738" y1="54.974" x2="94.1635" y2="71.8295" gradientUnits="userSpaceOnUse">
<stop stop-color="#249361"/>
<stop offset="1" stop-color="#3ECF8E"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="36.1558" y1="30.578" x2="54.4844" y2="65.0806" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

+1
View File
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = ""
server-unix-socket-mode = "660"
admin-server-port = ""
admin-server-config-enabled = false
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = ""
server-unix-socket-mode = "660"
admin-server-port = ""
admin-server-config-enabled = false
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = ""
server-unix-socket-mode = "660"
admin-server-port = ""
admin-server-config-enabled = false
+1
View File
@@ -36,3 +36,4 @@ server-timing-enabled = false
server-unix-socket = ""
server-unix-socket-mode = "660"
admin-server-port = ""
admin-server-config-enabled = false
@@ -36,5 +36,6 @@ server-timing-enabled = true
server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777"
admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test"
app.settings.test2 = "test"
@@ -36,5 +36,6 @@ server-timing-enabled = false
server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777"
admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test"
app.settings.test2 = "test"
@@ -36,5 +36,6 @@ server-timing-enabled = true
server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777"
admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test"
app.settings.test2 = "test"
+1
View File
@@ -36,4 +36,5 @@ server-timing-enabled = false
server-unix-socket = ""
server-unix-socket-mode = "660"
admin-server-port = ""
admin-server-config-enabled = false
app.settings.test = "Bool False"
+1
View File
@@ -39,3 +39,4 @@ PGRST_SERVER_TIMING_ENABLED: true
PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock
PGRST_SERVER_UNIX_SOCKET_MODE: 777
PGRST_ADMIN_SERVER_PORT: 3001
PGRST_ADMIN_SERVER_CONFIG_ENABLED: true
+1
View File
@@ -36,5 +36,6 @@ server-timing-enabled = true
server-unix-socket = "/tmp/pgrst_io_test.sock"
server-unix-socket-mode = "777"
admin-server-port = 3001
admin-server-config-enabled = true
app.settings.test = "test"
app.settings.test2 = "test"
+111
View File
@@ -673,6 +673,15 @@ def test_admin_config(defaultenv):
"Should get a success response from the admin server containing current configuration"
with run(env=defaultenv) as postgrest:
response = postgrest.admin.get("/config")
assert response.status_code == 404
env = {
**defaultenv,
"PGRST_ADMIN_SERVER_CONFIG_ENABLED": "true",
}
with run(env=env) as postgrest:
response = postgrest.admin.get("/config")
print(response.text)
assert response.status_code == 200
@@ -1596,3 +1605,105 @@ def test_schema_cache_startup_load_with_in_db_config(defaultenv, metapostgrest):
response = metapostgrest.session.post("/rpc/reset_db_schemas_config")
assert response.text == ""
assert response.status_code == 204
def test_jwt_cache_purges_expired_entries(defaultenv):
"test expired cache entries are purged on cache miss"
# The verification of actual cache size reduction is done manually, see https://github.com/PostgREST/postgrest/pull/3801#issuecomment-2620776041
# This test is written for code coverage of purgeExpired function
relativeSeconds = lambda sec: int(
(datetime.now(timezone.utc) + timedelta(seconds=sec)).timestamp()
)
headers = lambda sec: jwtauthheader(
{"role": "postgrest_test_author", "exp": relativeSeconds(sec)},
SECRET,
)
env = {
**defaultenv,
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
"PGRST_JWT_SECRET": SECRET,
"PGRST_DB_CONFIG": "false",
}
with run(env=env) as postgrest:
# Generate two unique JWT tokens
# The 1 second sleep is needed for it generate a unique token
hdrs1 = headers(5)
postgrest.session.get("/authors_only", headers=hdrs1)
time.sleep(1)
hdrs2 = headers(5)
postgrest.session.get("/authors_only", headers=hdrs2)
# Wait 5 seconds for the tokens to expire
time.sleep(5)
hdrs3 = headers(5)
# Make another request which should cause a cache miss and so
# the purgeExpired function will be triggered.
#
# This should remove the 2 expired tokens but adds another to cache
response = postgrest.session.get("/authors_only", headers=hdrs3)
assert response.status_code == 200
def test_pgrst_log_503_client_error_to_stderr(defaultenv):
"PostgREST should log 503 errors to stderr"
env = {
**defaultenv,
"PGAPPNAME": "test-io",
}
with run(env=env) as postgrest:
postgrest.session.get("/rpc/terminate_pgrst?appname=test-io")
output = postgrest.read_stdout(nlines=6)
log_message = '{"code":"PGRST001","details":"no connection to the server\\n","hint":null,"message":"Database client error. Retrying the connection."}\n'
assert any(log_message in line for line in output)
def test_invalidate_jwt_cache_when_secret_changes(tmp_path, defaultenv):
"JWT cache should be emptied after jwt-secret is changed in a config reload"
headers = jwtauthheader({"role": "postgrest_test_author"}, SECRET)
external_secret_file = tmp_path / "jwt-secret-config"
external_secret_file.write_text(SECRET)
env = {
**defaultenv,
"PGRST_JWT_SECRET": f"@{external_secret_file}",
"PGRST_DB_CHANNEL_ENABLED": "true",
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400", # enable cache
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous", # required for NOTIFY
}
with run(env=env) as postgrest:
response = postgrest.session.get("/authors_only", headers=headers)
assert response.status_code == 200 # jwt gets cached
# change external file
external_secret_file.write_text("invalid" * 5)
# reload config and external file with NOTIFY
# jwt-cache should get empty
response = postgrest.session.post("/rpc/reload_pgrst_config")
assert response.text == ""
assert response.status_code == 204
sleep_until_postgrest_config_reload()
# now the request should fail because the cached token is removed
response = postgrest.session.get("/authors_only", headers=headers)
assert response.status_code == 401
+2 -2
View File
@@ -103,8 +103,8 @@ postJsonArrayTest(){
echo "Running memory usage tests.."
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "27M"
jsonKeyTest "1M" "POST" "/leak?columns=blob" "20M"
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "20M"
jsonKeyTest "1M" "POST" "/leak?columns=blob" "21M"
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "21M"
jsonKeyTest "10M" "POST" "/rpc/leak?columns=blob" "32M"
jsonKeyTest "10M" "POST" "/leak?columns=blob" "32M"
@@ -375,3 +375,31 @@ spec = describe "custom media types" $ do
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/octet-stream"]
}
context "media type parser fails" $ do
it "sends media type as is" $
request methodGet "/items" (acceptHdrs "undefined") ""
`shouldRespondWith`
[json| {"code":"PGRST107","details":null,"hint":null,"message":"None of these media types are available: undefined"} |]
{ matchStatus = 406 }
context "media type parser allowed characters" $ do
it "regression test allowing charset=utf-8" $
request methodPost "/rpc/overloaded_default"
[("Content-Type", "application/json; charset=utf-8")]
[json|{"must_param":1}|]
`shouldRespondWith`
[json|{"val":1}|]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
it "handle unrecognized parameters leniently" $ do
request methodPost "/rpc/overloaded_default"
[("Content-Type", "application/json; $$ unrecognized-chars=ignored $$")]
[json|{"must_param":1}|]
`shouldRespondWith`
[json|{"val":1}|]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
+1
View File
@@ -151,6 +151,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configDbTxAllowOverride = True
, configDbTxRollbackAll = True
, configAdminServerPort = Nothing
, configAdminServerConfigEnabled = False
, configRoleSettings = mempty
, configRoleIsoLvl = mempty
, configInternalSCSleep = Nothing