Compare commits
@@ -1,5 +1,5 @@
|
|||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-14-0
|
image_family: freebsd-14-2
|
||||||
|
|
||||||
build_task:
|
build_task:
|
||||||
# Don't change this name without adjusting .github/workflows/build.yaml
|
# Don't change this name without adjusting .github/workflows/build.yaml
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# TODO: Remove this once a new actionlint release has been cut
|
||||||
|
# and made its way to us through nixpkgs.
|
||||||
|
self-hosted-runner:
|
||||||
|
labels:
|
||||||
|
- ubuntu-24.04-arm
|
||||||
@@ -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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: ${{ inputs.upload }}
|
|
||||||
path: ${{ steps.download.outputs.artifacts }}
|
|
||||||
if-no-files-found: error
|
|
||||||
@@ -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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
|
||||||
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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
|
||||||
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 }}-
|
|
||||||
@@ -11,15 +11,16 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- uses: nixbuild/nix-quick-install-action@60e9c39264d4714139af3cdf15f691b19eec3530 # v28
|
- uses: nixbuild/nix-quick-install-action@9f63be77f412a248c9d9a65a4c82cf066cdf8f0c # v35
|
||||||
with:
|
with:
|
||||||
nix_conf: |-
|
nix_conf: |-
|
||||||
always-allow-substitutes = true
|
always-allow-substitutes = true
|
||||||
max-jobs = auto
|
max-jobs = auto
|
||||||
- uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15
|
- uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
|
||||||
with:
|
with:
|
||||||
name: postgrest
|
name: postgrest
|
||||||
authToken: ${{ inputs.authToken }}
|
authToken: ${{ inputs.authToken }}
|
||||||
|
skipPush: ${{ inputs.authToken == '' }}
|
||||||
- if: ${{ inputs.tools }}
|
- if: ${{ inputs.tools }}
|
||||||
run: nix-env -f default.nix -iA ${{ inputs.tools }}
|
run: nix-env -f default.nix -iA ${{ inputs.tools }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# This script builds PostgREST in a remote ARM server
|
|
||||||
|
|
||||||
[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; }
|
|
||||||
[ -z "$2" ] && { echo "Missing 2nd argument: Build environment directory name"; exit 1; }
|
|
||||||
[ -z "$3" ] && { echo "Missing 3rd argument: GHC version"; exit 1; }
|
|
||||||
|
|
||||||
PGRST_GITHUB_COMMIT="$1"
|
|
||||||
SCRIPT_DIR="$2"
|
|
||||||
|
|
||||||
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
|
|
||||||
# latest is a shortcut documented on https://www.haskell.org/ghcup/guide/#tags-and-shortcuts
|
|
||||||
CABAL_VERSION="latest"
|
|
||||||
GHC_VERSION="$3"
|
|
||||||
|
|
||||||
install_packages() {
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get upgrade -y
|
|
||||||
sudo apt-get install -y git build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 llvm libnuma-dev zlib1g-dev libpq-dev jq gcc
|
|
||||||
sudo apt-get clean
|
|
||||||
}
|
|
||||||
|
|
||||||
install_ghcup() {
|
|
||||||
export BOOTSTRAP_HASKELL_NONINTERACTIVE=1
|
|
||||||
export BOOTSTRAP_HASKELL_MINIMAL=1
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
|
|
||||||
source ~/.ghcup/env
|
|
||||||
}
|
|
||||||
|
|
||||||
install_cabal() {
|
|
||||||
ghcup upgrade
|
|
||||||
ghcup install cabal $CABAL_VERSION
|
|
||||||
ghcup set cabal $CABAL_VERSION
|
|
||||||
}
|
|
||||||
|
|
||||||
install_ghc() {
|
|
||||||
ghcup upgrade
|
|
||||||
ghcup install ghc $GHC_VERSION
|
|
||||||
ghcup set ghc $GHC_VERSION
|
|
||||||
}
|
|
||||||
|
|
||||||
install_packages
|
|
||||||
|
|
||||||
# Add ghcup to the PATH for this session
|
|
||||||
[ -f ~/.ghcup/env ] && source ~/.ghcup/env
|
|
||||||
|
|
||||||
ghcup --version || install_ghcup
|
|
||||||
ghcup set cabal $CABAL_VERSION || install_cabal
|
|
||||||
ghcup set ghc $GHC_VERSION || install_ghc
|
|
||||||
|
|
||||||
cd ~/$SCRIPT_DIR
|
|
||||||
|
|
||||||
# Clone the repository and build the project
|
|
||||||
git clone https://github.com/PostgREST/postgrest.git
|
|
||||||
cd postgrest
|
|
||||||
git checkout $PGRST_GITHUB_COMMIT
|
|
||||||
cabal v2-update && cabal v2-build
|
|
||||||
|
|
||||||
# Strip unused symbols from executable
|
|
||||||
PGRST_BIN=$(cabal exec which postgrest | tail -1)
|
|
||||||
strip $PGRST_BIN
|
|
||||||
|
|
||||||
# Copy the built binary to the Dockerfile directory
|
|
||||||
cp $PGRST_BIN ~/$DOCKER_BUILD_DIR
|
|
||||||
|
|
||||||
# Move and compress the built binary
|
|
||||||
mkdir -p ~/$SCRIPT_DIR/result
|
|
||||||
mv $PGRST_BIN ~/$SCRIPT_DIR/result
|
|
||||||
cd ~/$SCRIPT_DIR
|
|
||||||
tar -cJf result.tar.xz result
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# PostgREST docker hub image
|
|
||||||
|
|
||||||
FROM ubuntu:noble@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15 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
|
|
||||||
|
|
||||||
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,50 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -Eeuo pipefail
|
|
||||||
|
|
||||||
# This script publishes the Docker ARM images to Docker Hub.
|
|
||||||
|
|
||||||
[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; }
|
|
||||||
[ -z "$2" ] && { echo "Missing 2nd argument: Docker repo"; exit 1; }
|
|
||||||
[ -z "$3" ] && { echo "Missing 3rd argument: Docker username"; exit 1; }
|
|
||||||
[ -z "$4" ] && { echo "Missing 4th argument: Docker password"; exit 1; }
|
|
||||||
[ -z "$5" ] && { echo "Missing 5th argument: Build environment directory name"; exit 1; }
|
|
||||||
[ -z "$6" ] && { echo "Missing 6th argument: PostgREST version"; exit 1; }
|
|
||||||
|
|
||||||
PGRST_GITHUB_COMMIT="$1"
|
|
||||||
DOCKER_REPO="$2"
|
|
||||||
DOCKER_USER="$3"
|
|
||||||
DOCKER_PASS="$4"
|
|
||||||
SCRIPT_DIR="$5"
|
|
||||||
PGRST_VERSION="$6"
|
|
||||||
|
|
||||||
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
|
|
||||||
|
|
||||||
clean_env()
|
|
||||||
{
|
|
||||||
sudo docker logout
|
|
||||||
}
|
|
||||||
|
|
||||||
# Login to Docker
|
|
||||||
sudo docker logout
|
|
||||||
{ echo $DOCKER_PASS | sudo docker login -u $DOCKER_USER --password-stdin; } || { echo "Couldn't login to docker"; exit 1; }
|
|
||||||
|
|
||||||
trap clean_env sigint sigterm exit
|
|
||||||
|
|
||||||
# Move to the docker build environment
|
|
||||||
cd ~/$DOCKER_BUILD_DIR
|
|
||||||
|
|
||||||
# Push final images to Docker hub
|
|
||||||
# NOTE: This command publishes a separate ARM image because the builds cannot
|
|
||||||
# be added to the manifest if they are not in the registry beforehand.
|
|
||||||
# This image must be manually deleted from Docker Hub at the end of the process.
|
|
||||||
sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \
|
|
||||||
-t $DOCKER_REPO/postgrest:$PGRST_VERSION-arm \
|
|
||||||
--push .
|
|
||||||
|
|
||||||
# Add the arm images to the manifest
|
|
||||||
# NOTE: This assumes that there already is a `postgrest:<version>` image
|
|
||||||
# for the amd64 architecture pushed to Docker Hub
|
|
||||||
sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:$PGRST_VERSION $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
|
|
||||||
[ "$PGRST_VERSION" != "devel" ] && sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:latest $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
|
|
||||||
|
|
||||||
sudo docker logout
|
|
||||||
@@ -1,203 +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 static
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: postgrest-linux-static-x64
|
|
||||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: postgrest-docker-x64
|
|
||||||
path: postgrest-docker.tar.gz
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
macos:
|
|
||||||
name: Nix - MacOS
|
|
||||||
runs-on: macos-12
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
cache: |
|
|
||||||
~/.stack/pantry
|
|
||||||
~/.stack/snapshots
|
|
||||||
~/.stack/stack.sqlite3
|
|
||||||
# no artifact for Linux, because we use the static build
|
|
||||||
|
|
||||||
- name: MacOS
|
|
||||||
runs-on: macos-12
|
|
||||||
cache: |
|
|
||||||
~/.stack/pantry
|
|
||||||
~/.stack/snapshots
|
|
||||||
~/.stack/stack.sqlite3
|
|
||||||
artifact: postgrest-macos-x64
|
|
||||||
|
|
||||||
- name: Windows
|
|
||||||
runs-on: windows-2022
|
|
||||||
cache: |
|
|
||||||
~\AppData\Roaming\stack\pantry
|
|
||||||
~\AppData\Local\Programs\stack\pantry
|
|
||||||
~\AppData\Roaming\stack\snapshots
|
|
||||||
~\AppData\Local\Programs\stack\snapshots
|
|
||||||
~\AppData\Roaming\stack\stack.sqlite3
|
|
||||||
~\AppData\Local\Programs\stack\stack.sqlite3
|
|
||||||
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
|
|
||||||
artifact: postgrest-windows-x64
|
|
||||||
|
|
||||||
name: Stack - ${{ matrix.name }}
|
|
||||||
runs-on: ${{ matrix.runs-on }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- uses: haskell-actions/setup@33585e1a16afa5875e124b0ebc89dd0c2f872c21 # v2.7.3
|
|
||||||
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
|
|
||||||
if: matrix.artifact
|
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.artifact }}
|
|
||||||
path: |
|
|
||||||
result/postgrest
|
|
||||||
result/postgrest.exe
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
freebsd:
|
|
||||||
name: Stack - FreeBSD from CirrusCI
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- uses: ./.github/actions/artifact-from-cirrus
|
|
||||||
with:
|
|
||||||
token: ${{ github.token }}
|
|
||||||
task: Build FreeBSD (Stack)
|
|
||||||
download: bin
|
|
||||||
upload: postgrest-freebsd-x64
|
|
||||||
|
|
||||||
|
|
||||||
cabal:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
ghc: ['9.6.4', '9.8.2']
|
|
||||||
fail-fast: false
|
|
||||||
name: Cabal - Linux GHC ${{ matrix.ghc }}
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- uses: haskell-actions/setup@33585e1a16afa5875e124b0ebc89dd0c2f872c21 # v2.7.3
|
|
||||||
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
|
|
||||||
@@ -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-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- v[0-9]+
|
|
||||||
tags:
|
|
||||||
- devel
|
|
||||||
- v*
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
# Terminate all previous runs of the same workflow for the same tag, but never
|
|
||||||
# cancel for the main / release branch pipelines.
|
|
||||||
group: ci-${{ (github.ref_type == 'tag' && github.ref) || github.run_id }}
|
|
||||||
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
name: Check
|
|
||||||
if: startsWith(github.ref, 'refs/heads/')
|
|
||||||
uses: ./.github/workflows/check.yaml
|
|
||||||
secrets:
|
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
|
|
||||||
|
|
||||||
docs:
|
|
||||||
name: Docs
|
|
||||||
if: startsWith(github.ref, 'refs/heads/')
|
|
||||||
uses: ./.github/workflows/docs.yaml
|
|
||||||
secrets:
|
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
|
||||||
|
|
||||||
|
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
if: startsWith(github.ref, 'refs/heads/')
|
|
||||||
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 }}
|
|
||||||
|
|
||||||
|
|
||||||
arm:
|
|
||||||
name: Build / Cabal - aarch64 GHC 9.4.8
|
|
||||||
if: vars.SSH_ARM_ENABLED
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
outputs:
|
|
||||||
remotepath: ${{ steps.Remote-Dir.outputs.remotepath }}
|
|
||||||
env:
|
|
||||||
GITHUB_COMMIT: ${{ github.sha }}
|
|
||||||
GHC_VERSION: '9.4.8'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- id: Remote-Dir
|
|
||||||
name: Unique directory name for the remote build
|
|
||||||
run: echo "remotepath=postgrest-build-$(uuidgen)" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: Copy script files to the remote server
|
|
||||||
uses: appleboy/scp-action@master
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SSH_ARM_HOST }}
|
|
||||||
username: ubuntu
|
|
||||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
|
||||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
|
||||||
source: ".github/scripts/arm/*"
|
|
||||||
target: ${{ steps.Remote-Dir.outputs.remotepath }}
|
|
||||||
strip_components: 3
|
|
||||||
- name: Build ARM
|
|
||||||
uses: appleboy/ssh-action@master
|
|
||||||
env:
|
|
||||||
REMOTE_DIR: ${{ steps.Remote-Dir.outputs.remotepath }}
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SSH_ARM_HOST }}
|
|
||||||
username: ubuntu
|
|
||||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
|
||||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
|
||||||
command_timeout: 120m
|
|
||||||
script_stop: true
|
|
||||||
envs: GITHUB_COMMIT,REMOTE_DIR,GHC_VERSION
|
|
||||||
script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$REMOTE_DIR" "$GHC_VERSION"
|
|
||||||
- name: Download binaries from remote server
|
|
||||||
uses: nicklasfrahm/scp-action@main
|
|
||||||
with:
|
|
||||||
direction: download
|
|
||||||
host: ${{ secrets.SSH_ARM_HOST }}
|
|
||||||
username: ubuntu
|
|
||||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
|
||||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
|
||||||
source: "${{ steps.Remote-Dir.outputs.remotepath }}/result.tar.xz"
|
|
||||||
target: "result.tar.xz"
|
|
||||||
- name: Extract downloaded binaries
|
|
||||||
run: tar -xvf result.tar.xz && rm result.tar.xz
|
|
||||||
- name: Save aarch64 executable as artifact
|
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: postgrest-ubuntu-aarch64
|
|
||||||
path: result/postgrest
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
tag:
|
|
||||||
name: Release / 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 &&
|
|
||||||
startsWith(github.ref, 'refs/heads/') &&
|
|
||||||
needs.docs.result == 'success' &&
|
|
||||||
needs.test.result == 'success' &&
|
|
||||||
needs.build.result == 'success' &&
|
|
||||||
(needs.arm.result == 'skipped' || success())
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- docs
|
|
||||||
- test
|
|
||||||
- build
|
|
||||||
- arm
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
prepare:
|
|
||||||
name: Release / Prepare
|
|
||||||
if: |
|
|
||||||
startsWith(github.ref, 'refs/tags/') &&
|
|
||||||
needs.build.result == 'success' &&
|
|
||||||
(needs.arm.result == 'skipped' || success())
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- build
|
|
||||||
- arm
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: release-changes
|
|
||||||
path: CHANGES.md
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
github:
|
|
||||||
name: Release / GitHub
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- prepare
|
|
||||||
if: success() || needs.prepare.result == 'success'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- name: Download all artifacts
|
|
||||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
|
||||||
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-x64.tar.xz" \
|
|
||||||
-C artifacts/postgrest-linux-static-x64 postgrest
|
|
||||||
|
|
||||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-x64.tar.xz" \
|
|
||||||
-C artifacts/postgrest-macos-x64 postgrest
|
|
||||||
|
|
||||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x64.tar.xz" \
|
|
||||||
-C artifacts/postgrest-freebsd-x64 postgrest
|
|
||||||
|
|
||||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
|
|
||||||
-C artifacts/postgrest-ubuntu-aarch64 postgrest
|
|
||||||
|
|
||||||
zip "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x64.zip" \
|
|
||||||
artifacts/postgrest-windows-x64/postgrest.exe
|
|
||||||
|
|
||||||
- name: Save release bundle
|
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
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: Release / Docker Hub
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- prepare
|
|
||||||
if: |
|
|
||||||
vars.DOCKER_REPO && vars.DOCKER_USER &&
|
|
||||||
(success() || needs.prepare.result == 'success')
|
|
||||||
env:
|
|
||||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
|
||||||
DOCKER_USER: ${{ vars.DOCKER_USER }}
|
|
||||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- name: Download Docker image
|
|
||||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
|
||||||
with:
|
|
||||||
name: postgrest-docker-x64
|
|
||||||
- name: Publish images on Docker Hub
|
|
||||||
run: |
|
|
||||||
docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
|
|
||||||
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}"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
else
|
|
||||||
echo "Skipping push to 'latest' tag for pre-release..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
docker-description:
|
|
||||||
name: Release / Docker Hub Description
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
if: |
|
|
||||||
vars.DOCKER_REPO && vars.DOCKER_USER &&
|
|
||||||
github.ref == 'refs/tags/devel'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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
|
|
||||||
|
|
||||||
|
|
||||||
docker-arm:
|
|
||||||
name: Release / Docker Hub Arm
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
needs:
|
|
||||||
- arm
|
|
||||||
- docker
|
|
||||||
env:
|
|
||||||
GITHUB_COMMIT: ${{ github.sha }}
|
|
||||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
|
||||||
DOCKER_USER: ${{ vars.DOCKER_USER }}
|
|
||||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- name: Publish images for ARM builds on Docker Hub
|
|
||||||
uses: appleboy/ssh-action@master
|
|
||||||
env:
|
|
||||||
REMOTE_DIR: ${{ needs.arm.outputs.remotepath }}
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SSH_ARM_HOST }}
|
|
||||||
username: ubuntu
|
|
||||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
|
||||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
|
||||||
script_stop: true
|
|
||||||
envs: GITHUB_COMMIT,DOCKER_REPO,DOCKER_USER,DOCKER_PASS,REMOTE_DIR,GITHUB_REF_NAME
|
|
||||||
script: bash ~/$REMOTE_DIR/docker-publish.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" "$GITHUB_REF_NAME"
|
|
||||||
|
|
||||||
|
|
||||||
clean-arm:
|
|
||||||
name: Build / Cleanup
|
|
||||||
needs:
|
|
||||||
- arm
|
|
||||||
- docker-arm
|
|
||||||
if: ${{ always() && vars.SSH_ARM_ENABLED }}
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
env:
|
|
||||||
REMOTE_DIR: ${{ needs.arm.outputs.remotepath }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- name: Remove uploaded files from server
|
|
||||||
uses: appleboy/ssh-action@master
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.SSH_ARM_HOST }}
|
|
||||||
username: ubuntu
|
|
||||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
|
||||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
|
||||||
envs: REMOTE_DIR
|
|
||||||
script: rm -rf $REMOTE_DIR
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
name: Docs
|
name: Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
push:
|
||||||
secrets:
|
branches:
|
||||||
CACHIX_AUTH_TOKEN:
|
- main
|
||||||
required: false
|
- v[0-9]+
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -25,9 +25,9 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Setup Nix Environment
|
- name: Setup Nix Environment
|
||||||
uses: ./.github/actions/setup-nix
|
uses: ./.github/actions/setup-nix
|
||||||
with:
|
with:
|
||||||
@@ -39,9 +39,9 @@ jobs:
|
|||||||
|
|
||||||
spellcheck:
|
spellcheck:
|
||||||
name: Spellcheck
|
name: Spellcheck
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Setup Nix Environment
|
- name: Setup Nix Environment
|
||||||
uses: ./.github/actions/setup-nix
|
uses: ./.github/actions/setup-nix
|
||||||
with:
|
with:
|
||||||
@@ -56,9 +56,9 @@ jobs:
|
|||||||
linkcheck:
|
linkcheck:
|
||||||
name: Linkcheck
|
name: Linkcheck
|
||||||
if: github.base_ref == 'main'
|
if: github.base_ref == 'main'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Setup Nix Environment
|
- name: Setup Nix Environment
|
||||||
uses: ./.github/actions/setup-nix
|
uses: ./.github/actions/setup-nix
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
name: Upload Reports
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows:
|
|
||||||
- CI
|
|
||||||
- Test
|
|
||||||
branches-ignore:
|
|
||||||
- devel
|
|
||||||
- v[0-9]+
|
|
||||||
- v[0-9]+.[0-9]+.[0-9]+
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
loadtest:
|
|
||||||
name: Loadtest
|
|
||||||
permissions:
|
|
||||||
checks: write
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure'
|
|
||||||
steps:
|
|
||||||
- name: Download from Artifacts
|
|
||||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: loadtest.md
|
|
||||||
path: artifacts
|
|
||||||
- name: Upload to GitHub Checks
|
|
||||||
uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 # v2.0.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
sha: ${{ github.event.workflow_run.head_sha }}
|
|
||||||
name: Loadtest Results
|
|
||||||
conclusion: neutral
|
|
||||||
output: |
|
|
||||||
{"summary":""}
|
|
||||||
output_text_description_file: artifacts/loadtest.md
|
|
||||||
@@ -1,138 +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-22.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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1
|
|
||||||
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-22.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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
- 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-22.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
|
||||||
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 > loadtest/loadtest.md
|
|
||||||
- name: Upload report
|
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
|
||||||
with:
|
|
||||||
name: loadtest.md
|
|
||||||
path: loadtest/loadtest.md
|
|
||||||
if-no-files-found: error
|
|
||||||
@@ -32,7 +32,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
|||||||
</td>
|
</td>
|
||||||
<td align="center" valign="middle">
|
<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">
|
<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>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" valign="middle">
|
<td align="center" valign="middle">
|
||||||
|
|||||||
@@ -5,6 +5,93 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## 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
|
||||||
|
|
||||||
|
- #3867, Fix startup for arm64 docker image - @wolfgangwalther
|
||||||
|
|
||||||
|
## [12.2.4] - 2025-01-18
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #3779, Always log the schema cache load time - @steve-chavez
|
||||||
|
- #3706, Fix insert with `missing=default` uses default value of domain instead of column - @taimoorzaeem
|
||||||
|
|
||||||
|
## [12.2.3] - 2024-08-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #3091, Broken link in OpenAPI description `externalDocs` - @salim-b
|
||||||
|
- #3659, Embed One-to-One relationship with different column order properly - @wolfgangwalther
|
||||||
|
- #3504, Remove `format` from `rowFilter` parameters in OpenAPI - @dantheman2865
|
||||||
|
- #3660, Fix regression that loaded the schema cache before the in-database configuration - @steve-chavez, @laurenceisla
|
||||||
|
|
||||||
|
## [12.2.2] - 2024-07-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #3093, Nested empty embeds no longer show empty values and are correctly omitted - @laurenceisla
|
||||||
|
- #3644, Make --dump-schema work with in-database pgrst.db_schemas setting - @wolfgangwalther
|
||||||
|
- #3644, Show number of timezones in schema cache load report - @wolfgangwalther
|
||||||
|
- #3644, List correct enum options in OpenApi output when multiple types with same name are present - @wolfgangwalther
|
||||||
|
- #3523, Fix schema cache loading retry without backoff - @steve-chavez
|
||||||
|
|
||||||
|
## [12.2.1] - 2024-06-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #3147, Don't reload schema cache on every listener failure - @steve-chavez
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- #3592, Architecture diagram now supports dark mode and has links - @laurenceisla
|
||||||
|
- #3616, The schema isolation diagram now supports dark mode and uses well-known schemas - @laurenceisla
|
||||||
|
|
||||||
## [12.2.0] - 2024-06-11
|
## [12.2.0] - 2024-06-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://www.patreon.com/postgrest)
|
[](https://www.patreon.com/postgrest)
|
||||||
[](https://www.paypal.me/postgrest)
|
|
||||||
[](https://gitter.im/begriffs/postgrest)
|
|
||||||
[](http://postgrest.org)
|
[](http://postgrest.org)
|
||||||
[](https://hub.docker.com/r/postgrest/postgrest/)
|
[](https://hub.docker.com/r/postgrest/postgrest/)
|
||||||
[](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain)
|
[](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain)
|
||||||
@@ -43,7 +41,7 @@ API than you are likely to write from scratch.
|
|||||||
</td>
|
</td>
|
||||||
<td align="center" valign="middle">
|
<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">
|
<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>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" valign="middle">
|
<td align="center" valign="middle">
|
||||||
@@ -145,11 +143,7 @@ and the [API guide](http://postgrest.org/en/stable/api.html).
|
|||||||
|
|
||||||
## Supporting development
|
## Supporting development
|
||||||
|
|
||||||
You can help PostgREST ongoing maintenance and development by:
|
You can help PostgREST ongoing maintenance and development by making a regular donation through Patreon https://www.patreon.com/postgrest
|
||||||
|
|
||||||
- Making a regular donation through Patreon https://www.patreon.com/postgrest
|
|
||||||
|
|
||||||
- Alternatively, you can make a one-time donation via Paypal https://www.paypal.me/postgrest
|
|
||||||
|
|
||||||
Every donation will be spent on making PostgREST better for the whole community.
|
Every donation will be spent on making PostgREST better for the whole community.
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
index-state: hackage.haskell.org 2024-05-17T23:41:49Z
|
index-state: hackage.haskell.org 2025-02-01T14:59:33Z
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# PostgREST
|
# PostgREST
|
||||||
|
|
||||||
[](https://gitter.im/begriffs/postgrest)
|
|
||||||
[](https://www.patreon.com/postgrest)
|
[](https://www.patreon.com/postgrest)
|
||||||
[](https://www.paypal.me/postgrest)
|
|
||||||
[](http://postgrest.org)
|
[](http://postgrest.org)
|
||||||
[](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain)
|
[](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain)
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ write from scratch.
|
|||||||
</td>
|
</td>
|
||||||
<td align="center" valign="middle">
|
<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">
|
<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>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" valign="middle">
|
<td align="center" valign="middle">
|
||||||
|
|||||||
@@ -5,35 +5,20 @@ The ER diagrams were created with https://github.com/BurntSushi/erd/.
|
|||||||
You can go download erd from https://github.com/BurntSushi/erd/releases and then do:
|
You can go download erd from https://github.com/BurntSushi/erd/releases and then do:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./erd_static-x86-64 -i film.er -o ../_static/film.png
|
./erd_static-x86-64 -i ./er/film.er -o ../_static/film.png
|
||||||
```
|
```
|
||||||
|
|
||||||
The fonts used belong to the GNU FreeFont family. You can download them here: http://ftp.gnu.org/gnu/freefont/
|
The fonts used belong to the GNU FreeFont family. You can download them here: http://ftp.gnu.org/gnu/freefont/
|
||||||
|
|
||||||
## LaTeX
|
## UML
|
||||||
|
|
||||||
The schema structure diagram is done with LaTeX. You can use a GUI like https://www.mathcha.io/editor to create the .tex file.
|
The UML diagrams are created with https://plantuml.com/.
|
||||||
|
|
||||||
Then use this command to generate the png file.
|
PlantUML only creates one diagram per file.
|
||||||
|
That's why we need to create another one for dark mode.
|
||||||
|
For example, for the file [uml/arch.uml](uml/arch.uml) there's [uml/dark/arch-dark.uml](uml/dark/arch-dark.uml) which includes the first one:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
postgrest-docs-render
|
plantuml -tsvg uml/arch.uml -o ../../_static
|
||||||
```
|
plantuml -tsvg -darkmode uml/dark/arch-dark.uml -o ../../../_static
|
||||||
|
|
||||||
LaTeX is used because it's a tweakable plain text format.
|
|
||||||
|
|
||||||
You can install the full latex suite with `nix`:
|
|
||||||
|
|
||||||
```
|
|
||||||
nix-env -iA texlive.combined.scheme-full
|
|
||||||
```
|
|
||||||
|
|
||||||
To tweak the file with a live reload environment use:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# open the pdf(zathura used as an example)
|
|
||||||
zathura db.pdf &
|
|
||||||
|
|
||||||
# live reload with entr
|
|
||||||
echo db.tex | entr pdflatex --shell-escape -halt-on-error db.tex
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
\documentclass[convert]{standalone}
|
|
||||||
\usepackage{amsmath}
|
|
||||||
\usepackage{tikz}
|
|
||||||
\usepackage{mathdots}
|
|
||||||
\usepackage{yhmath}
|
|
||||||
\usepackage{cancel}
|
|
||||||
\usepackage{color}
|
|
||||||
\usepackage{siunitx}
|
|
||||||
\usepackage{array}
|
|
||||||
\usepackage{multirow}
|
|
||||||
\usepackage{amssymb}
|
|
||||||
\usepackage{gensymb}
|
|
||||||
\usepackage{tabularx}
|
|
||||||
\usepackage{booktabs}
|
|
||||||
\usetikzlibrary{fadings}
|
|
||||||
\usetikzlibrary{patterns}
|
|
||||||
\usetikzlibrary{shadows.blur}
|
|
||||||
\usetikzlibrary{shapes}
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
|
|
||||||
\newcommand\customScale{0.35}
|
|
||||||
|
|
||||||
\begin{tikzpicture}[x=0.75pt,y=0.75pt,yscale=-1,xscale=1, scale=\customScale, every node/.style={scale=\customScale}]
|
|
||||||
|
|
||||||
%Shape: Can [id:dp7234864758664346]
|
|
||||||
\draw [fill={rgb, 255:red, 47; green, 97; blue, 144 } ,fill opacity=1 ] (497.5,51.5) -- (497.5,255.5) .. controls (497.5,275.66) and (423.18,292) .. (331.5,292) .. controls (239.82,292) and (165.5,275.66) .. (165.5,255.5) -- (165.5,51.5) .. controls (165.5,31.34) and (239.82,15) .. (331.5,15) .. controls (423.18,15) and (497.5,31.34) .. (497.5,51.5) .. controls (497.5,71.66) and (423.18,88) .. (331.5,88) .. controls (239.82,88) and (165.5,71.66) .. (165.5,51.5) ;
|
|
||||||
%Shape: Rectangle [id:dp7384065579958246]
|
|
||||||
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (189,115) -- (252.5,115) -- (252.5,155) -- (189,155) -- cycle ;
|
|
||||||
%Shape: Rectangle [id:dp24763906430298177]
|
|
||||||
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (292,118) -- (362,118) -- (362,158) -- (292,158) -- cycle ;
|
|
||||||
%Shape: Rectangle [id:dp3775601612537265]
|
|
||||||
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (397,114) -- (467,114) -- (467,154) -- (397,154) -- cycle ;
|
|
||||||
%Shape: Rectangle [id:dp7071457022893852]
|
|
||||||
\draw [fill={rgb, 255:red, 248; green, 231; blue, 28 } ,fill opacity=1 ] (269,199) -- (397.5,199) -- (397.5,273) -- (269,273) -- cycle ;
|
|
||||||
%Straight Lines [id:da8846759047437789]
|
|
||||||
\draw (268,234) -- (226.44,155.77) ;
|
|
||||||
\draw [shift={(225.5,154)}, rotate = 422.02] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
|
|
||||||
%Straight Lines [id:da6908444738113828]
|
|
||||||
\draw (309.5,198) -- (307.6,161) ;
|
|
||||||
\draw [shift={(307.5,159)}, rotate = 447.06] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
|
|
||||||
%Straight Lines [id:da7168757864413169]
|
|
||||||
\draw (398.5,233) -- (431.72,154.84) ;
|
|
||||||
\draw [shift={(432.5,153)}, rotate = 473.03] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
|
|
||||||
%Up Down Arrow [id:dp14059754167108496]
|
|
||||||
\draw [fill={rgb, 255:red, 126; green, 211; blue, 33 } ,fill opacity=1 ] (312.5,288.5) -- (330,273) -- (347.5,288.5) -- (338.75,288.5) -- (338.75,319.5) -- (347.5,319.5) -- (330,335) -- (312.5,319.5) -- (321.25,319.5) -- (321.25,288.5) -- cycle ;
|
|
||||||
|
|
||||||
% Text Node
|
|
||||||
\draw (201,129) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
|
|
||||||
% Text Node
|
|
||||||
\draw (307,130) node [anchor=north west][inner sep=0.75pt] [align=left ] {tables};
|
|
||||||
% Text Node
|
|
||||||
\draw (414,127) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
|
|
||||||
% Text Node
|
|
||||||
\draw (300,203) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 0; green, 0; blue, 0 } ,opacity=1 ] [align=center] { \\ views \\ + \\ \ \ functions};
|
|
||||||
|
|
||||||
% Text Node
|
|
||||||
\draw (322,178) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{api}};
|
|
||||||
% Text Node
|
|
||||||
\draw (190,97) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{internal}};
|
|
||||||
% Text Node
|
|
||||||
\draw (300,99) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{private}};
|
|
||||||
% Text Node
|
|
||||||
\draw (417,101) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{core}};
|
|
||||||
% Text Node
|
|
||||||
\draw (358,306) node [anchor=north west][inner sep=0.75pt] [align=left] {REST};
|
|
||||||
|
|
||||||
\end{tikzpicture}
|
|
||||||
|
|
||||||
|
|
||||||
\end{document}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
@startuml
|
@startuml
|
||||||
|
|
||||||
|
skinparam backgroundColor transparent
|
||||||
|
|
||||||
package "PostgREST" {
|
package "PostgREST" {
|
||||||
() HTTP as HTTPAPI
|
() HTTP as HTTPAPI
|
||||||
HTTPAPI - [Auth]
|
HTTPAPI - [Auth]
|
||||||
@@ -14,11 +16,9 @@ package "PostgREST" {
|
|||||||
[Admin] -r- () HTTPADMIN
|
[Admin] -r- () HTTPADMIN
|
||||||
[Config] -l- () CLI
|
[Config] -l- () CLI
|
||||||
|
|
||||||
[Config] <-l~ Listener
|
[Config] <-r~ Listener
|
||||||
|
|
||||||
HTTPADMIN -[hidden]r- CLI
|
HTTPADMIN -[hidden]r- CLI
|
||||||
Listener -[hidden]r- [Schema Cache]
|
|
||||||
Listener -[hidden]r- [Config]
|
|
||||||
[Schema Cache] -l[hidden]- [Config]
|
[Schema Cache] -l[hidden]- [Config]
|
||||||
[Schema Cache] -l[hidden]- [Admin]
|
[Schema Cache] -l[hidden]- [Admin]
|
||||||
[Schema Cache] -l[hidden]- CLI
|
[Schema Cache] -l[hidden]- CLI
|
||||||
@@ -26,16 +26,16 @@ package "PostgREST" {
|
|||||||
|
|
||||||
|
|
||||||
database "PostgreSQL" {
|
database "PostgreSQL" {
|
||||||
node "API schema" as API {
|
|
||||||
rectangle "Functions, Views"
|
|
||||||
}
|
|
||||||
node Authorization {
|
node Authorization {
|
||||||
rectangle "Roles, GRANT, RLS"
|
rectangle "Roles, GRANT, RLS"
|
||||||
}
|
}
|
||||||
|
node "API schema" as API {
|
||||||
|
rectangle "Functions, Views"
|
||||||
|
}
|
||||||
rectangle "Tables, extensions" as tbs
|
rectangle "Tables, extensions" as tbs
|
||||||
API -d- tbs
|
API -d- tbs
|
||||||
|
|
||||||
API -[hidden]l- Authorization
|
API -l[hidden]- Authorization
|
||||||
}
|
}
|
||||||
|
|
||||||
:user:
|
:user:
|
||||||
@@ -67,4 +67,18 @@ end note
|
|||||||
note top of Listener
|
note top of Listener
|
||||||
LISTEN session
|
LISTEN session
|
||||||
end note
|
end note
|
||||||
|
|
||||||
|
url of Admin is [[../references/admin_server.html#admin-server]]
|
||||||
|
url of API is [[../explanations/schema_isolation.html]]
|
||||||
|
url of Auth is [[../references/auth.html#authn]]
|
||||||
|
url of Authorization is [[../explanations/db_authz.html]]
|
||||||
|
url of CLI is [[../references/cli.html#cli]]
|
||||||
|
url of "Connection Pool" is [[../references/connection_pool.html]]
|
||||||
|
url of Config is [[../references/configuration.html#configuration]]
|
||||||
|
url of HTTPADMIN is [[https://aosabook.org/en/posa/warp.html]]
|
||||||
|
url of HTTPAPI is [[https://aosabook.org/en/posa/warp.html]]
|
||||||
|
url of Listener is [[../references/listener.html#listener]]
|
||||||
|
url of Proxy is [[../explanations/nginx.html]]
|
||||||
|
url of "Schema Cache" is [[../references/schema_cache.html#schema-cache]]
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
!include ../arch.uml
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@startuml
|
||||||
|
!include ../sch-iso.uml
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
skinparam backgroundColor transparent
|
||||||
|
skinparam linetype ortho
|
||||||
|
|
||||||
|
skinparam node {
|
||||||
|
backgroundColor transparent
|
||||||
|
borderThickness 1
|
||||||
|
}
|
||||||
|
|
||||||
|
database "PostgreSQL" {
|
||||||
|
node public {
|
||||||
|
rectangle tables_public as "tables"
|
||||||
|
}
|
||||||
|
|
||||||
|
node extensions as "**extensions**" {
|
||||||
|
}
|
||||||
|
|
||||||
|
node API as "<size:20>api" {
|
||||||
|
rectangle vf_api as "views + functions"
|
||||||
|
}
|
||||||
|
|
||||||
|
tables_public <-- vf_api
|
||||||
|
extensions <-- vf_api
|
||||||
|
}
|
||||||
|
|
||||||
|
vf_api <-[thickness=3]-> () PostgREST
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -149,3 +149,7 @@ html[data-theme="dark"] .img-translucent img {
|
|||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.svg-container-md {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="us-ascii" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="411px" preserveAspectRatio="none" style="width:315px;height:411px;" version="1.1" viewBox="0 0 315 411" width="315px" zoomAndPan="magnify"><defs/><g><!--cluster PostgreSQL--><g id="cluster_PostgreSQL"><path d="M6,16 C6,6 152,6 152,6 C152,6 298,6 298,16 L298,309.14 C298,319.14 152,319.14 152,319.14 C152,319.14 6,319.14 6,309.14 L6,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><path d="M6,16 C6,26 152,26 152,26 C152,26 298,26 298,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="81.375" x="111.3125" y="42.9659">PostgreSQL</text></g><!--cluster public--><g id="cluster_public"><polygon fill="none" points="30,77,40,67,150,67,150,155.07,140,165.07,30,165.07,30,77" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="140" x2="150" y1="77" y2="67"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="30" x2="140" y1="77" y2="77"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="140" x2="140" y1="77" y2="165.07"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="44.625" x="63.6875" y="94.9659">public</text></g><!--cluster API--><g id="cluster_API"><polygon fill="none" points="68,199.07,78,189.07,236,189.07,236,285.14,226,295.14,68,295.14,68,199.07" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="226" x2="236" y1="199.07" y2="189.07"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="68" x2="226" y1="199.07" y2="199.07"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="226" x2="226" y1="199.07" y2="295.14"/><text fill="#FFFFFF" font-family="sans-serif" font-size="20" font-weight="bold" lengthAdjust="spacing" textLength="29.875" x="133.0625" y="223.45">api</text></g><!--entity tables_public--><g id="elem_tables_public"><rect fill="#313139" height="39.0679" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="61" x="69.5" y="110"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="41" x="79.5" y="134.9659">tables</text></g><!--entity extensions--><g id="elem_extensions"><polygon fill="none" points="165.81,115,175.81,105,282.185,105,282.185,144.0679,272.185,154.0679,165.81,154.0679,165.81,115" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="272.185" x2="282.185" y1="115" y2="105"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="165.81" x2="272.185" y1="115" y2="115"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="272.185" x2="272.185" y1="115" y2="154.0679"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="76.375" x="180.81" y="139.9659">extensions</text></g><!--entity vf_api--><g id="elem_vf_api"><rect fill="#313139" height="39.0679" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="136" x="84" y="240.07"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="116" x="94" y="265.0359">views + functions</text></g><!--entity PostgREST--><g id="elem_PostgREST"><ellipse cx="152" cy="371.14" fill="#313139" rx="8" ry="8" style="stroke:#E7E7E7;stroke-width:0.5;"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="70" x="117" y="403.1059">PostgREST</text></g><!--reverse link tables_public to vf_api--><g id="link_tables_public_vf_api"><path d="M107.25,155.29 C107.25,179.74 107.25,215.13 107.25,239.67 " fill="none" id="tables_public-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="107.25,149.29,103.25,158.29,107.25,154.29,111.25,158.29,107.25,149.29" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--reverse link extensions to vf_api--><g id="link_extensions_vf_api"><path d="M192.91,160.31 C192.91,185.07 192.91,216.68 192.91,239.61 " fill="none" id="extensions-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="192.91,154.31,188.91,163.31,192.91,159.31,196.91,163.31,192.91,154.31" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--link vf_api to PostgREST--><g id="link_vf_api_PostgREST"><path d="M152,285.55 C152,309.98 152,338.34 152,356.18 " fill="none" id="vf_api-PostgREST" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="152,279.55,148,288.55,152,284.55,156,288.55,152,279.55" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="152,362.18,156,353.18,152,357.18,148,353.18,152,362.18" style="stroke:#E7E7E7;stroke-width:3.0;"/></g><!--SRC=[KypCIyufJKbLqDFJBqxEqCqipjShpSq10000]--></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -50,7 +50,7 @@ source_suffix = ".rst"
|
|||||||
master_doc = "index"
|
master_doc = "index"
|
||||||
|
|
||||||
# This is overriden by readthedocs with the version tag anyway
|
# This is overriden by readthedocs with the version tag anyway
|
||||||
version = "devel"
|
version = "12.2"
|
||||||
# To avoid repetition in <title> we set this to an empty string.
|
# To avoid repetition in <title> we set this to an empty string.
|
||||||
release = ""
|
release = ""
|
||||||
|
|
||||||
@@ -292,15 +292,18 @@ def setup(app):
|
|||||||
app.add_css_file("css/custom.css")
|
app.add_css_file("css/custom.css")
|
||||||
|
|
||||||
|
|
||||||
# taken from https://github.com/sphinx-doc/sphinx/blob/82dad44e5bd3776ecb6fd8ded656bc8151d0e63d/sphinx/util/requests.py#L42
|
user_agent = (
|
||||||
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0"
|
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: these pages are returning 403 right now. Verify if they can be fixed by addressing the issue:
|
|
||||||
# https://github.com/PostgREST/postgrest/issues/3544
|
|
||||||
linkcheck_ignore = [
|
linkcheck_ignore = [
|
||||||
r"https://stackoverflow.com/",
|
# Odd SSL error
|
||||||
r"https://www.patreon.com/postgrest",
|
r"https://www.dripdepot.com",
|
||||||
r"https://blog.frankel.ch/poor-man-api",
|
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
|
# sphinx-tabs configuration
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ Community Tutorials
|
|||||||
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
|
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
|
||||||
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
|
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
|
||||||
|
|
||||||
* `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
* `PostgREST + Auth0: Create REST API in minutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest-1-introduction/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
||||||
|
|
||||||
* `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.
|
|
||||||
|
|
||||||
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
|
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
|
||||||
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
|
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
|
||||||
@@ -37,6 +34,7 @@ Templates
|
|||||||
Example Apps
|
Example Apps
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* `archtika <https://github.com/thiloho/archtika>`_ - self‑hosted CMS
|
||||||
* `delibrium-postgrest <https://gitlab.com/delibrium/delibrium-postgrest/>`_ - example school API and front-end in Vue.js
|
* `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
|
* `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
|
* `general <https://github.com/PierreRochard/general>`_ - example auth back-end
|
||||||
@@ -62,6 +60,7 @@ External Notification
|
|||||||
|
|
||||||
These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows functions to initiate actions outside the database such as sending emails.
|
These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows functions to initiate actions outside the database such as sending emails.
|
||||||
|
|
||||||
|
* `pg-notify-stdout <https://github.com/mkleczek/pg-notify-stdout>`_ - writes notifications to standard output (use in shell scripts etc.)
|
||||||
* `pg-notify-webhook <https://github.com/vbalasu/pg-notify-webhook>`_ - trigger webhooks from PostgreSQL's LISTEN/NOTIFY
|
* `pg-notify-webhook <https://github.com/vbalasu/pg-notify-webhook>`_ - trigger webhooks from PostgreSQL's LISTEN/NOTIFY
|
||||||
* `pgsql-listen-exchange <https://github.com/gmr/pgsql-listen-exchange>`_ - RabbitMQ
|
* `pgsql-listen-exchange <https://github.com/gmr/pgsql-listen-exchange>`_ - RabbitMQ
|
||||||
* `postgres-websockets <https://github.com/diogob/postgres-websockets>`_ - expose web sockets for PostgreSQL's LISTEN/NOTIFY
|
* `postgres-websockets <https://github.com/diogob/postgres-websockets>`_ - expose web sockets for PostgreSQL's LISTEN/NOTIFY
|
||||||
@@ -89,7 +88,7 @@ Client-Side Libraries
|
|||||||
* `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go
|
* `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go
|
||||||
* `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript
|
* `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript
|
||||||
* `postgrest-kt <https://github.com/supabase-community/postgrest-kt>`_ - Kotlin
|
* `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-rs <https://github.com/supabase-community/postgrest-rs>`_ - Rust
|
||||||
* `postgrest-swift <https://github.com/supabase-community/postgrest-swift>`_ - Swift
|
* `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.
|
* `redux-postgrest <https://github.com/andytango/redux-postgrest>`_ - TypeScript/JS, client integrated with (React) Redux.
|
||||||
|
|||||||
@@ -6,7 +6,22 @@ This page describes the architecture of PostgREST.
|
|||||||
Bird's Eye View
|
Bird's Eye View
|
||||||
===============
|
===============
|
||||||
|
|
||||||
.. image:: ../_static/arch.png
|
You can click on the components to navigate to their respective documentation.
|
||||||
|
|
||||||
|
.. container:: img-dark
|
||||||
|
|
||||||
|
.. See https://github.com/sphinx-doc/sphinx/issues/2240#issuecomment-187366626
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<object width="100%" data="../_static/arch-dark.svg" type="image/svg+xml"></object>
|
||||||
|
|
||||||
|
.. container:: img-light
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<object width="100%" data="../_static/arch.svg" type="image/svg+xml"></object>
|
||||||
|
|
||||||
|
|
||||||
Code Map
|
Code Map
|
||||||
========
|
========
|
||||||
@@ -77,5 +92,4 @@ The HTTP server is provided by `Warp <https://aosabook.org/en/posa/warp.html>`_.
|
|||||||
Listener
|
Listener
|
||||||
--------
|
--------
|
||||||
|
|
||||||
`Listener.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Listener.hs>`_ is in charge of maintaining a `LISTEN session <https://www.postgresql.org/docs/current/sql-listen.html>`_
|
`Listener.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Listener.hs>`_ is in charge of the :ref:`listener`.
|
||||||
that keeps the :ref:`schema_cache` and the :ref:`in_db_config` up to date.
|
|
||||||
|
|||||||
@@ -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.
|
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
|
.. code-block:: postgres
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Installation
|
Installation
|
||||||
############
|
############
|
||||||
|
|
||||||
The release page has `pre-compiled binaries for macOS, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_ .
|
The release page has `pre-compiled binaries for macOS, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_.
|
||||||
The Linux binary is a static executable that can be run on any Linux distribution.
|
The Linux binary is a static executable that can be run on any Linux distribution.
|
||||||
|
|
||||||
You can also use your OS package manager.
|
You can also use your OS package manager.
|
||||||
@@ -190,7 +190,7 @@ When a pre-built binary does not exist for your system you can build the project
|
|||||||
|
|
||||||
You can build PostgREST from source with `Stack <https://github.com/commercialhaskell/stack>`_. It will install any necessary Haskell dependencies on your system.
|
You can build PostgREST from source with `Stack <https://github.com/commercialhaskell/stack>`_. It will install any necessary Haskell dependencies on your system.
|
||||||
|
|
||||||
* `Install Stack <https://docs.haskellstack.org/en/stable/README/#how-to-install-stack>`_ for your platform
|
* `Install Stack <https://docs.haskellstack.org/en/stable/#how-to-install-stack>`_ for your platform
|
||||||
* Install Library Dependencies
|
* Install Library Dependencies
|
||||||
|
|
||||||
===================== =======================================
|
===================== =======================================
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.. note::
|
|
||||||
|
|
||||||
This page is a work in progress.
|
|
||||||
|
|
||||||
.. _schema_isolation:
|
.. _schema_isolation:
|
||||||
|
|
||||||
Schema Isolation
|
Schema Isolation
|
||||||
@@ -12,6 +8,18 @@ A PostgREST instance exposes all the tables, views, and functions of a single `P
|
|||||||
It is recommended that you don't expose tables on your API schema. Instead expose views and functions which insulate the internal details from the outside world.
|
It is recommended that you don't expose tables on your API schema. Instead expose views and functions which insulate the internal details from the outside world.
|
||||||
This allows you to change the internals of your schema and maintain backwards compatibility. It also keeps your code easier to refactor, and provides a natural way to do API versioning.
|
This allows you to change the internals of your schema and maintain backwards compatibility. It also keeps your code easier to refactor, and provides a natural way to do API versioning.
|
||||||
|
|
||||||
.. container:: img-translucent
|
.. container:: svg-container-md
|
||||||
|
|
||||||
.. image:: ../_static/db.png
|
.. container:: img-dark
|
||||||
|
|
||||||
|
.. See https://github.com/sphinx-doc/sphinx/issues/2240#issuecomment-187366626
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<object width="100%" data="../_static/sch-iso-dark.svg" type="image/svg+xml"></object>
|
||||||
|
|
||||||
|
.. container:: img-light
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<object width="100%" data="../_static/sch-iso.svg" type="image/svg+xml"></object>
|
||||||
|
|||||||
@@ -16,15 +16,9 @@ PostgREST Documentation
|
|||||||
.. image:: https://img.shields.io/docker/pulls/postgrest/postgrest.svg
|
.. image:: https://img.shields.io/docker/pulls/postgrest/postgrest.svg
|
||||||
:target: https://hub.docker.com/r/postgrest/postgrest/
|
:target: https://hub.docker.com/r/postgrest/postgrest/
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg
|
|
||||||
:target: https://gitter.im/begriffs/postgrest
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854
|
.. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854
|
||||||
:target: https://www.patreon.com/postgrest
|
:target: https://www.patreon.com/postgrest
|
||||||
|
|
||||||
.. image:: https://img.shields.io/badge/Donate-PayPal-green.svg
|
|
||||||
:target: https://www.paypal.com/paypalme/postgrest
|
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations.
|
PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations.
|
||||||
@@ -45,17 +39,17 @@ Sponsors
|
|||||||
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
|
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
|
||||||
|
|
||||||
.. image:: ../static/gnuhost.png
|
.. 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
|
.. container:: img-dark
|
||||||
|
|
||||||
.. image:: ../static/neon-dark.jpg
|
.. 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
|
.. container:: img-light
|
||||||
|
|
||||||
.. image:: ../static/neon.jpg
|
.. image:: ../static/neon.jpg
|
||||||
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
|
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
@@ -71,12 +65,12 @@ Sponsors
|
|||||||
|
|
||||||
.. container:: img-dark
|
.. 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
|
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||||
|
|
||||||
.. container:: img-light
|
.. 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
|
: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
|
.. image:: ../static/tembo.png
|
||||||
@@ -113,7 +107,7 @@ PostgREST has a focused scope. It works well with other tools like Nginx. This f
|
|||||||
Getting Support
|
Getting Support
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
The project has a friendly and growing community. For discussions, use the Github `discussions page <https://github.com/PostgREST/postgrest/discussions>`_ or join our `chat room <https://gitter.im/begriffs/postgrest>`_. You can also report or search for bugs/features on the Github `issues <https://github.com/PostgREST/postgrest/issues>`_ page.
|
The project has a friendly and growing community. For discussions, use the Github `discussions page <https://github.com/PostgREST/postgrest/discussions>`_. You can also report or search for bugs/features on the Github `issues <https://github.com/PostgREST/postgrest/issues>`_ page.
|
||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
-------------
|
-------------
|
||||||
@@ -215,22 +209,14 @@ In Production
|
|||||||
Here are some companies that use PostgREST in production.
|
Here are some companies that use PostgREST in production.
|
||||||
|
|
||||||
* `Catarse <https://www.catarse.me>`_
|
* `Catarse <https://www.catarse.me>`_
|
||||||
* `Datrium <https://www.datrium.com>`_
|
|
||||||
* `Drip Depot <https://www.dripdepot.com>`_
|
* `Drip Depot <https://www.dripdepot.com>`_
|
||||||
* `Image-charts <https://www.image-charts.com>`_
|
* `Image-charts <https://www.image-charts.com>`_
|
||||||
* `Moat <https://www.oracle.com/advertising/measurement/>`_
|
|
||||||
* `Netwo <https://www.netwo.io>`_
|
* `Netwo <https://www.netwo.io>`_
|
||||||
* `Nimbus <https://www.nimbusfacility.com/sg/home>`_
|
* `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>`_.
|
- 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>`_
|
* `OpenBooking <https://openbooking.ch>`_
|
||||||
* `Redsmin <https://www.redsmin.com>`_
|
|
||||||
* `Sompani <https://www.sompani.com>`_
|
|
||||||
* `Supabase <https://supabase.com>`_
|
* `Supabase <https://supabase.com>`_
|
||||||
|
|
||||||
.. Failing links
|
|
||||||
* `eGull <http://www.egull.co>`_
|
|
||||||
* `MotionDynamic - Fast highly dynamic video generation at scale <https://motiondynamic.tech>`_
|
|
||||||
|
|
||||||
Testimonials
|
Testimonials
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -12,10 +12,10 @@ booleans
|
|||||||
BOM
|
BOM
|
||||||
Bytea
|
Bytea
|
||||||
Cardano
|
Cardano
|
||||||
casted
|
|
||||||
cd
|
cd
|
||||||
centric
|
centric
|
||||||
CLI
|
CLI
|
||||||
|
CMS
|
||||||
coercible
|
coercible
|
||||||
conf
|
conf
|
||||||
Cloudflare
|
Cloudflare
|
||||||
@@ -43,7 +43,6 @@ Github
|
|||||||
Google
|
Google
|
||||||
grantor
|
grantor
|
||||||
GraphQL
|
GraphQL
|
||||||
Greenplum
|
|
||||||
gte
|
gte
|
||||||
GUC
|
GUC
|
||||||
Haskell
|
Haskell
|
||||||
@@ -146,8 +145,6 @@ SHA
|
|||||||
signup
|
signup
|
||||||
SIGUSR
|
SIGUSR
|
||||||
sl
|
sl
|
||||||
spreaded
|
|
||||||
Spreaded
|
|
||||||
SQL
|
SQL
|
||||||
sql
|
sql
|
||||||
SQLSTATE
|
SQLSTATE
|
||||||
@@ -171,6 +168,7 @@ unikernel
|
|||||||
unix
|
unix
|
||||||
updatable
|
updatable
|
||||||
unfulfillable
|
unfulfillable
|
||||||
|
unselected
|
||||||
Untyped
|
Untyped
|
||||||
UPSERT
|
UPSERT
|
||||||
Upsert
|
Upsert
|
||||||
|
|||||||
@@ -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.
|
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::
|
.. important::
|
||||||
|
|
||||||
@@ -55,10 +55,12 @@ Metrics
|
|||||||
|
|
||||||
Provides :ref:`metrics`.
|
Provides :ref:`metrics`.
|
||||||
|
|
||||||
|
.. _runtime_config:
|
||||||
|
|
||||||
Runtime Configuration
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -72,6 +74,8 @@ Provides a ``config`` endpoint that returns the runtime :ref:`configuration`.
|
|||||||
db-channel-enabled = false
|
db-channel-enabled = false
|
||||||
...
|
...
|
||||||
|
|
||||||
|
.. _runtime_schema_cache:
|
||||||
|
|
||||||
Runtime Schema Cache
|
Runtime Schema Cache
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,20 @@
|
|||||||
Aggregate Functions
|
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::
|
.. 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, append it to a column in the ``select`` parameter, like so:
|
||||||
|
|
||||||
To use an aggregate function, you append the function to a value in the ``select`` parameter, like so:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/orders?select=amount.sum()"
|
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
|
.. 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
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
|
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
|
.. code-block:: json
|
||||||
|
|
||||||
@@ -51,67 +65,55 @@ This will return a row for each unique value in the ``order_date`` column, with
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
.. note::
|
The ``count()`` Aggregate
|
||||||
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()``
|
|
||||||
===========================
|
|
||||||
|
|
||||||
.. note::
|
.. 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 a special case because it can be used with or without an aggregated column. For example:
|
||||||
``count()`` is treated specially, as it can be used without an associated column. Take for example the following query:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/orders?select=count(),order_date"
|
curl "http://localhost:3000/orders?select=count(),observation_count:observation.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``:
|
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"count": 4,
|
"count": 4,
|
||||||
|
"observation_count": 2,
|
||||||
"order_date": "2023-01-01"
|
"order_date": "2023-01-01"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"count": 2,
|
"count": 2,
|
||||||
|
"observation_count": 1,
|
||||||
"order_date": "2023-01-02"
|
"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.
|
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``).
|
||||||
Renaming and Casting
|
This is due to how PostgreSQL itself implements the ``count()`` function.
|
||||||
====================
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Casting Aggregates
|
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()``.
|
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.
|
||||||
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:
|
So we need to cast it to a compatible type (e.g. ``numeric``) right before the aggregate function:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
|
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
|
.. 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
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/orders?select=amount.avg()::int"
|
curl "http://localhost:3000/orders?select=amount.avg()::int"
|
||||||
|
|
||||||
You will then receive the rounded average as the result:
|
|
||||||
|
|
||||||
.. code-block:: json
|
.. 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.
|
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.
|
||||||
Using Embedded Resources as Grouping Columns
|
To get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, we would do the following:
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
|
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
|
.. 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 "to-one" relationship, but this can be done on "to-many" relationships as well (although there are few obvious use cases).
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
.. 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.
|
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:
|
||||||
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:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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:
|
This will take the ``max`` and ``min`` subscription date of every customer and group it by the ``order_date`` column:
|
||||||
|
|
||||||
.. 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:
|
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Functions as RPC
|
Functions as RPC
|
||||||
================
|
================
|
||||||
|
|
||||||
*"A single resource can be the equivalent of a database function, with the power to abstract state changes over any number of storage items"* -- `Roy T. Fielding <http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743>`_
|
*"A single resource can be the equivalent of a database function, with the power to abstract state changes over any number of storage items"* -- `Roy T. Fielding <https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743>`_
|
||||||
|
|
||||||
Functions can perform any operation allowed by PostgreSQL (read data, modify data, :ref:`raise errors <raise_error>`, and even DDL operations). Every function in the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` is executable under the :code:`/rpc` prefix.
|
Functions can perform any operation allowed by PostgreSQL (read data, modify data, :ref:`raise errors <raise_error>`, and even DDL operations). Every function in the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` is executable under the :code:`/rpc` prefix.
|
||||||
|
|
||||||
@@ -71,6 +71,40 @@ The function parameter names match the JSON object keys in the POST case, for th
|
|||||||
|
|
||||||
.. _function_single_json:
|
.. _function_single_json:
|
||||||
|
|
||||||
|
Functions with an array of JSON objects
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
If you want to pass multiple JSON objects to a Postgres function (an array of objects), you can create a function with a parameter of type ``json`` or ``jsonb``.
|
||||||
|
|
||||||
|
Within the curl request, this JSON must be embedded in an object where they key matches the same name as the function's ``json`` or ``jsonb`` parameter.
|
||||||
|
This will allow you to loop over the array of JSON objects within the Postgres function.
|
||||||
|
|
||||||
|
This practice may allow you to reduce the number of ``curl`` requests required to accomplish a task.
|
||||||
|
|
||||||
|
For instance, assume we have created this function in the database.
|
||||||
|
|
||||||
|
.. code-block:: postgres
|
||||||
|
|
||||||
|
CREATE FUNCTION update_data(p_json jsonb)
|
||||||
|
RETURNS void AS $$
|
||||||
|
DECLARE
|
||||||
|
json_item json;
|
||||||
|
BEGIN
|
||||||
|
FOR json_item IN SELECT jsonb_array_elements(p_json) LOOP
|
||||||
|
UPDATE data_table SET data_text_column = (json_item->>'data_text')::text
|
||||||
|
WHERE data_int_column = (json_item->>'data_int')::integer;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
||||||
|
A ``curl`` request using the POST method would look like the following:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
curl "http://localhost:3000/rpc/update_data" \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{ "p_json": [ { "data_text": "one", "data_int": "1" }, { "data_text": "two", "data_int": "2" } ] }'
|
||||||
|
|
||||||
Functions with a single unnamed JSON parameter
|
Functions with a single unnamed JSON parameter
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
@@ -264,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.
|
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:
|
||||||
|
|
||||||
Scalar functions
|
Scalar functions
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ The following preferences are supported.
|
|||||||
- ``Prefer: return``. See :ref:`prefer_return`.
|
- ``Prefer: return``. See :ref:`prefer_return`.
|
||||||
- ``Prefer: count``. See :ref:`prefer_count`.
|
- ``Prefer: count``. See :ref:`prefer_count`.
|
||||||
- ``Prefer: resolution``. See :ref:`prefer_resolution`.
|
- ``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: max-affected``, See :ref:`prefer_max_affected`.
|
||||||
- ``Prefer: tx``. See :ref:`prefer_tx`.
|
- ``Prefer: tx``. See :ref:`prefer_tx`.
|
||||||
- ``Prefer: params``. See :ref:`prefer_params`.
|
- ``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"}
|
{"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:
|
.. _prefer_max_affected:
|
||||||
|
|
||||||
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" \
|
curl "http://localhost:3000/people" \
|
||||||
-H "Accept: application/json"
|
-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:
|
||||||
|
|
||||||
Builtin Media Type Handlers
|
Builtin Media Type Handlers
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ cs :code:`@>` contains e.g. :code:`?tags=cs.{example,
|
|||||||
cd :code:`<@` contained in e.g. :code:`?values=cd.{1,2,3}`
|
cd :code:`<@` contained in e.g. :code:`?values=cd.{1,2,3}`
|
||||||
ov :code:`&&` overlap (have points in common), e.g. :code:`?period=ov.[2017-01-01,2017-06-30]` –
|
ov :code:`&&` overlap (have points in common), e.g. :code:`?period=ov.[2017-01-01,2017-06-30]` –
|
||||||
also supports array types, use curly braces instead of square brackets e.g.
|
also supports array types, use curly braces instead of square brackets e.g.
|
||||||
:code: `?arr=ov.{1,3}`
|
:code:`?arr=ov.{1,3}`
|
||||||
sl :code:`<<` strictly left of, e.g. :code:`?range=sl.(1,10)`
|
sl :code:`<<` strictly left of, e.g. :code:`?range=sl.(1,10)`
|
||||||
sr :code:`>>` strictly right of
|
sr :code:`>>` strictly right of
|
||||||
nxr :code:`&<` does not extend to the right of, e.g. :code:`?range=nxr.(1,10)`
|
nxr :code:`&<` does not extend to the right of, e.g. :code:`?range=nxr.(1,10)`
|
||||||
@@ -525,45 +525,6 @@ To bulk insert JSON post an array of objects having all-matching keys
|
|||||||
]
|
]
|
||||||
EOF
|
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:
|
.. _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" \
|
-X PATCH -H "Content-Type: application/json" \
|
||||||
-d '{ "category": "child" }'
|
-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::
|
.. warning::
|
||||||
|
|
||||||
@@ -623,18 +590,34 @@ You can make an upsert with :code:`POST` and the :code:`Prefer: resolution=merge
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
curl "http://localhost:3000/employees" \
|
curl "http://localhost:3000/products" \
|
||||||
-X POST -H "Content-Type: application/json" \
|
-X POST -H "Content-Type: application/json" \
|
||||||
-H "Prefer: resolution=merge-duplicates" \
|
-H "Prefer: resolution=merge-duplicates" \
|
||||||
-d @- << EOF
|
-d @- << EOF
|
||||||
[
|
[
|
||||||
{ "id": 1, "name": "Old employee 1", "salary": 30000 },
|
{ "sku": "CL2031", "name": "Existing T-shirt", "price": 35 },
|
||||||
{ "id": 2, "name": "Old employee 2", "salary": 42000 },
|
{ "sku": "CL2040", "name": "Existing Hoodie", "price": 60 },
|
||||||
{ "id": 3, "name": "New employee 3", "salary": 50000 }
|
{ "sku": "AC1022", "name": "New Cap", "price": 30 }
|
||||||
]
|
]
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
By default, upsert operates based on the primary key columns, you must specify all of them. You can also choose to ignore the duplicates with :code:`Prefer: resolution=ignore-duplicates`. This works best when the primary key is natural, but it's also possible to use it if the primary key is surrogate (example: "id serial primary key"). For more details read `this issue <https://github.com/PostgREST/postgrest/issues/1118>`_.
|
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` with :ref:`prefer_missing`:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
curl "http://localhost:3000/employees?colums=id,name,salary" \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-H "Prefer: resolution=merge-duplicates, missing=default" \
|
||||||
|
-d @- << EOF
|
||||||
|
[
|
||||||
|
{ "id": 1, "name": "Existing employee 1", "salary": 30000 },
|
||||||
|
{ "id": 2, "name": "Existing employee 2", "salary": 42000 },
|
||||||
|
{ "name": "New employee 3", "salary": 50000 }
|
||||||
|
]
|
||||||
|
EOF
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
After creating a table or changing its primary key, you must refresh PostgREST schema cache for upsert to work properly. To learn how to refresh the cache see :ref:`schema_reloading`.
|
After creating a table or changing its primary key, you must refresh PostgREST schema cache for upsert to work properly. To learn how to refresh the cache see :ref:`schema_reloading`.
|
||||||
@@ -739,6 +722,7 @@ Using ``offset`` to target a different subset of rows is also possible.
|
|||||||
'#estimated-count': 'pagination_count.html#estimated-count',
|
'#estimated-count': 'pagination_count.html#estimated-count',
|
||||||
'#prefer-return-headers-only': 'preferences.html#headers-only',
|
'#prefer-return-headers-only': 'preferences.html#headers-only',
|
||||||
'#prefer-return-representation': 'preferences.html#full',
|
'#prefer-return-representation': 'preferences.html#full',
|
||||||
|
'#bulk-insert-default': 'preferences.html#prefer-missing',
|
||||||
};
|
};
|
||||||
|
|
||||||
let willRedirectTo = redirects[hash];
|
let willRedirectTo = redirects[hash];
|
||||||
|
|||||||
@@ -159,7 +159,15 @@ You can specify the literal value as we saw earlier, or reference a filename to
|
|||||||
JWT Claims Validation
|
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
|
JWT Security
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|||||||
@@ -135,14 +135,12 @@ To reload the configuration via signal, send a SIGUSR2 signal to the server proc
|
|||||||
Configuration Reload with NOTIFY
|
Configuration Reload with NOTIFY
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
To reload the configuration from within the database, you can use a NOTIFY command.
|
To reload the configuration from within the database, you can use the ``NOTIFY`` command. See :ref:`listener`.
|
||||||
|
|
||||||
.. code:: postgresql
|
.. code:: postgresql
|
||||||
|
|
||||||
NOTIFY pgrst, 'reload config'
|
NOTIFY pgrst, 'reload config'
|
||||||
|
|
||||||
The ``"pgrst"`` notification channel is enabled by default. You can name the channel with :ref:`db-channel` and enable or disable it with :ref:`db-channel-enabled`.
|
|
||||||
|
|
||||||
.. _config_full_list:
|
.. _config_full_list:
|
||||||
|
|
||||||
List of parameters
|
List of parameters
|
||||||
@@ -163,6 +161,30 @@ admin-server-port
|
|||||||
|
|
||||||
Specifies the port for the :ref:`admin_server`.
|
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.*:
|
||||||
|
|
||||||
app.settings.*
|
app.settings.*
|
||||||
@@ -696,6 +718,7 @@ log-level
|
|||||||
log-level = "info"
|
log-level = "info"
|
||||||
|
|
||||||
# All the above plus events for development purposes are logged
|
# All the above plus events for development purposes are logged
|
||||||
|
# Logs connection pool events and the schema cache parsing time
|
||||||
log-level = "debug"
|
log-level = "debug"
|
||||||
|
|
||||||
Because currently there's no buffering for logging, the levels with minimal logging(``crit/error``) will increase throughput.
|
Because currently there's no buffering for logging, the levels with minimal logging(``crit/error``) will increase throughput.
|
||||||
@@ -856,7 +879,7 @@ server-timing-enabled
|
|||||||
**In-Database** pgrst.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`.
|
See :ref:`server-timing_header`.
|
||||||
|
|
||||||
.. _server-unix-socket:
|
.. _server-unix-socket:
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.. _listener:
|
||||||
|
|
||||||
|
Listener
|
||||||
|
########
|
||||||
|
|
||||||
|
PostgREST uses `LISTEN <https://www.postgresql.org/docs/current/sql-listen.html>`_ to reload its :ref:`Schema Cache <schema_reloading_notify>` and :ref:`Configuration <config_reloading_notify>` via `NOTIFY <https://www.postgresql.org/docs/current/sql-notify.html>`_.
|
||||||
|
This is useful in environments where you can’t send SIGUSR1 or SIGUSR2 Unix Signals.
|
||||||
|
Like on cloud managed containers or on Windows systems.
|
||||||
|
|
||||||
|
.. code:: postgresql
|
||||||
|
|
||||||
|
NOTIFY pgrst, 'reload schema'; -- reload schema cache
|
||||||
|
NOTIFY pgrst, 'reload config'; -- reload config
|
||||||
|
NOTIFY pgrst; -- reload both
|
||||||
|
|
||||||
|
By default, the LISTEN channel is enabled (:ref:`db-channel-enabled`) and named ``pgrst`` (:ref:`db-channel`).
|
||||||
|
|
||||||
|
Listener on Read Replicas
|
||||||
|
=========================
|
||||||
|
|
||||||
|
|
||||||
|
The ``LISTEN`` and ``NOTIFY`` commands do not work on PostgreSQL read replicas.
|
||||||
|
Thus, if you connect PostgREST to a read replica the Listener will fail to start.
|
||||||
|
|
||||||
|
.. code:: psql
|
||||||
|
|
||||||
|
-- check if the instance is a replica
|
||||||
|
postgres=# select pg_is_in_recovery();
|
||||||
|
pg_is_in_recovery
|
||||||
|
-------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
postgres=# LISTEN pgrst;
|
||||||
|
ERROR: cannot execute LISTEN during recovery
|
||||||
|
|
||||||
|
To work around this, you can connect the Listener to the primary while still using the :ref:`connection_pool` on the replica.
|
||||||
|
|
||||||
|
This can be done by using the standard `libpq multiple hosts <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS>`_ and `target_session_attrs <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS>`_ in your :ref:`connection string <db-uri>`.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
db-uri = "postgres://read_replica.host,primary.host/mydb?target_session_attrs=read-only"
|
||||||
|
|
||||||
|
This will cause the :ref:`connection_pool` to connect to the read replica host and ``LISTEN`` on the fallback primary host.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Under the hood, PostgREST forces `target_session_attrs=read-write <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-TARGET-SESSION-ATTRS>`_ for the ``LISTEN`` session.
|
||||||
|
|
||||||
|
.. _listener_automatic_recovery:
|
||||||
|
|
||||||
|
Automatic Recovery
|
||||||
|
==================
|
||||||
|
|
||||||
|
The listener will retry reconnecting to the database if connection loss happens.
|
||||||
|
|
||||||
|
- It will retry forever with exponential backoff, with a maximum backoff time of 32 seconds between retries. Each of these attempts are :ref:`logged <pgrst_logging>`.
|
||||||
|
- Automatic recovery can be disabled by setting :ref:`db-pool-automatic-recovery` to ``false``.
|
||||||
|
- To ensure a valid state, the listener reloads the :ref:`schema_cache` and :ref:`configuration` when recovering.
|
||||||
@@ -22,7 +22,11 @@ PostgREST logs basic request information to ``stdout``, including the authentica
|
|||||||
127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 - "" "curl/7.64.0"
|
127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 - "" "curl/7.64.0"
|
||||||
127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 - "" "curl/7.64.0"
|
127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 - "" "curl/7.64.0"
|
||||||
|
|
||||||
For diagnostic information about the server itself, PostgREST logs to ``stderr``. It includes the server version and also the version of the connected PostgreSQL.
|
For diagnostic information about the server itself, PostgREST logs to ``stderr``:
|
||||||
|
|
||||||
|
- The full version of the connected PostgreSQL database.
|
||||||
|
- :ref:`schema_cache` statistics.
|
||||||
|
- The messages received by the :ref:`listener`.
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
@@ -30,6 +34,12 @@ For diagnostic information about the server itself, PostgREST logs to ``stderr``
|
|||||||
06/May/2024:08:16:11 -0500: Attempting to connect to the database...
|
06/May/2024:08:16:11 -0500: Attempting to connect to the database...
|
||||||
06/May/2024:08:16:11 -0500: Successfully connected to PostgreSQL 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit
|
06/May/2024:08:16:11 -0500: Successfully connected to PostgreSQL 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit
|
||||||
06/May/2024:08:16:11 -0500: Listening on port 3000
|
06/May/2024:08:16:11 -0500: Listening on port 3000
|
||||||
|
06/May/2024:08:16:11 -0500: Listening for notifications on the "pgrst" channel
|
||||||
|
06/May/2024:08:16:11 -0500: Config reloaded
|
||||||
|
06/May/2024:08:16:11 -0500: Schema cache queried in 3.8 milliseconds
|
||||||
|
06/May/2024:08:16:11 -0500: Schema cache loaded 15 Relations, 8 Relationships, 8 Functions, 0 Domain Representations, 4 Media Type Handlers
|
||||||
|
06/May/2024:14:11:27 -0500: Received a config reload message on the "pgrst" channel
|
||||||
|
06/May/2024:14:11:27 -0500: Config reloaded
|
||||||
|
|
||||||
Database Logs
|
Database Logs
|
||||||
-------------
|
-------------
|
||||||
@@ -80,7 +90,7 @@ Restart the database and watch the log file in real-time to understand how HTTP
|
|||||||
Metrics
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -199,7 +209,7 @@ You can enable tracing HTTP requests by setting :ref:`server-trace-header`. Spec
|
|||||||
Server-Timing Header
|
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.
|
This header communicates metrics of the different phases in the request-response cycle.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|||||||
@@ -47,16 +47,12 @@ For docker you can do:
|
|||||||
Schema Cache Reloading with NOTIFY
|
Schema Cache Reloading with NOTIFY
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
PostgREST also allows you to reload its schema cache through PostgreSQL `NOTIFY <https://www.postgresql.org/docs/current/sql-notify.html>`_.
|
To reload the schema cache from within the database, you can use the ``NOTIFY`` command. See :ref:`listener`.
|
||||||
|
|
||||||
.. code-block:: postgres
|
.. code-block:: postgres
|
||||||
|
|
||||||
NOTIFY pgrst, 'reload schema'
|
NOTIFY pgrst, 'reload schema'
|
||||||
|
|
||||||
This is useful in environments where you can’t send the SIGUSR1 Unix Signal. Like on cloud managed containers or on Windows systems.
|
|
||||||
|
|
||||||
The ``pgrst`` notification channel is enabled by default. For configuring the channel, see :ref:`db-channel` and :ref:`db-channel-enabled`.
|
|
||||||
|
|
||||||
.. _auto_schema_reloading:
|
.. _auto_schema_reloading:
|
||||||
|
|
||||||
Automatic Schema Cache Reloading
|
Automatic Schema Cache Reloading
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ sphinx-rtd-dark-mode>=1.3.0
|
|||||||
sphinx-tabs>=3.2.0
|
sphinx-tabs>=3.2.0
|
||||||
sphinx>=5.0.2
|
sphinx>=5.0.2
|
||||||
sphinxext-opengraph==0.9.1
|
sphinxext-opengraph==0.9.1
|
||||||
urllib3==2.2.1
|
urllib3==2.2.3
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
nix-env -i haskellPackages.postgrest
|
nix-env -i postgrest
|
||||||
|
|
||||||
.. group-tab:: Windows
|
.. group-tab:: Windows
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,7 @@ To make an API we'll simply be building a database. All the endpoints and permis
|
|||||||
|
|
||||||
By the end of this tutorial you'll have a working database, PostgREST server, and a simple single-user todo list API.
|
By the end of this tutorial you'll have a working database, PostgREST server, and a simple single-user todo list API.
|
||||||
|
|
||||||
Step 1. Relax, we'll help
|
Step 1. Install PostgreSQL
|
||||||
-------------------------
|
|
||||||
|
|
||||||
As you begin the tutorial, pop open the project `chat room <https://gitter.im/begriffs/postgrest>`_ in another tab. There are a nice group of people active in the project and we'll help you out if you get stuck.
|
|
||||||
|
|
||||||
Step 2. Install PostgreSQL
|
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
If you're already familiar with using PostgreSQL and have it installed on your system you can use the existing installation (see :ref:`pg-dependency` for minimum requirements). For this tutorial we'll describe how to use the database in Docker because database configuration is otherwise too complicated for a simple tutorial.
|
If you're already familiar with using PostgreSQL and have it installed on your system you can use the existing installation (see :ref:`pg-dependency` for minimum requirements). For this tutorial we'll describe how to use the database in Docker because database configuration is otherwise too complicated for a simple tutorial.
|
||||||
@@ -48,7 +43,7 @@ This will run the Docker instance as a daemon and expose port 5432 to the host s
|
|||||||
In this case, you will need to change the **first** of the two 5432 to something else, for example to :code:`5433:5432`. Remember to also adjust the port in your config file in Step 5!
|
In this case, you will need to change the **first** of the two 5432 to something else, for example to :code:`5433:5432`. Remember to also adjust the port in your config file in Step 5!
|
||||||
|
|
||||||
|
|
||||||
Step 3. Install PostgREST
|
Step 2. Install PostgREST
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
Using a Package Manager
|
Using a Package Manager
|
||||||
@@ -122,7 +117,7 @@ If everything is working correctly it will print out its version and the availab
|
|||||||
</details>
|
</details>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Step 4. Create Database for API
|
Step 3. Create Database for API
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
Connect to the SQL console (psql) inside the container. To do so, run this from your command line:
|
Connect to the SQL console (psql) inside the container. To do so, run this from your command line:
|
||||||
@@ -185,7 +180,7 @@ Now quit out of psql; it's time to start the API!
|
|||||||
|
|
||||||
\q
|
\q
|
||||||
|
|
||||||
Step 5. Run PostgREST
|
Step 4. Run PostgREST
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
PostgREST can use a configuration file to tell it how to connect to the database. Create a file :code:`tutorial.conf` with this inside:
|
PostgREST can use a configuration file to tell it how to connect to the database. Create a file :code:`tutorial.conf` with this inside:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
owner = "NixOS";
|
owner = "NixOS";
|
||||||
repo = "nixpkgs";
|
repo = "nixpkgs";
|
||||||
date = "2024-04-20";
|
date = "2024-05-29";
|
||||||
rev = "92d295f588631b0db2da509f381b4fb1e74173c5";
|
rev = "a15e8d1b3d9e6496c4a3214e2104f6d28dfa7df7";
|
||||||
tarballHash = "162w28y4i5c8g5qhjvs827qxphf2a8n4c8fwhcywzl1j1a35h2im";
|
tarballHash = "sha256:0r4a4165f4n1zlpnyjrdrwrg86n1b6g3axsdh3j5iizpmjrlxmd7";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,14 @@ let
|
|||||||
}
|
}
|
||||||
{ });
|
{ });
|
||||||
|
|
||||||
hasql-pool = lib.dontCheck prev.hasql-pool_1_0_1;
|
hasql-pool = lib.dontCheck (prev.callHackageDirect
|
||||||
|
{
|
||||||
|
pkg = "hasql-pool";
|
||||||
|
ver = "1.0.1";
|
||||||
|
sha256 = "sha256-Hf1f7lX0LWkjrb25SDBovCYPRdmUP1H6pAxzi7kT4Gg=";
|
||||||
|
}
|
||||||
|
{ }
|
||||||
|
);
|
||||||
|
|
||||||
postgresql-libpq = lib.dontCheck
|
postgresql-libpq = lib.dontCheck
|
||||||
(prev.postgresql-libpq.override {
|
(prev.postgresql-libpq.override {
|
||||||
@@ -67,8 +74,8 @@ let
|
|||||||
hasql-notifications = lib.dontCheck (prev.callHackageDirect
|
hasql-notifications = lib.dontCheck (prev.callHackageDirect
|
||||||
{
|
{
|
||||||
pkg = "hasql-notifications";
|
pkg = "hasql-notifications";
|
||||||
ver = "0.2.2.0";
|
ver = "0.2.2.2";
|
||||||
sha256 = "sha256-73OQ9/su2qvO7HavF3xuuNWLXSXyB9reBUQDaHys06I=";
|
sha256 = "sha256-myKwlug7OgTa/qP6mHfCD+5Q8IhM17JvpJBfSo+M01k=";
|
||||||
}
|
}
|
||||||
{ }
|
{ }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
, aspellDicts
|
, aspellDicts
|
||||||
, buildToolbox
|
, buildToolbox
|
||||||
, checkedShellScript
|
, checkedShellScript
|
||||||
, imagemagick
|
|
||||||
, python3
|
, python3
|
||||||
, python3Packages
|
, python3Packages
|
||||||
, texlive
|
|
||||||
, writers
|
, writers
|
||||||
, plantuml
|
, plantuml
|
||||||
}:
|
}:
|
||||||
@@ -61,36 +59,15 @@ let
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
render =
|
render =
|
||||||
let
|
|
||||||
pdflatex = texlive.combine {
|
|
||||||
inherit (texlive)
|
|
||||||
amsmath
|
|
||||||
booktabs
|
|
||||||
cancel
|
|
||||||
gensymb
|
|
||||||
mathdots
|
|
||||||
multirow
|
|
||||||
pgf
|
|
||||||
pgf-blur
|
|
||||||
scheme-basic
|
|
||||||
siunitx
|
|
||||||
standalone
|
|
||||||
yhmath
|
|
||||||
;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
checkedShellScript
|
checkedShellScript
|
||||||
{
|
{
|
||||||
name = "postgrest-docs-render";
|
name = "postgrest-docs-render";
|
||||||
docs = "Render the diagrams.";
|
docs = "Render the diagrams.";
|
||||||
workingDir = "/docs/_diagrams";
|
workingDir = "/docs/_diagrams";
|
||||||
withTmpDir = true;
|
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
${pdflatex}/bin/pdflatex -halt-on-error -output-directory="$tmpdir" db.tex
|
${plantuml}/bin/plantuml -tsvg uml/*.uml -o ../../_static
|
||||||
${imagemagick}/bin/convert -density 300 "$tmpdir/db.pdf" ../_static/db.png
|
${plantuml}/bin/plantuml -tsvg -darkmode uml/dark/*.uml -o ../../../_static
|
||||||
|
|
||||||
${plantuml}/bin/plantuml arch.uml -o ../_static
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
server =
|
server =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: postgrest
|
name: postgrest
|
||||||
version: 12.2.0
|
version: 12.2.12
|
||||||
synopsis: REST API for any Postgres database
|
synopsis: REST API for any Postgres database
|
||||||
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
description: Reads the schema of a PostgreSQL database and creates RESTful routes
|
||||||
for tables, views, and functions, supporting all HTTP methods that security
|
for tables, views, and functions, supporting all HTTP methods that security
|
||||||
@@ -110,7 +110,7 @@ library
|
|||||||
, gitrev >= 1.2 && < 1.4
|
, gitrev >= 1.2 && < 1.4
|
||||||
, hasql >= 1.6.1.1 && < 1.7
|
, hasql >= 1.6.1.1 && < 1.7
|
||||||
, hasql-dynamic-statements >= 0.3.1 && < 0.4
|
, 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-pool >= 1.0.1 && < 1.1
|
||||||
, hasql-transaction >= 1.0.1 && < 1.1
|
, hasql-transaction >= 1.0.1 && < 1.1
|
||||||
, heredoc >= 0.2 && < 0.3
|
, heredoc >= 0.2 && < 0.3
|
||||||
|
|||||||
@@ -56,8 +56,11 @@ admin appState req respond = do
|
|||||||
in
|
in
|
||||||
respond $ Wai.responseLBS status [] mempty
|
respond $ Wai.responseLBS status [] mempty
|
||||||
["config"] -> do
|
["config"] -> do
|
||||||
config <- AppState.getConfig appState
|
config@Config.AppConfig{configAdminServerConfigEnabled} <- AppState.getConfig appState
|
||||||
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
|
if configAdminServerConfigEnabled then
|
||||||
|
respond $ Wai.responseLBS HTTP.status200 [] (LBS.fromStrict $ encodeUtf8 $ Config.toText config)
|
||||||
|
else
|
||||||
|
respond $ Wai.responseLBS HTTP.status404 [] mempty
|
||||||
["schema_cache"] -> do
|
["schema_cache"] -> do
|
||||||
sCache <- AppState.getSchemaCache appState
|
sCache <- AppState.getSchemaCache appState
|
||||||
respond $ Wai.responseLBS HTTP.status200 [] (maybe mempty JSON.encode sCache)
|
respond $ Wai.responseLBS HTTP.status200 [] (maybe mempty JSON.encode sCache)
|
||||||
|
|||||||
@@ -68,14 +68,14 @@ run appState = do
|
|||||||
|
|
||||||
observer $ AppStartObs prettyVersion
|
observer $ AppStartObs prettyVersion
|
||||||
|
|
||||||
AppState.connectionWorker appState
|
AppState.schemaCacheLoader appState -- Loads the initial SchemaCache
|
||||||
Unix.installSignalHandlers (AppState.getMainThreadId appState) (AppState.connectionWorker appState) (AppState.reReadConfig False appState)
|
Unix.installSignalHandlers (AppState.getMainThreadId appState) (AppState.schemaCacheLoader appState) (AppState.readInDbConfig False appState)
|
||||||
|
|
||||||
Listener.runListener appState
|
Listener.runListener appState
|
||||||
|
|
||||||
Admin.runAdmin appState (serverSettings conf)
|
Admin.runAdmin appState (serverSettings conf)
|
||||||
|
|
||||||
let app = postgrest configLogLevel appState (AppState.connectionWorker appState)
|
let app = postgrest configLogLevel appState (AppState.schemaCacheLoader appState)
|
||||||
|
|
||||||
case configServerUnixSocket of
|
case configServerUnixSocket of
|
||||||
Just path -> do
|
Just path -> do
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
module PostgREST.AppState
|
module PostgREST.AppState
|
||||||
( AppState
|
( AppState
|
||||||
, AuthResult(..)
|
, AuthResult(..)
|
||||||
|
, JwtCacheState(..)
|
||||||
, destroy
|
, destroy
|
||||||
, getConfig
|
, getConfig
|
||||||
, getSchemaCache
|
, getSchemaCache
|
||||||
@@ -13,7 +14,7 @@ module PostgREST.AppState
|
|||||||
, getNextDelay
|
, getNextDelay
|
||||||
, getNextListenerDelay
|
, getNextListenerDelay
|
||||||
, getTime
|
, getTime
|
||||||
, getJwtCache
|
, getJwtCacheState
|
||||||
, getSocketREST
|
, getSocketREST
|
||||||
, getSocketAdmin
|
, getSocketAdmin
|
||||||
, init
|
, init
|
||||||
@@ -24,12 +25,11 @@ module PostgREST.AppState
|
|||||||
, putPgVersion
|
, putPgVersion
|
||||||
, putIsListenerOn
|
, putIsListenerOn
|
||||||
, usePool
|
, usePool
|
||||||
, reReadConfig
|
, readInDbConfig
|
||||||
, connectionWorker
|
, schemaCacheLoader
|
||||||
, getObserver
|
, getObserver
|
||||||
, isLoaded
|
, isLoaded
|
||||||
, isPending
|
, isPending
|
||||||
, waitForListenerCanStart
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
@@ -84,43 +84,45 @@ data AuthResult = AuthResult
|
|||||||
, authRole :: BS.ByteString
|
, 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
|
data AppState = AppState
|
||||||
-- | Database connection pool
|
-- | Database connection pool
|
||||||
{ statePool :: SQL.Pool
|
{ statePool :: SQL.Pool
|
||||||
-- | Database server version, will be updated by the connectionWorker
|
-- | Database server version
|
||||||
, statePgVersion :: IORef PgVersion
|
, statePgVersion :: IORef PgVersion
|
||||||
-- | No schema cache at the start. Will be filled in by the connectionWorker
|
-- | Schema cache
|
||||||
, stateSchemaCache :: IORef (Maybe SchemaCache)
|
, stateSchemaCache :: IORef (Maybe SchemaCache)
|
||||||
-- | The schema cache status
|
-- | The schema cache status
|
||||||
, stateSCacheStatus :: IORef SchemaCacheStatus
|
, stateSCacheStatus :: IORef SchemaCacheStatus
|
||||||
-- | The connection status
|
|
||||||
, stateConnStatus :: IORef ConnectionStatus
|
|
||||||
-- | State of the LISTEN channel
|
-- | State of the LISTEN channel
|
||||||
, stateIsListenerOn :: IORef Bool
|
, stateIsListenerOn :: IORef Bool
|
||||||
-- | starts the connection worker with a debounce
|
-- | starts the connection worker with a debounce
|
||||||
, debouncedConnectionWorker :: IO ()
|
, debouncedSCacheLoader :: IO ()
|
||||||
-- | Binary semaphore used to sync the listener with the connectionWorker.
|
|
||||||
, stateListenerCanStart :: MVar ()
|
|
||||||
-- | Config that can change at runtime
|
-- | Config that can change at runtime
|
||||||
, stateConf :: IORef AppConfig
|
, stateConf :: IORef AppConfig
|
||||||
-- | Time used for verifying JWT expiration
|
-- | Time used for verifying JWT expiration
|
||||||
, stateGetTime :: IO UTCTime
|
, stateGetTime :: IO UTCTime
|
||||||
-- | Used for killing the main thread in case a subthread fails
|
-- | Used for killing the main thread in case a subthread fails
|
||||||
, stateMainThreadId :: ThreadId
|
, stateMainThreadId :: ThreadId
|
||||||
-- | Keeps track of the next delay for db connection retry
|
-- | Keeps track of the next delay for db connection retry
|
||||||
, stateNextDelay :: IORef Int
|
, stateNextDelay :: IORef Int
|
||||||
-- | Keeps track of the next delay for the listener
|
-- | Keeps track of the next delay for the listener
|
||||||
, stateNextListenerDelay :: IORef Int
|
, stateNextListenerDelay :: IORef Int
|
||||||
-- | JWT Cache
|
-- | JWT Cache
|
||||||
, jwtCache :: C.Cache ByteString AuthResult
|
, jwtCacheState :: JwtCacheState
|
||||||
-- | Network socket for REST API
|
-- | Network socket for REST API
|
||||||
, stateSocketREST :: NS.Socket
|
, stateSocketREST :: NS.Socket
|
||||||
-- | Network socket for the admin UI
|
-- | Network socket for the admin UI
|
||||||
, stateSocketAdmin :: Maybe NS.Socket
|
, stateSocketAdmin :: Maybe NS.Socket
|
||||||
-- | Observation handler
|
-- | Observation handler
|
||||||
, stateObserver :: ObservationHandler
|
, stateObserver :: ObservationHandler
|
||||||
, stateLogger :: Logger.LoggerState
|
, stateLogger :: Logger.LoggerState
|
||||||
, stateMetrics :: Metrics.MetricsState
|
, stateMetrics :: Metrics.MetricsState
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | Schema cache status
|
-- | Schema cache status
|
||||||
@@ -129,15 +131,8 @@ data SchemaCacheStatus
|
|||||||
| SCPending
|
| SCPending
|
||||||
deriving Eq
|
deriving Eq
|
||||||
|
|
||||||
-- | Current database connection status
|
|
||||||
data ConnectionStatus
|
|
||||||
= ConnEstablished
|
|
||||||
| ConnPending
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
type AppSockets = (NS.Socket, Maybe NS.Socket)
|
type AppSockets = (NS.Socket, Maybe NS.Socket)
|
||||||
|
|
||||||
|
|
||||||
init :: AppConfig -> IO AppState
|
init :: AppConfig -> IO AppState
|
||||||
init conf@AppConfig{configLogLevel, configDbPoolSize} = do
|
init conf@AppConfig{configLogLevel, configDbPoolSize} = do
|
||||||
loggerState <- Logger.init
|
loggerState <- Logger.init
|
||||||
@@ -151,36 +146,44 @@ init conf@AppConfig{configLogLevel, configDbPoolSize} = do
|
|||||||
|
|
||||||
initWithPool :: AppSockets -> SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO AppState
|
initWithPool :: AppSockets -> SQL.Pool -> AppConfig -> Logger.LoggerState -> Metrics.MetricsState -> ObservationHandler -> IO AppState
|
||||||
initWithPool (sock, adminSock) pool conf loggerState metricsState observer = do
|
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
|
appState <- AppState pool
|
||||||
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
|
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
|
||||||
<*> newIORef Nothing
|
<*> newIORef Nothing
|
||||||
<*> newIORef SCPending
|
<*> newIORef SCPending
|
||||||
<*> newIORef ConnPending
|
|
||||||
<*> newIORef False
|
<*> newIORef False
|
||||||
<*> pure (pure ())
|
<*> pure (pure ())
|
||||||
<*> newEmptyMVar
|
|
||||||
<*> newIORef conf
|
<*> newIORef conf
|
||||||
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||||
<*> myThreadId
|
<*> myThreadId
|
||||||
<*> newIORef 0
|
<*> newIORef 0
|
||||||
<*> newIORef 1
|
<*> newIORef 1
|
||||||
<*> C.newCache Nothing
|
<*> pure (JwtCacheState cache debounce)
|
||||||
<*> pure sock
|
<*> pure sock
|
||||||
<*> pure adminSock
|
<*> pure adminSock
|
||||||
<*> pure observer
|
<*> pure observer
|
||||||
<*> pure loggerState
|
<*> pure loggerState
|
||||||
<*> pure metricsState
|
<*> pure metricsState
|
||||||
|
|
||||||
debWorker <-
|
deb <-
|
||||||
let decisecond = 100000 in
|
let decisecond = 100000 in
|
||||||
mkDebounce defaultDebounceSettings
|
mkDebounce defaultDebounceSettings
|
||||||
{ debounceAction = internalConnectionWorker appState
|
{ debounceAction = retryingSchemaCacheLoad appState
|
||||||
, debounceFreq = decisecond
|
, debounceFreq = decisecond
|
||||||
, debounceEdge = leadingEdge -- runs the worker at the start and the end
|
, debounceEdge = leadingEdge -- runs the worker at the start and the end
|
||||||
}
|
}
|
||||||
|
|
||||||
return appState { debouncedConnectionWorker = debWorker}
|
return appState { debouncedSCacheLoader = deb}
|
||||||
|
|
||||||
destroy :: AppState -> IO ()
|
destroy :: AppState -> IO ()
|
||||||
destroy = destroyPool
|
destroy = destroyPool
|
||||||
@@ -279,8 +282,9 @@ usePool AppState{stateObserver=observer, stateMainThreadId=mainThreadId, ..} ses
|
|||||||
SQL.ServerError{} ->
|
SQL.ServerError{} ->
|
||||||
when (Error.status (Error.PgError False err) >= HTTP.status500) $
|
when (Error.status (Error.PgError False err) >= HTTP.status500) $
|
||||||
observer $ QueryErrorCodeHighObs err
|
observer $ QueryErrorCodeHighObs err
|
||||||
SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _)) ->
|
err@(SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ClientError _))) ->
|
||||||
pure ()
|
-- An error on the client-side, usually indicates problems wth connection
|
||||||
|
observer $ QueryErrorCodeHighObs err
|
||||||
)
|
)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -306,41 +310,18 @@ getSchemaCache = readIORef . stateSchemaCache
|
|||||||
putSchemaCache :: AppState -> Maybe SchemaCache -> IO ()
|
putSchemaCache :: AppState -> Maybe SchemaCache -> IO ()
|
||||||
putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState)
|
putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState)
|
||||||
|
|
||||||
connectionWorker :: AppState -> IO ()
|
schemaCacheLoader :: AppState -> IO ()
|
||||||
connectionWorker = debouncedConnectionWorker
|
schemaCacheLoader = debouncedSCacheLoader
|
||||||
|
|
||||||
getNextDelay :: AppState -> IO Int
|
getNextDelay :: AppState -> IO Int
|
||||||
getNextDelay = readIORef . stateNextDelay
|
getNextDelay = readIORef . stateNextDelay
|
||||||
|
|
||||||
putNextDelay :: AppState -> Int -> IO ()
|
|
||||||
putNextDelay = atomicWriteIORef . stateNextDelay
|
|
||||||
|
|
||||||
getNextListenerDelay :: AppState -> IO Int
|
getNextListenerDelay :: AppState -> IO Int
|
||||||
getNextListenerDelay = readIORef . stateNextListenerDelay
|
getNextListenerDelay = readIORef . stateNextListenerDelay
|
||||||
|
|
||||||
putNextListenerDelay :: AppState -> Int -> IO ()
|
putNextListenerDelay :: AppState -> Int -> IO ()
|
||||||
putNextListenerDelay = atomicWriteIORef . stateNextListenerDelay
|
putNextListenerDelay = atomicWriteIORef . stateNextListenerDelay
|
||||||
|
|
||||||
--------------------------------------------------------------------------------------
|
|
||||||
-------------------------------------------IMPORTANT----------------------------------
|
|
||||||
--------------------------------------------------------------------------------------
|
|
||||||
-- Both of these function ensure there's no parallel connection attempts between the listener and the connection pool.
|
|
||||||
-- Doing that raised an error with GSSAPI as discussed on https://github.com/PostgREST/postgrest/issues/3569.
|
|
||||||
-- Until the root cause is found and solved, we need to prevent parallel connection attempts.
|
|
||||||
|
|
||||||
-- tryPutMVar doesn't lock the thread. It should always succeed since
|
|
||||||
-- the connectionWorker is the only mvar producer.
|
|
||||||
setListenerCanStart :: AppState -> IO ()
|
|
||||||
setListenerCanStart appState = void $ tryPutMVar (stateListenerCanStart appState) ()
|
|
||||||
|
|
||||||
-- | As this IO action uses `takeMVar` internally, it will only return once
|
|
||||||
-- `stateListenerCanStart` has been set using `setListenerCanStart`.
|
|
||||||
waitForListenerCanStart :: AppState -> IO ()
|
|
||||||
waitForListenerCanStart = takeMVar . stateListenerCanStart
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------------
|
|
||||||
--------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
getConfig :: AppState -> IO AppConfig
|
getConfig :: AppState -> IO AppConfig
|
||||||
getConfig = readIORef . stateConf
|
getConfig = readIORef . stateConf
|
||||||
|
|
||||||
@@ -350,8 +331,8 @@ putConfig = atomicWriteIORef . stateConf
|
|||||||
getTime :: AppState -> IO UTCTime
|
getTime :: AppState -> IO UTCTime
|
||||||
getTime = stateGetTime
|
getTime = stateGetTime
|
||||||
|
|
||||||
getJwtCache :: AppState -> C.Cache ByteString AuthResult
|
getJwtCacheState :: AppState -> JwtCacheState
|
||||||
getJwtCache = jwtCache
|
getJwtCacheState = jwtCacheState
|
||||||
|
|
||||||
getSocketREST :: AppState -> NS.Socket
|
getSocketREST :: AppState -> NS.Socket
|
||||||
getSocketREST = stateSocketREST
|
getSocketREST = stateSocketREST
|
||||||
@@ -362,175 +343,125 @@ getSocketAdmin = stateSocketAdmin
|
|||||||
getMainThreadId :: AppState -> ThreadId
|
getMainThreadId :: AppState -> ThreadId
|
||||||
getMainThreadId = stateMainThreadId
|
getMainThreadId = stateMainThreadId
|
||||||
|
|
||||||
getIsListenerOn :: AppState -> IO Bool
|
isConnEstablished :: AppState -> IO Bool
|
||||||
getIsListenerOn appState = do
|
isConnEstablished appState = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
if configDbChannelEnabled then
|
if configDbChannelEnabled then -- if the listener is enabled, we can be sure the connection is up
|
||||||
readIORef $ stateIsListenerOn appState
|
readIORef $ stateIsListenerOn appState
|
||||||
else
|
else -- otherwise the only way to check the connection is to make a query
|
||||||
pure True
|
isRight <$> usePool appState (SQL.sql "SELECT 1")
|
||||||
|
|
||||||
putIsListenerOn :: AppState -> Bool -> IO ()
|
putIsListenerOn :: AppState -> Bool -> IO ()
|
||||||
putIsListenerOn = atomicWriteIORef . stateIsListenerOn
|
putIsListenerOn = atomicWriteIORef . stateIsListenerOn
|
||||||
|
|
||||||
isConnEstablished :: AppState -> IO Bool
|
|
||||||
isConnEstablished x = do
|
|
||||||
conf <- getConfig x
|
|
||||||
if configDbChannelEnabled conf
|
|
||||||
then do -- if the listener is enabled, we can be sure the connection status is always up to date
|
|
||||||
st <- readIORef $ stateConnStatus x
|
|
||||||
return $ st == ConnEstablished
|
|
||||||
else -- otherwise the only way to check the connection is to make a query
|
|
||||||
isRight <$> usePool x (SQL.sql "SELECT 1")
|
|
||||||
|
|
||||||
isLoaded :: AppState -> IO Bool
|
isLoaded :: AppState -> IO Bool
|
||||||
isLoaded x = do
|
isLoaded x = do
|
||||||
scacheStatus <- readIORef $ stateSCacheStatus x
|
scacheStatus <- readIORef $ stateSCacheStatus x
|
||||||
connEstablished <- isConnEstablished x
|
connEstablished <- isConnEstablished x
|
||||||
listenerOn <- getIsListenerOn x
|
return $ scacheStatus == SCLoaded && connEstablished
|
||||||
return $ scacheStatus == SCLoaded && connEstablished && listenerOn
|
|
||||||
|
|
||||||
isPending :: AppState -> IO Bool
|
isPending :: AppState -> IO Bool
|
||||||
isPending x = do
|
isPending x = do
|
||||||
scacheStatus <- readIORef $ stateSCacheStatus x
|
scacheStatus <- readIORef $ stateSCacheStatus x
|
||||||
connStatus <- readIORef $ stateConnStatus x
|
connEstablished <- isConnEstablished x
|
||||||
listenerOn <- getIsListenerOn x
|
return $ scacheStatus == SCPending || not connEstablished
|
||||||
return $ scacheStatus == SCPending || connStatus == ConnPending || not listenerOn
|
|
||||||
|
|
||||||
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
|
putSCacheStatus :: AppState -> SchemaCacheStatus -> IO ()
|
||||||
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
|
putSCacheStatus = atomicWriteIORef . stateSCacheStatus
|
||||||
|
|
||||||
putConnStatus :: AppState -> ConnectionStatus -> IO ()
|
|
||||||
putConnStatus = atomicWriteIORef . stateConnStatus
|
|
||||||
|
|
||||||
getObserver :: AppState -> ObservationHandler
|
getObserver :: AppState -> ObservationHandler
|
||||||
getObserver = stateObserver
|
getObserver = stateObserver
|
||||||
|
|
||||||
-- | Load the SchemaCache by using a connection from the pool.
|
-- | Try to load the schema cache and retry if it fails.
|
||||||
loadSchemaCache :: AppState -> IO SchemaCacheStatus
|
|
||||||
loadSchemaCache appState@AppState{stateObserver=observer} = do
|
|
||||||
conf@AppConfig{..} <- getConfig appState
|
|
||||||
(resultTime, result) <-
|
|
||||||
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
|
|
||||||
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
|
|
||||||
case result of
|
|
||||||
Left e -> do
|
|
||||||
putSCacheStatus appState SCPending
|
|
||||||
putSchemaCache appState Nothing
|
|
||||||
observer $ SchemaCacheErrorObs e
|
|
||||||
return SCPending
|
|
||||||
|
|
||||||
Right sCache -> do
|
|
||||||
-- IMPORTANT: While the pending schema cache state starts from running the above querySchemaCache, only at this stage we block API requests due to the usage of an
|
|
||||||
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
|
|
||||||
putSCacheStatus appState SCPending
|
|
||||||
putSchemaCache appState $ Just sCache
|
|
||||||
observer $ SchemaCacheQueriedObs resultTime
|
|
||||||
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
|
||||||
observer $ SchemaCacheLoadedObs t
|
|
||||||
putSCacheStatus appState SCLoaded
|
|
||||||
return SCLoaded
|
|
||||||
|
|
||||||
-- | The purpose of this worker is to obtain a healthy connection to pg and an
|
|
||||||
-- up-to-date schema cache(SchemaCache). This method is meant to be called
|
|
||||||
-- multiple times by the same thread, but does nothing if the previous
|
|
||||||
-- invocation has not terminated. In all cases this method does not halt the
|
|
||||||
-- calling thread, the work is performed in a separate thread.
|
|
||||||
--
|
--
|
||||||
-- Background thread that does the following :
|
-- This is done by repeatedly: 1) flushing the pool, 2) querying the version and validating that the postgres version is supported by us, and 3) loading the schema cache.
|
||||||
-- 1. Tries to connect to pg server and will keep trying until success.
|
-- It's necessary to flush the pool:
|
||||||
-- 2. Checks if the pg version is supported and if it's not it kills the main
|
--
|
||||||
-- program.
|
-- + Because connections cache the pg catalog(see #2620)
|
||||||
-- 3. Obtains the sCache. If this fails, it goes back to 1.
|
-- + For rapid recovery. Otherwise, the pool idle or lifetime timeout would have to be reached for new healthy connections to be acquired.
|
||||||
internalConnectionWorker :: AppState -> IO ()
|
retryingSchemaCacheLoad :: AppState -> IO ()
|
||||||
internalConnectionWorker appState@AppState{stateObserver=observer, stateMainThreadId=mainThreadId} = work
|
retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThreadId=mainThreadId} =
|
||||||
|
void $ retrying retryPolicy shouldRetry (\RetryStatus{rsIterNumber, rsPreviousDelay} -> do
|
||||||
|
when (rsIterNumber > 0) $ do
|
||||||
|
let delay = fromMaybe 0 rsPreviousDelay `div` oneSecondInUs
|
||||||
|
observer $ ConnectionRetryObs delay
|
||||||
|
putNextListenerDelay appState delay
|
||||||
|
|
||||||
|
flushPool appState
|
||||||
|
|
||||||
|
(,) <$> qPgVersion <*> (qInDbConfig *> qSchemaCache)
|
||||||
|
)
|
||||||
where
|
where
|
||||||
work = do
|
qPgVersion :: IO (Maybe PgVersion)
|
||||||
|
qPgVersion = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
observer DBConnectAttemptObs
|
|
||||||
connStatus <- establishConnection appState
|
|
||||||
case connStatus of
|
|
||||||
ConnPending ->
|
|
||||||
unless configDbPoolAutomaticRecovery $ do
|
|
||||||
observer ExitDBNoRecoveryObs
|
|
||||||
killThread mainThreadId
|
|
||||||
ConnEstablished -> do
|
|
||||||
actualPgVersion <- getPgVersion appState
|
|
||||||
when (actualPgVersion < minimumPgVersion) $ do
|
|
||||||
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
|
|
||||||
killThread mainThreadId
|
|
||||||
observer (DBConnectedObs $ pgvFullName actualPgVersion)
|
|
||||||
-- Wake up the Listener
|
|
||||||
when configDbChannelEnabled $
|
|
||||||
setListenerCanStart appState
|
|
||||||
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
|
|
||||||
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
|
|
||||||
when configDbConfig $ reReadConfig False appState
|
|
||||||
scStatus <- loadSchemaCache appState
|
|
||||||
case scStatus of
|
|
||||||
SCLoaded ->
|
|
||||||
-- do nothing and proceed if the load was successful
|
|
||||||
return ()
|
|
||||||
SCPending ->
|
|
||||||
-- retry reloading the schema cache
|
|
||||||
work
|
|
||||||
|
|
||||||
-- | Repeatedly flush the pool, and check if a connection from the
|
|
||||||
-- pool allows access to the PostgreSQL database.
|
|
||||||
--
|
|
||||||
-- Releasing the pool is key for rapid recovery. Otherwise, the pool
|
|
||||||
-- timeout would have to be reached for new healthy connections to be acquired.
|
|
||||||
-- Which might not happen if the server is busy with requests. No idle
|
|
||||||
-- connection, no pool timeout.
|
|
||||||
--
|
|
||||||
-- It's also necessary to release the pool connections because they cache the pg catalog(see #2620)
|
|
||||||
--
|
|
||||||
-- The connection tries are capped, but if the connection times out no error is
|
|
||||||
-- thrown, just 'False' is returned.
|
|
||||||
establishConnection :: AppState -> IO ConnectionStatus
|
|
||||||
establishConnection appState@AppState{stateObserver=observer} =
|
|
||||||
retrying retryPolicy shouldRetry $
|
|
||||||
const $ flushPool appState >> getConnectionStatus
|
|
||||||
where
|
|
||||||
getConnectionStatus :: IO ConnectionStatus
|
|
||||||
getConnectionStatus = do
|
|
||||||
pgVersion <- usePool appState (queryPgVersion False) -- No need to prepare the query here, as the connection might not be established
|
pgVersion <- usePool appState (queryPgVersion False) -- No need to prepare the query here, as the connection might not be established
|
||||||
case pgVersion of
|
case pgVersion of
|
||||||
Left e -> do
|
Left e -> do
|
||||||
observer $ ConnectionPgVersionErrorObs e
|
observer $ QueryPgVersionError e
|
||||||
putConnStatus appState ConnPending
|
unless configDbPoolAutomaticRecovery $ do
|
||||||
return ConnPending
|
observer ExitDBNoRecoveryObs
|
||||||
Right version -> do
|
killThread mainThreadId
|
||||||
putConnStatus appState ConnEstablished
|
return Nothing
|
||||||
putPgVersion appState version
|
Right actualPgVersion -> do
|
||||||
return ConnEstablished
|
when (actualPgVersion < minimumPgVersion) $ do
|
||||||
|
observer $ ExitUnsupportedPgVersion actualPgVersion minimumPgVersion
|
||||||
|
killThread mainThreadId
|
||||||
|
observer $ DBConnectedObs $ pgvFullName actualPgVersion
|
||||||
|
putPgVersion appState actualPgVersion
|
||||||
|
return $ Just actualPgVersion
|
||||||
|
|
||||||
shouldRetry :: RetryStatus -> ConnectionStatus -> IO Bool
|
qInDbConfig :: IO ()
|
||||||
shouldRetry rs isConnSucc = do
|
qInDbConfig = do
|
||||||
AppConfig{..} <- getConfig appState
|
AppConfig{..} <- getConfig appState
|
||||||
let
|
when configDbConfig $ readInDbConfig False appState
|
||||||
delay = fromMaybe 0 (rsPreviousDelay rs) `div` oneSecondInUs
|
|
||||||
itShould = ConnPending == isConnSucc && configDbPoolAutomaticRecovery
|
qSchemaCache :: IO (Maybe SchemaCache)
|
||||||
when itShould $ observer $ ConnectionRetryObs delay
|
qSchemaCache = do
|
||||||
when itShould $ putNextDelay appState delay
|
conf@AppConfig{..} <- getConfig appState
|
||||||
|
(resultTime, result) <-
|
||||||
|
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
|
||||||
|
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
|
||||||
|
case result of
|
||||||
|
Left e -> do
|
||||||
|
putSCacheStatus appState SCPending
|
||||||
|
putSchemaCache appState Nothing
|
||||||
|
observer $ SchemaCacheErrorObs e
|
||||||
|
return Nothing
|
||||||
|
|
||||||
|
Right sCache -> do
|
||||||
|
-- IMPORTANT: While the pending schema cache state starts from running the above querySchemaCache, only at this stage we block API requests due to the usage of an
|
||||||
|
-- IORef on putSchemaCache. This is why SCacheStatus is put at SCPending here to signal the Admin server (using isPending) that we're on a recovery state.
|
||||||
|
putSCacheStatus appState SCPending
|
||||||
|
putSchemaCache appState $ Just sCache
|
||||||
|
observer $ SchemaCacheQueriedObs resultTime
|
||||||
|
(t, _) <- timeItT $ observer $ SchemaCacheSummaryObs $ showSummary sCache
|
||||||
|
observer $ SchemaCacheLoadedObs t
|
||||||
|
putSCacheStatus appState SCLoaded
|
||||||
|
return $ Just sCache
|
||||||
|
|
||||||
|
shouldRetry :: RetryStatus -> (Maybe PgVersion, Maybe SchemaCache) -> IO Bool
|
||||||
|
shouldRetry _ (pgVer, sCache) = do
|
||||||
|
AppConfig{..} <- getConfig appState
|
||||||
|
let itShould = configDbPoolAutomaticRecovery && (isNothing pgVer || isNothing sCache)
|
||||||
return itShould
|
return itShould
|
||||||
|
|
||||||
retryPolicy :: RetryPolicy
|
retryPolicy :: RetryPolicy
|
||||||
retryPolicy =
|
retryPolicy =
|
||||||
let
|
let delayMicroseconds = 32*oneSecondInUs {-32 seconds-} in
|
||||||
delayMicroseconds = 32000000 -- 32 seconds
|
|
||||||
in
|
|
||||||
capDelay delayMicroseconds $ exponentialBackoff oneSecondInUs
|
capDelay delayMicroseconds $ exponentialBackoff oneSecondInUs
|
||||||
oneSecondInUs = 1000000 -- | One second in microseconds
|
|
||||||
|
|
||||||
-- | Re-reads the config plus config options from the db
|
oneSecondInUs = 1000000 -- one second in microseconds
|
||||||
reReadConfig :: Bool -> AppState -> IO ()
|
|
||||||
reReadConfig startingUp appState@AppState{stateObserver=observer} = do
|
-- | Reads the in-db config and reads the config file again
|
||||||
AppConfig{..} <- getConfig appState
|
-- | 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, jwtCacheState=JwtCacheState{jwtCache}} = do
|
||||||
|
conf <- getConfig appState
|
||||||
pgVer <- getPgVersion appState
|
pgVer <- getPgVersion appState
|
||||||
dbSettings <-
|
dbSettings <-
|
||||||
if configDbConfig then do
|
if configDbConfig conf then do
|
||||||
qDbSettings <- usePool appState (queryDbSettings (dumpQi <$> configDbPreConfig) configDbPreparedStatements)
|
qDbSettings <- usePool appState (queryDbSettings (dumpQi <$> configDbPreConfig conf) (configDbPreparedStatements conf))
|
||||||
case qDbSettings of
|
case qDbSettings of
|
||||||
Left e -> do
|
Left e -> do
|
||||||
observer $ ConfigReadErrorObs e
|
observer $ ConfigReadErrorObs e
|
||||||
@@ -539,8 +470,8 @@ reReadConfig startingUp appState@AppState{stateObserver=observer} = do
|
|||||||
else
|
else
|
||||||
pure mempty
|
pure mempty
|
||||||
(roleSettings, roleIsolationLvl) <-
|
(roleSettings, roleIsolationLvl) <-
|
||||||
if configDbConfig then do
|
if configDbConfig conf then do
|
||||||
rSettings <- usePool appState (queryRoleSettings pgVer configDbPreparedStatements)
|
rSettings <- usePool appState (queryRoleSettings pgVer (configDbPreparedStatements conf))
|
||||||
case rSettings of
|
case rSettings of
|
||||||
Left e -> do
|
Left e -> do
|
||||||
observer $ QueryRoleSettingsErrorObs e
|
observer $ QueryRoleSettingsErrorObs e
|
||||||
@@ -548,7 +479,7 @@ reReadConfig startingUp appState@AppState{stateObserver=observer} = do
|
|||||||
Right x -> pure x
|
Right x -> pure x
|
||||||
else
|
else
|
||||||
pure mempty
|
pure mempty
|
||||||
readAppConfig dbSettings configFilePath (Just configDbUri) roleSettings roleIsolationLvl >>= \case
|
readAppConfig dbSettings (configFilePath conf) (Just $ configDbUri conf) roleSettings roleIsolationLvl >>= \case
|
||||||
Left err ->
|
Left err ->
|
||||||
if startingUp then
|
if startingUp then
|
||||||
panic err -- die on invalid config if the program is starting up
|
panic err -- die on invalid config if the program is starting up
|
||||||
@@ -556,6 +487,14 @@ reReadConfig startingUp appState@AppState{stateObserver=observer} = do
|
|||||||
observer $ ConfigInvalidObs err
|
observer $ ConfigInvalidObs err
|
||||||
Right newConf -> do
|
Right newConf -> do
|
||||||
putConfig appState newConf
|
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
|
if startingUp then
|
||||||
pass
|
pass
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ import System.Clock (TimeSpec (..))
|
|||||||
import System.IO.Unsafe (unsafePerformIO)
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
import System.TimeIt (timeItT)
|
import System.TimeIt (timeItT)
|
||||||
|
|
||||||
import PostgREST.AppState (AppState, AuthResult (..), getConfig,
|
import PostgREST.AppState (AppState, AuthResult (..),
|
||||||
getJwtCache, getTime)
|
JwtCacheState (..), getConfig,
|
||||||
|
getJwtCacheState, getTime)
|
||||||
import PostgREST.Config (AppConfig (..), JSPath, JSPathExp (..))
|
import PostgREST.Config (AppConfig (..), JSPath, JSPathExp (..))
|
||||||
import PostgREST.Error (Error (..))
|
import PostgREST.Error (Error (..))
|
||||||
|
|
||||||
@@ -131,11 +132,35 @@ middleware appState app req respond = do
|
|||||||
-- | Used to retrieve and insert JWT to JWT Cache
|
-- | Used to retrieve and insert JWT to JWT Cache
|
||||||
getJWTFromCache :: AppState -> ByteString -> Int -> IO (Either Error AuthResult) -> UTCTime -> IO (Either Error AuthResult)
|
getJWTFromCache :: AppState -> ByteString -> Int -> IO (Either Error AuthResult) -> UTCTime -> IO (Either Error AuthResult)
|
||||||
getJWTFromCache appState token maxLifetime parseJwt utc = do
|
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
|
authResult <- maybe parseJwt (pure . Right) checkCache
|
||||||
|
|
||||||
case (authResult,checkCache) of
|
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 ()
|
_ -> pure ()
|
||||||
|
|
||||||
return authResult
|
return authResult
|
||||||
|
|||||||
@@ -42,9 +42,11 @@ main CLI{cliCommand, cliPath} = do
|
|||||||
AppState.destroy
|
AppState.destroy
|
||||||
(\appState -> case cliCommand of
|
(\appState -> case cliCommand of
|
||||||
CmdDumpConfig -> do
|
CmdDumpConfig -> do
|
||||||
when configDbConfig $ AppState.reReadConfig True appState
|
when configDbConfig $ AppState.readInDbConfig True appState
|
||||||
putStr . Config.toText =<< AppState.getConfig appState
|
putStr . Config.toText =<< AppState.getConfig appState
|
||||||
CmdDumpSchema -> putStrLn =<< dumpSchema appState
|
CmdDumpSchema -> do
|
||||||
|
when configDbConfig $ AppState.readInDbConfig True appState
|
||||||
|
putStrLn =<< dumpSchema appState
|
||||||
CmdRun -> App.run appState)
|
CmdRun -> App.run appState)
|
||||||
|
|
||||||
-- | Dump SchemaCache schema to JSON
|
-- | Dump SchemaCache schema to JSON
|
||||||
@@ -126,6 +128,9 @@ exampleConfigFile =
|
|||||||
[str|## Admin server used for checks. It's disabled by default unless a port is specified.
|
[str|## Admin server used for checks. It's disabled by default unless a port is specified.
|
||||||
|# admin-server-port = 3001
|
|# 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
|
|## The database role to use when no client authentication is provided
|
||||||
|# db-anon-role = "anon"
|
|# db-anon-role = "anon"
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ data AppConfig = AppConfig
|
|||||||
, configServerUnixSocket :: Maybe FilePath
|
, configServerUnixSocket :: Maybe FilePath
|
||||||
, configServerUnixSocketMode :: FileMode
|
, configServerUnixSocketMode :: FileMode
|
||||||
, configAdminServerPort :: Maybe Int
|
, configAdminServerPort :: Maybe Int
|
||||||
|
, configAdminServerConfigEnabled :: Bool
|
||||||
, configRoleSettings :: RoleSettings
|
, configRoleSettings :: RoleSettings
|
||||||
, configRoleIsoLvl :: RoleIsolationLvl
|
, configRoleIsoLvl :: RoleIsolationLvl
|
||||||
, configInternalSCSleep :: Maybe Int32
|
, configInternalSCSleep :: Maybe Int32
|
||||||
@@ -180,6 +181,7 @@ toText conf =
|
|||||||
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
|
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
|
||||||
,("server-unix-socket-mode", q . T.pack . showSocketMode)
|
,("server-unix-socket-mode", q . T.pack . showSocketMode)
|
||||||
,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
|
,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
|
||||||
|
,("admin-server-config-enabled", T.toLower . show . configAdminServerConfigEnabled)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- quote all app.settings
|
-- quote all app.settings
|
||||||
@@ -286,6 +288,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
|
|||||||
<*> (fmap T.unpack <$> optString "server-unix-socket")
|
<*> (fmap T.unpack <$> optString "server-unix-socket")
|
||||||
<*> parseSocketFileMode "server-unix-socket-mode"
|
<*> parseSocketFileMode "server-unix-socket-mode"
|
||||||
<*> optInt "admin-server-port"
|
<*> optInt "admin-server-port"
|
||||||
|
<*> (fromMaybe False <$> optBool "admin-server-config-enabled")
|
||||||
<*> pure roleSettings
|
<*> pure roleSettings
|
||||||
<*> pure roleIsolationLvl
|
<*> pure roleIsolationLvl
|
||||||
<*> optInt "internal-schema-cache-sleep"
|
<*> optInt "internal-schema-cache-sleep"
|
||||||
|
|||||||
@@ -27,16 +27,10 @@ runListener appState = do
|
|||||||
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
|
-- | Starts a LISTEN connection and handles notifications. It recovers with exponential backoff with a cap of 32 seconds, if the LISTEN connection is lost.
|
||||||
retryingListen :: AppState -> IO ()
|
retryingListen :: AppState -> IO ()
|
||||||
retryingListen appState = do
|
retryingListen appState = do
|
||||||
AppState.waitForListenerCanStart appState
|
|
||||||
AppConfig{..} <- AppState.getConfig appState
|
AppConfig{..} <- AppState.getConfig appState
|
||||||
let
|
let
|
||||||
dbChannel = toS configDbChannel
|
dbChannel = toS configDbChannel
|
||||||
handleFinally err = do
|
handleFinally err = do
|
||||||
-- assume we lost notifications, call the connection worker which will also reload the schema cache
|
|
||||||
-- and will setListenerCanStart again
|
|
||||||
-- TODO: When the connection error is only on the Listener, it's wasteful to call the connectionWorker everytime.
|
|
||||||
AppState.connectionWorker appState
|
|
||||||
|
|
||||||
AppState.putIsListenerOn appState False
|
AppState.putIsListenerOn appState False
|
||||||
observer $ DBListenFail dbChannel (Right err)
|
observer $ DBListenFail dbChannel (Right err)
|
||||||
unless configDbPoolAutomaticRecovery $
|
unless configDbPoolAutomaticRecovery $
|
||||||
@@ -60,6 +54,8 @@ retryingListen appState = do
|
|||||||
|
|
||||||
delay <- AppState.getNextListenerDelay appState
|
delay <- AppState.getNextListenerDelay appState
|
||||||
when (delay > 1) $ do -- if we did a retry
|
when (delay > 1) $ do -- if we did a retry
|
||||||
|
-- assume we lost notifications, refresh the schema cache
|
||||||
|
AppState.schemaCacheLoader appState
|
||||||
-- reset the delay
|
-- reset the delay
|
||||||
AppState.putNextListenerDelay appState 1
|
AppState.putNextListenerDelay appState 1
|
||||||
|
|
||||||
@@ -78,8 +74,8 @@ retryingListen appState = do
|
|||||||
handleNotification channel msg =
|
handleNotification channel msg =
|
||||||
if | BS.null msg -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader
|
if | BS.null msg -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader
|
||||||
| msg == "reload schema" -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader
|
| msg == "reload schema" -> observer (DBListenerGotSCacheMsg channel) >> cacheReloader
|
||||||
| msg == "reload config" -> observer (DBListenerGotConfigMsg channel) >> AppState.reReadConfig False appState
|
| msg == "reload config" -> observer (DBListenerGotConfigMsg channel) >> AppState.readInDbConfig False appState
|
||||||
| otherwise -> pure () -- Do nothing if anything else than an empty message is sent
|
| otherwise -> pure () -- Do nothing if anything else than an empty message is sent
|
||||||
|
|
||||||
cacheReloader =
|
cacheReloader =
|
||||||
AppState.connectionWorker appState
|
AppState.schemaCacheLoader appState
|
||||||
|
|||||||
@@ -87,9 +87,6 @@ observationLogger loggerState logLevel obs = case obs of
|
|||||||
o@(HasqlPoolObs _) -> do
|
o@(HasqlPoolObs _) -> do
|
||||||
when (logLevel >= LogDebug) $ do
|
when (logLevel >= LogDebug) $ do
|
||||||
logWithZTime loggerState $ observationMessage o
|
logWithZTime loggerState $ observationMessage o
|
||||||
o@(SchemaCacheLoadedObs _) -> do
|
|
||||||
when (logLevel >= LogDebug) $ do
|
|
||||||
logWithZTime loggerState $ observationMessage o
|
|
||||||
PoolRequest ->
|
PoolRequest ->
|
||||||
pure ()
|
pure ()
|
||||||
PoolRequestFullfilled ->
|
PoolRequestFullfilled ->
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{-# LANGUAGE DeriveAnyClass #-}
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
|
||||||
module PostgREST.MediaType
|
module PostgREST.MediaType
|
||||||
( MediaType(..)
|
( MediaType(..)
|
||||||
, MTVndPlanOption (..)
|
, MTVndPlanOption (..)
|
||||||
@@ -11,17 +11,16 @@ module PostgREST.MediaType
|
|||||||
, decodeMediaType
|
, decodeMediaType
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Data.ByteString as BS
|
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 Network.HTTP.Types.Header (Header, hContentType)
|
||||||
|
|
||||||
import Data.Map (fromList, (!?))
|
import Protolude
|
||||||
import qualified Data.Text as T (break, drop, dropWhile,
|
|
||||||
dropWhileEnd, null, splitOn,
|
|
||||||
toLower)
|
|
||||||
import Data.Text.Encoding (decodeLatin1)
|
|
||||||
import Protolude
|
|
||||||
|
|
||||||
-- | Enumeration of currently supported media types
|
-- | Enumeration of currently supported media types
|
||||||
data MediaType
|
data MediaType
|
||||||
@@ -104,6 +103,9 @@ toMimePlanFormat PlanText = "text"
|
|||||||
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
|
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
|
||||||
-- MTVndPlan MTApplicationJSON PlanText []
|
-- 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\""
|
-- >>> decodeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
||||||
-- MTVndPlan MTTextCSV PlanJSON []
|
-- MTVndPlan MTTextCSV PlanJSON []
|
||||||
--
|
--
|
||||||
@@ -150,7 +152,10 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
|
|||||||
("*","*",_) -> MTAny
|
("*","*",_) -> MTAny
|
||||||
_ -> MTOther mt'
|
_ -> MTOther mt'
|
||||||
where
|
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
|
params = fromList $ map (first T.toLower) params' -- normalize parameter names to lowercase, per RFC 7321
|
||||||
getPlan fmt = MTVndPlan mtFor fmt $
|
getPlan fmt = MTVndPlan mtFor fmt $
|
||||||
[PlanAnalyze | inOpts "analyze" ] ++
|
[PlanAnalyze | inOpts "analyze" ] ++
|
||||||
@@ -166,21 +171,39 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
|
|||||||
checkArrayNullStrip = if strippedNulls then MTVndArrayJSONStrip else MTApplicationJSON
|
checkArrayNullStrip = if strippedNulls then MTVndArrayJSONStrip else MTApplicationJSON
|
||||||
|
|
||||||
-- | Split a Media Type string into components
|
-- | Split a Media Type string into components
|
||||||
-- >>> tokenizeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
||||||
-- ("application","vnd.pgrst.plan+json",[("for","text/csv")])
|
-- Right ("application","vnd.pgrst.plan+json",[("for","text/csv")])
|
||||||
-- >>> tokenizeMediaType "*/*"
|
--
|
||||||
-- ("*","*",[])
|
-- >>> P.parse tokenizeMediaType "" "*/*"
|
||||||
-- >>> tokenizeMediaType "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
|
-- Right ("*","*",[])
|
||||||
-- ("application","vnd.pgrst.plan",[("wat","application/json"),("text/csv\"","")])
|
--
|
||||||
tokenizeMediaType :: Text -> (Text, Text, [(Text, Text)])
|
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
|
||||||
tokenizeMediaType t = (mainType, subType, params)
|
-- Right ("application","vnd.pgrst.plan",[("wat","application/json;text/csv")])
|
||||||
where
|
--
|
||||||
(mainType, rest) = T.break (== '/') t
|
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+text; for=\"text/xml\"; options=analyze|verbose|settings|buffers|wal"
|
||||||
(subType, restParams) = T.break (== ';') $ T.drop 1 rest
|
-- Right ("application","vnd.pgrst.plan+text",[("for","text/xml"),("options","analyze|verbose|settings|buffers|wal")])
|
||||||
params =
|
|
||||||
let rp = T.drop 1 restParams
|
-- TODO: Improve mediatype parser as per RFC 2045 https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
|
||||||
in if T.null rp then [] else map param $ T.splitOn ";" rp -- FIXME: breaks if there's a ';' in a quoted value
|
tokenizeMediaType :: P.Parser (Text, Text, [(Text, Text)])
|
||||||
param p =
|
tokenizeMediaType = do
|
||||||
let (k, v) = T.break (== '=') p
|
mainType <- P.many1 (P.alphaNum <|> P.oneOf ".*")
|
||||||
in (k, dropAround (== '"') $ T.drop 1 v) -- FIXME: doesn't unescape quotes in values
|
P.char '/'
|
||||||
dropAround p = T.dropWhile p . T.dropWhileEnd p
|
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 '\"')
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ data Observation
|
|||||||
| AppStartObs ByteString
|
| AppStartObs ByteString
|
||||||
| AppServerPortObs NS.PortNumber
|
| AppServerPortObs NS.PortNumber
|
||||||
| AppServerUnixObs FilePath
|
| AppServerUnixObs FilePath
|
||||||
| DBConnectAttemptObs
|
|
||||||
| ExitUnsupportedPgVersion PgVersion PgVersion
|
| ExitUnsupportedPgVersion PgVersion PgVersion
|
||||||
| ExitDBNoRecoveryObs
|
| ExitDBNoRecoveryObs
|
||||||
| ExitDBFatalError ObsFatalError SQL.UsageError
|
| ExitDBFatalError ObsFatalError SQL.UsageError
|
||||||
@@ -39,7 +38,6 @@ data Observation
|
|||||||
| SchemaCacheSummaryObs Text
|
| SchemaCacheSummaryObs Text
|
||||||
| SchemaCacheLoadedObs Double
|
| SchemaCacheLoadedObs Double
|
||||||
| ConnectionRetryObs Int
|
| ConnectionRetryObs Int
|
||||||
| ConnectionPgVersionErrorObs SQL.UsageError
|
|
||||||
| DBListenStart Text
|
| DBListenStart Text
|
||||||
| DBListenFail Text (Either SQL.ConnectionError (Either SomeException ()))
|
| DBListenFail Text (Either SQL.ConnectionError (Either SomeException ()))
|
||||||
| DBListenRetry Int
|
| DBListenRetry Int
|
||||||
@@ -50,6 +48,7 @@ data Observation
|
|||||||
| ConfigSucceededObs
|
| ConfigSucceededObs
|
||||||
| QueryRoleSettingsErrorObs SQL.UsageError
|
| QueryRoleSettingsErrorObs SQL.UsageError
|
||||||
| QueryErrorCodeHighObs SQL.UsageError
|
| QueryErrorCodeHighObs SQL.UsageError
|
||||||
|
| QueryPgVersionError SQL.UsageError
|
||||||
| PoolAcqTimeoutObs SQL.UsageError
|
| PoolAcqTimeoutObs SQL.UsageError
|
||||||
| HasqlPoolObs SQL.Observation
|
| HasqlPoolObs SQL.Observation
|
||||||
| PoolRequest
|
| PoolRequest
|
||||||
@@ -69,8 +68,6 @@ observationMessage = \case
|
|||||||
"Listening on port " <> show port
|
"Listening on port " <> show port
|
||||||
AppServerUnixObs sock ->
|
AppServerUnixObs sock ->
|
||||||
"Listening on unix socket " <> show sock
|
"Listening on unix socket " <> show sock
|
||||||
DBConnectAttemptObs ->
|
|
||||||
"Attempting to connect to the database..."
|
|
||||||
DBConnectedObs ver ->
|
DBConnectedObs ver ->
|
||||||
"Successfully connected to " <> ver
|
"Successfully connected to " <> ver
|
||||||
ExitUnsupportedPgVersion pgVer minPgVer ->
|
ExitUnsupportedPgVersion pgVer minPgVer ->
|
||||||
@@ -78,7 +75,7 @@ observationMessage = \case
|
|||||||
ExitDBNoRecoveryObs ->
|
ExitDBNoRecoveryObs ->
|
||||||
"Automatic recovery disabled, exiting."
|
"Automatic recovery disabled, exiting."
|
||||||
ExitDBFatalError ServerAuthError usageErr ->
|
ExitDBFatalError ServerAuthError usageErr ->
|
||||||
jsonMessage usageErr
|
"Failed to establish a connection. " <> jsonMessage usageErr
|
||||||
ExitDBFatalError ServerPgrstBug usageErr ->
|
ExitDBFatalError ServerPgrstBug usageErr ->
|
||||||
"This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues. " <> jsonMessage usageErr
|
"This is probably a bug in PostgREST, please report it at https://github.com/PostgREST/postgrest/issues. " <> jsonMessage usageErr
|
||||||
ExitDBFatalError ServerError42P05 usageErr ->
|
ExitDBFatalError ServerError42P05 usageErr ->
|
||||||
@@ -95,8 +92,8 @@ observationMessage = \case
|
|||||||
"Schema cache loaded in " <> showMillis resultTime <> " milliseconds"
|
"Schema cache loaded in " <> showMillis resultTime <> " milliseconds"
|
||||||
ConnectionRetryObs delay ->
|
ConnectionRetryObs delay ->
|
||||||
"Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
|
"Attempting to reconnect to the database in " <> (show delay::Text) <> " seconds..."
|
||||||
ConnectionPgVersionErrorObs usageErr ->
|
QueryPgVersionError usageErr ->
|
||||||
jsonMessage usageErr
|
"Failed to query the PostgreSQL version. " <> jsonMessage usageErr
|
||||||
DBListenStart channel -> do
|
DBListenStart channel -> do
|
||||||
"Listening for notifications on the " <> show channel <> " channel"
|
"Listening for notifications on the " <> show channel <> " channel"
|
||||||
DBListenFail channel listenErr ->
|
DBListenFail channel listenErr ->
|
||||||
|
|||||||
@@ -622,7 +622,11 @@ generateRelSelectField (Node ReadPlan{relToParent=Just rel, select, relName, rel
|
|||||||
where
|
where
|
||||||
rsSelName = fromMaybe relName relAlias
|
rsSelName = fromMaybe relName relAlias
|
||||||
rsEmbedMode = if relIsToOne rel then JsonObject else JsonArray
|
rsEmbedMode = if relIsToOne rel then JsonObject else JsonArray
|
||||||
rsEmptyEmbed = null select && null forest
|
rsEmptyEmbed = hasOnlyNullEmbed (null select) forest
|
||||||
|
hasOnlyNullEmbed = foldr checkIfNullEmbed
|
||||||
|
checkIfNullEmbed :: ReadPlanTree -> Bool -> Bool
|
||||||
|
checkIfNullEmbed (Node ReadPlan{select=s} f) isNullEmbed =
|
||||||
|
isNullEmbed && hasOnlyNullEmbed (null s) f
|
||||||
generateRelSelectField _ = Nothing
|
generateRelSelectField _ = Nothing
|
||||||
|
|
||||||
generateSpreadSelectFields :: ReadPlan -> [SpreadSelectField]
|
generateSpreadSelectFields :: ReadPlan -> [SpreadSelectField]
|
||||||
|
|||||||
@@ -296,8 +296,7 @@ makeRowFilter tn c =
|
|||||||
& required ?~ False
|
& required ?~ False
|
||||||
& schema .~ ParamOther ((mempty :: ParamOtherSchema)
|
& schema .~ ParamOther ((mempty :: ParamOtherSchema)
|
||||||
& in_ .~ ParamQuery
|
& in_ .~ ParamQuery
|
||||||
& type_ ?~ SwaggerString
|
& type_ ?~ SwaggerString))
|
||||||
& format ?~ colType c))
|
|
||||||
|
|
||||||
makeRowFilters :: Text -> [Column] -> [(Text, Param)]
|
makeRowFilters :: Text -> [Column] -> [(Text, Param)]
|
||||||
makeRowFilters tn = fmap (makeRowFilter tn)
|
makeRowFilters tn = fmap (makeRowFilter tn)
|
||||||
@@ -402,7 +401,7 @@ postgrestSpec (prettyVersion, docsVersion) rels pds ti (s, h, p, b) sd allowSecu
|
|||||||
& description ?~ fromMaybe "This is a dynamic API generated by PostgREST" dDesc)
|
& description ?~ fromMaybe "This is a dynamic API generated by PostgREST" dDesc)
|
||||||
& externalDocs ?~ ((mempty :: ExternalDocs)
|
& externalDocs ?~ ((mempty :: ExternalDocs)
|
||||||
& description ?~ "PostgREST Documentation"
|
& description ?~ "PostgREST Documentation"
|
||||||
& url .~ URL ("https://postgrest.org/en/" <> docsVersion <> "/api.html"))
|
& url .~ URL ("https://postgrest.org/en/" <> docsVersion <> "/references/api.html"))
|
||||||
& host .~ h'
|
& host .~ h'
|
||||||
& definitions .~ fromList (makeTableDef rels <$> ti)
|
& definitions .~ fromList (makeTableDef rels <$> ti)
|
||||||
& parameters .~ fromList (makeParamDefs ti)
|
& parameters .~ fromList (makeParamDefs ti)
|
||||||
|
|||||||
@@ -95,13 +95,14 @@ instance JSON.ToJSON SchemaCache where
|
|||||||
]
|
]
|
||||||
|
|
||||||
showSummary :: SchemaCache -> Text
|
showSummary :: SchemaCache -> Text
|
||||||
showSummary (SchemaCache tbls rels routs reps mediaHdlrs _) =
|
showSummary (SchemaCache tbls rels routs reps mediaHdlrs tzs) =
|
||||||
T.intercalate ", "
|
T.intercalate ", "
|
||||||
[ show (HM.size tbls) <> " Relations"
|
[ show (HM.size tbls) <> " Relations"
|
||||||
, show (HM.size rels) <> " Relationships"
|
, show (HM.size rels) <> " Relationships"
|
||||||
, show (HM.size routs) <> " Functions"
|
, show (HM.size routs) <> " Functions"
|
||||||
, show (HM.size reps) <> " Domain Representations"
|
, show (HM.size reps) <> " Domain Representations"
|
||||||
, show (HM.size mediaHdlrs) <> " Media Type Handlers"
|
, show (HM.size mediaHdlrs) <> " Media Type Handlers"
|
||||||
|
, show (S.size tzs) <> " Timezones"
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | A view foreign key or primary key dependency detected on its source table
|
-- | A view foreign key or primary key dependency detected on its source table
|
||||||
@@ -646,7 +647,7 @@ tablesSqlQuery pgVer =
|
|||||||
information_schema._pg_truetypid(a.*, t.*),
|
information_schema._pg_truetypid(a.*, t.*),
|
||||||
information_schema._pg_truetypmod(a.*, t.*)
|
information_schema._pg_truetypmod(a.*, t.*)
|
||||||
)::integer AS character_maximum_length,
|
)::integer AS character_maximum_length,
|
||||||
COALESCE(bt.typname, t.typname)::name AS udt_name,
|
COALESCE(bt.oid, t.oid) AS base_type,
|
||||||
a.attnum::integer AS position
|
a.attnum::integer AS position
|
||||||
FROM pg_attribute a
|
FROM pg_attribute a
|
||||||
LEFT JOIN pg_description AS d
|
LEFT JOIN pg_description AS d
|
||||||
@@ -690,14 +691,13 @@ tablesSqlQuery pgVer =
|
|||||||
FROM columns info
|
FROM columns info
|
||||||
LEFT OUTER JOIN (
|
LEFT OUTER JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
n.nspname AS s,
|
e.enumtypid,
|
||||||
t.typname AS n,
|
|
||||||
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS vals
|
array_agg(e.enumlabel ORDER BY e.enumsortorder) AS vals
|
||||||
FROM pg_type t
|
FROM pg_type t
|
||||||
JOIN pg_enum e ON t.oid = e.enumtypid
|
JOIN pg_enum e ON t.oid = e.enumtypid
|
||||||
JOIN pg_namespace n ON n.oid = t.typnamespace
|
JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
GROUP BY s,n
|
GROUP BY enumtypid
|
||||||
) AS enum_info ON info.udt_name = enum_info.n
|
) AS enum_info ON info.base_type = enum_info.enumtypid
|
||||||
WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema')
|
WHERE info.table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||||
GROUP BY info.table_schema, info.table_name
|
GROUP BY info.table_schema, info.table_name
|
||||||
),
|
),
|
||||||
@@ -818,20 +818,20 @@ tablesSqlQuery pgVer =
|
|||||||
columnDefault -- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
|
columnDefault -- typbasetype and typdefaultbin handles `CREATE DOMAIN .. DEFAULT val`, attidentity/attgenerated handles generated columns, pg_get_expr gets the default of a column
|
||||||
| pgVer >= pgVersion120 = [q|
|
| pgVer >= pgVersion120 = [q|
|
||||||
CASE
|
CASE
|
||||||
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
|
WHEN (t.typbasetype != 0) AND (ad.adbin IS NULL) THEN pg_get_expr(t.typdefaultbin, 0)
|
||||||
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
|
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
|
||||||
WHEN a.attgenerated = 's' THEN null
|
WHEN a.attgenerated = 's' THEN null
|
||||||
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
||||||
END|]
|
END|]
|
||||||
| pgVer >= pgVersion100 = [q|
|
| pgVer >= pgVersion100 = [q|
|
||||||
CASE
|
CASE
|
||||||
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
|
WHEN (t.typbasetype != 0) AND (ad.adbin IS NULL) THEN pg_get_expr(t.typdefaultbin, 0)
|
||||||
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
|
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
|
||||||
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
||||||
END|]
|
END|]
|
||||||
| otherwise = [q|
|
| otherwise = [q|
|
||||||
CASE
|
CASE
|
||||||
WHEN t.typbasetype != 0 THEN pg_get_expr(t.typdefaultbin, 0)
|
WHEN (t.typbasetype != 0) AND (ad.adbin IS NULL) THEN pg_get_expr(t.typdefaultbin, 0)
|
||||||
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
|
||||||
END|]
|
END|]
|
||||||
|
|
||||||
@@ -872,7 +872,7 @@ allM2OandO2ORels pgVer =
|
|||||||
JOIN LATERAL (
|
JOIN LATERAL (
|
||||||
SELECT
|
SELECT
|
||||||
array_agg(row(cols.attname, refs.attname) order by ord) AS cols_and_fcols,
|
array_agg(row(cols.attname, refs.attname) order by ord) AS cols_and_fcols,
|
||||||
jsonb_agg(cols.attname order by ord) AS cols
|
jsonb_agg(cols.attname order by cols.attnum) AS cols
|
||||||
FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord)
|
FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord)
|
||||||
JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col
|
JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col
|
||||||
JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref
|
JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import Protolude
|
|||||||
|
|
||||||
|
|
||||||
-- | User friendly version number such as '1.1.1'.
|
-- | User friendly version number such as '1.1.1'.
|
||||||
-- Pre-release versions are tagged as such, e.g., '1.1.1.1 (pre-release)'.
|
-- Pre-release versions are tagged as such, e.g., '1.1 (pre-release)'.
|
||||||
-- If a git hash is available, it's added to the version, e.g., '1.1.1 (abcdef0)'.
|
-- If a git hash is available, it's added to the version, e.g., '1.1.1 (abcdef0)'.
|
||||||
prettyVersion :: ByteString
|
prettyVersion :: ByteString
|
||||||
prettyVersion =
|
prettyVersion =
|
||||||
@@ -31,14 +31,14 @@ prettyVersion =
|
|||||||
|
|
||||||
-- | Version number used in docs.
|
-- | Version number used in docs.
|
||||||
-- Pre-release versions link to the latest docs
|
-- Pre-release versions link to the latest docs
|
||||||
-- Uses only the two first components of the version. Example: 'v1.1'
|
-- Uses only the first component of the version. Example: 'v1'
|
||||||
docsVersion :: Text
|
docsVersion :: Text
|
||||||
docsVersion
|
docsVersion
|
||||||
| isPreRelease = "latest"
|
| isPreRelease = "latest"
|
||||||
| otherwise = "v" <> (T.intercalate "." . map show . take 2 $ versionBranch version)
|
| otherwise = "v" <> (T.intercalate "." . map show . take 1 $ versionBranch version)
|
||||||
|
|
||||||
|
|
||||||
-- | Versions with four components (e.g., '1.1.1.1') are treated as pre-releases.
|
-- | Versions with two components (e.g., '1.1') are treated as pre-releases.
|
||||||
isPreRelease :: Bool
|
isPreRelease :: Bool
|
||||||
isPreRelease =
|
isPreRelease =
|
||||||
length (versionBranch version) == 4
|
length (versionBranch version) == 2
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ nix:
|
|||||||
extra-deps:
|
extra-deps:
|
||||||
- configurator-pg-0.2.10
|
- configurator-pg-0.2.10
|
||||||
- fuzzyset-0.2.4
|
- fuzzyset-0.2.4
|
||||||
- hasql-notifications-0.2.2.0
|
- hasql-notifications-0.2.2.2
|
||||||
- hasql-pool-1.0.1
|
- hasql-pool-1.0.1
|
||||||
- postgresql-libpq-0.10.1.0
|
- postgresql-libpq-0.10.1.0
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ packages:
|
|||||||
original:
|
original:
|
||||||
hackage: fuzzyset-0.2.4
|
hackage: fuzzyset-0.2.4
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hasql-notifications-0.2.2.0@sha256:a4e591ef3f06647b056567d3b66948c4a85371f05deb5434edb6ce190f7c845d,2021
|
hackage: hasql-notifications-0.2.2.2@sha256:d1d6bc0d3ee5e418fc12ea023b78739e0decba6c34e2b43bec55b89e18bd4412,2025
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
sha256: bd7192a5e82ef6dbac711c3433408a0330c8db1cd3482be1ccd4fbd0a63bc2f6
|
sha256: 83a9cbb179b1efd0b2acd6509583c7afcdbe63469ab033d8581d48d675a80b44
|
||||||
size: 452
|
size: 452
|
||||||
original:
|
original:
|
||||||
hackage: hasql-notifications-0.2.2.0
|
hackage: hasql-notifications-0.2.2.2
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
|
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ nix:
|
|||||||
|
|
||||||
extra-deps:
|
extra-deps:
|
||||||
- fuzzyset-0.2.4
|
- fuzzyset-0.2.4
|
||||||
- hasql-notifications-0.2.2.0
|
- hasql-notifications-0.2.2.2
|
||||||
- hasql-pool-1.0.1
|
- hasql-pool-1.0.1
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ packages:
|
|||||||
original:
|
original:
|
||||||
hackage: fuzzyset-0.2.4
|
hackage: fuzzyset-0.2.4
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hasql-notifications-0.2.2.0@sha256:a4e591ef3f06647b056567d3b66948c4a85371f05deb5434edb6ce190f7c845d,2021
|
hackage: hasql-notifications-0.2.2.2@sha256:d1d6bc0d3ee5e418fc12ea023b78739e0decba6c34e2b43bec55b89e18bd4412,2025
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
sha256: bd7192a5e82ef6dbac711c3433408a0330c8db1cd3482be1ccd4fbd0a63bc2f6
|
sha256: 83a9cbb179b1efd0b2acd6509583c7afcdbe63469ab033d8581d48d675a80b44
|
||||||
size: 452
|
size: 452
|
||||||
original:
|
original:
|
||||||
hackage: hasql-notifications-0.2.2.0
|
hackage: hasql-notifications-0.2.2.2
|
||||||
- completed:
|
- completed:
|
||||||
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
|
hackage: hasql-pool-1.0.1@sha256:3cfb4c7153a6c536ac7e126c17723e6d26ee03794954deed2d72bcc826d05a40,2302
|
||||||
pantry-tree:
|
pantry-tree:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 4.4 KiB |
@@ -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 |
@@ -218,6 +218,40 @@
|
|||||||
pdSchema: public
|
pdSchema: public
|
||||||
pdVolatility: Volatile
|
pdVolatility: Volatile
|
||||||
|
|
||||||
|
- - qiName: reset_db_schemas_config
|
||||||
|
qiSchema: public
|
||||||
|
- - pdDescription: null
|
||||||
|
pdFuncSettings: []
|
||||||
|
pdHasVariadic: false
|
||||||
|
pdName: reset_db_schemas_config
|
||||||
|
pdParams: []
|
||||||
|
pdReturnType:
|
||||||
|
contents:
|
||||||
|
contents:
|
||||||
|
qiName: void
|
||||||
|
qiSchema: pg_catalog
|
||||||
|
tag: Scalar
|
||||||
|
tag: Single
|
||||||
|
pdSchema: public
|
||||||
|
pdVolatility: Volatile
|
||||||
|
|
||||||
|
- - qiName: change_db_schemas_config
|
||||||
|
qiSchema: public
|
||||||
|
- - pdDescription: null
|
||||||
|
pdFuncSettings: []
|
||||||
|
pdHasVariadic: false
|
||||||
|
pdName: change_db_schemas_config
|
||||||
|
pdParams: []
|
||||||
|
pdReturnType:
|
||||||
|
contents:
|
||||||
|
contents:
|
||||||
|
qiName: void
|
||||||
|
qiSchema: pg_catalog
|
||||||
|
tag: Scalar
|
||||||
|
tag: Single
|
||||||
|
pdSchema: public
|
||||||
|
pdVolatility: Volatile
|
||||||
|
|
||||||
- - qiName: change_db_schema_and_full_reload
|
- - qiName: change_db_schema_and_full_reload
|
||||||
qiSchema: public
|
qiSchema: public
|
||||||
- - pdDescription: null
|
- - pdDescription: null
|
||||||
|
|||||||
@@ -36,3 +36,4 @@ server-timing-enabled = false
|
|||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
admin-server-config-enabled = false
|
||||||
|
|||||||
@@ -36,3 +36,4 @@ server-timing-enabled = false
|
|||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
admin-server-config-enabled = false
|
||||||
|
|||||||
@@ -36,3 +36,4 @@ server-timing-enabled = false
|
|||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
admin-server-config-enabled = false
|
||||||
|
|||||||
@@ -36,3 +36,4 @@ server-timing-enabled = false
|
|||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
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 = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
admin-server-config-enabled = true
|
||||||
app.settings.test = "test"
|
app.settings.test = "test"
|
||||||
app.settings.test2 = "test"
|
app.settings.test2 = "test"
|
||||||
|
|||||||
@@ -36,5 +36,6 @@ server-timing-enabled = false
|
|||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
admin-server-config-enabled = true
|
||||||
app.settings.test = "test"
|
app.settings.test = "test"
|
||||||
app.settings.test2 = "test"
|
app.settings.test2 = "test"
|
||||||
|
|||||||
@@ -36,5 +36,6 @@ server-timing-enabled = true
|
|||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
admin-server-config-enabled = true
|
||||||
app.settings.test = "test"
|
app.settings.test = "test"
|
||||||
app.settings.test2 = "test"
|
app.settings.test2 = "test"
|
||||||
|
|||||||
@@ -36,4 +36,5 @@ server-timing-enabled = false
|
|||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
admin-server-config-enabled = false
|
||||||
app.settings.test = "Bool False"
|
app.settings.test = "Bool False"
|
||||||
|
|||||||
@@ -39,3 +39,4 @@ PGRST_SERVER_TIMING_ENABLED: true
|
|||||||
PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock
|
PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock
|
||||||
PGRST_SERVER_UNIX_SOCKET_MODE: 777
|
PGRST_SERVER_UNIX_SOCKET_MODE: 777
|
||||||
PGRST_ADMIN_SERVER_PORT: 3001
|
PGRST_ADMIN_SERVER_PORT: 3001
|
||||||
|
PGRST_ADMIN_SERVER_CONFIG_ENABLED: true
|
||||||
|
|||||||
@@ -36,5 +36,6 @@ server-timing-enabled = true
|
|||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
admin-server-config-enabled = true
|
||||||
app.settings.test = "test"
|
app.settings.test = "test"
|
||||||
app.settings.test2 = "test"
|
app.settings.test2 = "test"
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ GRANT
|
|||||||
CREATE SCHEMA v1;
|
CREATE SCHEMA v1;
|
||||||
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
|
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
|
||||||
|
|
||||||
|
CREATE SCHEMA test;
|
||||||
|
GRANT USAGE ON SCHEMA test TO postgrest_test_anonymous;
|
||||||
|
|
||||||
CREATE TABLE authors_only ();
|
CREATE TABLE authors_only ();
|
||||||
GRANT SELECT ON authors_only TO postgrest_test_author;
|
GRANT SELECT ON authors_only TO postgrest_test_author;
|
||||||
|
|
||||||
@@ -226,3 +229,17 @@ $$ language sql;
|
|||||||
create function get_statement_timeout(items) returns text as $$
|
create function get_statement_timeout(items) returns text as $$
|
||||||
select current_setting('statement_timeout', true) as statement_timeout
|
select current_setting('statement_timeout', true) as statement_timeout
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
|
|
||||||
|
create function change_db_schemas_config() returns void as $_$
|
||||||
|
begin
|
||||||
|
alter role postgrest_test_authenticator set pgrst.db_schemas = 'test';
|
||||||
|
end $_$ volatile security definer language plpgsql;
|
||||||
|
|
||||||
|
create function reset_db_schemas_config() returns void as $_$
|
||||||
|
begin
|
||||||
|
alter role postgrest_test_authenticator reset pgrst.db_schemas;
|
||||||
|
end $_$ volatile security definer language plpgsql ;
|
||||||
|
|
||||||
|
create function test.get_current_schema() returns text as $$
|
||||||
|
select current_schema()::text;
|
||||||
|
$$ language sql;
|
||||||
|
|||||||