Compare commits
@@ -0,0 +1,15 @@
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-0
|
||||
|
||||
build_task:
|
||||
name: Build FreeBSD (Stack)
|
||||
install_script: pkg install -y postgresql13-client hs-stack
|
||||
|
||||
stack_cache:
|
||||
folders: /.stack
|
||||
fingerprint_script: cat postgrest.cabal stack.yaml.lock
|
||||
reupload_on_changes: false
|
||||
|
||||
build_script: stack build -j 1 --local-bin-path . --copy-bins
|
||||
bin_artifacts:
|
||||
path: postgrest
|
||||
@@ -1,9 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -21,7 +21,7 @@ For questions on how to use PostgREST, please use
|
||||
### Reporting an Issue
|
||||
|
||||
* Make sure you test against the latest [stable release](https://github.com/PostgREST/postgrest/releases/latest)
|
||||
and also against the latest [devel release](https://github.com/PostgREST/postgrest/releases/tag/devel).
|
||||
and also against the latest [nightly release](https://github.com/PostgREST/postgrest/releases/tag/nightly).
|
||||
It is possible we already fixed the bug you're experiencing.
|
||||
|
||||
* Provide steps to reproduce the issue, including your OS version and
|
||||
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
Before reporting a bug:
|
||||
If your database schema has changed while the PostgREST server is running,
|
||||
send the server a SIGUSR1 signal or restart it(http://postgrest.org/en/stable/admin.html#schema-reloading)
|
||||
to ensure the schema cache is not stale. This sometimes fixes apparent bugs.
|
||||
-->
|
||||
### Environment
|
||||
|
||||
* PostgreSQL version: (if using docker, specify the image)
|
||||
* PostgREST version: (if using docker, specify the image)
|
||||
* Operating system:
|
||||
|
||||
### Description of issue
|
||||
|
||||
(Expected behavior vs actual behavior)
|
||||
|
||||
(Steps to reproduce: Include a minimal SQL definition plus how you make the request to PostgREST and the response body)
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help us improve
|
||||
type: Bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Before reporting a bug:
|
||||
If your database schema has changed while the PostgREST server is running,
|
||||
send the server a SIGUSR1 signal or restart it (http://postgrest.org/en/stable/admin.html#schema-reloading) to ensure the schema cache is not stale. This sometimes fixes apparent bugs.
|
||||
-->
|
||||
### Environment
|
||||
|
||||
* PostgreSQL version: (if using docker, specify the image)
|
||||
* PostgREST version: (if using docker, specify the image)
|
||||
* Operating system:
|
||||
|
||||
### Description of issue
|
||||
|
||||
Describe the behavior you expected vs the actual behavior. Include:
|
||||
|
||||
- A minimal SQL definition.
|
||||
- How you make the request to PostgREST (curl command preferred).
|
||||
- The PostgREST response.
|
||||
@@ -1 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an enhancement for this project
|
||||
type: Feature
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
## Solution
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
@@ -2,20 +2,5 @@
|
||||
When submitting a new feature or fix:
|
||||
|
||||
- Add a new entry to the CHANGELOG - https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md#unreleased
|
||||
- If relevant, update the docs
|
||||
- Use a prefix for the PR title or commits, e.g. "fix: description of the fix".
|
||||
+ `add`, Add a new feature
|
||||
+ `amend`, To amend an unrealease commit
|
||||
+ `change`, Breaking changes
|
||||
+ `chore`, Maintenance, update sponsors, changelog, readme etc
|
||||
+ `ci`, CI configuration files and scripts
|
||||
+ `docs`, Documentation
|
||||
+ `fix`, Bug fix
|
||||
+ `nix`, Related to Nix
|
||||
+ `perf`, Performance improvements
|
||||
+ `refactor`, Refactoring code
|
||||
+ `remove`, Remove a feature or fix
|
||||
+ `test`, Adding tests
|
||||
+ Other prefixes may be used if necessary
|
||||
- If there's a breaking change, add `BREAKING CHANGE` and an explanation to your commit message
|
||||
- If relevant, update the docs - https://github.com/PostgREST/postgrest-docs
|
||||
-->
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# TODO: Remove this once a new actionlint release has been cut
|
||||
# and made its way to us through nixpkgs.
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- macos-15-intel
|
||||
- macos-26
|
||||
- ubuntu-24.04-arm
|
||||
- ubuntu-slim
|
||||
@@ -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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ inputs.prefix }}-
|
||||
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ inputs.prefix }}-
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Run anywhere
|
||||
|
||||
description: Runs the same code either in a VM or on the bare machine
|
||||
|
||||
inputs:
|
||||
vm:
|
||||
description: Which VM to run on.
|
||||
envs:
|
||||
description: List of relevant environment variables, which might need to be copied into the VM.
|
||||
prepare:
|
||||
description: Code to run in a prepare step, e.g. installing dependencies.
|
||||
run:
|
||||
description: Code to run as the main action.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: ${{ inputs.vm == 'freebsd' }}
|
||||
uses: vmactions/freebsd-vm@5a72679103d223925653750faa878a143340fbd0 # v1.5.0
|
||||
with:
|
||||
envs: ${{ inputs.envs }}
|
||||
prepare: ${{ inputs.prepare }}
|
||||
# Work around https://github.com/vmactions/freebsd-vm/issues/59
|
||||
run: |
|
||||
pw user add -n action -m
|
||||
su action -c '${{ inputs.run }}'
|
||||
- if: ${{ inputs.vm == '' }}
|
||||
name: Prepare
|
||||
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
|
||||
run: ${{ inputs.prepare }}
|
||||
- if: ${{ inputs.vm == '' }}
|
||||
name: Run
|
||||
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
|
||||
run: ${{ inputs.run }}
|
||||
@@ -11,16 +11,11 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: nixbuild/nix-quick-install-action@9f63be77f412a248c9d9a65a4c82cf066cdf8f0c # v35
|
||||
with:
|
||||
nix_conf: |-
|
||||
always-allow-substitutes = true
|
||||
max-jobs = auto
|
||||
- uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
|
||||
- uses: cachix/install-nix-action@v16
|
||||
- uses: cachix/cachix-action@v10
|
||||
with:
|
||||
name: postgrest
|
||||
authToken: ${{ inputs.authToken }}
|
||||
skipPush: ${{ inputs.authToken == '' }}
|
||||
- if: ${{ inputs.tools }}
|
||||
run: nix-env -f default.nix -iA ${{ inputs.tools }}
|
||||
shell: bash
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Get the FreeBSD PostgREST executable built by CirrusCI for the given GITHUB_COMMIT in GITHUB_REPOSITORY
|
||||
|
||||
# We use the GitHub API for 'check suites' to find the corresponding CirrusCI job, see:
|
||||
# https://docs.github.com/en/rest/reference/checks#list-check-suites-for-a-git-reference
|
||||
|
||||
cirrus_artifact_name=bin
|
||||
gh_accept_header="Accept: application/vnd.github.v3+json"
|
||||
|
||||
get_gh_check_runs_url() {
|
||||
gh_checks_list_url="https://api.github.com/repos/$GITHUB_REPOSITORY/commits/$GITHUB_COMMIT/check-suites"
|
||||
>&2 echo "Getting list of check-suites from $gh_checks_list_url ..."
|
||||
curl --fail -H "$gh_accept_header" "$gh_checks_list_url" \
|
||||
| jq -r '.check_suites[] | select(.app.slug == "cirrus-ci") | .check_runs_url'
|
||||
}
|
||||
|
||||
wait_for_cirrusci() {
|
||||
gh_check_runs_url="$(get_gh_check_runs_url)"
|
||||
>&2 echo "Waiting to CirrusCI run to complete (two hours maximum)..."
|
||||
for _ in $(seq 1 120); do
|
||||
echo "Checking for CirrusCI task status at $gh_check_runs_url ..."
|
||||
status=$(curl --fail "$gh_check_runs_url" | jq -r '.check_runs[] | .status')
|
||||
if [ "$status" == "completed" ]; then
|
||||
break
|
||||
else
|
||||
echo "CirrusCI task is still $status, waiting..."
|
||||
sleep 60
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# The CirrusCI taskid can change if a new check run is started for the same commit,
|
||||
# e.g. when pushing both a branch and tag. We make sure that we have the very
|
||||
# latest taskid by re-loading the 'gh_check_runs_url' and the 'check run' itself.
|
||||
get_cirrus_taskid() {
|
||||
gh_check_runs_url="$(get_gh_check_runs_url)"
|
||||
>&2 echo "Getting the CirrusCI task id from $gh_check_runs_url ..."
|
||||
curl --fail -H "$gh_accept_header" "$gh_check_runs_url" \
|
||||
| jq -r '.check_runs[] | .external_id'
|
||||
}
|
||||
|
||||
download_artifact() {
|
||||
cirrus_task_id="$(get_cirrus_taskid)"
|
||||
cirrus_artifact_url="https://api.cirrus-ci.com/v1/artifact/task/$cirrus_task_id/$cirrus_artifact_name.zip"
|
||||
>&2 echo "Attemping to download the CirrusCI artifact from $cirrus_artifact_url ..."
|
||||
curl --fail "$cirrus_artifact_url" -o freebsd.zip
|
||||
}
|
||||
|
||||
wait_for_cirrusci
|
||||
download_artifact
|
||||
|
||||
echo "Unpacking executable..."
|
||||
unzip freebsd.zip -d .
|
||||
rm -rf freebsd.zip
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Tag a release that will be built with Github Actions. The version is
|
||||
# read from 'postgrest.cabal'.
|
||||
|
||||
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
|
||||
echo "Tagging version v$version"
|
||||
git tag -f "v$version"
|
||||
|
||||
echo "Pushing tag..."
|
||||
git push -f origin "refs/tags/v$version"
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices"
|
||||
],
|
||||
"baseBranchPatterns": [
|
||||
"main",
|
||||
"/^v[0-9]+/"
|
||||
],
|
||||
"rebaseWhen": "conflicted",
|
||||
"pip_requirements": {
|
||||
"enabled": false
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchBaseBranches": [
|
||||
"/^v[0-9]+/"
|
||||
],
|
||||
"matchManagers": [
|
||||
"haskell-cabal"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"matchBaseBranches": [
|
||||
"/^v[0-9]+/"
|
||||
],
|
||||
"groupName": "all dependencies"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"haskell-cabal"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"base",
|
||||
"bytestring",
|
||||
"containers",
|
||||
"directory",
|
||||
"mtl",
|
||||
"parsec",
|
||||
"process",
|
||||
"text"
|
||||
],
|
||||
"groupName": "GHC dependencies"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"haskell-cabal"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"hasql",
|
||||
"hasql-dynamic-statements",
|
||||
"hasql-notifications",
|
||||
"hasql-transaction",
|
||||
"hasql-pool"
|
||||
],
|
||||
"groupName": "hasql"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"haskell-cabal"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"fuzzyset"
|
||||
],
|
||||
"allowedVersions": "<0.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 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; }
|
||||
|
||||
PGRST_GITHUB_COMMIT="$1"
|
||||
SCRIPT_DIR="$2"
|
||||
|
||||
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
|
||||
|
||||
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 3.6.0.0
|
||||
ghcup set cabal 3.6.0.0
|
||||
}
|
||||
|
||||
install_ghc() {
|
||||
ghcup install ghc 8.10.7
|
||||
ghcup set ghc 8.10.7
|
||||
}
|
||||
|
||||
install_packages
|
||||
|
||||
# Add ghcup to the PATH for this session
|
||||
[ -f ~/.ghcup/env ] && source ~/.ghcup/env
|
||||
|
||||
ghcup --version || install_ghcup
|
||||
cabal --version || install_cabal
|
||||
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
|
||||
|
||||
# Copy the built binary to the Dockerfile directory
|
||||
PGRST_BIN=$(cabal exec which postgrest | tail -1)
|
||||
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
|
||||
@@ -0,0 +1,16 @@
|
||||
# PostgREST docker hub image
|
||||
|
||||
FROM ubuntu:focal 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
|
||||
|
||||
CMD postgrest
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 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="v$6"
|
||||
IS_PRERELEASE="$7"
|
||||
|
||||
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
|
||||
[ -z $IS_PRERELEASE ] && sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:latest $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
|
||||
|
||||
sudo docker logout
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-slim
|
||||
# It triggers only when PR is already merged on either:
|
||||
#
|
||||
# - The merge event itself (action != labeled) or
|
||||
# - A label event with the right label (backport ...).
|
||||
#
|
||||
# The result will be that we can add the label before or after merge,
|
||||
# but the workflow will only run once the PR had been merged.
|
||||
if: >
|
||||
github.event.pull_request.merged &&
|
||||
(
|
||||
github.event.action != 'labeled' ||
|
||||
startsWith(github.event.label.name, 'backport')
|
||||
)
|
||||
steps:
|
||||
|
||||
# This actions creates the github token using the postgrest app secrets
|
||||
- name: Create Github App Token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.POSTGREST_CI_APP_ID }}
|
||||
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
permission-workflows: write # required when backporting CI changes
|
||||
|
||||
# This is required for backport action to cherry-pick the PR
|
||||
- name: Fetch PR ref
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
# Backport action that creates the PR with given settings
|
||||
- name: Create backport PR
|
||||
uses: korthout/backport-action@2e830a1d0b8269505846ddd407a70876913ad1f8 # v4.6
|
||||
with:
|
||||
github_token: ${{ steps.app-token.outputs.token }}
|
||||
pull_description: 'Backport for #${pull_number}.'
|
||||
pull_title: '${target_branch}: ${pull_title}'
|
||||
@@ -1,189 +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/**
|
||||
- flake.lock
|
||||
- .cirrus.yml
|
||||
- cabal.project*
|
||||
- postgrest.cabal
|
||||
- stack.yaml*
|
||||
- '**.hs'
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: build-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
static:
|
||||
name: Nix - Linux x86-64 static
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build static executable
|
||||
run: nix-build -A postgrestStatic -A postgrestStatic.tests
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: postgrest-linux-static-x86-64
|
||||
path: result/bin/postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build Docker image
|
||||
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
|
||||
- name: Save built Docker image as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: postgrest-docker-x86-64
|
||||
path: postgrest-docker.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
macos:
|
||||
name: Nix - MacOS
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Install nix-build-uncached
|
||||
run: nix-env -f default.nix -iA nix-build-uncached
|
||||
|
||||
- name: Build everything (default.nix)
|
||||
run: nix-build-uncached
|
||||
|
||||
- name: Build everything (shell.nix)
|
||||
run: nix-build-uncached shell.nix
|
||||
|
||||
|
||||
stack:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: FreeBSD x86-64
|
||||
runs-on: ubuntu-24.04
|
||||
vm: freebsd
|
||||
artifact: postgrest-freebsd-x86-64
|
||||
deps: pkg install -y git postgresql16-client hs-stack
|
||||
|
||||
- name: Linux aarch64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
artifact: postgrest-ubuntu-aarch64
|
||||
deps: sudo apt-get update && sudo apt-get install libpq-dev
|
||||
|
||||
- name: MacOS aarch64
|
||||
runs-on: macos-14
|
||||
artifact: postgrest-macos-aarch64
|
||||
deps: brew link --force libpq
|
||||
|
||||
- name: MacOS x86-64
|
||||
runs-on: macos-15-intel
|
||||
artifact: postgrest-macos-x86-64
|
||||
deps: brew link --force libpq
|
||||
|
||||
- name: Windows
|
||||
runs-on: windows-2022
|
||||
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
|
||||
artifact: postgrest-windows-x86-64
|
||||
|
||||
name: Stack - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
# Putting .stack in the working directory helps with moving this in and out of the FreeBSD VM.
|
||||
STACK_ROOT: ${{ github.workspace }}/.stack
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- if: ${{ !matrix.vm }}
|
||||
uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
|
||||
with:
|
||||
# This must match the version in stack.yaml's resolver
|
||||
ghc-version: 9.6.7
|
||||
enable-stack: true
|
||||
stack-no-global: true
|
||||
stack-setup-ghc: true
|
||||
- name: Cache .stack
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: .stack
|
||||
prefix: ${{ matrix.vm }}${{ matrix.vm && '-' }}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: ${{ matrix.vm }}${{ matrix.vm && '-' }}stack-work-${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
|
||||
suffix: ${{ hashFiles('main/**/*.hs', 'src/**/*.hs') }}
|
||||
- name: Build with Stack
|
||||
uses: ./.github/actions/run-anywhere
|
||||
with:
|
||||
vm: ${{ matrix.vm }}
|
||||
envs: STACK_ROOT
|
||||
prepare: ${{ matrix.deps }}
|
||||
run: |
|
||||
stack build --lock-file error-on-write --local-bin-path result --copy-bins
|
||||
strip result/postgrest*
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: |
|
||||
result/postgrest
|
||||
result/postgrest.exe
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
cabal:
|
||||
strategy:
|
||||
matrix:
|
||||
ghc: ['9.6.7', '9.8.4']
|
||||
fail-fast: false
|
||||
name: Cabal - Linux x86-64 - GHC ${{ matrix.ghc }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: haskell-actions/setup@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0
|
||||
with:
|
||||
ghc-version: ${{ matrix.ghc }}
|
||||
- name: Cache .cabal
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/packages
|
||||
~/.cabal/store
|
||||
prefix: cabal-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
|
||||
suffix: ${{ hashFiles('postgrest.cabal', 'cabal.project') }}
|
||||
- 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,53 +0,0 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: style-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-style:
|
||||
name: Lint & Style
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- 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
|
||||
|
||||
commit:
|
||||
if: github.event_name != 'push' # we don't run this on a push, a failure on push disrupts the release workflow
|
||||
name: Commit
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 100 # fetch history (last 100 commits) instead of default shallow clone history, this is deemed enough for a PR history
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: gitTools.commitCheck.bin
|
||||
- name: Run commitlint (check locally with `nix-shell --run postgrest-commitlint`)
|
||||
run: |
|
||||
# Fetch target branch explicitly
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
|
||||
# Run commitlint
|
||||
postgrest-commitlint --from origin/${{ github.base_ref }} --to HEAD
|
||||
@@ -4,66 +4,482 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
- rel-*
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- rel-*
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
uses: ./.github/workflows/check.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
uses: ./.github/workflows/docs.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
test:
|
||||
name: Test
|
||||
uses: ./.github/workflows/test.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
tag:
|
||||
name: Tag
|
||||
concurrency:
|
||||
# Never tag outdated commits on the main branch by skipping superseded commits
|
||||
group: ci-tag-${{ (github.ref == 'refs/heads/main' && github.ref) || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
if: vars.RELEASE_ENABLED
|
||||
runs-on: ubuntu-slim
|
||||
needs:
|
||||
- docs
|
||||
- test
|
||||
- build
|
||||
Lint-Style:
|
||||
name: Lint & check code style
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
|
||||
- name: Tag latest commit
|
||||
tools: style
|
||||
- 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
|
||||
|
||||
|
||||
Test-Nix:
|
||||
name: Test (Nix)
|
||||
runs-on: ubuntu-latest
|
||||
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@v3
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: tests
|
||||
|
||||
- name: Run coverage (IO tests and Spec tests against PostgreSQL 14)
|
||||
run: postgrest-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@v3.1.0
|
||||
with:
|
||||
files: ./coverage/codecov.json
|
||||
|
||||
- name: Run doctests
|
||||
if: always()
|
||||
run: nix-shell --run postgrest-test-doctests
|
||||
|
||||
- name: Check the spec tests for idempotence
|
||||
if: always()
|
||||
run: postgrest-test-spec-idempotence
|
||||
|
||||
|
||||
Test-Pg-Nix:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pgVersion: [9.6, 10, 11, 12, 13, 14]
|
||||
name: Test PG ${{ matrix.pgVersion }} (Nix)
|
||||
runs-on: ubuntu-latest
|
||||
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@v3
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: tests withTools
|
||||
|
||||
- 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 }} -f test/io/fixtures.sql postgrest-test-io
|
||||
|
||||
- name: Run query cost tests
|
||||
if: always()
|
||||
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-querycost
|
||||
|
||||
|
||||
Test-Memory-Nix:
|
||||
name: Test memory (Nix)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: memory
|
||||
- name: Run memory tests
|
||||
run: postgrest-test-memory
|
||||
|
||||
|
||||
Build-Static-Nix:
|
||||
name: Build Linux static (Nix)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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@v3
|
||||
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@v3
|
||||
with:
|
||||
name: postgrest-docker-x64
|
||||
path: postgrest-docker.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build and push everything to Cachix (main branch only)
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
run: |
|
||||
nix-build
|
||||
nix-env -f default.nix -iA devTools
|
||||
postgrest-push-cachix
|
||||
|
||||
|
||||
Build-Stack:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux & test
|
||||
runs-on: ubuntu-latest
|
||||
cache: |
|
||||
~/.stack
|
||||
.stack-work
|
||||
test: true
|
||||
pgdir: /usr/lib/postgresql
|
||||
artifact: postgrest-ubuntu-x64
|
||||
|
||||
- name: MacOS & test
|
||||
runs-on: macos-latest
|
||||
cache: |
|
||||
~/.stack
|
||||
.stack-work
|
||||
test: true
|
||||
pgdir: /usr/local/Cellar/postgresql
|
||||
artifact: postgrest-macos-x64
|
||||
|
||||
- name: Windows
|
||||
runs-on: windows-latest
|
||||
cache: |
|
||||
~\AppData\Roaming\stack
|
||||
~\AppData\Local\Programs\stack
|
||||
.stack-work
|
||||
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
|
||||
# We'd need to make test/with_tmp_db run on Windows first
|
||||
# test: true
|
||||
artifact: postgrest-windows-x64
|
||||
|
||||
name: Build ${{ matrix.name }} (Stack)
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Stack working files cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ matrix.cache }}
|
||||
key: ${{ runner.os }}-${{ hashFiles('stack.yaml.lock') }}
|
||||
- name: Install dependencies
|
||||
if: ${{ matrix.deps }}
|
||||
run: ${{ matrix.deps }}
|
||||
- name: Build with Stack
|
||||
run: stack build --local-bin-path result --copy-bins
|
||||
- name: Run Spec tests with Stack
|
||||
if: ${{ matrix.test }}
|
||||
run: |
|
||||
postgresql_bin="$(find ${{ matrix.pgdir }} -maxdepth 2 -type d -name bin | head -n 1)"
|
||||
echo "Using PostgreSQL binaries at $postgresql_bin ..."
|
||||
PATH="$postgresql_bin:$PATH" test/with_tmp_db stack test
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: |
|
||||
result/postgrest
|
||||
result/postgrest.exe
|
||||
if-no-files-found: error
|
||||
|
||||
Get-FreeBSD-CirrusCI:
|
||||
name: Get FreeBSD build from CirrusCI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get FreeBSD executable from CirrusCI
|
||||
env:
|
||||
# GITHUB_SHA does weird things for pull request, so we roll our own:
|
||||
GITHUB_COMMIT: ${{github.event.pull_request.head.sha || github.sha}}
|
||||
run: .github/get_cirrusci_freebsd
|
||||
- name: Save executable as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: postgrest-freebsd-x64
|
||||
path: postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
Build-Cabal-Arm:
|
||||
name: Build aarch64 (Cabal)
|
||||
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/rel-') }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
remotepath: ${{ steps.Remote-Dir.outputs.remotepath }}
|
||||
env:
|
||||
GITHUB_COMMIT: ${{ github.sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- id: Remote-Dir
|
||||
name: Unique directory name for the remote build
|
||||
run: echo "::set-output name=remotepath::postgrest-build-$(uuidgen)"
|
||||
- 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
|
||||
script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$REMOTE_DIR"
|
||||
- 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@v2.3.1
|
||||
with:
|
||||
name: postgrest-ubuntu-aarch64
|
||||
path: result/postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
Prepare-Release:
|
||||
name: Prepare release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- Lint-Style
|
||||
- Test-Nix
|
||||
- Test-Pg-Nix
|
||||
- Test-Memory-Nix
|
||||
- Build-Static-Nix
|
||||
- Build-Stack
|
||||
#- Get-FreeBSD-CirrusCI
|
||||
- Build-Cabal-Arm
|
||||
outputs:
|
||||
version: ${{ steps.Identify-Version.outputs.version }}
|
||||
isprerelease: ${{ steps.Identify-Version.outputs.isprerelease }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: Identify-Version
|
||||
name: Identify the version to be released
|
||||
run: |
|
||||
tag_version="${GITHUB_REF##*/}"
|
||||
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
|
||||
if [ "$tag_version" != "v$cabal_version" ]; then
|
||||
echo "Tagged version ($tag_version) does not match the one in postgrest.cabal (v$cabal_version). Aborting release..."
|
||||
exit 1
|
||||
else
|
||||
git tag -f "devel"
|
||||
git push -f origin "devel"
|
||||
echo "Version to be released is $cabal_version"
|
||||
echo "::set-output name=version::$cabal_version"
|
||||
fi
|
||||
|
||||
if [[ "$cabal_version" != *.*.*.* ]]; then
|
||||
echo "Version is for a full release (version does not have four components)"
|
||||
else
|
||||
echo "Version is for a pre-release (version has four components, e.g., 1.1.1.1)"
|
||||
echo "::set-output name=isprerelease::1"
|
||||
fi
|
||||
- name: Identify changes from CHANGELOG.md
|
||||
run: |
|
||||
version="${{ steps.Identify-Version.outputs.version }}"
|
||||
isprerelease="${{ steps.Identify-Version.outputs.isprerelease }}"
|
||||
|
||||
if [ -n "$isprerelease" ]; then
|
||||
echo "Getting unreleased changes..."
|
||||
sed -n "1,/## Unreleased/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
else
|
||||
echo "Full release (no '-' in version), 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@v3
|
||||
with:
|
||||
name: release-changes
|
||||
path: CHANGES.md
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
Release-GitHub:
|
||||
name: Release on GitHub
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: Prepare-Release
|
||||
env:
|
||||
VERSION: ${{ needs.Prepare-Release.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
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-v$VERSION-linux-static-x64.tar.xz" \
|
||||
-C artifacts/postgrest-linux-static-x64 postgrest
|
||||
|
||||
# No need to release Ubuntu, as the static Linux binary built with Nix
|
||||
# covers all Linux use-cases
|
||||
#tar cfJv "release-bundle/postgrest-v$VERSION-ubuntu-x64.tar.xz" \
|
||||
# -C artifacts/postgrest-ubuntu-x64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-v$VERSION-macos-x64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-x64 postgrest
|
||||
|
||||
# TODO: Fix timeouts for FreeBSD builds in Cirrus
|
||||
#tar cJvf "release-bundle/postgrest-v$VERSION-freebsd-x64.tar.xz" \
|
||||
# -C artifacts/postgrest-freebsd-x64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-v$VERSION-ubuntu-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-ubuntu-aarch64 postgrest
|
||||
|
||||
zip "release-bundle/postgrest-v$VERSION-windows-x64.zip" \
|
||||
artifacts/postgrest-windows-x64/postgrest.exe
|
||||
|
||||
- name: Save release bundle
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release-bundle
|
||||
path: release-bundle
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish release on GitHub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
isprerelease="${{ needs.Prepare-Release.outputs.isprerelease }}"
|
||||
echo "Releasing version v$VERSION on GitHub (isprerelease=$isprerelease)..."
|
||||
|
||||
gh release delete "v$VERSION" || true
|
||||
gh release create "v$VERSION" \
|
||||
-F artifacts/release-changes/CHANGES.md \
|
||||
${isprerelease:+"--prerelease"} \
|
||||
release-bundle/*
|
||||
|
||||
|
||||
Release-Docker:
|
||||
name: Release on Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- Build-Cabal-Arm
|
||||
- Prepare-Release
|
||||
env:
|
||||
GITHUB_COMMIT: ${{ github.sha }}
|
||||
DOCKER_REPO: postgrest
|
||||
DOCKER_USER: stevechavez
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
||||
VERSION: ${{ needs.Prepare-Release.outputs.version }}
|
||||
ISPRERELEASE: ${{ needs.Prepare-Release.outputs.isprerelease }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: release
|
||||
- name: Download Docker image
|
||||
uses: actions/download-artifact@v3
|
||||
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:v$VERSION"
|
||||
docker push "$DOCKER_REPO/postgrest:v$VERSION"
|
||||
|
||||
# Only tag 'latest' for full releases
|
||||
if [[ -z "$ISPRERELEASE" ]]; then
|
||||
echo "Pushing to 'latest' tag for full release of v$VERSION ..."
|
||||
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
|
||||
docker push "$DOCKER_REPO"/postgrest:latest
|
||||
else
|
||||
echo "Skipping pushing to 'latest' tag for v$VERSION pre-release..."
|
||||
fi
|
||||
- name: Publish images for ARM builds on Docker Hub
|
||||
uses: appleboy/ssh-action@master
|
||||
env:
|
||||
REMOTE_DIR: ${{ needs.Build-Cabal-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,VERSION,ISPRERELEASE
|
||||
script: bash ~/$REMOTE_DIR/docker-publish.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" "$VERSION" "$ISPRERELEASE"
|
||||
# TODO: Enable dockerhub description update again, once a solution for the permission problem is found:
|
||||
# https://github.com/docker/hub-feedback/issues/1927
|
||||
# - name: Update descriptions on Docker Hub
|
||||
# env:
|
||||
# DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
||||
# run: |
|
||||
# if [[ -z "$ISPRERELEASE" ]]; then
|
||||
# echo "Updating description on Docker Hub..."
|
||||
# postgrest-release-dockerhub-description
|
||||
# else
|
||||
# echo "Skipping updating description for pre-release..."
|
||||
# fi
|
||||
|
||||
Clean-Arm-Server:
|
||||
name: Remove copied files from server
|
||||
needs:
|
||||
- Build-Cabal-Arm
|
||||
- Release-Docker
|
||||
if: ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/rel-')) }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- 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,54 +0,0 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
paths:
|
||||
- .github/workflows/docs.yaml
|
||||
- .github/actions/setup-nix/**
|
||||
- default.nix
|
||||
- nix/**
|
||||
- flake.lock
|
||||
- docs/**
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: docs-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.build.bin
|
||||
- run: postgrest-docs-build
|
||||
- run: git diff --exit-code HEAD locales || echo "Please commit changes to the locales/ folder after running postgrest-docs-build."
|
||||
|
||||
|
||||
spellcheck:
|
||||
name: Spellcheck
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.spellcheck.bin docs.dictcheck.bin
|
||||
- name: Run spellcheck
|
||||
run: postgrest-docs-spellcheck
|
||||
- name: Run dictcheck
|
||||
run: postgrest-docs-dictcheck
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Linkcheck
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '1 2 * * 3'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linkcheck:
|
||||
name: Linkcheck
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.linkcheck.bin
|
||||
|
||||
- name: Run Linkcheck
|
||||
id: linkcheck
|
||||
run: postgrest-docs-linkcheck
|
||||
|
||||
# This actions creates the github token using the postgrest app secrets
|
||||
- name: Create Github App Token (Runs only on linkcheck failure)
|
||||
id: app-token
|
||||
if: ${{ failure() && steps.linkcheck.outcome == 'failure' }} # only create the token on linkcheck failure
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.POSTGREST_CI_APP_ID }}
|
||||
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
|
||||
permission-issues: write # required for commenting on issues
|
||||
|
||||
- name: Notify on linkcheck failure by commenting on GH Issue 4106
|
||||
if: ${{ failure() && steps.linkcheck.outcome == 'failure' }}
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
issue-number: 4106
|
||||
body: |
|
||||
**Linkcheck Job Failed!**
|
||||
|
||||
A broken link was detected in the docs. Please check the [failed run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Loadtest
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
Loadtest-Nix:
|
||||
name: Loadtest (Nix)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: loadtest
|
||||
- name: Run loadtest
|
||||
run: |
|
||||
postgrest-loadtest-against main
|
||||
postgrest-loadtest-report > loadtest/loadtest.md
|
||||
- name: Upload report
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: loadtest.md
|
||||
path: loadtest/loadtest.md
|
||||
if-no-files-found: error
|
||||
@@ -1,183 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- devel
|
||||
- v*
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for the same tag.
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
github:
|
||||
name: GitHub
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-slim
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- 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: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Create release bundle with archives for all builds
|
||||
run: |
|
||||
find artifacts -type f -iname postgrest -exec chmod +x {} \;
|
||||
|
||||
mkdir -p release-bundle
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-linux-static-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-aarch64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-freebsd-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-ubuntu-aarch64 postgrest
|
||||
|
||||
zip --junk-paths "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x86-64.zip" \
|
||||
artifacts/postgrest-windows-x86-64/postgrest.exe
|
||||
|
||||
- name: Save release bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
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 CHANGES.md \
|
||||
--prerelease
|
||||
gh release upload --clobber devel release-bundle/*
|
||||
else
|
||||
gh release create "${GITHUB_REF_NAME}" \
|
||||
-t "${GITHUB_REF_NAME}" \
|
||||
--verify-tag \
|
||||
-F CHANGES.md \
|
||||
release-bundle/*
|
||||
fi
|
||||
|
||||
|
||||
docker:
|
||||
name: Docker Hub
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs:
|
||||
- github
|
||||
if: |
|
||||
vars.DOCKER_REPO && vars.DOCKER_USER
|
||||
env:
|
||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Download x86-64 Docker image
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: postgrest-docker-x86-64
|
||||
- name: Download aarch64 binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: postgrest-ubuntu-aarch64
|
||||
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
- uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
- name: Build aarch64 Docker image
|
||||
run: |
|
||||
# This only pushes the image via digest, not a tag. This will not appear
|
||||
# in the image list on Docker Hub, yet. It will be later added to the main
|
||||
# tag's manifest.
|
||||
docker buildx build \
|
||||
-t "$DOCKER_REPO/postgrest" \
|
||||
--platform linux/arm64 \
|
||||
--output push-by-digest=true,type=image,push=true \
|
||||
--metadata-file metadata.json \
|
||||
.
|
||||
echo "SHA256_ARM=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_ENV"
|
||||
- name: Publish images on Docker Hub
|
||||
run: |
|
||||
docker load -i postgrest-docker.tar.gz
|
||||
|
||||
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
|
||||
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
|
||||
docker buildx imagetools create --append \
|
||||
-t "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
|
||||
"$DOCKER_REPO/postgrest@$SHA256_ARM"
|
||||
|
||||
# Only tag 'latest' for full releases
|
||||
if [ "${GITHUB_REF_NAME}" != "devel" ]; then
|
||||
echo "Pushing to 'latest' tag for full release of ${GITHUB_REF_NAME} ..."
|
||||
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
|
||||
docker push "$DOCKER_REPO"/postgrest:latest
|
||||
docker buildx imagetools create --append \
|
||||
-t "$DOCKER_REPO/postgrest:latest" \
|
||||
"$DOCKER_REPO/postgrest@$SHA256_ARM"
|
||||
else
|
||||
echo "Skipping push to 'latest' tag for pre-release..."
|
||||
fi
|
||||
|
||||
- uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
|
||||
if: github.ref == 'refs/tags/devel'
|
||||
name: Docker Hub Description
|
||||
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
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Upload Reports
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Loadtest"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: Loadtest
|
||||
permissions:
|
||||
checks: write
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Download from Artifacts
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: ${{ github.event.workflow.name }}
|
||||
run_id: ${{github.event.workflow_run.id }}
|
||||
name: loadtest.md
|
||||
path: artifacts
|
||||
- name: Upload to GitHub Checks
|
||||
uses: LouisBrunner/checks-action@v1.2.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,178 +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/**
|
||||
- flake.lock
|
||||
- .stylish-haskell.yaml
|
||||
- cabal.project
|
||||
- postgrest.cabal
|
||||
- '**.hs'
|
||||
- test/**
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: test-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
# Hack for enabling color output, see:
|
||||
# https://github.com/actions/runner/issues/241#issuecomment-842566950
|
||||
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- 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 cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
|
||||
run: postgrest-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
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:
|
||||
# Latest version is tested via `coverage` above.
|
||||
pgVersion: [13, 14, 15, 16]
|
||||
name: PG ${{ matrix.pgVersion }}
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
# Hack for enabling color output, see:
|
||||
# https://github.com/actions/runner/issues/241#issuecomment-842566950
|
||||
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.testSpec.bin tests.testObservability.bin tests.testIO.bin tests.testBigSchema.bin withTools.pg-${{ matrix.pgVersion }}.bin cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run spec tests
|
||||
if: always()
|
||||
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-spec
|
||||
|
||||
- name: Run observability tests
|
||||
if: always()
|
||||
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-observability
|
||||
|
||||
- name: Run IO tests
|
||||
if: always()
|
||||
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-io -vv
|
||||
|
||||
- name: Run IO tests on a big schema
|
||||
if: always()
|
||||
run: postgrest-with-pg-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
|
||||
|
||||
|
||||
memory:
|
||||
name: Memory
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.testMemory.bin cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run memory tests
|
||||
run: postgrest-test-memory
|
||||
|
||||
|
||||
loadtest:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
kind: ['mixed', 'jwt-hs', 'jwt-hs-cache', 'jwt-hs-cache-worst', 'jwt-rsa', 'jwt-rsa-cache', 'jwt-rsa-cache-worst']
|
||||
name: Loadtest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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 cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run loadtest
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
|
||||
run: |
|
||||
if [ "$TARGET_BRANCH" = "main" ]; then
|
||||
latest_tag=$(git tag --sort=-creatordate --list "v*" | head -n1)
|
||||
else
|
||||
latest_tag=$(git tag --merged HEAD --sort=-creatordate "v*" | head -n1)
|
||||
fi
|
||||
postgrest-loadtest-against -k ${{ matrix.kind }} "$TARGET_BRANCH" "$latest_tag"
|
||||
postgrest-loadtest-report -g ${{ matrix.kind }} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
flake:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runs-on:
|
||||
- macos-15-intel # x86_64-darwin
|
||||
- macos-14 # aarch64-darwin
|
||||
- ubuntu-24.04 # x86_64-linux
|
||||
- ubuntu-24.04-arm # aarch64-linux
|
||||
name: Flake Check
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Run flake check
|
||||
run: |
|
||||
nix flake check
|
||||
@@ -14,7 +14,7 @@ site
|
||||
.#*
|
||||
*.swp
|
||||
result*
|
||||
dist-*
|
||||
dist-newstyle
|
||||
postgrest.hp
|
||||
postgrest.prof
|
||||
__pycache__
|
||||
@@ -22,7 +22,3 @@ __pycache__
|
||||
coverage
|
||||
.hpc
|
||||
loadtest
|
||||
.history
|
||||
.docs-build
|
||||
gen_targets.http
|
||||
gen_jwk.json
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
version: 2
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
@@ -4,35 +4,40 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
|
||||
## Sponsors
|
||||
|
||||
<table align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/cybertec.svg">
|
||||
<img width="222px" src="static/cybertec-new.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.svg">
|
||||
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
|
||||
<img width="296px" src="static/2ndquadrant.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.euronodes.com/postgrest" target="_blank">
|
||||
<img width="296px" src="static/euronodes.svg">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/neon.jpg">
|
||||
<a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/gnuhost.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/bytebase.svg">
|
||||
<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">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/oblivious.jpg">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -41,14 +46,12 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
|
||||
## Lead Backers
|
||||
|
||||
- [Roboflow](https://github.com/roboflow)
|
||||
- Evans Fernandes
|
||||
- [Jan Sommer](https://github.com/nerfpops)
|
||||
- [Franz Gusenbauer](https://www.igutech.at/)
|
||||
|
||||
## Backers
|
||||
|
||||
- Zac Miller
|
||||
- Tsingson Qin
|
||||
- Michel Pelletier
|
||||
- Jay Hannah
|
||||
@@ -73,31 +76,6 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
<img width="222px" src="static/timescaledb.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img max-width="222px" height="88" src="static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
|
||||
<img width="222px" src="static/2ndquadrant.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="222px" src="static/oblivious.jpg">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://code.build/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="222px" src="static/code-build.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/tembo.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,805 +1,27 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. From version `14.0` onwards PostgREST follows a `MAJOR.PATCH` two-part versioning. Only even-numbered MAJOR versions will be released, reserving odd-numbered MAJOR versions for development.
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [14.15] - 2026-07-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix admin server dying silently by @Vlix, @mkleczek, @steve-chavez in #5012
|
||||
|
||||
## [14.14] - 2026-06-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix admin server not logging cause of failure by @taimoorzaeem in #5012
|
||||
|
||||
## [14.13] - 2026-06-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix connection retrying message in `PGRST000` error by @netqo in #4980
|
||||
+ Remove redundant "Retrying the connection." from message because it is logged separately
|
||||
- Fix request failures when `work_mem` is set on a role by @laurenceisla in #4955
|
||||
|
||||
## [14.12] - 2026-05-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix race condition in pool_available metric causing negative values during network instability by @mkleczek in #4622
|
||||
|
||||
## [14.11] - 2026-05-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix login with uppercase and mixed case role names by @taimoorzaeem in #4678
|
||||
- Restore Listener query shape so it can be found in `pg_stat_activity` by @mkleczek in #4857 #4859
|
||||
- The LISTEN channel now automatically recovers when it stops working due to a PostgreSQL bug @laurenceisla in #3147
|
||||
- Fix misleading "Functions" name on schema cache summary in startup logs by @taimoorzaeem in #4821
|
||||
|
||||
## [14.10] - 2026-04-16
|
||||
|
||||
### Added
|
||||
|
||||
- Log when the pool is released during schema cache reload on `log-level=debug` by @mkleczek in #4668
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix unnecessary connection pool flushes during schema cache reloading by @mkleczek in #4645
|
||||
|
||||
## [14.9] - 2026-04-10
|
||||
|
||||
### Added
|
||||
|
||||
- Log host, port and pg version of listener database connection by @mkleczek in #4617 #4618
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove red herring warp logs on default log-level, only emit them on `log-level=debug` by @steve-chavez in #4799
|
||||
|
||||
## [14.8] - 2026-04-03
|
||||
|
||||
### Added
|
||||
|
||||
- Log a `HINT` when the LISTEN channel stops working due to a PostgreSQL bug by @laurenceisla in #4581
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix invalid OpenAPI 2.0 format for integer types (`smallint`, `integer`, `bigint`) by @arturbent0 in #4641
|
||||
|
||||
## [14.7] - 2026-03-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix not logging SIGTERM and SIGINT by @steve-chavez in #4728
|
||||
|
||||
## [14.6] - 2026-03-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix leaking table and function names when calculating error hint by @taimoorzaeem in #4675
|
||||
|
||||
## [14.5] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't hide async exceptions in logs by @stevechavez in #4646
|
||||
|
||||
## [14.4] - 2026-01-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure Listener connections are released by @mkleczek in #4614
|
||||
- Fix incorrectly filtering the returned representation for PATCH requests when using `or/and` filters by @laurenceisla in #3707
|
||||
- Fix listener running with exception masked after first failure by @mkleczek #4615
|
||||
|
||||
## [14.3] - 2026-01-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix performance and high memory usage of relation hint calculation by @mkleczek in #4462, #4463
|
||||
|
||||
## [14.2] - 2025-12-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `hasSingleUnnamedParam` incorrectly matching functions with named parameters by @joelonsql in #4553
|
||||
+ Functions with a single named parameter (e.g., `foo(data json)`) no longer incorrectly match the single-param fallback, returning a clean `PGRST202` error instead of a confusing PostgreSQL `42883` error.
|
||||
- Fix misleading logs on unsupported PostgreSQL versions by @taimoorzaeem in #4519
|
||||
- Fix regression where the `PGRST103` error response was truncated by @laurenceisla in #4455
|
||||
+ Happened when an `offset` was greater than the rows requested and `Prefer: count=exact` was sent.
|
||||
- Fix not returning `Content-Length` on empty HTTP `201` responses by @laurenceisla in #4518
|
||||
- Fix inaccurate Server-Timing header durations by @steve-chavez in #4522
|
||||
- Fix inaccurate "Schema cache queried" logs by @steve-chavez in #4522
|
||||
|
||||
## [14.1] - 2025-11-05
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fix `db-pre-config` function failing when function names are pg reserved words by @taimoorzaeem in #4380
|
||||
- Fix `server-host=!6` incorrectly binds to IPv4 address by @taimoorzaeem in #3202
|
||||
|
||||
## [14.0] - 2025-10-24
|
||||
|
||||
### Added
|
||||
|
||||
- Bounded JWT cache using the SIEVE algorithm by @mkleczek in #4084
|
||||
+ It now uses a fixed size cache instead of arbitrary sized cache.
|
||||
- Add `--ready` flag for postgrest healthcheck by @taimoorzaeem in #4239
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix not logging OpenAPI queries when `log-query=main-query` is enabled by @steve-chavez in #4226
|
||||
- Fix not logging explain query when `log-query=main-query` is enabled by @steve-chavez in #4319
|
||||
- Fix not logging transaction variables and db-pre-request function when `log-query=main-query` is enabled by @steve-chavez in #3934
|
||||
- Fix not logging the JSON message to stderr on a `PGRST002` error by @laurenceisla in #4129
|
||||
- Fix reloading the Schema Cache unnecessarily on a `PGRST002` error by @laurenceisla in #4367
|
||||
- Fix schema cache loading taking a long time for large schemas by @mkleczek in #4360, #3704
|
||||
|
||||
### Changed
|
||||
|
||||
- Drop support for PostgreSQL EOL version 12 by @wolfgangwalther in #3865
|
||||
- From now on PostgREST will follow a `MAJOR.PATCH` two-part versioning. Only even-numbered MAJOR versions will be released, reserving odd-numbered MAJOR versions for development.
|
||||
- Replaced `jwt-cache-max-lifetime` config with `jwt-cache-max-entries` by @mkleczek in #4084
|
||||
- `log-query` config now takes a boolean instead of a string value by @steve-chavez in #3934
|
||||
|
||||
## [13.0.8] - 2025-10-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix loading utf-8 config files with `ASCII` locale set by @taimoorzaeem in #4386
|
||||
|
||||
## [13.0.7] - 2025-09-14
|
||||
|
||||
### Added
|
||||
|
||||
- Improve the `PGRST106` error when the requested schema is invalid by @laurenceisla in #4089
|
||||
+ It now shows the invalid schema in the `message` field.
|
||||
+ The exposed schemas are now listed in the `hint` instead of the `message` field.
|
||||
- Improve error details of `PGRST301` error by @taimoorzaeem in #4051
|
||||
|
||||
## [13.0.6] - 2025-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix logging the Haskell type instead of the listener error message directly by @laurenceisla in #3588
|
||||
- Fix format of `IPv6` address logged at PostgREST startup by @taimoorzaeem in #4291
|
||||
- Fix empty enum in `preferParams` OpenAPI parameter by @laurenceisla in #4292
|
||||
|
||||
## [13.0.5] - 2025-08-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix OpenAPI broken docs link by @taimoorzaeem in #4080
|
||||
- Fix OpenAPI specification incorrectly exposing GET methods for volatile functions by @joelonsql in #4174
|
||||
- Fix empty spread embeddings return unexpected SQL error by @taimoorzaeem in #3887
|
||||
- Fix `/metrics` endpoint not responding with `Content-Type` header by @taimoorzaeem in #4271
|
||||
|
||||
## [13.0.4] - 2025-06-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix regression that makes full-text search not work on domain types based on `tsvector` by @laurenceisla in #4135
|
||||
- Fix `jwt-aud` config not failing when set to an invalid URI by @taimoorzaeem in #4132
|
||||
|
||||
## [13.0.3] - 2025-06-16
|
||||
|
||||
- Fix `max-affected` preference not failing with RPC when `handling=strict` by @taimoorzaeem in #4100
|
||||
- Fix a property definition's type in OpenAPI not showing the correct base type of a recursive domain by @laurenceisla in #4136
|
||||
|
||||
### Fixed
|
||||
|
||||
## [13.0.2] - 2025-06-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix regression that makes `ORDER BY` with nulls-order not work alongside limits by @laurenceisla in #4109
|
||||
|
||||
## [13.0.1] - 2025-06-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix jwt error returning HTTP status `400` for invalid role by @taimoorzaeem in #3601
|
||||
- Fix `db-extra-search-path` cannot be set to nothing by @taimoorzaeem in #4074
|
||||
+ It can now be disabled by setting it to empty string.
|
||||
+ Schema Cache load error is now logged including `db-schemas` and `db-extra-search-path` config values.
|
||||
|
||||
## [13.0.0] - 2025-05-08
|
||||
|
||||
### Added
|
||||
|
||||
- #3558, Add the `admin-server-host` config to set the host for the admin server - @develop7
|
||||
- #3607, Log to stderr when the JWT secret is less than 32 characters long - @laurenceisla
|
||||
- #2858, Performance improvements when calling RPCs via GET using indexes in more cases - @wolfgangwalther
|
||||
- #3560, Log resolved host in "Listening on ..." messages - @develop7
|
||||
- #3727, Log maximum pool size - @steve-chavez
|
||||
- #1536, Add string comparison feature for jwt-role-claim-key - @taimoorzaeem
|
||||
- #3747, Allow `not_null` value for the `is` operator - @taimoorzaeem
|
||||
- #2255, Apply `to_tsvector()` explicitly to the full-text search filtered column (excluding `tsvector` types) - @laurenceisla
|
||||
- #1578, Log the main SQL query to stderr at the current `log-level` when `log-query=main-query` - @laurenceisla
|
||||
- #3903, Log connection pool borrows on `log-level=debug` - @taimoorzaeem
|
||||
- #3041, Allow spreading one-to-many and many-to-many embedded resources - @laurenceisla
|
||||
+ The selected columns in the embedded resources are aggregated into arrays
|
||||
+ Aggregates are not supported
|
||||
- #2967, Add `Proxy-Status` header for better error response - @taimoorzaeem
|
||||
- #4016, Add `Content-Length` response header - @laurenceisla
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3693, Prevent spread embedding to allow aggregates when they are disabled - @laurenceisla
|
||||
- #3693, A nested spread embedding now correctly groups by the fields of its top parent relationship - @laurenceisla
|
||||
- #3693, Fix spread embedding errors when using the `count()` aggregate without a field - @laurenceisla
|
||||
+ Fixed `"column reference <col> is ambiguous"` error when selecting `?select=...table(col,count())`
|
||||
+ Fixed `"column <json_aggregate>.<alias> does not exist"` error when selecting `?select=...table(aias:count())`
|
||||
- #3727, Clarify "listening" logs - @steve-chavez
|
||||
- #3795, Clarify `Accept: vnd.pgrst.object` error message - @steve-chavez
|
||||
- #3697, #3602, Handle queries on non-existing table gracefully - @taimoorzaeem
|
||||
- #3600, #3926, Improve JWT errors - @taimoorzaeem
|
||||
- #3013, Fix `order=` with POST, PATCH, PUT and DELETE requests - @taimoorzaeem
|
||||
- #3965, Fix filter on unselected columns in a table-valued function - @taimoorzaeem
|
||||
- #4052, Fix schema cache load duplicate objects with different object type but same oid - @taimoorzaeem
|
||||
|
||||
### Changed
|
||||
|
||||
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
|
||||
- #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther
|
||||
- #2052, Dropped support for PostgreSQL 11 - @wolfgangwalther
|
||||
- #3508, PostgREST now fails to start when `server-port` and `admin-server-port` config options are the same - @develop7
|
||||
- #3607, PostgREST now fails to start when the JWT secret is less than 32 characters long - @laurenceisla
|
||||
- #3644, Fail schema cache lookup with invalid `db-schemas` or `db-extra-search-path` config - @wolfgangwalther
|
||||
- Previously, this would silently return 200 - OK on the root endpoint, but don't provide any usable endpoints.
|
||||
- Note: This also applies when deleting the `public` schema - both config options default to that.
|
||||
- #3757, Remove support for `Prefer: params=single-object` - @joelonsql
|
||||
+ This preference was deprecated in favor of Functions with an array of JSON objects
|
||||
- #3013, Drop support for Limited updates/deletes
|
||||
+ The feature was complicated and largely unused.
|
||||
- #3956, Drop `/config` endpoint of admin server - @steve-chavez
|
||||
+ The endpoint was at risk of being left unprotected when exposing it.
|
||||
+ The accompanying `admin-server-config-enabled` config was also dropped.
|
||||
- #3598, PostgREST now validates the `kid` parameter of the JWT - @wolfgangwalther
|
||||
+ If the JWT contains a ``kid`` parameter, then PostgREST will look for the JSON Web Key in the `jwt-secret`.
|
||||
+ If the JWT doesn't contain a `kid`, the behavior should be backwards compatible. PostgREST will try each key in the `jwt-secret` one by one until it finds one that works.
|
||||
- #3697, #3602, Querying non-existent table now returns `PGRST205` error instead of empty json - @taimoorzaeem
|
||||
- #3600, #3926, Improve JWT errors - @taimoorzaeem
|
||||
+ Return `PGRST301` error when `Bearer` in auth header is sent empty
|
||||
+ Diagnostic error messages instead of exposed internals
|
||||
+ Return new `PGRST303` error when jwt claims decoding fails
|
||||
- #3906, Return `PGRST125` and `PGRST126` errors instead of empty json - @taimoorzaeem
|
||||
|
||||
## [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-22
|
||||
|
||||
### 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
|
||||
|
||||
### Added
|
||||
|
||||
- #2887, Add Preference `max-affected` to limit affected resources - @taimoorzaeem
|
||||
- #3171, Add an ability to dump config via admin API - @skywriter
|
||||
- #3171, #3046, Log schema cache stats to stderr - @steve-chavez
|
||||
- #3210, Dump schema cache through admin API - @taimoorzaeem
|
||||
- #2676, Performance improvement on bulk json inserts, around 10% increase on requests per second by removing `json_typeof` from write queries - @steve-chavez
|
||||
- #3435, Add log-level=debug, for development purposes - @steve-chavez
|
||||
- #1526, Add `/metrics` endpoint on admin server - @steve-chavez
|
||||
- Exposes connection pool metrics, schema cache metrics
|
||||
- #3404, Show the failed MESSAGE or DETAIL in the `details` field of the `PGRST121` (could not parse RAISE 'PGRST') error - @laurenceisla
|
||||
- #3404, Show extra information in the `PGRST121` (could not parse RAISE 'PGRST') error - @laurenceisla
|
||||
+ Shows the failed MESSAGE or DETAIL in the `details` field
|
||||
+ Shows the correct JSON format in the `hints` field
|
||||
- #3340, Log when the LISTEN channel gets a notification - @steve-chavez
|
||||
- #3184, Log full pg version to stderr on connection - @steve-chavez
|
||||
- #3242. Add config `db-hoisted-tx-settings` to apply only hoisted function settings - @taimoorzaeem
|
||||
- #3214, #3229 Log connection pool events on log-level=debug - @steve-chavez, @laurenceisla
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3237, Dump media handlers and timezones with --dump-schema - @wolfgangwalther
|
||||
- #3323, #3324, Don't hide error on LISTEN channel failure - @steve-chavez
|
||||
- #3330, Incorrect admin server `/ready` response on slow schema cache loads - @steve-chavez
|
||||
- #3345, Fix in-database configuration values not loading for `pgrst.server_trace_header` and `pgrst.server_cors_allowed_origins` - @laurenceisla
|
||||
- #3404, Clarify the `PGRST121` (could not parse RAISE 'PGRST') error message - @laurenceisla
|
||||
- #3267, Fix wrong `503 Service Unavailable` on pg error `53400` - @taimoorzaeem
|
||||
- #2985, Fix not adding `application_name` on all connection strings - @steve-chavez
|
||||
- #3424, Admin `/live` and `/ready` now differentiates a failure as 500 status - @steve-chavez
|
||||
+ 503 status is still given when postgREST is in a recovering state
|
||||
- #3478, Media Types are parsed case insensitively - @develop7
|
||||
- #3533, #3536, Fix listener silently failing on read replica - @steve-chavez
|
||||
+ If the LISTEN connection fails, it's retried with exponential backoff
|
||||
- #3414, Force listener to connect to read-write instances using `target_session_attrs` - @steve-chavez
|
||||
- #3255, Fix incorrect `413 Request Entity Too Large` on pg errors `54*` - @taimoorzaeem
|
||||
- #3549, Remove verbosity from error logs starting with "An error occurred..." and replacing it with "Failed to..." - @laurenceisla
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Support for PostgreSQL versions 9.6, 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions. See https://www.postgresql.org/support/versioning/.
|
||||
- `Prefer: params=single-object` is deprecated. Use [a function with a single unnamed JSON parameter](https://postgrest.org/en/latest/references/api/functions.html#function-single-json) instead. - @steve-chavez
|
||||
|
||||
### Documentation
|
||||
|
||||
- #3289, Add dark mode. Can be toggled by a button in the bottom right corner. - @laurenceisla
|
||||
- #3384, Add architecture diagram and documentation - @steve-chavez
|
||||
|
||||
## [12.0.3] - 2024-05-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3149, Misleading "Starting PostgREST.." logs on schema cache reloading - @steve-chavez
|
||||
- #3205, Fix wrong subquery error returning a status of 400 Bad Request - @steve-chavez
|
||||
- #3224, Return status code 406 for non-accepted media type instead of code 415 - @wolfgangwalther
|
||||
- #3160, Fix using select= query parameter for custom media type handlers - @wolfgangwalther
|
||||
- #3361, Clarify PGRST204(column not found) error message - @steve-chavez
|
||||
- #3373, Remove rejected mediatype `application/vnd.pgrst.object+json` from response - @taimoorzaeem
|
||||
- #3418, Fix OpenAPI not tagging a FK column correctly on O2O relationships - @laurenceisla
|
||||
- #3256, Fix wrong http status for pg error `42P17 infinite recursion` - @taimoorzaeem
|
||||
|
||||
## [12.0.2] - 2023-12-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3124, Fix table's media type handlers not working for all schemas - @steve-chavez
|
||||
- #3126, Fix empty row on media type handler function - @steve-chavez
|
||||
|
||||
## [12.0.1] - 2023-12-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3054, Fix not allowing special characters in JSON keys - @laurenceisla
|
||||
- #2344, Replace JSON parser error with a clearer generic message - @develop7
|
||||
- #3100, Add missing in-database configuration option for `jwt-cache-max-lifetime` - @laurenceisla
|
||||
- #3089, The any media type handler now sets `Content-Type: application/octet-stream` by default instead of `Content-Type: application/json` - @steve-chavez
|
||||
|
||||
## [12.0.0] - 2023-12-01
|
||||
|
||||
### Added
|
||||
|
||||
- #1614, Add `db-pool-automatic-recovery` configuration to disable connection retrying - @taimoorzaeem
|
||||
- #2492, Allow full response control when raising exceptions - @taimoorzaeem, @laurenceisla
|
||||
- #2771, #2983, #3062, #3055 Add `Server-Timing` response header - @taimoorzaeem, @develop7, @laurenceisla
|
||||
- #2698, Add config `jwt-cache-max-lifetime` and implement JWT caching - @taimoorzaeem
|
||||
- #2943, Add `handling=strict/lenient` for Prefer header - @taimoorzaeem
|
||||
- #2441, Add config `server-cors-allowed-origins` to specify CORS origins - @taimoorzaeem
|
||||
- #2825, SQL handlers for custom media types - @steve-chavez
|
||||
+ Solves #1548, #2699, #2763, #2170, #1462, #1102, #1374, #2901
|
||||
- #2799, Add timezone in Prefer header - @taimoorzaeem
|
||||
- #3001, Add `statement_timeout` set on functions - @taimoorzaeem
|
||||
- #3045, Apply superuser settings on impersonated roles if they have PostgreSQL 15 `GRANT SET ON PARAMETER` privilege - @steve-chavez
|
||||
- #915, Add support for aggregate functions - @timabdulla
|
||||
+ The aggregate functions SUM(), MAX(), MIN(), AVG(), and COUNT() are now supported.
|
||||
+ It's disabled by default, you can enable it with `db-aggregates-enabled`.
|
||||
- #3057, Log all internal database errors to stderr - @laurenceisla
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3015, Fix unnecessary count() on RPC returning single - @steve-chavez
|
||||
- #1070, Fix HTTP status responses for upserts - @taimoorzaeem
|
||||
+ `PUT` returns `201` instead of `200` when rows are inserted
|
||||
+ `POST` with `Prefer: resolution=merge-duplicates` returns `200` instead of `201` when no rows are inserted
|
||||
- #3019, Transaction-Scoped Settings are now shown clearly in the Postgres logs - @laurenceisla
|
||||
+ Shows `set_config('pgrst.setting_name', $1)` instead of `setconfig($1, $2)`
|
||||
+ Does not apply to role settings and `app.settings.*`
|
||||
- #2420, Fix bogus message when listening on port 0 - @develop7
|
||||
- #3067, Fix Acquision Timeout errors logging to stderr when `log-level=crit` - @laurenceisla
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
|
||||
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
|
||||
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
|
||||
- Removed [db-use-legacy-gucs](https://postgrest.org/en/v11.2/references/configuration.html#db-use-legacy-gucs) - @laurenceisla
|
||||
+ All PostgreSQL versions now use GUCs in JSON format for [Headers, Cookies and JWT claims](https://postgrest.org/en/v12/references/transactions.html#request-headers-cookies-and-jwt-claims).
|
||||
|
||||
## [11.2.2] - 2023-10-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2824, Fix regression by reverting fix that returned 206 when first position = length in a `Range` header - @laurenceisla, @strengthless
|
||||
|
||||
## [11.2.1] - 2023-10-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2899, Fix `application/vnd.pgrst.array` not accepted as a valid mediatype - @taimoorzaeem
|
||||
- #2524, Fix schema cache and configuration reloading with `NOTIFY` not working on Windows - @diogob, @laurenceisla
|
||||
- #2915, Fix duplicate headers in response - @taimoorzaeem
|
||||
- #2824, Fix range request with first position same as length return status 206 - @taimoorzaeem
|
||||
- #2939, Fix wrong `Preference-Applied` with `Prefer: tx=commit` when transaction is rollbacked - @steve-chavez
|
||||
- #2939, Fix `count=exact` not being included in `Preference-Applied` - @steve-chavez
|
||||
- #2800, Fix not including to-one embed resources that had a `NULL` value in any of the selected fields when doing null filtering on them - @laurenceisla
|
||||
- #2846, Fix error when requesting `Prefer: count=<type>` and doing null filtering on embedded resources - @laurenceisla
|
||||
- #2959, Fix setting `default_transaction_isolation` unnecessarily - @steve-chavez
|
||||
- #2929, Fix arrow filtering on RPC returning dynamic TABLE with composite type - @steve-chavez
|
||||
- #2963, Fix RPCs not embedding correctly when using overloaded functions for computed relationships - @laurenceisla
|
||||
- #2970, Fix regression that rejects URI connection strings with certain unescaped characters in the password - @laurenceisla, @steve-chavez
|
||||
|
||||
## [11.2.0] - 2023-08-10
|
||||
|
||||
### Added
|
||||
|
||||
- #2523, Data representations - @aljungberg
|
||||
+ Allows for flexible API output formatting and input parsing on a per-column type basis using regular SQL functions configured in the database
|
||||
+ Enables greater flexibility in the form and shape of your APIs, both for output and input, making PostgREST a more versatile general-purpose API server
|
||||
+ Examples include base64 encode/decode your binary data (like a `bytea` column containing an image), choose whether to present a timestamp column as seconds since the Unix epoch or as an ISO 8601 string, or represent fixed precision decimals as strings, not doubles, to preserve precision
|
||||
+ ...and accept the same in `POST/PUT/PATCH` by configuring the reverse transformation(s)
|
||||
+ Other use-cases include custom representation of enums, arrays, nested objects, CSS hex colour strings, gzip compressed fields, metric to imperial conversions, and much more
|
||||
+ Works when using the `select` parameter to select only a subset of columns, embedding through complex joins, renaming fields, with views and computed columns
|
||||
+ Works when filtering on a formatted column without extra indexes by parsing to the canonical representation
|
||||
+ Works for data `RETURNING` operations, such as requesting the full body in a POST/PUT/PATCH with `Prefer: return=representation`
|
||||
+ Works for batch updates and inserts
|
||||
+ Completely optional, define the functions in the database and they will be used automatically everywhere
|
||||
+ Data representations preserve the ability to write to the original column and require no extra storage or complex triggers (compared to using `GENERATED ALWAYS` columns)
|
||||
+ Note: data representations require Postgres 10 (Postgres 11 if using `IN` predicates); data representations are not implemented for RPC
|
||||
- #2647, Allow to verify the PostgREST version in SQL: `select distinct application_name from pg_stat_activity`. - @laurenceisla
|
||||
- #2856, Add the `--version` CLI option that prints the version information - @laurenceisla
|
||||
- #1655, Improve `details` field of the singular error response - @taimoorzaeem
|
||||
- #740, Add `Preference-Applied` in response for `Prefer: return=representation/headers-only/minimal` - @taimoorzaeem
|
||||
- #1601, Add optional `nulls=stripped` parameter for mediatypes `application/vnd.pgrst.array+json` and `application/vnd.pgrst.object+json` - @taimoorzaeem
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2821, Fix OPTIONS not accepting all available media types - @steve-chavez
|
||||
- #2834, Fix compilation on Ubuntu by being compatible with GHC 9.0.2 - @steve-chavez
|
||||
- #2840, Fix `Prefer: missing=default` with DOMAIN default values - @steve-chavez
|
||||
- #2849, Fix HEAD unnecessarily executing aggregates - @steve-chavez
|
||||
- #2594, Fix unused index on jsonb/jsonb arrow filter and order (``/bets?data->>contractId=eq.1`` and ``/bets?order=data->>contractId``) - @steve-chavez
|
||||
- #2861, Fix character and bit columns with fixed length not inserting/updating properly - @laurenceisla
|
||||
+ Fixes the error "value too long for type character(1)" when the char length of the column was bigger than one.
|
||||
- #2862, Fix null filtering on embedded resource when using a column name equal to the relation name - @steve-chavez
|
||||
- #1586, Fix function parameters of type character and bit not ignoring length - @laurenceisla
|
||||
+ Fixes the error "value too long for type character(1)" when the char length of the parameter was bigger than one.
|
||||
- #2881, Fix error when a function returns `RECORD` or `SET OF RECORD` - @laurenceisla
|
||||
- #2896, Fix applying superuser settings for impersonated role - @steve-chavez
|
||||
|
||||
### Deprecated
|
||||
|
||||
- #2863, Deprecate resource embedding target disambiguation - @steve-chavez
|
||||
+ The `/table?select=*,other!fk(*)` must be used to disambiguate
|
||||
+ The server aids in choosing the `!fk` by sending a `hint` on the error whenever an ambiguous request happens.
|
||||
|
||||
## [11.1.0] - 2023-06-07
|
||||
|
||||
### Added
|
||||
|
||||
- #2786, Limit idle postgresql connection lifetime - @robx
|
||||
+ New option `db-pool-max-idletime` (default 30s).
|
||||
+ This is equivalent to the old option `db-pool-timeout` of PostgREST 10.0.0.
|
||||
+ A config alias for `db-pool-timeout` is included.
|
||||
- #2703, Add pre-config function - @steve-chavez
|
||||
+ New config option `db-pre-config`(empty by default)
|
||||
+ Allows using the in-database configuration without SUPERUSER
|
||||
- #2781, When `db-channel-enabled` is false, start automatic connection recovery on a new request when pool connections are closed with `pg_terminate_backend` - @steve-chavez
|
||||
+ Mitigates the lack of LISTEN/NOTIFY for schema cache reloading on read replicas.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2791, Fix dropping schema cache reload notifications - @steve-chavez
|
||||
- #2801, Stop retrying connection when "no password supplied" - @steve-chavez
|
||||
|
||||
## [11.0.1] - 2023-04-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2762, Fixes "permission denied for schema" error during schema cache load - @steve-chavez
|
||||
- #2756, Fix bad error message on generated columns when using `Prefer: missing=default` - @steve-chavez
|
||||
- #1139, Allow a 30 second skew for JWT validation - @steve-chavez
|
||||
+ It used to be 1 second, which was too strict
|
||||
|
||||
## [11.0.0] - 2023-04-16
|
||||
|
||||
### Added
|
||||
|
||||
- #1414, Add related orders - @steve-chavez
|
||||
+ On a many-to-one or one-to-one relationship, you can order a parent by a child column `/projects?select=*,clients(*)&order=clients(name).desc.nullsfirst`
|
||||
- #1233, #1907, #2566, Allow spreading embedded resources - @steve-chavez
|
||||
+ On a many-to-one or one-to-one relationship, you can unnest a json object with `/projects?select=*,...clients(client_name:name)`
|
||||
+ Allows including the join table columns when resource embedding
|
||||
+ Allows disambiguating a recursive m2m embed
|
||||
+ Allows disambiguating an embed that has a many-to-many relationship using two foreign keys on a junction
|
||||
- #2340, Allow embedding without selecting any column - @steve-chavez
|
||||
- #2563, Allow `is.null` or `not.is.null` on an embedded resource - @steve-chavez
|
||||
+ Offers a more flexible replacement for `!inner`, e.g. `/projects?select=*,clients(*)&clients=not.is.null`
|
||||
+ Allows doing an anti join, e.g. `/projects?select=*,clients(*)&clients=is.null`
|
||||
+ Allows using or across related tables conditions
|
||||
- #1100, Customizable OpenAPI title - @AnthonyFisi
|
||||
- #2506, Add `server-trace-header` for tracing HTTP requests. - @steve-chavez
|
||||
+ When the client sends the request header specified in the config it will be included in the response headers.
|
||||
- #2694, Make `db-root-spec` stable. - @steve-chavez
|
||||
+ This can be used to override the OpenAPI spec with a custom database function
|
||||
- #1567, On bulk inserts, missing values can get the column DEFAULT by using the `Prefer: missing=default` header - @steve-chavez
|
||||
- #2501, Allow filtering by`IS DISTINCT FROM` using the `isdistinct` operator, e.g. `/people?alias=isdistinct.foo`
|
||||
- #1569, Allow `any/all` modifiers on the `eq,like,ilike,gt,gte,lt,lte,match,imatch` operators, e.g. `/tbl?id=eq(any).{1,2,3}` - @steve-chavez
|
||||
- This converts the input into an array type
|
||||
- #2561, Configurable role settings - @steve-chavez
|
||||
- Database roles that are members of the connection role get their settings applied, e.g. doing
|
||||
`ALTER ROLE anon SET statement_timeout TO '5s'` will result in that `statement_timeout` getting applied for that role.
|
||||
- Works when switching roles when a JWT is sent
|
||||
- Settings can be reloaded with `NOTIFY pgrst, 'reload config'`.
|
||||
- #2468, Configurable transaction isolation level with `default_transaction_isolation` - @steve-chavez
|
||||
- Can be set per function `create function .. set default_transaction_isolation = 'repeatable read'`
|
||||
- Or per role `alter role .. set default_transaction_isolation = 'serializable'`
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2651, Add the missing `get` path item for RPCs to the OpenAPI output - @laurenceisla
|
||||
- #2648, Fix inaccurate error codes with new ones - @laurenceisla
|
||||
+ `PGRST204`: Column is not found
|
||||
+ `PGRST003`: Timed out when acquiring connection to db
|
||||
- #1652, Fix function call with arguments not inlining - @steve-chavez
|
||||
- #2705, Fix bug when using the `Range` header on `PATCH/DELETE` - @laurenceisla
|
||||
+ Fix the`"message": "syntax error at or near \"RETURNING\""` error
|
||||
+ Fix doing a limited update/delete when an `order` query parameter was present
|
||||
- #2742, Fix db settings and pg version queries not getting prepared - @steve-chavez
|
||||
- #2618, Fix `PATCH` requests not recognizing embedded filters and using the top-level resource instead - @steve-chavez
|
||||
|
||||
### Changed
|
||||
|
||||
- #2705, The `Range` header is now only considered on `GET` requests and is ignored for any other method - @laurenceisla
|
||||
+ Other methods should use the `limit/offset` query parameters for sub-ranges
|
||||
+ `PUT` requests no longer return an error when this header is present (using `limit/offset` still triggers the error)
|
||||
- #2733, Remove bulk RPC call with the `Prefer: params=multiple-objects` header. A function with a JSON array or object parameter should be used instead.
|
||||
|
||||
## [10.2.0] - 2023-04-12
|
||||
|
||||
### Added
|
||||
|
||||
- #2663, Limit maximal postgresql connection lifetime - @robx
|
||||
+ New option `db-pool-max-lifetime` (default 30m)
|
||||
+ `db-pool-acquisition-timeout` is no longer optional and defaults to 10s
|
||||
+ Fixes postgresql resource leak with long-lived connections (#2638)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2667, Fix `db-pool-acquisition-timeout` not logging to stderr when the timeout is reached - @steve-chavez
|
||||
|
||||
## [10.1.2] - 2023-02-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2565, Fix bad M2M embedding on RPC - @steve-chavez
|
||||
- #2575, Replace misleading error message when no function is found with a hint containing functions/parameters names suggestions - @laurenceisla
|
||||
- #2582, Move explanation about "single parameters" from the `message` to the `details` in the error output - @laurenceisla
|
||||
- #2569, Replace misleading error message when no relationship is found with a hint containing parent/child names suggestions - @laurenceisla
|
||||
- #1405, Add the required OpenAPI items object when the parameter is an array - @laurenceisla
|
||||
- #2592, Add upsert headers for POST requests to the OpenAPI output - @laurenceisla
|
||||
- #2623, Fix FK pointing to VIEW instead of TABLE in OpenAPI output - @laurenceisla
|
||||
- #2622, Consider any PostgreSQL authentication failure as fatal and exit immediately - @michivi
|
||||
- #2620, Fix `NOTIFY pgrst` not reloading the db connections catalog cache - @steve-chavez
|
||||
|
||||
## [10.1.1] - 2022-11-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2548, Fix regression when embedding views with partial references to multi column FKs - @wolfgangwalther
|
||||
- #2558, Fix regression when requesting limit=0 and `db-max-row` is set - @laurenceisla
|
||||
- #2542, Return a clear error without hitting the database when trying to update or insert an unknown column with `?columns` - @aljungberg
|
||||
|
||||
## [10.1.0] - 2022-10-28
|
||||
|
||||
### Added
|
||||
|
||||
- #2348, Add `db-pool-acquisition-timeout` configuration option, time in seconds to wait to acquire a connection. - @robx
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2261, #2349, #2467, Reduce allocations communication with PostgreSQL, particularly for request bodies. - @robx
|
||||
- #2401, #2444, Fix SIGUSR1 to fully flush connections pool. - @robx
|
||||
- #2428, Fix opening an empty transaction on failed resource embedding - @steve-chavez
|
||||
- #2455, Fix embedding the same table multiple times - @steve-chavez
|
||||
- #2518, Fix a regression when embedding views where base tables have a different column order for FK columns - @wolfgangwalther
|
||||
- #2458, Fix a regression with the location header when inserting into views with PKs from multiple tables - @wolfgangwalther
|
||||
- #2356, Fix a regression in openapi output with mode follow-privileges - @wolfgangwalther
|
||||
- #2283, Fix infinite recursion when loading schema cache with self-referencing view - @wolfgangwalther
|
||||
- #2343, Return status code 200 for PATCH requests which don't affect any rows - @wolfgangwalther
|
||||
- #2481, Treat computed relationships not marked SETOF as M2O/O2O relationship - @wolfgangwalther
|
||||
- #2534, Fix embedding a computed relationship with a normal relationship - @steve-chavez
|
||||
- #2362, Fix error message when [] is used inside select - @wolfgangwalther
|
||||
- #2475, Disallow !inner on computed columns - @wolfgangwalther
|
||||
- #2285, Ignore leading and trailing spaces in column names when parsing the query string - @wolfgangwalther
|
||||
- #2545, Fix UPSERT with PostgreSQL 15 - @wolfgangwalther
|
||||
- #2459, Fix embedding views with multiple references to the same base column - @wolfgangwalther
|
||||
|
||||
### Changed
|
||||
|
||||
- #2444, Removed `db-pool-timeout` option, because this was removed upstream in hasql-pool. - @robx
|
||||
- #2343, PATCH requests that don't affect any rows no longer return 404 - @wolfgangwalther
|
||||
- #2537, Stricter parsing of query string. Instead of silently ignoring, the parser now throws on invalid syntax like json paths for embeddings, hints for regular columns, empty casts or fts languages, etc. - @wolfgangwalther
|
||||
|
||||
### Deprecated
|
||||
|
||||
- #1385, Deprecate bulk-calls when including the `Prefer: params=multiple-objects` in the request. A function with a JSON array or object parameter should be used instead for a better performance.
|
||||
|
||||
## [10.0.0] - 2022-08-18
|
||||
|
||||
### Added
|
||||
|
||||
- #1933, #2109, Add a minimal health check endpoint - @steve-chavez
|
||||
+ For enabling this, the `admin-server-port` config must be set explictly
|
||||
+ A `<host>:<admin_server_port>/live` endpoint is available for checking if postgrest is running on its port/socket. 200 OK = alive, 503 = dead.
|
||||
+ A `<host>:<admin_server_port>/ready` endpoint is available for checking a correct internal state(the database connection plus the schema cache). 200 OK = ready, 503 = not ready.
|
||||
- #1988, Add the current user to the request log on stdout - @DavidLindbom, @wolfgangwalther
|
||||
- #1823, Add the ability to run postgrest without any configuration. - @wolfgangwalther
|
||||
+ #1991, Add the ability to run without `db-uri` using libpq's PG environment variables to connect. - @wolfgangwalther
|
||||
+ #1769, Add the ability to run without `db-schemas`, defaulting to `db-schemas=public`. - @wolfgangwalther
|
||||
+ #1689, Add the ability to run without `db-anon-role` disabling anonymous access. - @wolfgangwalther
|
||||
- #1543, Allow access to fields of composite types in select=, order= and filters through JSON operators -> and ->>. - @wolfgangwalther
|
||||
- #2075, Allow access to array items in ?select=, ?order= and filters through JSON operators -> and ->>. - @wolfgangwalther
|
||||
- #2156, #2211, Allow applying `limit/offset` to UPDATE/DELETE to only affect a subset of rows - @steve-chavez
|
||||
+ It requires an explicit `order` on a unique column(s)
|
||||
- #1917, Add error codes with the `"PGRST"` prefix to the error response body to differentiate PostgREST errors from PostgreSQL errors - @laurenceisla
|
||||
- #1917, Normalize the error response body by always having the `detail` and `hint` error fields with a `null` value if they are empty - @laurenceisla
|
||||
- #2176, Errors raised with `SQLSTATE` now include the message and the code in the response body - @laurenceisla
|
||||
- #2236, Support POSIX regular expression operators for row filtering - @enote-kane
|
||||
- #2202, Allow returning XML from RPCs - @fjf2002
|
||||
- #2268, Allow returning XML from single-column queries - @fjf2002
|
||||
- #2300, RPC POST for function w/single unnamed XML param #2300 - @fjf2002
|
||||
- #1564, Allow geojson output by specifying the `Accept: application/geo+json` media type - @steve-chavez
|
||||
+ Requires postgis >= 3.0
|
||||
+ Works for GET, RPC, POST/PATCH/DELETE with `Prefer: return=representation`.
|
||||
+ Resource embedding works and the embedded rows will go into the `properties` key
|
||||
+ In case of multiple geometries in the same table, you can choose which one will go into the `geometry` key with the usual `?select` query parameter.
|
||||
- #1082, Add security definitions to the OpenAPI output - @laurenceisla
|
||||
- #2378, Support http OPTIONS method on RPC and root path - @steve-chavez
|
||||
- #2354, Allow getting the EXPLAIN plan of a request by using the `Accept: application/vnd.pgrst.plan` header - @steve-chavez
|
||||
+ Only allowed if the `db-plan-enabled` config is set to true
|
||||
+ Can generate the plan for different media types using the `for` parameter: `Accept: application/vnd.pgrst.plan; for="application/vnd.pgrst.object"`
|
||||
+ Different options for the plan can be used with the `options` parameter: `Accept: application/vnd.pgrst.plan; options=analyze|verbose|settings|buffers|wal`
|
||||
+ The plan can be obtained in text or json by using different media type suffixes: `Accept: application/vnd.pgrst.plan+text` and `Accept: application/vnd.pgrst.plan+json`.
|
||||
- #2144, Support computed relationships which allow extending and overriding relationships for resource embedding - @steve-chavez, @wolfgangwalther
|
||||
- #1984, Detect one-to-one relationships for resource embedding - @steve-chavez
|
||||
+ Detected when there's a foreign key with a unique constraint or when a foreign key is also a primary key
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther
|
||||
- #2107, Clarify error for failed schema cache load. - @steve-chavez
|
||||
+ From `Database connection lost. Retrying the connection` to `Could not query the database for the schema cache. Retrying.`
|
||||
- #1771, Fix silently ignoring filter on a non-existent embedded resource - @steve-chavez
|
||||
- #2152, Remove functions, which are uncallable because of unnamend arguments from schema cache and OpenAPI output. - @wolfgangwalther
|
||||
- #2145, Fix accessing json array fields with -> and ->> in ?select= and ?order=. - @wolfgangwalther
|
||||
- #2155, Ignore `max-rows` on POST, PATCH, PUT and DELETE - @steve-chavez
|
||||
- #2254, Fix inferring a foreign key column as a primary key column on views - @steve-chavez
|
||||
- #2070, Restrict generated many-to-many relationships - @steve-chavez
|
||||
+ Only adds many-to-many relationships when: a table has FKs to two other tables and these FK columns are part of the table's PK columns.
|
||||
- #2278, Allow casting to types with underscores and numbers(e.g. `select=oid_array::_int4`) - @steve-chavez
|
||||
- #2277, #2238, #1643, Prevent views from breaking one-to-many/many-to-one embeds when using column or FK as target - @steve-chavez
|
||||
+ When using a column or FK as target for embedding(`/tbl?select=*,col-or-fk(*)`), only tables are now detected and views are not.
|
||||
+ You can still use a column or an inferred FK on a view to embed a table(`/view?select=*,col-or-fk(*)`)
|
||||
- #2317, Increase the `db-pool-timeout` to 1 hour to prevent frequent high connection latency - @steve-chavez
|
||||
- #2341, The search path now correctly identifies schemas with uppercase and special characters in their names (regression) - @laurenceisla
|
||||
- #2364, "404 Not Found" on nested routes and "405 Method Not Allowed" errors no longer start an empty database transaction - @steve-chavez
|
||||
- #2342, Fix inaccurate result count when an inner embed was selected after a normal embed in the query string - @laurenceisla
|
||||
- #2376, OPTIONS requests no longer start an empty database transaction - @steve-chavez
|
||||
- #2395, Allow using columns with dollar sign($) without double quoting in filters and `select` - @steve-chavez
|
||||
- #2410, Fix loop crash error on startup in Postgres 15 beta 3. Log: "UNION types \"char\" and text cannot be matched". - @yevon
|
||||
- #2397, Fix race conditions managing database connection helper - @robx
|
||||
- #2269, Allow `limit=0` in the request query to return an empty array - @gautam1168, @laurenceisla
|
||||
- #2401, Ensure database connections can't outlive SIGUSR1 - @robx
|
||||
|
||||
### Changed
|
||||
|
||||
- #2001, Return 204 No Content without Content-Type for RPCs returning VOID - @wolfgangwalther
|
||||
+ Previously, those RPCs would return "null" as a body with Content-Type: application/json.
|
||||
- #2156, `limit/offset` now limits the affected rows on UPDATE/DELETE - @steve-chavez
|
||||
+ Previously, `limit/offset` only limited the returned rows but not the actual updated rows
|
||||
- #2155, `max-rows` is no longer applied on POST/PATCH/PUT/DELETE returned rows - @steve-chavez
|
||||
+ This was misleading because the affected rows were not really affected by `max-rows`, only the returned rows were limited
|
||||
- #2070, Restrict generated many-to-many relationships - @steve-chavez
|
||||
+ A primary key that contains the foreign key columns is now needed for generating many-to-many relationships.
|
||||
- #2277, Views now are not detected when embedding using the column or FK as target (`/view?select=*,column(*)`) - @steve-chavez
|
||||
+ This embedding form was easily made ambiguous whenever a new view was added.
|
||||
+ You can use computed relationships to keep this embedding form working
|
||||
- #2312, Using `Prefer: return=representation` no longer returns a `Location` header - @laurenceisla
|
||||
- #1984, For the cases where one to one relationships are detected, json objects will be returned instead of json arrays of length 1
|
||||
+ If you wish to override this behavior, you can use computed relationships to return arrays again
|
||||
+ You can get the newly detected one-to-one relationships by using the `--dump-schema` option and filtering with [jq](https://github.com/jqlang/jq).
|
||||
```
|
||||
./postgrest --dump-schema \
|
||||
| jq '[.dbRelationships | .[] | .[1] | .[] | select(.relCardinality.tag == "O2O" and .relFTableIsView == false and .relTableIsView == false) | del(.relFTableIsView,.relTableIsView,.tag,.relIsSelf)]'
|
||||
```
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [9.0.1] - 2022-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2165, Fix json/jsonb columns should not have type in OpenAPI spec - @clrnd
|
||||
- #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther
|
||||
- #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez
|
||||
- #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther
|
||||
- #1724, Fix wrong CORS header Authentication -> Authorization - @wolfgangwalther
|
||||
- #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther
|
||||
- #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther
|
||||
- #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther
|
||||
- #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther
|
||||
- #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez
|
||||
- #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. - @laurenceisla
|
||||
+ Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available.
|
||||
- #2239, Fix misleading disambiguation error where the content of the `relationship` key looks like valid syntax - @laurenceisla
|
||||
- #2294, Disable parallel GC for better performance on higher core CPUs - @steve-chavez
|
||||
- #1076, Fix using CPU while idle - @steve-chavez
|
||||
- #2165, Fix json/jsonb columns should not have type in OpenAPI spec - @clrnd
|
||||
- #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther
|
||||
- #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez
|
||||
- #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther
|
||||
- #1724, Fix wrong CORS header Authentication -> Authorization - @wolfgangwalther
|
||||
- #2120, Fix reading database configuration properly when `=` is present in value - @wolfgangwalther
|
||||
- #2135, Remove trigger functions from schema cache and OpenAPI output, because they can't be called directly anyway. - @wolfgangwalther
|
||||
- #2101, Remove aggregates, procedures and window functions from the schema cache and OpenAPI output. - @wolfgangwalther
|
||||
- #2153, Fix --dump-schema running with a wrong PG version. - @wolfgangwalther
|
||||
- #2042, Keep working when EMFILE(Too many open files) is reached. - @steve-chavez
|
||||
- #2147, Ignore `Content-Type` headers for `GET` requests when calling RPCs. - @laurenceisla
|
||||
+ Previously, `GET` without parameters, but with `Content-Type: text/plain` or `Content-Type: application/octet-stream` would fail with `404 Not Found`, even if a function without arguments was available.
|
||||
- #2239, Fix misleading disambiguation error where the content of the `relationship` key looks like valid syntax - @laurenceisla
|
||||
- #2294, Disable parallel GC for better performance on higher core CPUs - @steve-chavez
|
||||
- #1076, Fix using CPU while idle - @steve-chavez
|
||||
|
||||
## [9.0.0] - 2021-11-25
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# PostgREST Docker Hub image for aarch64.
|
||||
# The x86-64 is a single-static-binary image built via Nix, see:
|
||||
# nix/tools/docker/README.md
|
||||
|
||||
FROM ubuntu:resolute@sha256:b7f48194d4d8b763a478a621cdc81c27be222ba2206ca3ca6bc42b49685f3d9e AS postgrest
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY postgrest /usr/bin/postgrest
|
||||
RUN chmod +x /usr/bin/postgrest
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
USER 1000
|
||||
|
||||
# Use the array form to avoid running the command using bash, which does not handle `SIGTERM` properly.
|
||||
# See https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop
|
||||
CMD ["postgrest"]
|
||||
@@ -1,6 +1,8 @@
|
||||

|
||||

|
||||
|
||||
[](https://www.patreon.com/postgrest)
|
||||
[](https://www.paypal.me/postgrest)
|
||||
[](https://gitter.im/begriffs/postgrest)
|
||||
[](http://postgrest.org)
|
||||
[](https://hub.docker.com/r/postgrest/postgrest/)
|
||||
[](https://github.com/PostgREST/postgrest/actions?query=branch%3Amain)
|
||||
@@ -13,35 +15,40 @@ API than you are likely to write from scratch.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<table align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/cybertec.svg">
|
||||
<img width="222px" src="static/cybertec-new.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.svg">
|
||||
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
|
||||
<img width="296px" src="static/2ndquadrant.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.euronodes.com/postgrest" target="_blank">
|
||||
<img width="296px" src="static/euronodes.svg">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/neon.jpg">
|
||||
<a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/gnuhost.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/bytebase.svg">
|
||||
<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">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/oblivious.jpg">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -52,8 +59,8 @@ Big thanks to our sponsors! You can join them by supporting PostgREST on [Patreo
|
||||
|
||||
## Usage
|
||||
|
||||
1. See the docs for [how to install PostgREST on your platform](https://docs.postgrest.org/en/stable/explanations/install.html). You can also [use Docker](https://docs.postgrest.org/en/stable/explanations/install.html#docker).
|
||||
|
||||
1. Download the binary ([latest release](https://github.com/PostgREST/postgrest/releases/latest))
|
||||
for your platform.
|
||||
2. Invoke for help:
|
||||
|
||||
```bash
|
||||
@@ -61,7 +68,7 @@ Big thanks to our sponsors! You can join them by supporting PostgREST on [Patreo
|
||||
```
|
||||
## [Documentation](http://postgrest.org)
|
||||
|
||||
Latest documentation is at [postgrest.org](http://postgrest.org). You can contribute to the docs in [PostgREST/postgrest/docs](https://github.com/PostgREST/postgrest/tree/main/docs).
|
||||
Latest documentation is at [postgrest.org](http://postgrest.org). You can contribute to the docs in [PostgREST/postgrest-docs](https://github.com/PostgREST/postgrest-docs).
|
||||
|
||||
## Performance
|
||||
|
||||
@@ -138,14 +145,14 @@ and the [API guide](http://postgrest.org/en/stable/api.html).
|
||||
|
||||
## Supporting development
|
||||
|
||||
You can help PostgREST ongoing maintenance and development by making a regular donation through Patreon https://www.patreon.com/postgrest
|
||||
You can help PostgREST ongoing maintenance and development by:
|
||||
|
||||
- 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.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are always welcome and appreciated. Please see the [Contributing guidelines](https://github.com/PostgREST/postgrest/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## Thanks
|
||||
|
||||
The PostgREST organization is grateful to:
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
packages: postgrest.cabal
|
||||
tests: true
|
||||
@@ -1 +0,0 @@
|
||||
index-state: hackage.haskell.org 2026-07-11T17:34:10Z
|
||||
@@ -1,33 +1,12 @@
|
||||
{ system ? builtins.currentSystem
|
||||
|
||||
, compiler ? "ghc948"
|
||||
|
||||
, # Commit of the Nixpkgs repository that we want to use.
|
||||
# It defaults to reading the inputs from flake.lock, which serves
|
||||
# as a compatibility layer for non-flake builds / default.nix / shell.nix.
|
||||
nixpkgsVersion ? let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
in
|
||||
{
|
||||
inherit (lock.nodes.nixpkgs.locked) owner repo rev;
|
||||
tarballHash = lock.nodes.nixpkgs.locked.narHash;
|
||||
}
|
||||
|
||||
, # Nix files that describe the Nixpkgs repository. We evaluate the expression
|
||||
# using `import` below.
|
||||
nixpkgs ? let inherit (nixpkgsVersion) owner repo rev tarballHash; in
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
|
||||
sha256 = tarballHash;
|
||||
}
|
||||
}:
|
||||
|
||||
let
|
||||
name =
|
||||
"postgrest";
|
||||
|
||||
compiler =
|
||||
"ghc8107";
|
||||
|
||||
# PostgREST source files, filtered based on the rules in the .gitignore files
|
||||
# and file extensions. We want to include as little as possible, as the files
|
||||
# and file extensions. We want to include as litte as possible, as the files
|
||||
# added here will increase the space used in the Nix store and trigger the
|
||||
# build of new Nix derivations when changed.
|
||||
src =
|
||||
@@ -35,6 +14,18 @@ let
|
||||
(pkgs.gitignoreSource ./.)
|
||||
[ ".cabal" ".hs" ".lhs" "LICENSE" ];
|
||||
|
||||
# Commit of the Nixpkgs repository that we want to use.
|
||||
nixpkgsVersion =
|
||||
import nix/nixpkgs-version.nix;
|
||||
|
||||
# Nix files that describe the Nixpkgs repository. We evaluate the expression
|
||||
# using `import` below.
|
||||
nixpkgs =
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
|
||||
sha256 = nixpkgsVersion.tarballHash;
|
||||
};
|
||||
|
||||
allOverlays =
|
||||
import nix/overlays;
|
||||
|
||||
@@ -43,40 +34,51 @@ let
|
||||
allOverlays.build-toolbox
|
||||
allOverlays.checked-shell-script
|
||||
allOverlays.gitignore
|
||||
allOverlays.postgresql-default
|
||||
allOverlays.postgresql-legacy
|
||||
allOverlays.postgresql-future
|
||||
(allOverlays.haskell-packages { inherit compiler; })
|
||||
allOverlays.slocat
|
||||
];
|
||||
|
||||
# Evaluated expression of the Nixpkgs repository.
|
||||
pkgs =
|
||||
import nixpkgs { inherit overlays system; };
|
||||
import nixpkgs { inherit overlays; };
|
||||
|
||||
postgresqlVersions =
|
||||
[
|
||||
{ name = "pg-17"; postgresql = pkgs.postgresql_17.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-16"; postgresql = pkgs.postgresql_16.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14; }
|
||||
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13; }
|
||||
{ name = "postgresql-12"; postgresql = pkgs.postgresql_12; }
|
||||
{ name = "postgresql-11"; postgresql = pkgs.postgresql_11; }
|
||||
{ name = "postgresql-10"; postgresql = pkgs.postgresql_10; }
|
||||
{ name = "postgresql-9.6"; postgresql = pkgs.postgresql_9_6; }
|
||||
];
|
||||
|
||||
haskellPackages = pkgs.haskell.packages."${compiler}";
|
||||
patches =
|
||||
pkgs.callPackage nix/patches { };
|
||||
|
||||
# Dynamic derivation for PostgREST
|
||||
postgrest = pkgs.lib.pipe (haskellPackages.callCabal2nix name src { }) [
|
||||
# To allow ghc-datasize to be used.
|
||||
lib.disableLibraryProfiling
|
||||
# We are never going to use dynamic haskell libraries anyway. "Dynamic" refers to how
|
||||
# non-haskell deps are linked. All haskell dependencies are always statically linked.
|
||||
lib.disableSharedLibraries
|
||||
];
|
||||
postgrest =
|
||||
pkgs.haskell.packages."${compiler}".callCabal2nix name src { };
|
||||
|
||||
staticHaskellPackage = import nix/static.nix { inherit compiler name pkgs src; };
|
||||
# Function that derives a fully static Haskell package based on
|
||||
# nh2/static-haskell-nix
|
||||
staticHaskellPackage =
|
||||
import nix/static-haskell-package.nix { inherit nixpkgs compiler patches allOverlays; };
|
||||
|
||||
# Options passed to cabal in dev tools and tests
|
||||
devCabalOptions =
|
||||
"-f dev --test-show-detail=direct";
|
||||
|
||||
profiledHaskellPackages =
|
||||
pkgs.haskell.packages."${compiler}".extend (self: super:
|
||||
{
|
||||
mkDerivation =
|
||||
args:
|
||||
super.mkDerivation (args // { enableLibraryProfiling = true; });
|
||||
}
|
||||
);
|
||||
|
||||
inherit (pkgs.haskell) lib;
|
||||
in
|
||||
rec {
|
||||
@@ -84,21 +86,21 @@ rec {
|
||||
|
||||
# Derivation for the PostgREST Haskell package, including the executable,
|
||||
# libraries and documentation. We disable running the test suite on Nix
|
||||
# builds, as they require a database to be set up. We split the binary
|
||||
# into a separate output, so that the default distribution via flake.nix
|
||||
# has a much smaller closure size.
|
||||
postgrestPackage = pkgs.lib.pipe postgrest [
|
||||
lib.dontCheck
|
||||
lib.enableSeparateBinOutput
|
||||
(haskellPackages.generateOptparseApplicativeCompletions [ "postgrest" ])
|
||||
];
|
||||
# builds, as they require a database to be set up.
|
||||
postgrestPackage =
|
||||
lib.dontCheck postgrest;
|
||||
|
||||
# Static executable.
|
||||
postgrestStatic =
|
||||
lib.justStaticExecutables (lib.dontCheck (staticHaskellPackage name src));
|
||||
|
||||
# Profiled dynamic executable.
|
||||
postgrestProfiled = pkgs.lib.pipe postgrestPackage [
|
||||
lib.enableExecutableProfiling
|
||||
lib.enableLibraryProfiling
|
||||
lib.dontHaddock
|
||||
];
|
||||
postgrestProfiled =
|
||||
lib.enableExecutableProfiling (
|
||||
lib.dontHaddock (
|
||||
lib.dontCheck (profiledHaskellPackages.callCabal2nix name src { })
|
||||
)
|
||||
);
|
||||
|
||||
inherit (postgrest) env;
|
||||
|
||||
@@ -108,40 +110,34 @@ rec {
|
||||
inherit (pkgs.haskell.packages."${compiler}") ghcWithPackages;
|
||||
};
|
||||
|
||||
# Used by CI on MacOS
|
||||
inherit (pkgs) nix-build-uncached;
|
||||
|
||||
### Tools
|
||||
|
||||
cabalTools =
|
||||
pkgs.callPackage nix/tools/cabalTools.nix { inherit devCabalOptions postgrest; };
|
||||
|
||||
withTools =
|
||||
pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions postgrest; };
|
||||
|
||||
# Development tools.
|
||||
devTools =
|
||||
pkgs.callPackage nix/tools/devTools.nix { inherit tests style devCabalOptions hsie withTools; };
|
||||
|
||||
# Documentation tools.
|
||||
docs =
|
||||
pkgs.callPackage nix/tools/docs.nix { };
|
||||
|
||||
# Git tools.
|
||||
gitTools =
|
||||
pkgs.callPackage nix/tools/gitTools.nix { };
|
||||
# Docker images and loading script.
|
||||
docker =
|
||||
pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; };
|
||||
|
||||
# Load testing tools.
|
||||
loadtest =
|
||||
pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; };
|
||||
|
||||
# Script for running memory tests.
|
||||
memory =
|
||||
pkgs.callPackage nix/tools/memory.nix { inherit postgrestProfiled withTools; };
|
||||
|
||||
# Utility for updating the pinned version of Nixpkgs.
|
||||
nixpkgsTools =
|
||||
pkgs.callPackage nix/tools/nixpkgsTools.nix { };
|
||||
|
||||
# Scripts for publishing new releases.
|
||||
release =
|
||||
pkgs.callPackage nix/tools/release.nix { };
|
||||
pkgs.callPackage nix/tools/release { };
|
||||
|
||||
# Linting and styling tools.
|
||||
style =
|
||||
@@ -153,14 +149,8 @@ rec {
|
||||
inherit postgrest devCabalOptions withTools;
|
||||
ghc = pkgs.haskell.compiler."${compiler}";
|
||||
inherit (pkgs.haskell.packages."${compiler}") hpc-codecov;
|
||||
inherit (pkgs.haskell.packages."${compiler}") weeder;
|
||||
};
|
||||
} // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux rec {
|
||||
# Static executable.
|
||||
inherit (staticHaskellPackage) postgrestStatic;
|
||||
inherit (staticHaskellPackage) packagesStatic;
|
||||
|
||||
# Docker images and loading script.
|
||||
docker =
|
||||
pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; };
|
||||
withTools =
|
||||
pkgs.callPackage nix/tools/withTools.nix { inherit devCabalOptions postgresqlVersions postgrest; };
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
_build
|
||||
Pipfile.lock
|
||||
*.aux
|
||||
*.log
|
||||
_diagrams/db.pdf
|
||||
misspellings
|
||||
unuseddict
|
||||
*.mo
|
||||
@@ -1,28 +0,0 @@
|
||||
# PostgREST documentation https://postgrest.org/
|
||||
|
||||
PostgREST docs use the reStructuredText format, check this [cheatsheet](https://github.com/ralsina/rst-cheatsheet/blob/master/rst-cheatsheet.rst) to get acquainted with it.
|
||||
|
||||
To build the docs locally, see [the Nix development readme](/nix/README.md#documentation).
|
||||
|
||||
## Documentation structure
|
||||
|
||||
This documentation is structured according to tutorials-howtos-topics-references. For more details on the rationale of this structure,
|
||||
see https://www.divio.com/blog/documentation.
|
||||
|
||||
## Translating
|
||||
|
||||
To create `.po` files for translation into a new language pass the language code as the first argument to `postgrest-docs-build`.
|
||||
|
||||
Example to add German/de:
|
||||
|
||||
```
|
||||
postgrest-docs-build de
|
||||
```
|
||||
|
||||
The livereload server also supports a language/locale argument to show the translated docs during translation:
|
||||
|
||||
```
|
||||
postgrest-docs-serve de
|
||||
```
|
||||
|
||||
Spellcheck is currently only available for the default language.
|
||||
@@ -1,24 +0,0 @@
|
||||
## ERD
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
./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/
|
||||
|
||||
## UML
|
||||
|
||||
The UML diagrams are created with https://plantuml.com/.
|
||||
|
||||
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
|
||||
plantuml -tsvg uml/arch.uml -o ../../_static
|
||||
plantuml -tsvg -darkmode uml/dark/arch-dark.uml -o ../../../_static
|
||||
```
|
||||
@@ -1,15 +0,0 @@
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Box_Office]
|
||||
*bo_date
|
||||
*+film_id
|
||||
gross_revenue
|
||||
|
||||
[Films]
|
||||
*id
|
||||
+director_id
|
||||
title
|
||||
`...`
|
||||
|
||||
Box_Office +--1 Films
|
||||
@@ -1,12 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Employees]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
+supervisor_id
|
||||
|
||||
Employees 1--* Employees
|
||||
@@ -1,51 +0,0 @@
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeSerif"}
|
||||
|
||||
[Films]
|
||||
*id
|
||||
+director_id
|
||||
title
|
||||
year
|
||||
rating
|
||||
language
|
||||
|
||||
[Directors]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
|
||||
[Actors]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
|
||||
[Roles]
|
||||
*+film_id
|
||||
*+actor_id
|
||||
character
|
||||
|
||||
[Competitions]
|
||||
*id
|
||||
name
|
||||
year
|
||||
|
||||
[Nominations]
|
||||
*+competition_id
|
||||
*+film_id
|
||||
rank
|
||||
|
||||
[Technical_Specs]
|
||||
*+film_id
|
||||
runtime
|
||||
camera
|
||||
sound
|
||||
|
||||
Roles *--1 Actors
|
||||
Roles *--1 Films
|
||||
|
||||
Nominations *--1 Competitions
|
||||
Nominations *--1 Films
|
||||
|
||||
Films *--1 Directors
|
||||
|
||||
Films 1--1 Technical_Specs
|
||||
@@ -1,20 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Addresses]
|
||||
*id
|
||||
name
|
||||
city
|
||||
state
|
||||
postal_code
|
||||
|
||||
[Orders]
|
||||
*id
|
||||
name
|
||||
+billing_address_id
|
||||
+shipping_address_id
|
||||
|
||||
Orders *--1 Addresses
|
||||
Orders *--1 Addresses
|
||||
@@ -1,16 +0,0 @@
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Premieres]
|
||||
*id
|
||||
location
|
||||
date
|
||||
+film_id
|
||||
|
||||
[Films]
|
||||
*id
|
||||
+director_id
|
||||
title
|
||||
`...`
|
||||
|
||||
Premieres *--1 Films
|
||||
@@ -1,12 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Presidents]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
+predecessor_id
|
||||
|
||||
Presidents 1--? Presidents
|
||||
@@ -1,18 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Users]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
username
|
||||
|
||||
[Subscriptions]
|
||||
*+subscriber_id
|
||||
*+subscribed_id
|
||||
type
|
||||
|
||||
Users 1--* Subscriptions
|
||||
Subscriptions *--1 Users
|
||||
@@ -1,96 +0,0 @@
|
||||
@startuml
|
||||
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
package "PostgREST" {
|
||||
() HTTP as HTTPAPI
|
||||
HTTPAPI - [Auth]
|
||||
[Auth] -r.> [ApiRequest]
|
||||
[ApiRequest] -r.> [Plan]
|
||||
[Plan] -r.> [Query]
|
||||
[Query] - () "Connection Pool" : "\t"
|
||||
[Plan] -u-> [Schema Cache]:uses
|
||||
[Schema Cache] <- () Listener : reloads
|
||||
|
||||
() HTTP as HTTPADMIN
|
||||
[Admin] -r- () HTTPADMIN
|
||||
[Config] -l- () CLI
|
||||
|
||||
[Config] <-r~ Listener
|
||||
|
||||
HTTPADMIN -[hidden]r- CLI
|
||||
[Schema Cache] -l[hidden]- [Config]
|
||||
[Schema Cache] -l[hidden]- [Admin]
|
||||
[Schema Cache] -l[hidden]- CLI
|
||||
}
|
||||
|
||||
|
||||
database "PostgreSQL" {
|
||||
node Authorization {
|
||||
rectangle "Roles, GRANT, RLS"
|
||||
}
|
||||
node "API schema" as API {
|
||||
rectangle "Functions, Views"
|
||||
}
|
||||
rectangle "Tables, extensions" as tbs
|
||||
API -d- tbs
|
||||
|
||||
API -l[hidden]- Authorization
|
||||
}
|
||||
|
||||
:user:
|
||||
hexagon Proxy
|
||||
:user: .r-> Proxy : request with JWT
|
||||
HTTPAPI <.l- Proxy
|
||||
|
||||
hexagon ExternalAuth
|
||||
ExternalAuth -u[hidden]- Proxy
|
||||
:user: .r-> ExternalAuth : login
|
||||
:user: <.r- ExternalAuth : JWT
|
||||
|
||||
:operator: .d-> HTTPADMIN
|
||||
:operator: .d-> CLI
|
||||
|
||||
|
||||
PostgreSQL <.developer : "\t"
|
||||
Listener -r.> "PostgreSQL"
|
||||
"Connection Pool" -r.> "PostgreSQL" : "\t\t"
|
||||
|
||||
note bottom of Auth
|
||||
Validates the JWT
|
||||
end note
|
||||
|
||||
note bottom of ApiRequest
|
||||
Parses the URL syntax
|
||||
end note
|
||||
|
||||
note bottom of Plan
|
||||
Generates internal AST
|
||||
end note
|
||||
|
||||
note bottom of Query
|
||||
Generates the SQL
|
||||
end note
|
||||
|
||||
note top of Listener
|
||||
LISTEN session
|
||||
end note
|
||||
|
||||
url of ExternalAuth is [[../explanations/external_auth.html]]
|
||||
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 ApiRequest is [[../explanations/architecture.html#api-request]]
|
||||
url of Plan is [[../explanations/architecture.html#plan]]
|
||||
url of Query is [[../explanations/architecture.html#query]]
|
||||
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 [[../explanations/architecture.html#http]]
|
||||
url of HTTPAPI is [[../explanations/architecture.html#http]]
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
@startuml
|
||||
!include ../arch.uml
|
||||
@enduml
|
||||
@@ -1,3 +0,0 @@
|
||||
@startuml
|
||||
!include ../sch-iso.uml
|
||||
@enduml
|
||||
@@ -1,29 +0,0 @@
|
||||
@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
|
||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,155 +0,0 @@
|
||||
.wy-nav-content {
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
#postgrest-documentation > h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.wy-menu.rst-pro {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
div.line-block {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
#sponsors {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#sponsors h2 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#sponsors img{
|
||||
margin: 10px;
|
||||
width: 13em; /* ".. image::" does not apply width properly to SVGs */
|
||||
}
|
||||
|
||||
#thanks{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#thanks img{
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#thanks h2{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#thanks p{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#thanks ul{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
max-width: 800px;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.wy-table-responsive table td {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.wy-table-responsive {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
#tutorials span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#references span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#explanations span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#how-tos span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#ecosystem span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#integrations span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#api span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tweaks for dark mode from extension: sphinx-rtd-dark-theme */
|
||||
|
||||
html[data-theme="dark"] .highlight {
|
||||
background-color: #17181c !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .sphinx-tabs-tab {
|
||||
color: var(--dark-link-color);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .sphinx-tabs-panel {
|
||||
border: 1px solid #404040;
|
||||
border-top: 0;
|
||||
background: #141414;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
|
||||
border: 1px solid #404040;
|
||||
border-bottom: 1px solid #141414;
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] [role="tablist"] {
|
||||
border-bottom: 1px solid #404040;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .btn-neutral {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .img-dark {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
html:not([data-theme="dark"]) .img-dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .img-light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html:not([data-theme="dark"]) .img-light {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .img-translucent img {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
.img-translucent img {
|
||||
transition: background-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.svg-container-md {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 156 B |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="391px" preserveAspectRatio="none" style="width:328px;height:391px;" version="1.1" viewBox="0 0 328 391" width="328px" zoomAndPan="magnify"><defs/><g><!--cluster PostgreSQL--><g id="cluster_PostgreSQL"><path d="M6,16 C6,6 158.5,6 158.5,6 C158.5,6 311,6 311,16 L311,293.59 C311,303.59 158.5,303.59 158.5,303.59 C158.5,303.59 6,303.59 6,293.59 L6,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><path d="M6,16 C6,26 158.5,26 158.5,26 C158.5,26 311,26 311,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="92.2305" x="112.3848" y="40.9951">PostgreSQL</text></g><!--cluster public--><g id="cluster_public"><polygon fill="none" points="30,74,40,64,153,64,153,146.29,143,156.29,30,156.29,30,74" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="143" x2="153" y1="74" y2="64"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="30" x2="143" y1="74" y2="74"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="143" x2="143" y1="74" y2="156.29"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="47.9063" x="63.5469" y="89.9951">public</text></g><!--cluster API--><g id="cluster_API"><polygon fill="none" points="70,190.29,80,180.29,246,180.29,246,269.59,236,279.59,70,279.59,70,190.29" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="236" x2="246" y1="190.29" y2="180.29"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="70" x2="236" y1="190.29" y2="190.29"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="236" x2="236" y1="190.29" y2="279.59"/><text fill="#FFFFFF" font-family="sans-serif" font-size="20" font-weight="bold" lengthAdjust="spacing" textLength="34.668" x="136.666" y="211.8545">api</text></g><!--entity tables_public--><g id="elem_tables_public"><rect fill="#313139" height="36.2969" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="62.752" x="71.62" y="104"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="42.752" x="81.62" y="126.9951">tables</text></g><!--entity extensions--><g id="elem_extensions"><polygon fill="none" points="169.14,109,179.14,99,294.8695,99,294.8695,135.2969,284.8695,145.2969,169.14,145.2969,169.14,109" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="284.8695" x2="294.8695" y1="109" y2="99"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="169.14" x2="284.8695" y1="109" y2="109"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="284.8695" x2="284.8695" y1="109" y2="145.2969"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="85.7295" x="184.14" y="131.9951">extensions</text></g><!--entity vf_api--><g id="elem_vf_api"><rect fill="#313139" height="36.2969" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="144.6465" x="85.68" y="227.29"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="124.6465" x="95.68" y="250.2851">views + functions</text></g><!--entity PostgREST--><g id="elem_PostgREST"><ellipse cx="158" cy="352.59" 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="74.6895" x="120.6553" y="382.5851">PostgREST</text></g><!--reverse link tables_public to vf_api--><g id="link_tables_public_vf_api"><path d="M110.03,146.6 C110.03,169.85 110.03,203.55 110.03,226.86 " fill="none" id="tables_public-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="110.03,140.6,106.03,149.6,110.03,145.6,114.03,149.6,110.03,140.6" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--reverse link extensions to vf_api--><g id="link_extensions_vf_api"><path d="M199.73,151.63 C199.73,175.24 199.73,205.18 199.73,226.88 " fill="none" id="extensions-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="199.73,145.63,195.73,154.63,199.73,150.63,203.73,154.63,199.73,145.63" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--link vf_api to PostgREST--><g id="link_vf_api_PostgREST"><path d="M158,269.62 C158,292.9 158,320.34 158,337.81 " fill="none" id="vf_api-PostgREST" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="158,263.62,154,272.62,158,268.62,162,272.62,158,263.62" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="158,343.81,162,334.81,158,338.81,154,334.81,158,343.81" style="stroke:#E7E7E7;stroke-width:3.0;"/></g><!--SRC=[KypCIyufJKbLqDFJBqxEqCqipjShpSq10000]--></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,328 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# PostgREST documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Oct 9 16:53:00 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx_tabs.tabs",
|
||||
"sphinx_copybutton",
|
||||
"sphinxext.opengraph",
|
||||
"sphinx_rtd_dark_mode",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# This is overriden by readthedocs with the version tag anyway
|
||||
version = "14"
|
||||
# To avoid repetition in <title> we set this to an empty string.
|
||||
release = ""
|
||||
|
||||
# General information about the project.
|
||||
project = "PostgREST " + version
|
||||
author = "Joe Nelson, Steve Chavez"
|
||||
copyright = "2017, " + author
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = "en"
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "shared/*.rst"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
# keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents.
|
||||
# "<project> v<release> documentation" by default.
|
||||
# html_title = u'PostgREST v0.4.0.0'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = "_static/favicon.ico"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
# html_extra_path = []
|
||||
|
||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
||||
# bottom, using the given strftime format.
|
||||
# The empty string is equivalent to '%b %d, %Y'.
|
||||
# html_last_updated_fmt = None
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
||||
# html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# 'ja' uses this config value.
|
||||
# 'zh' user can custom change `jieba` dictionary path.
|
||||
# html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "PostgRESTdoc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, "PostgREST.tex", "PostgREST Documentation", author, "manual"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [(master_doc, "postgrest", "PostgREST Documentation", [author], 1)]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(
|
||||
master_doc,
|
||||
"PostgREST",
|
||||
"PostgREST Documentation",
|
||||
author,
|
||||
"PostgREST",
|
||||
"REST API for any PostgreSQL database",
|
||||
"Web",
|
||||
),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
# -- Custom setup ---------------------------------------------------------
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_css_file("css/custom.css")
|
||||
|
||||
|
||||
user_agent = (
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
|
||||
)
|
||||
|
||||
linkcheck_ignore = [
|
||||
# 403 only in CI / GitHub Actions
|
||||
r"https://www.patreon.com/postgrest",
|
||||
r"https://blog.frankel.ch/poor-man-api",
|
||||
r"https://www.cybertec-postgresql.com/.*",
|
||||
# Odd SSL error
|
||||
r"https://www.dripdepot.com",
|
||||
r"https://www.euronodes.com",
|
||||
# New GitHub UI delays comment load, so anchor fails
|
||||
r"https://github.com/.*#issuecomment",
|
||||
# Random 500 Internal Server Error
|
||||
r"https://jwt.io",
|
||||
]
|
||||
|
||||
# sphinx-tabs configuration
|
||||
sphinx_tabs_disable_tab_closing = True
|
||||
|
||||
# sphinx_rtd_dark_mode configuration
|
||||
default_dark_mode = False
|
||||
|
||||
# sphinxext-opengraph configuration
|
||||
|
||||
ogp_image = "_images/logo.png"
|
||||
ogp_use_first_image = True
|
||||
ogp_enable_meta_description = True
|
||||
ogp_description_length = 300
|
||||
|
||||
## RTD sets html_baseurl, ensures we use the correct env for canonical URLs
|
||||
## Useful to generate correct meta tags for Open Graph
|
||||
## Refs: https://github.com/readthedocs/readthedocs.org/issues/10226, https://github.com/urllib3/urllib3/pull/3064
|
||||
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
|
||||
@@ -1,96 +0,0 @@
|
||||
.. _community_tutorials:
|
||||
|
||||
Community Tutorials
|
||||
-------------------
|
||||
|
||||
* `Building a Contacts List with PostgREST and Vue.js <https://www.youtube.com/watch?v=iHtsALtD5-U>`_ -
|
||||
In this video series, DigitalOcean shows how to build and deploy an Nginx + PostgREST(using a managed PostgreSQL database) + Vue.js webapp in an Ubuntu server droplet.
|
||||
|
||||
* `PostgREST + Auth0: Create REST API in minutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest-1-introduction/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
||||
|
||||
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
|
||||
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
|
||||
|
||||
* `How PostgreSQL triggers work when called with a PostgREST PATCH HTTP request <https://blog.fgribreau.com/2020/11/how-postgresql-triggers-works-when.html>`_ - A tutorial to see how the old and new values are set or not when doing a PATCH request to PostgREST.
|
||||
|
||||
* `REST Data Service on YugabyteDB / PostgreSQL <https://dev.to/yugabyte/rest-data-service-on-yugabytedb-postgresql-5f2h>`_
|
||||
|
||||
* `Build data-driven applications with Workers and PostgreSQL <https://developers.cloudflare.com/workers/tutorials/postgres/>`_ - A tutorial on how to integrate with PostgREST and PostgreSQL using Cloudflare Workers.
|
||||
|
||||
* `A poor man's API <https://blog.frankel.ch/poor-man-api>`_ - Shows how to integrate PostgREST with Apache APISIX as an alternative to Nginx.
|
||||
|
||||
.. * `Accessing a PostgreSQL database in Godot 4 via PostgREST <https://peterkingsbury.com/2022/08/16/godot-postgresql-postgrest/>`_
|
||||
|
||||
.. _templates:
|
||||
|
||||
Templates
|
||||
---------
|
||||
|
||||
* `compose-postgrest <https://github.com/mattddowney/compose-postgrest>`_ - docker-compose setup with Nginx and HTML example
|
||||
* `svelte-postgrest-template <https://github.com/guyromm/svelte-postgrest-template>`_ - Svelte/SvelteKit, PostgREST, EveryLayout and social auth
|
||||
|
||||
.. _eco_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
|
||||
* `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
|
||||
* `guild-operators <https://github.com/cardano-community/koios-artifacts/tree/main/files/grest>`_ - example queries and functions that the Cardano Community uses for their Guild Operators' Repository
|
||||
* `PostGUI <https://github.com/priyank-purohit/PostGUI>`_ - React Material UI admin panel
|
||||
* `prospector <https://github.com/sfcta/prospector>`_ - data warehouse and visualization platform
|
||||
|
||||
.. _devops:
|
||||
|
||||
DevOps
|
||||
------
|
||||
|
||||
* `cloudgov-demo-postgrest <https://github.com/GSA/cloudgov-demo-postgrest>`_ - demo for a federally-compliant REST API on cloud.gov
|
||||
* `cloudstark/helm-charts <https://github.com/cloudstark/helm-charts/tree/master/postgrest>`_ - helm chart to deploy PostgREST to a Kubernetes cluster via a Deployment and Service
|
||||
* `cyril-sabourault/postgrest-cloud-run <https://github.com/cyril-sabourault/postgrest-cloud-run>`_ - expose a PostgreSQL database on Cloud SQL using Cloud Run
|
||||
* `eyberg/postgrest <https://repo.ops.city/v2/packages/eyberg/postgrest/10.1.1/x86_64/show>`_ - run PostgREST as a Nanos unikernel
|
||||
* `jbkarle/postgrest <https://github.com/jbkarle/postgrest>`_ - helm chart with a demo database for development and test purposes
|
||||
|
||||
.. _eco_external_notification:
|
||||
|
||||
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.
|
||||
|
||||
* `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
|
||||
* `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
|
||||
* `postgresql2websocket <https://github.com/frafra/postgresql2websocket>`_ - Websockets
|
||||
|
||||
|
||||
.. _eco_extensions:
|
||||
|
||||
Extensions
|
||||
----------
|
||||
|
||||
* `aiodata <https://github.com/Exahilosys/aiodata>`_ - Python, event-based proxy and caching client.
|
||||
* `pg-safeupdate <https://github.com/eradman/pg-safeupdate>`_ - prevent full-table updates or deletes
|
||||
* `postgrest-node <https://github.com/seveibar/postgrest-node>`_ - Run a PostgREST server in Node.js via npm module
|
||||
* `PostgREST-writeAPI <https://github.com/ppKrauss/PostgREST-writeAPI>`_ - generate Nginx rewrite rules to fit an OpenAPI spec
|
||||
|
||||
.. _clientside_libraries:
|
||||
|
||||
Client-Side Libraries
|
||||
---------------------
|
||||
|
||||
* `postgrest-csharp <https://github.com/supabase-community/postgrest-csharp>`_ - C#
|
||||
* `postgrest-dart <https://github.com/supabase/postgrest-dart>`_ - Dart
|
||||
* `postgrest-ex <https://github.com/supabase-community/postgrest-ex>`_ - Elixir
|
||||
* `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go
|
||||
* `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript
|
||||
* `postgrest-kt <https://github.com/supabase-community/postgrest-kt>`_ - Kotlin
|
||||
* `postgrest-py <https://github.com/supabase/postgrest-py>`_ - Python
|
||||
* `postgrest-rs <https://github.com/supabase-community/postgrest-rs>`_ - Rust
|
||||
* `postgrest-swift <https://github.com/supabase-community/postgrest-swift>`_ - Swift
|
||||
* `redux-postgrest <https://github.com/andytango/redux-postgrest>`_ - TypeScript/JS, client integrated with (React) Redux.
|
||||
* `vue-postgrest <https://github.com/technowledgy/vue-postgrest>`_ - Vue.js
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
Architecture
|
||||
############
|
||||
|
||||
This page describes the architecture of PostgREST.
|
||||
|
||||
Bird's Eye View
|
||||
===============
|
||||
|
||||
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
|
||||
========
|
||||
|
||||
This section talks briefly about various important modules.
|
||||
|
||||
Main
|
||||
----
|
||||
|
||||
The starting point of the program is `Main.hs <https://github.com/PostgREST/postgrest/blob/main/main/Main.hs>`_.
|
||||
|
||||
CLI
|
||||
---
|
||||
|
||||
Main then calls `CLI.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/CLI.hs>`_, which is in charge of :ref:`cli`.
|
||||
|
||||
App
|
||||
---
|
||||
|
||||
`App.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/App.hs>`_ is then in charge of composing the different modules.
|
||||
|
||||
Auth
|
||||
----
|
||||
|
||||
`Auth.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Auth.hs>`_ is in charge of :ref:`authn`.
|
||||
|
||||
Api Request
|
||||
-----------
|
||||
|
||||
`ApiRequest.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/ApiRequest.hs>`_ is in charge of parsing the URL query string (following PostgREST syntax), the request headers, and the request body.
|
||||
|
||||
A request might be rejected at this level if it's invalid. For example when providing an unknown media type to PostgREST or using an unknown HTTP method.
|
||||
|
||||
Plan
|
||||
----
|
||||
|
||||
Using the Schema Cache, `Plan.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Plan.hs>`_ generates an internal AST, filling out-of-band SQL details (like an ``ON CONFLICT (pk)`` clause) required to complete the user request.
|
||||
|
||||
A request might be rejected at this level if it's invalid. For example when doing resource embedding on a nonexistent resource.
|
||||
|
||||
Query
|
||||
-----
|
||||
|
||||
`Query.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Query.hs>`_ generates the SQL queries (parametrized and prepared) required to satisfy the user request.
|
||||
|
||||
Only at this stage a connection from the pool might be used.
|
||||
|
||||
Schema Cache
|
||||
------------
|
||||
|
||||
`SchemaCache.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/SchemaCache.hs>`_ is in charge of :ref:`schema_cache`.
|
||||
|
||||
Config
|
||||
------
|
||||
|
||||
`Config.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Config.hs>`_ is in charge of :ref:`configuration`.
|
||||
|
||||
Admin
|
||||
-----
|
||||
|
||||
`Admin.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Admin.hs>`_ is in charge of the :ref:`admin_server`.
|
||||
|
||||
HTTP
|
||||
----
|
||||
|
||||
The HTTP server is provided by `Warp <https://aosabook.org/en/posa/warp.html>`_.
|
||||
|
||||
Listener
|
||||
--------
|
||||
|
||||
`Listener.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Listener.hs>`_ is in charge of the :ref:`listener`.
|
||||
@@ -1,198 +0,0 @@
|
||||
.. _db_authz:
|
||||
|
||||
Database Authorization
|
||||
######################
|
||||
|
||||
Database authorization is the process of granting and verifying database access permissions. PostgreSQL manages permissions using the concept of roles.
|
||||
|
||||
Users and Groups
|
||||
================
|
||||
|
||||
A role can be thought of as either a database user, or a group of database users, depending on how the role is set up.
|
||||
|
||||
Roles for Each Web User
|
||||
-----------------------
|
||||
|
||||
PostgREST can accommodate either viewpoint. If you treat a role as a single user then :ref:`user_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.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
|
||||
|
||||
CREATE TABLE chat (
|
||||
message_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
message_time TIMESTAMP NOT NULL DEFAULT now(),
|
||||
message_from NAME NOT NULL DEFAULT current_user,
|
||||
message_to NAME NOT NULL,
|
||||
message_subject VARCHAR(64) NOT NULL,
|
||||
message_body TEXT
|
||||
);
|
||||
|
||||
ALTER TABLE chat ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
We want to enforce a policy that ensures a user can see only those messages sent by them or intended for them. Also we want to prevent a user from forging the ``message_from`` column with another person's name.
|
||||
|
||||
PostgreSQL allows us to set this policy with row-level security:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE POLICY chat_policy ON chat
|
||||
USING ((message_to = current_user) OR (message_from = current_user))
|
||||
WITH CHECK (message_from = current_user)
|
||||
|
||||
Anyone accessing the generated API endpoint for the chat table will see exactly the rows they should, without our needing custom imperative server-side coding.
|
||||
|
||||
.. warning::
|
||||
|
||||
Roles are namespaced per-cluster rather than per-database so they may be prone to collision.
|
||||
|
||||
Web Users Sharing Role
|
||||
----------------------
|
||||
|
||||
Alternately database roles can represent groups instead of (or in addition to) individual users. You may choose that all signed-in users for a web app share the role ``webuser``. You can distinguish individual users by including extra claims in the JWT such as email.
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"role": "webuser",
|
||||
"email": "john@doe.com"
|
||||
}
|
||||
|
||||
SQL code can access claims through PostgREST :ref:`tx_settings`. For instance to get the email claim, call this function:
|
||||
|
||||
.. code:: sql
|
||||
|
||||
current_setting('request.jwt.claims', true)::json->>'email';
|
||||
|
||||
.. note::
|
||||
|
||||
For PostgreSQL < 14
|
||||
|
||||
.. code:: sql
|
||||
|
||||
current_setting('request.jwt.claim.email', true);
|
||||
|
||||
This allows JWT generation services to include extra information and your database code to react to it. For instance the RLS example could be modified to use this ``current_setting`` rather than ``current_user``. The second ``'true'`` argument tells ``current_setting`` to return NULL if the setting is missing from the current configuration.
|
||||
|
||||
Hybrid User-Group Roles
|
||||
-----------------------
|
||||
|
||||
You can mix the group and individual role policies. For instance we could still have a webuser role and individual users which inherit from it:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE webuser NOLOGIN;
|
||||
-- grant this role access to certain tables etc
|
||||
|
||||
CREATE ROLE user000 NOLOGIN;
|
||||
GRANT webuser TO user000;
|
||||
-- now user000 can do whatever webuser can
|
||||
|
||||
GRANT user000 TO authenticator;
|
||||
-- allow authenticator to switch into user000 role
|
||||
-- (the role itself has nologin)
|
||||
|
||||
Schemas
|
||||
=======
|
||||
|
||||
You must explicitly allow roles to access the exposed schemas in :ref:`db-schemas`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT USAGE ON SCHEMA api TO webuser;
|
||||
|
||||
Tables
|
||||
======
|
||||
|
||||
To let web users access tables you must grant them privileges for the operations you want them to do.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT
|
||||
SELECT
|
||||
, INSERT
|
||||
, UPDATE(message_body)
|
||||
, DELETE
|
||||
ON chat TO webuser;
|
||||
|
||||
You can also choose on which table columns the operation is valid. In the above example, the web user can only update the ``message_body`` column.
|
||||
|
||||
.. _func_privs:
|
||||
|
||||
Functions
|
||||
=========
|
||||
|
||||
By default, when a function is created, the privilege to execute it is not restricted by role. The function access is ``PUBLIC`` — executable by all roles (more details at `PostgreSQL Privileges page <https://www.postgresql.org/docs/current/ddl-priv.html>`_). This is not ideal for an API schema. To disable this behavior, you can run the following SQL statement:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
||||
|
||||
This will change the privileges for all functions created in the future in all schemas. Currently there is no way to limit it to a single schema. In our opinion it's a good practice anyway.
|
||||
|
||||
.. note::
|
||||
|
||||
It is however possible to limit the effect of this clause only to functions you define. You can put the above statement at the beginning of the API schema definition, and then at the end reverse it with:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO PUBLIC;
|
||||
|
||||
This will work because the :code:`alter default privileges` statement has effect on function created *after* it is executed. See `PostgreSQL alter default privileges <https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html>`_ for more details.
|
||||
|
||||
After that, you'll need to grant EXECUTE privileges on functions explicitly:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT EXECUTE ON FUNCTION login TO anonymous;
|
||||
GRANT EXECUTE ON FUNCTION signup TO anonymous;
|
||||
|
||||
You can also grant execute on all functions in a schema to a higher privileged role:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO web_user;
|
||||
|
||||
Security definer
|
||||
----------------
|
||||
|
||||
A function is executed with the privileges of the user who calls it. This means that the user has to have all permissions to do the operations the function performs.
|
||||
If the function accesses private database objects, your :ref:`API roles <roles>` won't be able to successfully execute the function.
|
||||
|
||||
Another option is to define the function with the :code:`SECURITY DEFINER` option. Then only one permission check will take place, the permission to call the function, and the operations in the function will have the authority of the user who owns the function itself.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- login as a user wich has privileges on the private schemas
|
||||
|
||||
-- create a sample function
|
||||
create or replace function login(email text, pass text, out token text) as $$
|
||||
begin
|
||||
-- access to a private schema called 'auth'
|
||||
select auth.user_role(email, pass) into _role;
|
||||
-- other operations
|
||||
-- ...
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
Note the ``SECURITY DEFINER`` keywords at the end of the function. See `PostgreSQL documentation <https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY>`_ for more details.
|
||||
|
||||
Views
|
||||
=====
|
||||
|
||||
Views are invoked with the privileges of the view owner, much like functions with the ``SECURITY DEFINER`` option. When created by a SUPERUSER role, all `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ policies will be bypassed.
|
||||
|
||||
If you're on PostgreSQL >= 15, this behavior can be changed by specifying the ``security_invoker`` option.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE VIEW sample_view WITH (security_invoker = true) AS
|
||||
SELECT * FROM sample_table;
|
||||
|
||||
On PostgreSQL < 15, you can create a non-SUPERUSER role and make this role the view's owner.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE api_views_owner NOSUPERUSER NOBYPASSRLS;
|
||||
ALTER VIEW sample_view OWNER TO api_views_owner;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.. _external_auth:
|
||||
|
||||
External Authentication
|
||||
-----------------------
|
||||
|
||||
JWT from Auth0
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
An external service like `Auth0 <https://auth0.com/>`_ can do the hard work transforming OAuth from Github, Twitter, Google etc into a JWT suitable for PostgREST. Auth0 can also handle email signup and password reset flows.
|
||||
|
||||
To use Auth0, create `an application <https://auth0.com/docs/get-started/applications>`_ for your app and `an API <https://auth0.com/docs/get-started/apis>`_ for your PostgREST server. Auth0 supports both HS256 and RS256 scheme for the issued tokens for APIs. For simplicity, you may first try HS256 scheme while creating your API on Auth0. Your application should use your PostgREST API's `API identifier <https://auth0.com/docs/get-started/apis/api-settings>`_ by setting it with the `audience parameter <https://auth0.com/docs/secure/tokens/access-tokens/get-access-tokens#control-access-token-audience>`_ during the authorization request. This will ensure that Auth0 will issue an access token for your PostgREST API. For PostgREST to verify the access token, you will need to set ``jwt-secret`` on PostgREST config file with your API's signing secret.
|
||||
@@ -1,220 +0,0 @@
|
||||
.. _install:
|
||||
|
||||
Installation
|
||||
############
|
||||
|
||||
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.
|
||||
|
||||
You can also use your OS package manager.
|
||||
|
||||
.. include:: ../shared/installation.rst
|
||||
|
||||
.. _pg-dependency:
|
||||
|
||||
Supported PostgreSQL versions
|
||||
=============================
|
||||
|
||||
=============== =================================
|
||||
**Supported** PostgreSQL >= 13
|
||||
=============== =================================
|
||||
|
||||
PostgREST works with all PostgreSQL versions still `officially supported <https://www.postgresql.org/support/versioning/>`_.
|
||||
|
||||
|
||||
Running PostgREST
|
||||
=================
|
||||
|
||||
If you downloaded PostgREST from the release page, first extract the compressed file to obtain the executable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# For UNIX platforms
|
||||
tar Jxf postgrest-[version]-[platform].tar.xz
|
||||
|
||||
# On Windows you should unzip the file
|
||||
|
||||
Now you can run PostgREST with the :code:`--help` flag to see usage instructions:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Running postgrest binary
|
||||
./postgrest --help
|
||||
|
||||
# Running postgrest installed from a package manager
|
||||
postgrest --help
|
||||
|
||||
# You should see a usage help message
|
||||
|
||||
The PostgREST server reads a configuration file as its only argument:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
postgrest /path/to/postgrest.conf
|
||||
|
||||
# You can also generate a sample config file with
|
||||
# postgrest -e > postgrest.conf
|
||||
# You'll need to edit this file and remove the usage parts for postgrest to read it
|
||||
|
||||
For a complete reference of the configuration file, see :ref:`configuration`.
|
||||
|
||||
.. note::
|
||||
|
||||
If you see a dialog box like this on Windows, it may be that the :code:`pg_config` program is not in your system path.
|
||||
|
||||
.. image:: ../_static/win-err-dialog.png
|
||||
|
||||
It usually lives in :code:`C:\Program Files\PostgreSQL\<version>\bin`. See this `article <https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/>`_ about how to modify the system path.
|
||||
|
||||
To test that the system path is set correctly, run ``pg_config`` from the command line. You should see it output a list of paths.
|
||||
|
||||
Docker
|
||||
======
|
||||
|
||||
You can get the `official PostgREST Docker image <https://hub.docker.com/r/postgrest/postgrest>`_ with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# pull the latest version
|
||||
docker pull postgrest/postgrest
|
||||
|
||||
# to pull a particular version, use one of the versions on https://hub.docker.com/r/postgrest/postgrest/tags
|
||||
docker pull postgrest/postgrest:<version>
|
||||
|
||||
To configure the container image, use :ref:`env_variables_config`.
|
||||
|
||||
There are two ways to run the PostgREST container: with an existing external database, or through docker-compose.
|
||||
|
||||
Containerized PostgREST with native PostgreSQL
|
||||
----------------------------------------------
|
||||
|
||||
The first way to run PostgREST in Docker is to connect it to an existing native database on the host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run the server
|
||||
docker run --rm --net=host \
|
||||
-e PGRST_DB_URI="postgres://app_user:password@localhost/postgres" \
|
||||
postgrest/postgrest
|
||||
|
||||
The database connection string above is just an example. Adjust the role and password as necessary. You may need to edit PostgreSQL's :code:`pg_hba.conf` to grant the user local login access.
|
||||
|
||||
.. note::
|
||||
|
||||
Docker on Mac does not support the :code:`--net=host` flag. Instead you'll need to create an IP address alias to the host. Requests for the IP address from inside the container are unable to resolve and fall back to resolution by the host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo ifconfig lo0 10.0.0.10 alias
|
||||
|
||||
You should then use 10.0.0.10 as the host in your database connection string. Also remember to include the IP address in the :code:`listen_address` within postgresql.conf. For instance:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
listen_addresses = 'localhost,10.0.0.10'
|
||||
|
||||
You might also need to add a new IPv4 local connection within pg_hba.conf. For instance:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
host all all 10.0.0.10/32 trust
|
||||
|
||||
The docker command will then look like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run the server
|
||||
docker run --rm -p 3000:3000 \
|
||||
-e PGRST_DB_URI="postgres://app_user:password@10.0.0.10/postgres" \
|
||||
postgrest/postgrest
|
||||
|
||||
.. _pg-in-docker:
|
||||
|
||||
Containerized PostgREST *and* db with docker-compose
|
||||
----------------------------------------------------
|
||||
|
||||
To avoid having to install the database at all, you can run both it and the server in containers and link them together with docker-compose. Use this configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# docker-compose.yml
|
||||
|
||||
version: '3'
|
||||
services:
|
||||
server:
|
||||
image: postgrest/postgrest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
PGRST_SERVER_HOST: 0.0.0.0 # necessary for `postgrest --ready` flag to work
|
||||
PGRST_DB_URI: postgres://app_user:password@db:5432/app_db
|
||||
PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_DB: app_db
|
||||
POSTGRES_USER: app_user
|
||||
POSTGRES_PASSWORD: password
|
||||
# Uncomment this if you want to persist the data.
|
||||
# volumes:
|
||||
# - "./pgdata:/var/lib/postgresql/data"
|
||||
|
||||
Go into the directory where you saved this file and run :code:`docker-compose up`. You will see the logs of both the database and PostgREST, and be able to access the latter on port 3000.
|
||||
|
||||
If you want to have a visual overview of your API in your browser you can add swagger-ui to your :code:`docker-compose.yml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# in services:
|
||||
swagger:
|
||||
image: swaggerapi/swagger-ui
|
||||
ports:
|
||||
- "8080:8080"
|
||||
expose:
|
||||
- "8080"
|
||||
environment:
|
||||
API_URL: http://localhost:3000/
|
||||
|
||||
With this you can see the swagger-ui in your browser on port 8080.
|
||||
|
||||
.. _build_source:
|
||||
|
||||
Building from Source
|
||||
====================
|
||||
|
||||
When a pre-built binary does not exist for your system you can build the project from source.
|
||||
|
||||
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/#how-to-install-stack>`_ for your platform
|
||||
* Install Library Dependencies
|
||||
|
||||
===================== =======================================
|
||||
Operating System Dependencies
|
||||
===================== =======================================
|
||||
Ubuntu/Debian libpq-dev, libgmp-dev, zlib1g-dev
|
||||
CentOS/Fedora/Red Hat postgresql-devel, zlib-devel, gmp-devel
|
||||
BSD postgresql12-client
|
||||
macOS libpq, gmp
|
||||
===================== =======================================
|
||||
|
||||
* Build and install binary
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/PostgREST/postgrest.git
|
||||
cd postgrest
|
||||
|
||||
# adjust local-bin-path to taste
|
||||
stack build --install-ghc --copy-bins --local-bin-path /usr/local/bin
|
||||
|
||||
.. note::
|
||||
|
||||
- If building fails and your system has less than 1GB of memory, try adding a swap file.
|
||||
- `--install-ghc` flag is only needed for the first build and can be omitted in the subsequent builds.
|
||||
|
||||
* Check that the server is installed: :code:`postgrest --help`.
|
||||
@@ -1,102 +0,0 @@
|
||||
.. _nginx:
|
||||
|
||||
Nginx
|
||||
=====
|
||||
|
||||
PostgREST is a fast way to construct a RESTful API. Its default behavior is great for scaffolding in development. When it's time to go to production it works great too, as long as you take precautions.
|
||||
PostgREST is a small sharp tool that focuses on performing the API-to-database mapping. We rely on a reverse proxy like Nginx for additional safeguards.
|
||||
|
||||
The first step is to create an Nginx configuration file that proxies requests to an underlying PostgREST server.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
http {
|
||||
# ...
|
||||
# upstream configuration
|
||||
upstream postgrest {
|
||||
server localhost:3000;
|
||||
}
|
||||
# ...
|
||||
server {
|
||||
# ...
|
||||
# expose to the outside world
|
||||
location /api/ {
|
||||
default_type application/json;
|
||||
proxy_hide_header Content-Location;
|
||||
add_header Content-Location /api/$upstream_http_content_location;
|
||||
proxy_set_header Connection "";
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://postgrest/;
|
||||
}
|
||||
# ...
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
For ubuntu, if you already installed nginx through :code:`apt` you can add this to the config file in
|
||||
:code:`/etc/nginx/sites-enabled/default`.
|
||||
|
||||
.. _https:
|
||||
|
||||
HTTPS
|
||||
-----
|
||||
|
||||
PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how <https://nginx.org/en/docs/http/configuring_https_servers.html>`_.
|
||||
|
||||
Rate Limiting
|
||||
-------------
|
||||
|
||||
Nginx supports "leaky bucket" rate limiting (see `official docs <https://nginx.org/en/docs/http/ngx_http_limit_req_module.html>`_). Using standard Nginx configuration, routes can be grouped into *request zones* for rate limiting. For instance we can define a zone for login attempts:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
|
||||
|
||||
This creates a shared memory zone called "login" to store a log of IP addresses that access the rate limited urls. The space reserved, 10 MB (:code:`10m`) will give us enough space to store a history of 160k requests. We have chosen to allow only allow one request per second (:code:`1r/s`).
|
||||
|
||||
Next we apply the zone to certain routes, like a hypothetical function called :code:`login`.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
location /rpc/login/ {
|
||||
# apply rate limiting
|
||||
limit_req zone=login burst=5;
|
||||
}
|
||||
|
||||
The burst argument tells Nginx to start dropping requests if more than five queue up from a specific IP.
|
||||
|
||||
Nginx rate limiting is general and indiscriminate. To rate limit each authenticated request individually you will need to add logic in a :ref:`Custom Validation <custom_validation>` function.
|
||||
|
||||
Alternate URL Structure
|
||||
-----------------------
|
||||
|
||||
As discussed in :ref:`singular_plural`, there are no special URL forms for singular resources in PostgREST, only operators for filtering. Thus there are no URLs like :code:`/people/1`. It would be specified instead as
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/people?id=eq.1" \
|
||||
-H "Accept: application/vnd.pgrst.object+json"
|
||||
|
||||
This allows compound primary keys and makes the intent for singular response independent of a URL convention.
|
||||
|
||||
Nginx rewrite rules allow you to simulate the familiar URL convention. The following example adds a rewrite rule for all table endpoints, but you'll want to restrict it to those tables that have a numeric simple primary key named "id."
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
# support /endpoint/:id url style
|
||||
location ~ ^/([a-z_]+)/([0-9]+) {
|
||||
|
||||
# make the response singular
|
||||
proxy_set_header Accept 'application/vnd.pgrst.object+json';
|
||||
|
||||
# assuming an upstream named "postgrest"
|
||||
proxy_pass http://postgrest/$1?id=eq.$2;
|
||||
|
||||
}
|
||||
|
||||
.. TODO
|
||||
.. Administration
|
||||
.. API Versioning
|
||||
.. HTTP Caching
|
||||
.. Upgrading
|
||||
@@ -1,25 +0,0 @@
|
||||
.. _schema_isolation:
|
||||
|
||||
Schema Isolation
|
||||
================
|
||||
|
||||
A PostgREST instance exposes all the tables, views, and functions of a single `PostgreSQL schema <https://www.postgresql.org/docs/current/ddl-schemas.html>`_ (a namespace of database objects). This means private data or implementation details can go inside different private schemas and be invisible to HTTP clients.
|
||||
|
||||
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.
|
||||
|
||||
.. container:: svg-container-md
|
||||
|
||||
.. 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>
|
||||
@@ -1,201 +0,0 @@
|
||||
.. _create_soap_endpoint:
|
||||
|
||||
Create a SOAP endpoint
|
||||
======================
|
||||
|
||||
:author: `fjf2002 <https://github.com/fjf2002>`_
|
||||
|
||||
PostgREST supports :ref:`custom_media`. With a bit of work, SOAP endpoints become possible.
|
||||
|
||||
Minimal Example
|
||||
---------------
|
||||
|
||||
This example will simply return the request body, inside a tag ``therequestbodywas``.
|
||||
|
||||
Add the following function to your PostgreSQL database:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "text/xml" as pg_catalog.xml;
|
||||
|
||||
CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS "text/xml" AS $$
|
||||
DECLARE
|
||||
nsarray CONSTANT text[][] := ARRAY[
|
||||
ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/']
|
||||
];
|
||||
BEGIN
|
||||
RETURN xmlelement(
|
||||
NAME "soapenv:Envelope",
|
||||
XMLATTRIBUTES('http://schemas.xmlsoap.org/soap/envelope/' AS "xmlns:soapenv"),
|
||||
xmlelement(NAME "soapenv:Header"),
|
||||
xmlelement(
|
||||
NAME "soapenv:Body",
|
||||
xmlelement(
|
||||
NAME theRequestBodyWas,
|
||||
(xpath('/soapenv:Envelope/soapenv:Body', $1, nsarray))[1]
|
||||
)
|
||||
)
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
Do not forget to refresh the :ref:`PostgREST schema cache <schema_reloading>`.
|
||||
|
||||
Use ``curl`` for a first test:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/rpc/my_soap_endpoint \
|
||||
--header 'Content-Type: text/xml' \
|
||||
--header 'Accept: text/xml' \
|
||||
--data-binary @- <<XML
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<mySOAPContent>
|
||||
My SOAP Content
|
||||
</mySOAPContent>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
XML
|
||||
|
||||
The output should contain the original request body within the ``therequestbodywas`` entity,
|
||||
and should roughly look like:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<therequestbodywas>
|
||||
<soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<mySOAPContent>
|
||||
My SOAP Content
|
||||
</mySOAPContent>
|
||||
</soapenv:Body>
|
||||
</therequestbodywas>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
|
||||
A more elaborate example
|
||||
------------------------
|
||||
|
||||
Here we have a SOAP service that converts a fraction to a decimal value,
|
||||
with pass-through of PostgreSQL errors to the SOAP response.
|
||||
Please note that in production you probably should not pass through plain database errors
|
||||
potentially disclosing internals to the client, but instead handle the errors directly.
|
||||
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- helper function
|
||||
CREATE OR REPLACE FUNCTION _soap_envelope(body xml)
|
||||
RETURNS xml
|
||||
LANGUAGE sql
|
||||
AS $function$
|
||||
SELECT xmlelement(
|
||||
NAME "soapenv:Envelope",
|
||||
XMLATTRIBUTES('http://schemas.xmlsoap.org/soap/envelope/' AS "xmlns:soapenv"),
|
||||
xmlelement(NAME "soapenv:Header"),
|
||||
xmlelement(NAME "soapenv:Body", body)
|
||||
);
|
||||
$function$;
|
||||
|
||||
-- helper function
|
||||
CREATE OR REPLACE FUNCTION _soap_exception(
|
||||
faultcode text,
|
||||
faultstring text
|
||||
)
|
||||
RETURNS xml
|
||||
LANGUAGE sql
|
||||
AS $function$
|
||||
SELECT _soap_envelope(
|
||||
xmlelement(NAME "soapenv:Fault",
|
||||
xmlelement(NAME "faultcode", faultcode),
|
||||
xmlelement(NAME "faultstring", faultstring)
|
||||
)
|
||||
);
|
||||
$function$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fraction_to_decimal(xml)
|
||||
RETURNS "text/xml"
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
nsarray CONSTANT text[][] := ARRAY[
|
||||
ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/']
|
||||
];
|
||||
exc_msg text;
|
||||
exc_detail text;
|
||||
exc_hint text;
|
||||
exc_sqlstate text;
|
||||
BEGIN
|
||||
-- simulating a statement that results in an exception:
|
||||
RETURN _soap_envelope(xmlelement(
|
||||
NAME "decimalValue",
|
||||
(
|
||||
(xpath('/soapenv:Envelope/soapenv:Body/fraction/numerator/text()', $1, nsarray))[1]::text::int
|
||||
/
|
||||
(xpath('/soapenv:Envelope/soapenv:Body/fraction/denominator/text()', $1, nsarray))[1]::text::int
|
||||
)::text::xml
|
||||
));
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
GET STACKED DIAGNOSTICS
|
||||
exc_msg := MESSAGE_TEXT,
|
||||
exc_detail := PG_EXCEPTION_DETAIL,
|
||||
exc_hint := PG_EXCEPTION_HINT,
|
||||
exc_sqlstate := RETURNED_SQLSTATE;
|
||||
RAISE WARNING USING
|
||||
MESSAGE = exc_msg,
|
||||
DETAIL = exc_detail,
|
||||
HINT = exc_hint;
|
||||
RETURN _soap_exception(faultcode => exc_sqlstate, faultstring => concat(exc_msg, ', DETAIL: ', exc_detail, ', HINT: ', exc_hint));
|
||||
END
|
||||
$function$;
|
||||
|
||||
Let's test the ``fraction_to_decimal`` service with illegal values:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://localhost:3000/rpc/fraction_to_decimal \
|
||||
--header 'Content-Type: text/xml' \
|
||||
--header 'Accept: text/xml' \
|
||||
--data-binary @- <<XML
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<fraction>
|
||||
<numerator>42</numerator>
|
||||
<denominator>0</denominator>
|
||||
</fraction>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
XML
|
||||
|
||||
The output should roughly look like:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Header/>
|
||||
<soapenv:Body>
|
||||
<soapenv:Fault>
|
||||
<faultcode>22012</faultcode>
|
||||
<faultstring>division by zero, DETAIL: , HINT: </faultstring>
|
||||
</soapenv:Fault>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
For more information concerning PostgREST, cf.
|
||||
|
||||
- :ref:`function_single_unnamed`
|
||||
- :ref:`custom_media`. See :ref:`any_handler`, if you need to support an ``application/soap+xml`` media type or if you want to respond with XML without sending a media type.
|
||||
- :ref:`Nginx reverse proxy <nginx>`
|
||||
|
||||
For SOAP reference, visit
|
||||
|
||||
- the specification at https://www.w3.org/TR/soap/
|
||||
- shorter more practical advice is available at https://www.w3schools.com/xml/xml_soap.asp
|
||||
@@ -1,326 +0,0 @@
|
||||
|
||||
.. _providing_html_htmx:
|
||||
|
||||
Providing HTML Content Using Htmx
|
||||
=================================
|
||||
|
||||
:author: `Laurence Isla <https://github.com/laurenceisla>`_
|
||||
|
||||
This how-to shows a way to return HTML content and use the `htmx library <https://htmx.org/>`_ to handle the AJAX requests.
|
||||
Htmx expects an HTML response and uses it to replace an element inside the DOM (see the `htmx introduction <https://htmx.org/docs/#introduction>`_ in the docs).
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-demo.gif
|
||||
|
||||
.. warning::
|
||||
|
||||
This is a proof of concept showing what can be achieved using both technologies.
|
||||
We are working on `plmustache <https://github.com/PostgREST/plmustache>`_ which will further improve the HTML aspect of this how-to.
|
||||
|
||||
Preparatory Configuration
|
||||
-------------------------
|
||||
|
||||
We will make a to-do app based on the :ref:`tut0`, so make sure to complete it before continuing.
|
||||
|
||||
To simplify things, we won't be using authentication, so grant all permissions on the ``todos`` table to the ``web_anon`` user.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
grant all on api.todos to web_anon;
|
||||
grant usage, select on sequence api.todos_id_seq to web_anon;
|
||||
|
||||
Next, add the ``text/html`` as a :ref:`custom_media`. With this, PostgREST can identify the request made by your web browser (with the ``Accept: text/html`` header)
|
||||
and return a raw HTML document file.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "text/html" as text;
|
||||
|
||||
Creating an HTML Response
|
||||
-------------------------
|
||||
|
||||
Let's create a function that returns a basic HTML file, using `Pico CSS <https://picocss.com>`_ for styling and
|
||||
`Ionicons <https://ionic.io/ionicons>`_ to show some icons later.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.index() returns "text/html" as $$
|
||||
select $html$
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PostgREST + HTMX To-Do List</title>
|
||||
<!-- Pico CSS for CSS styling -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<article>
|
||||
<h5 style="text-align: center;">
|
||||
PostgREST + HTMX To-Do List
|
||||
</h5>
|
||||
</article>
|
||||
</main>
|
||||
<!-- Script for Ionicons icons -->
|
||||
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
$html$;
|
||||
$$ language sql;
|
||||
|
||||
The web browser will open the web page at ``http://localhost:3000/rpc/index``.
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-simple.jpg
|
||||
|
||||
.. _html_htmx_list_create:
|
||||
|
||||
Listing and Creating To-Dos
|
||||
---------------------------
|
||||
|
||||
Now, let's show a list of the to-dos already inserted in the database.
|
||||
For that, we'll also need a function to help us sanitize the HTML content that may be present in the task.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.sanitize_html(text) returns text as $$
|
||||
select replace(replace(replace(replace(replace($1, '&', '&'), '"', '"'),'>', '>'),'<', '<'), '''', ''')
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.html_todo(api.todos) returns text as $$
|
||||
select format($html$
|
||||
<div>
|
||||
<%2$s>
|
||||
%3$s
|
||||
</%2$s>
|
||||
</div>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 's' else 'span' end,
|
||||
api.sanitize_html($1.task)
|
||||
);
|
||||
$$ language sql stable;
|
||||
|
||||
create or replace function api.html_all_todos() returns text as $$
|
||||
select coalesce(
|
||||
string_agg(api.html_todo(t), '<hr/>' order by t.id),
|
||||
'<p><em>There is nothing else to do.</em></p>'
|
||||
)
|
||||
from api.todos t;
|
||||
$$ language sql;
|
||||
|
||||
These two functions are used to build the to-do list template. We won't use them as PostgREST endpoints.
|
||||
|
||||
- The ``api.html_todo`` function uses the table ``api.todos`` as a parameter and formats each item into a list element ``<li>``.
|
||||
The PostgreSQL `format <https://www.postgresql.org/docs/current/functions-string.html#FUNCTIONS-STRING-FORMAT>`_ is useful to that end.
|
||||
It replaces the values according to the position in the template, e.g. ``%1$s`` will be replaced with the value of ``$1.id`` (the first parameter).
|
||||
|
||||
- The ``api.html_all_todos`` function returns the ``<ul>`` wrapper for all the list elements.
|
||||
It uses `string_arg <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ to concatenate all the to-dos in a single text value.
|
||||
It also returns an alternative message, instead of a list, when the ``api.todos`` table is empty.
|
||||
|
||||
Next, let's add an endpoint to register a to-do in the database and modify the ``/rpc/index`` page accordingly.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.add_todo(_task text) returns "text/html" as $$
|
||||
insert into api.todos(task) values (_task);
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.index() returns "text/html" as $$
|
||||
select $html$
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PostgREST + HTMX To-Do List</title>
|
||||
<!-- Pico CSS for CSS styling -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet"/>
|
||||
<!-- htmx for AJAX requests -->
|
||||
<script src="https://unpkg.com/htmx.org"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container"
|
||||
style="max-width: 600px"
|
||||
hx-headers='{"Accept": "text/html"}'>
|
||||
<article>
|
||||
<h5 style="text-align: center;">
|
||||
PostgREST + HTMX To-Do List
|
||||
</h5>
|
||||
<form hx-post="/rpc/add_todo"
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="submit"
|
||||
hx-on="htmx:afterRequest: this.reset()">
|
||||
<input type="text" name="_task" placeholder="Add a todo...">
|
||||
</form>
|
||||
<div id="todo-list-area">
|
||||
$html$
|
||||
|| api.html_all_todos() ||
|
||||
$html$
|
||||
<div>
|
||||
</article>
|
||||
</main>
|
||||
<!-- Script for Ionicons icons -->
|
||||
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
$html$;
|
||||
$$ language sql;
|
||||
|
||||
- The ``/rpc/add_todo`` endpoint allows us to add a new to-do using the ``_task`` parameter and returns an ``html`` with all the to-dos in the database.
|
||||
|
||||
- The ``/rpc/index`` now adds the ``hx-headers='{"Accept": "text/html"}'`` tag to the ``<body>``.
|
||||
This will make sure that all htmx elements inside the body send this header, otherwise PostgREST won't recognize it as HTML.
|
||||
|
||||
There is also a ``<form>`` element that uses the htmx library. Let's break it down:
|
||||
|
||||
+ ``hx-post="/rpc/add_todo"``: sends an AJAX POST request to the ``/rpc/add_todo`` endpoint, with the value of the ``_task`` from the ``<input>`` element.
|
||||
|
||||
+ ``hx-target="#todo-list-area"``: the HTML content returned from the request will go inside ``<div id="todo-list-area"></div>`` (which is the list of to-dos).
|
||||
|
||||
+ ``hx-trigger="submit"``: htmx will do this request when submitting the form (by pressing enter while inside the ``<input>``).
|
||||
|
||||
+ ``hx-on="htmx:afterRequest: this.reset()">``: this is a Javascript command that clears the form `after the request is done <https://htmx.org/events/#htmx:afterRequest>`_.
|
||||
|
||||
With this, the ``http://localhost:3000/rpc/index`` page lists all the todos and adds new ones by submitting tasks in the input element.
|
||||
Don't forget to refresh the :ref:`schema cache <schema_reloading>`.
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-insert.gif
|
||||
|
||||
Editing and Deleting To-Dos
|
||||
---------------------------
|
||||
|
||||
Now, let's modify ``api.html_todo`` and make it more functional.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.html_todo(api.todos) returns text as $$
|
||||
select format($html$
|
||||
<div class="grid">
|
||||
<div id="todo-edit-area-%1$s">
|
||||
<form id="edit-task-state-%1$s"
|
||||
hx-post="/rpc/change_todo_state"
|
||||
hx-vals='{"_id": %1$s, "_done": %4$s}'
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="click">
|
||||
<%2$s style="cursor: pointer">
|
||||
%3$s
|
||||
</%2$s>
|
||||
</form>
|
||||
</div>
|
||||
<div style="text-align: right">
|
||||
<button class="outline"
|
||||
hx-get="/rpc/html_editable_task"
|
||||
hx-vals='{"_id": "%1$s"}'
|
||||
hx-target="#todo-edit-area-%1$s"
|
||||
hx-trigger="click">
|
||||
<span>
|
||||
<ion-icon name="create"></ion-icon>
|
||||
</span>
|
||||
</button>
|
||||
<button class="outline contrast"
|
||||
hx-post="/rpc/delete_todo"
|
||||
hx-vals='{"_id": %1$s}'
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="click">
|
||||
<span>
|
||||
<ion-icon name="trash" style="color: #f87171"></ion-icon>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 's' else 'span' end,
|
||||
api.sanitize_html($1.task),
|
||||
(not $1.done)::text
|
||||
);
|
||||
$$ language sql stable;
|
||||
|
||||
Let's deconstruct the new htmx features added:
|
||||
|
||||
- The ``<form>`` element is configured as follows:
|
||||
|
||||
+ ``hx-post="/rpc/change_todo_state"``: does an AJAX POST request to that endpoint. It will toggle the ``done`` state of the to-do.
|
||||
|
||||
+ ``hx-vals='{"_id": %1$s, "_done": %4$s}'``: adds the parameters to the request.
|
||||
This is an alternative to using hidden inputs inside the ``<form>``.
|
||||
|
||||
+ ``hx-trigger="click"``: htmx does the request after clicking on the element.
|
||||
|
||||
- For the first ``<button>``:
|
||||
|
||||
+ ``hx-get="/rpc/html_editable_task"``: it does an AJAX GET request to that endpoint.
|
||||
It returns an HTML with an input that will allow us to edit the task.
|
||||
|
||||
+ ``hx-target="#todo-edit-area"``: the returned HTML will replace the element with this id.
|
||||
In this case, this replaces an individual task, not the whole list.
|
||||
|
||||
+ ``hx-vals='{"id": "eq.%1$s"}'``: adds the query parameters to the GET request.
|
||||
Note that this needs the ``eq.`` operator because it represents a table column not a function parameter.
|
||||
|
||||
- For the second ``<button>``:
|
||||
|
||||
+ ``hx-post="/rpc/delete_todo"``: this post request will delete the corresponding to-do.
|
||||
|
||||
Clicking on the first button will enable the task editing.
|
||||
That's why we create the ``api.html_editable_task`` function as an endpoint:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.html_editable_task(_id int) returns "text/html" as $$
|
||||
select format ($html$
|
||||
<form id="edit-task-%1$s"
|
||||
hx-post="/rpc/change_todo_task"
|
||||
hx-headers='{"Accept": "text/html"}'
|
||||
hx-vals='{"_id": %1$s}'
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="submit,focusout">
|
||||
<input id="task-%1$s" type="text" name="_task" value="%2$s" autofocus>
|
||||
</form>
|
||||
$html$,
|
||||
id,
|
||||
api.sanitize_html(task)
|
||||
)
|
||||
from api.todos
|
||||
where id = _id;
|
||||
$$ language sql;
|
||||
|
||||
In this example, this will return an input field that allows us to edit the corresponding to-do task.
|
||||
|
||||
Finally, let's add the endpoints that will modify and delete the to-dos in the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.change_todo_state(_id int, _done boolean) returns "text/html" as $$
|
||||
update api.todos set done = _done where id = _id;
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.change_todo_task(_id int, _task text) returns "text/html" as $$
|
||||
update api.todos set task = _task where id = _id;
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.delete_todo(_id int) returns "text/html" as $$
|
||||
delete from api.todos where id = _id;
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
All of those functions return an HTML list of to-dos that will replace the outdated one:
|
||||
|
||||
- The ``api.change_todo_state`` function updates the ``done`` column using the ``_id`` and the ``_done`` values from the request.
|
||||
|
||||
- The ``api.delete_todo`` function deletes a to-do using the ``_id`` value from the request.
|
||||
|
||||
- The ``api.change_todo_task`` function modifies the ``task`` column using the ``_id`` and the ``_task`` value from the request.
|
||||
|
||||
After refreshing the :ref:`schema cache <schema_reloading>`, the page at ``http://localhost:3000/rpc/index`` will allow us to edit, delete and complete any to-do.
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-edit-delete.gif
|
||||
|
||||
With that, we completed the to-do list functionality.
|
||||
@@ -1,124 +0,0 @@
|
||||
.. _providing_img:
|
||||
|
||||
Providing images for ``<img>``
|
||||
==============================
|
||||
|
||||
:author: `pkel <https://github.com/pkel>`_
|
||||
|
||||
In this how-to, you will learn how to create an endpoint for providing images to HTML :code:`<img>` tags without client side JavaScript. In fact, the presented technique is suitable for providing not only images, but arbitrary files.
|
||||
|
||||
We will start with a minimal example that highlights the general concept.
|
||||
Afterwards we present a more detailed solution that fixes a few shortcomings of the first approach.
|
||||
|
||||
.. warning::
|
||||
|
||||
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
|
||||
|
||||
Minimal Example
|
||||
---------------
|
||||
|
||||
First, we need a public table for storing the files.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table files(
|
||||
id int primary key
|
||||
, blob bytea
|
||||
);
|
||||
|
||||
Let's assume this table contains an image of two cute kittens with id 42. We can retrieve this image in binary format from our PostgREST API by using :ref:`custom_media`:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "application/octet-stream" as bytea;
|
||||
|
||||
create or replace function file(id int) returns "application/octet-stream" as $$
|
||||
select blob from files where id = file.id;
|
||||
$$ language sql;
|
||||
|
||||
Now we can request the RPC endpoint :code:`/rpc/file?id=42` with the :code:`Accept: application/octet-stream` header.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "localhost:3000/rpc/file?id=42" -H "Accept: application/octet-stream"
|
||||
|
||||
|
||||
Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work. That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
|
||||
Instead, the :code:`Accept: image/webp` header is sent by many web browsers by default.
|
||||
|
||||
Luckily we can change the accepted media type in the function like so:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "image/webp" as bytea;
|
||||
|
||||
create or replace function file(id int) returns "image/webp" as $$
|
||||
select blob from files where id = file.id;
|
||||
$$ language sql;
|
||||
|
||||
Now, the image will be displayed in the HTML page:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<img src="http://localhost:3000/file?id=42" alt="Cute Kittens"/>
|
||||
|
||||
Improved Version
|
||||
----------------
|
||||
|
||||
The basic solution has some shortcomings:
|
||||
|
||||
1. The response :code:`Content-Type` header is set to :code:`image/webp`.
|
||||
This might be a problem if you want to specify a different format for the file.
|
||||
2. Download requests (e.g. Right Click -> Save Image As) to :code:`/files?select=blob&id=eq.42` will propose :code:`files` as filename.
|
||||
This might confuse users.
|
||||
3. Requests to the binary endpoint are not cached.
|
||||
This will cause unnecessary load on the database.
|
||||
|
||||
The following improved version addresses these problems.
|
||||
First, in addition to the minimal example, we need to store the media types and names of our files in the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
alter table files
|
||||
add column type text generated always as (byteamagic_mime(substr(blob, 0, 4100))) stored,
|
||||
add column name text;
|
||||
|
||||
This uses the :code:`byteamagic_mime()` function from the `pg_byteamagic extension <https://github.com/nmandery/pg_byteamagic>`_ to automatically generate the type in the :code:`files` table. To guess the type of a file, it's generally enough to look at the beginning of the file, which is more efficient.
|
||||
|
||||
Next, we set modify the function to set the content type and filename.
|
||||
We use this opportunity to configure some basic, client-side caching.
|
||||
For production, you probably want to configure additional caches, e.g. on the :ref:`reverse proxy <nginx>`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "*/*" as bytea;
|
||||
|
||||
create function file(id int) returns "*/*" as
|
||||
$$
|
||||
declare headers text;
|
||||
declare blob bytea;
|
||||
begin
|
||||
select format(
|
||||
'[{"Content-Type": "%s"},'
|
||||
'{"Content-Disposition": "inline; filename=\"%s\""},'
|
||||
'{"Cache-Control": "max-age=259200"}]'
|
||||
, files.type, files.name)
|
||||
from files where files.id = file.id into headers;
|
||||
perform set_config('response.headers', headers, true);
|
||||
select files.blob from files where files.id = file.id into blob;
|
||||
if FOUND -- special var, see https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-DIAGNOSTICS
|
||||
then return(blob);
|
||||
else raise sqlstate 'PT404' using
|
||||
message = 'NOT FOUND',
|
||||
detail = 'File not found',
|
||||
hint = format('%s seems to be an invalid file id', file.id);
|
||||
end if;
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
With this, we can obtain the cat image from :code:`/rpc/file?id=42`. Thus, the resulting HTML will be:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<img src="http://localhost:3000/rpc/file?id=42" alt="Cute Kittens"/>
|
||||
@@ -1,312 +0,0 @@
|
||||
.. _sql-user-management-using-postgres-users-and-passwords:
|
||||
|
||||
SQL User Management using postgres' users and passwords
|
||||
=======================================================
|
||||
|
||||
:author: `fjf2002 <https://github.com/fjf2002>`_
|
||||
|
||||
|
||||
This is an alternative to chapter :ref:`sql_user_management`, solely using the PostgreSQL built-in table `pg_catalog.pg_authid <https://www.postgresql.org/docs/current/catalog-pg-authid.html>`_ for user management. This means
|
||||
|
||||
- no dedicated user table (aside from :code:`pg_authid`) is required
|
||||
|
||||
- PostgreSQL's users and passwords (i. e. the stuff in :code:`pg_authid`) are also used at the PostgREST level.
|
||||
|
||||
.. note::
|
||||
Only PostgreSQL users with SCRAM-SHA-256 password hashes (the default since PostgreSQL v14) are supported.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is experimental. We can't give you any guarantees, especially concerning security. Use at your own risk.
|
||||
|
||||
|
||||
|
||||
Working with pg_authid and SCRAM-SHA-256 hashes
|
||||
-----------------------------------------------
|
||||
|
||||
As in :ref:`sql_user_management`, we create a :code:`basic_auth` schema:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- We put things inside the basic_auth schema to hide
|
||||
-- them from public view. Certain public procs/views will
|
||||
-- refer to helpers and tables inside.
|
||||
CREATE SCHEMA basic_auth;
|
||||
|
||||
|
||||
As in :ref:`sql_user_management`, we create the :code:`pgcrypto` and :code:`pgjwt` extensions. Here we prefer to put the extensions in its own schemas:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE SCHEMA ext_pgcrypto;
|
||||
ALTER SCHEMA ext_pgcrypto OWNER TO postgres;
|
||||
CREATE EXTENSION pgcrypto WITH SCHEMA ext_pgcrypto;
|
||||
|
||||
|
||||
Concerning the `pgjwt extension <https://github.com/michelp/pgjwt>`_, please cf. to :ref:`jwt-from-sql`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE SCHEMA ext_pgjwt;
|
||||
ALTER SCHEMA ext_pgjwt OWNER TO postgres;
|
||||
CREATE EXTENSION pgjwt WITH SCHEMA ext_pgjwt;
|
||||
|
||||
|
||||
In order to be able to work with postgres' SCRAM-SHA-256 password hashes, we also need the PBKDF2 key derivation function. Luckily there is `a PL/pgSQL implementation on stackoverflow <https://stackoverflow.com/a/72805848>`_:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION basic_auth.pbkdf2(salt bytea, pw text, count integer, desired_length integer, algorithm text) RETURNS bytea
|
||||
LANGUAGE plpgsql IMMUTABLE
|
||||
AS $$
|
||||
DECLARE
|
||||
hash_length integer;
|
||||
block_count integer;
|
||||
output bytea;
|
||||
the_last bytea;
|
||||
xorsum bytea;
|
||||
i_as_int32 bytea;
|
||||
i integer;
|
||||
j integer;
|
||||
k integer;
|
||||
BEGIN
|
||||
algorithm := lower(algorithm);
|
||||
CASE algorithm
|
||||
WHEN 'md5' then
|
||||
hash_length := 16;
|
||||
WHEN 'sha1' then
|
||||
hash_length = 20;
|
||||
WHEN 'sha256' then
|
||||
hash_length = 32;
|
||||
WHEN 'sha512' then
|
||||
hash_length = 64;
|
||||
ELSE
|
||||
RAISE EXCEPTION 'Unknown algorithm "%"', algorithm;
|
||||
END CASE;
|
||||
--
|
||||
block_count := ceil(desired_length::real / hash_length::real);
|
||||
--
|
||||
FOR i in 1 .. block_count LOOP
|
||||
i_as_int32 := E'\\000\\000\\000'::bytea || chr(i)::bytea;
|
||||
i_as_int32 := substring(i_as_int32, length(i_as_int32) - 3);
|
||||
--
|
||||
the_last := salt::bytea || i_as_int32;
|
||||
--
|
||||
xorsum := ext_pgcrypto.HMAC(the_last, pw::bytea, algorithm);
|
||||
the_last := xorsum;
|
||||
--
|
||||
FOR j IN 2 .. count LOOP
|
||||
the_last := ext_pgcrypto.HMAC(the_last, pw::bytea, algorithm);
|
||||
|
||||
-- xor the two
|
||||
FOR k IN 1 .. length(xorsum) LOOP
|
||||
xorsum := set_byte(xorsum, k - 1, get_byte(xorsum, k - 1) # get_byte(the_last, k - 1));
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
--
|
||||
IF output IS NULL THEN
|
||||
output := xorsum;
|
||||
ELSE
|
||||
output := output || xorsum;
|
||||
END IF;
|
||||
END LOOP;
|
||||
--
|
||||
RETURN substring(output FROM 1 FOR desired_length);
|
||||
END $$;
|
||||
|
||||
ALTER FUNCTION basic_auth.pbkdf2(salt bytea, pw text, count integer, desired_length integer, algorithm text) OWNER TO postgres;
|
||||
|
||||
|
||||
Analogous to how :ref:`sql_user_management` creates the function :code:`basic_auth.user_role`, we create a helper function to check the user's password, here with another name and signature (since we want the username, not an email address).
|
||||
But contrary to :ref:`sql_user_management`, this function does not use a dedicated :code:`users` table with passwords, but instead utilizes the built-in table `pg_catalog.pg_authid <https://www.postgresql.org/docs/current/catalog-pg-authid.html>`_:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION basic_auth.check_user_pass(username text, password text) RETURNS name
|
||||
LANGUAGE sql
|
||||
AS
|
||||
$$
|
||||
SELECT rolname AS username
|
||||
FROM pg_authid
|
||||
-- regexp-split scram hash:
|
||||
CROSS JOIN LATERAL regexp_match(rolpassword, '^SCRAM-SHA-256\$(.*):(.*)\$(.*):(.*)$') AS rm
|
||||
-- identify regexp groups with sane names:
|
||||
CROSS JOIN LATERAL (SELECT rm[1]::integer AS iteration_count, decode(rm[2], 'base64') as salt, decode(rm[3], 'base64') AS stored_key, decode(rm[4], 'base64') AS server_key, 32 AS digest_length) AS stored_password_part
|
||||
-- calculate pbkdf2-digest:
|
||||
CROSS JOIN LATERAL (SELECT basic_auth.pbkdf2(salt, check_user_pass.password, iteration_count, digest_length, 'sha256')) AS digest_key(digest_key)
|
||||
-- based on that, calculate hashed passwort part:
|
||||
CROSS JOIN LATERAL (SELECT ext_pgcrypto.digest(ext_pgcrypto.hmac('Client Key', digest_key, 'sha256'), 'sha256') AS stored_key, ext_pgcrypto.hmac('Server Key', digest_key, 'sha256') AS server_key) AS check_password_part
|
||||
WHERE rolpassword IS NOT NULL
|
||||
AND pg_authid.rolname = check_user_pass.username
|
||||
-- verify password:
|
||||
AND check_password_part.stored_key = stored_password_part.stored_key
|
||||
AND check_password_part.server_key = stored_password_part.server_key;
|
||||
$$;
|
||||
|
||||
ALTER FUNCTION basic_auth.check_user_pass(username text, password text) OWNER TO postgres;
|
||||
|
||||
|
||||
|
||||
Public User Interface
|
||||
---------------------
|
||||
|
||||
Analogous to :ref:`sql_user_management`, we create a login function which takes a username and password and returns a JWT if the credentials match a user in the internal table.
|
||||
Here we use the username instead of the email address to identify a user.
|
||||
|
||||
|
||||
Logins
|
||||
~~~~~~
|
||||
|
||||
As described in :ref:`jwt-from-sql`, we'll create a JWT token inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
|
||||
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- if you are not using psql, you need to replace :DBNAME with the current database's name.
|
||||
ALTER DATABASE :DBNAME SET "app.jwt_secret" to 'reallyreallyreallyreallyverysafe';
|
||||
|
||||
|
||||
CREATE FUNCTION public.login(username text, password text, OUT token text)
|
||||
LANGUAGE plpgsql security definer
|
||||
AS $$
|
||||
DECLARE
|
||||
_role name;
|
||||
BEGIN
|
||||
-- check email and password
|
||||
SELECT basic_auth.check_user_pass(username, password) INTO _role;
|
||||
IF _role IS NULL THEN
|
||||
RAISE invalid_password USING message = 'invalid user or password';
|
||||
END IF;
|
||||
--
|
||||
SELECT ext_pgjwt.sign(
|
||||
row_to_json(r), current_setting('app.jwt_secret')
|
||||
) AS token
|
||||
FROM (
|
||||
SELECT login.username as role,
|
||||
extract(epoch FROM now())::integer + 60*60 AS exp
|
||||
) r
|
||||
INTO token;
|
||||
END;
|
||||
$$;
|
||||
|
||||
ALTER FUNCTION public.login(username text, password text) OWNER TO postgres;
|
||||
|
||||
|
||||
|
||||
Permissions
|
||||
~~~~~~~~~~~
|
||||
|
||||
Analogous to :ref:`sql_user_management`:
|
||||
Your database roles need access to the schema, tables, views and functions in order to service HTTP requests.
|
||||
Recall from the :ref:`roles` that PostgREST uses special roles to process requests, namely the authenticator and
|
||||
anonymous roles. Below is an example of permissions that allow anonymous users to attempt to log in.
|
||||
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE anon NOINHERIT;
|
||||
CREATE role authenticator NOINHERIT LOGIN PASSWORD 'secret';
|
||||
GRANT anon TO authenticator;
|
||||
|
||||
GRANT EXECUTE ON FUNCTION public.login(username text, password text) TO anon;
|
||||
|
||||
|
||||
Since the above :code:`login` function is defined as `security definer <https://www.postgresql.org/docs/current/sql-createfunction.html#id-1.9.3.67.10.2>`_,
|
||||
the anonymous user :code:`anon` doesn't need permission to access the table :code:`pg_catalog.pg_authid` .
|
||||
:code:`grant execute on function` is included for clarity but it might not be needed, see :ref:`func_privs` for more details.
|
||||
|
||||
Choose a secure password for role :code:`authenticator`.
|
||||
Do not forget to configure PostgREST to use the :code:`authenticator` user to connect, and to use the :code:`anon` user as anonymous user.
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
Let us create a sample user:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE foo PASSWORD 'bar';
|
||||
|
||||
|
||||
Test at the SQL level
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Execute:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
SELECT * FROM public.login('foo', 'bar');
|
||||
|
||||
|
||||
This should return a single scalar field like:
|
||||
|
||||
::
|
||||
|
||||
token
|
||||
-----------------------------------------------------------------------------------------------------------------------------
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTg4ODQ3fQ.idBBHuDiQuN_S7JJ2v3pBOr9QypCliYQtCgwYOzAqEk
|
||||
(1 row)
|
||||
|
||||
|
||||
Test at the REST level
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
An API request to call this function would look like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/login" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "username": "foo", "password": "bar" }'
|
||||
|
||||
The response would look like the snippet below. Try decoding the token at `jwt.io <https://jwt.io/>`_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!)
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VwcCIsImV4cCI6MTY2ODE4ODQzN30.WSytcouNMQe44ZzOQit2AQsqTKFD5mIvT3z2uHwdoYY"
|
||||
}
|
||||
|
||||
|
||||
|
||||
A more sophisticated test at the REST level
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Let's add a table, intended for the :code:`foo` user:
|
||||
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE TABLE public.foobar(foo int, bar text, baz float);
|
||||
ALTER TABLE public.foobar owner TO postgres;
|
||||
|
||||
|
||||
Now try to get the table's contents with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/foobar"
|
||||
|
||||
|
||||
This should fail --- of course, we haven't specified the user, thus PostgREST falls back to the :code:`anon` user and denies access.
|
||||
Add an :code:`Authorization` header. Please use the token value from the login function call above instead of the one provided below.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/foobar" \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY"
|
||||
|
||||
|
||||
This will fail again --- we get :code:`Permission denied to set role`. We forgot to allow the authenticator role to switch into this user by executing:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT foo TO authenticator;
|
||||
|
||||
|
||||
Re-execute the last REST request. We fail again --- we also forgot to grant permissions for :code:`foo` on the table. Execute:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT SELECT ON TABLE public.foobar TO foo;
|
||||
|
||||
Now the REST request should succeed. An empty JSON array :code:`[]` is returned.
|
||||
@@ -1,208 +0,0 @@
|
||||
.. _sql_user_management:
|
||||
|
||||
SQL User Management
|
||||
===================
|
||||
|
||||
As mentioned on :ref:`jwt_generation`, an external service can provide user management and coordinate with the PostgREST server using JWT. It's also possible to support logins entirely through SQL. It's a fair bit of work, so get ready.
|
||||
|
||||
Storing Users and Passwords
|
||||
---------------------------
|
||||
|
||||
The following table, functions, and triggers will live in a :code:`basic_auth` schema that you shouldn't expose publicly in the API. The public views and functions will live in a different schema which internally references this internal information.
|
||||
|
||||
First we'll need a table to keep track of our users:
|
||||
|
||||
.. code:: sql
|
||||
|
||||
-- We put things inside the basic_auth schema to hide
|
||||
-- them from public view. Certain public procs/views will
|
||||
-- refer to helpers and tables inside.
|
||||
|
||||
create table
|
||||
basic_auth.users (
|
||||
email text primary key check ( email ~* '^.+@.+\..+$' ),
|
||||
pass text not null check (length(pass) < 512),
|
||||
role name not null check (length(role) < 512)
|
||||
);
|
||||
|
||||
We would like the role to be a foreign key to actual database roles, however PostgreSQL does not support these constraints against the :code:`pg_roles` table. We'll use a trigger to manually enforce it.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function
|
||||
basic_auth.check_role_exists() returns trigger as $$
|
||||
begin
|
||||
if not exists (select 1 from pg_roles as r where r.rolname = new.role) then
|
||||
raise foreign_key_violation using message =
|
||||
'unknown database role: ' || new.role;
|
||||
return null;
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
create constraint trigger ensure_user_role_exists
|
||||
after insert or update on basic_auth.users
|
||||
for each row
|
||||
execute procedure basic_auth.check_role_exists();
|
||||
|
||||
Next we'll use the pgcrypto extension and a trigger to keep passwords safe in the :code:`users` table.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create extension pgcrypto;
|
||||
|
||||
create function
|
||||
basic_auth.encrypt_pass() returns trigger as $$
|
||||
begin
|
||||
if tg_op = 'INSERT' or new.pass <> old.pass then
|
||||
new.pass = crypt(new.pass, gen_salt('bf'));
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger encrypt_pass
|
||||
before insert or update on basic_auth.users
|
||||
for each row
|
||||
execute procedure basic_auth.encrypt_pass();
|
||||
|
||||
With the table in place we can make a helper to check a password against the encrypted column. It returns the database role for a user if the email and password are correct.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function
|
||||
basic_auth.user_role(email text, pass text) returns name
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
return (
|
||||
select role from basic_auth.users
|
||||
where users.email = user_role.email
|
||||
and users.pass = crypt(user_role.pass, users.pass)
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
.. _public_ui:
|
||||
|
||||
Public User Interface
|
||||
---------------------
|
||||
|
||||
In the previous section we created an internal table to store user information. Here we create a login function which takes an email address and password and returns JWT if the credentials match a user in the internal table.
|
||||
|
||||
Permissions
|
||||
~~~~~~~~~~~
|
||||
|
||||
Your database roles need access to the schema, tables, views and functions in order to service HTTP requests.
|
||||
Recall from the :ref:`roles` that PostgREST uses special roles to process requests, namely the authenticator and
|
||||
anonymous roles. Below is an example of permissions that allow anonymous users to create accounts and attempt to log in.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create role anon noinherit;
|
||||
create role authenticator noinherit;
|
||||
grant anon to authenticator;
|
||||
|
||||
Then, add ``db-anon-role`` to the configuration file to allow anonymous requests.
|
||||
|
||||
.. code:: ini
|
||||
|
||||
db-anon-role = "anon"
|
||||
|
||||
.. _jwt-from-sql:
|
||||
|
||||
JWT from SQL
|
||||
~~~~~~~~~~~~
|
||||
|
||||
You can create JWT tokens in SQL using the `pgjwt extension <https://github.com/michelp/pgjwt>`_. It's simple and requires only pgcrypto. If you're on an environment like Amazon RDS which doesn't support installing new extensions, you can still manually run the `SQL inside pgjwt <https://github.com/michelp/pgjwt/blob/master/pgjwt--0.1.1.sql>`_ (you'll need to replace ``@extschema@`` with another schema or just delete it) which creates the functions you will need.
|
||||
|
||||
Next write a function that returns the token. The one below returns a token with a hard-coded role, which expires five minutes after it was issued. Note this function has a hard-coded secret as well.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION jwt_test(OUT token text) AS $$
|
||||
SELECT public.sign(
|
||||
row_to_json(r), 'reallyreallyreallyreallyverysafe'
|
||||
) AS token
|
||||
FROM (
|
||||
SELECT
|
||||
'my_role'::text as role,
|
||||
extract(epoch from now())::integer + 300 AS exp
|
||||
) r;
|
||||
$$ LANGUAGE sql;
|
||||
|
||||
PostgREST exposes this function to clients via a POST request to ``/rpc/jwt_test``.
|
||||
|
||||
.. note::
|
||||
|
||||
To avoid hard-coding the secret in functions, save it as a property of the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- run this once
|
||||
ALTER DATABASE mydb SET "app.jwt_secret" TO 'reallyreallyreallyreallyverysafe';
|
||||
|
||||
-- then all functions can refer to app.jwt_secret
|
||||
SELECT sign(
|
||||
row_to_json(r), current_setting('app.jwt_secret')
|
||||
) AS token
|
||||
FROM ...
|
||||
|
||||
Logins
|
||||
~~~~~~
|
||||
|
||||
As described in `JWT from SQL`_, we'll create a JWT inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- login should be on your exposed schema
|
||||
create function
|
||||
login(email text, pass text, out token text) as $$
|
||||
declare
|
||||
_role name;
|
||||
begin
|
||||
-- check email and password
|
||||
select basic_auth.user_role(email, pass) into _role;
|
||||
if _role is null then
|
||||
raise invalid_password using message = 'invalid user or password';
|
||||
end if;
|
||||
|
||||
select sign(
|
||||
row_to_json(r), 'reallyreallyreallyreallyverysafe'
|
||||
) as token
|
||||
from (
|
||||
select _role as role, login.email as email,
|
||||
extract(epoch from now())::integer + 60*60 as exp
|
||||
) r
|
||||
into token;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
grant execute on function login(text,text) to anon;
|
||||
|
||||
Since the above :code:`login` function is defined as `security definer <https://www.postgresql.org/docs/current/sql-createfunction.html#id-1.9.3.67.10.2>`_,
|
||||
the anonymous user :code:`anon` doesn't need permission to read the :code:`basic_auth.users` table. It doesn't even need permission to access the :code:`basic_auth` schema.
|
||||
:code:`grant execute on function` is included for clarity but it might not be needed, see :ref:`func_privs` for more details.
|
||||
|
||||
An API request to call this function would look like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/login" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "email": "foo@bar.com", "pass": "foobar" }'
|
||||
|
||||
The response would look like the snippet below. Try decoding the token at `jwt.io <https://jwt.io/>`_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!)
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZvb0BiYXIuY29tIiwicGFzcyI6ImZvb2JhciJ9.37066TTRlh-1hXhnA9oO9Pj6lgL6zFuJU0iCHhuCFno"
|
||||
}
|
||||
|
||||
|
||||
Alternatives
|
||||
~~~~~~~~~~~~
|
||||
|
||||
See the how-to :ref:`sql-user-management-using-postgres-users-and-passwords` for a similar way that completely avoids the table :code:`basic_auth.users`.
|
||||
@@ -1,490 +0,0 @@
|
||||
.. _working_with_types:
|
||||
|
||||
Working with PostgreSQL data types
|
||||
==================================
|
||||
|
||||
:author: `Laurence Isla <https://github.com/laurenceisla>`_
|
||||
|
||||
PostgREST makes use of PostgreSQL string representations to work with data types. Thanks to this, you can use special values, such as ``now`` for timestamps, ``yes`` for booleans or time values including the time zones. This page describes how you can take advantage of these string representations and some alternatives to perform operations on different PostgreSQL data types.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
|
||||
.. NOTE: Titles are ordered alphabetically. New entries should respect this order.
|
||||
|
||||
Arrays
|
||||
------
|
||||
|
||||
To handle `array types <https://www.postgresql.org/docs/current/arrays.html>`_ you can use string representation or JSON array format.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table movies (
|
||||
id int primary key,
|
||||
title text not null,
|
||||
tags text[],
|
||||
performance_times time[]
|
||||
);
|
||||
|
||||
You can insert a new value using string representation.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/movies" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Paddington",
|
||||
"tags": "{family,comedy,not streamable}",
|
||||
"performance_times": "{12:40,15:00,20:00}"
|
||||
}
|
||||
EOF
|
||||
|
||||
Or you could send the same data using JSON array format:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/movies" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Paddington",
|
||||
"tags": ["family", "comedy", "not streamable"],
|
||||
"performance_times": ["12:40", "15:00", "20:00"]
|
||||
}
|
||||
EOF
|
||||
|
||||
To query the data you can use arrow operators. See :ref:`composite_array_columns`.
|
||||
|
||||
Multidimensional Arrays
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Similarly to one-dimensional arrays, both the string representation and JSON array format are allowed.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- This new column stores the cinema, floor and auditorium numbers in that order
|
||||
alter table movies
|
||||
add column cinema_floor_auditorium int[][][];
|
||||
|
||||
You can now update the item using JSON array format:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/movies?id=eq.1" \
|
||||
-X PATCH -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"cinema_floor_auditorium": [ [ [1,2], [6,7] ], [ [3,5], [8,9] ] ]
|
||||
}
|
||||
EOF
|
||||
|
||||
Then, for example, to query the auditoriums that are located in the first cinema (position 0 in the array) and on the second floor (position 1 in the next inner array), we can use the arrow operators this way:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"title": "Paddington",
|
||||
"auditorium": [6,7]
|
||||
}
|
||||
]
|
||||
|
||||
Bytea
|
||||
-----
|
||||
|
||||
To send raw binary to PostgREST you need a function with a single unnamed parameter of `bytea type <https://www.postgresql.org/docs/current/datatype-binary.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table files (
|
||||
id int primary key generated always as identity,
|
||||
file bytea
|
||||
);
|
||||
|
||||
create function upload_binary(bytea) returns void as $$
|
||||
insert into files (file) values ($1);
|
||||
$$ language sql;
|
||||
|
||||
Let's download the PostgREST logo for our test.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "https://postgrest.org/en/latest/_images/logo.png" -o postgrest-logo.png
|
||||
|
||||
Now, to send the file ``postgrest-logo.png`` we need to set the ``Content-Type: application/octet-stream`` header in the request:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/upload_binary" \
|
||||
-X POST -H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@postgrest-logo.png"
|
||||
|
||||
To get the image from the database, use :ref:`custom_media` like so:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "image/png" as bytea;
|
||||
|
||||
create or replace get_image(id int) returns "image/png" as $$
|
||||
select file from files where id = $1;
|
||||
$$ language sql;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/get_image?id=1" \
|
||||
-H "Accept: image/png"
|
||||
|
||||
See :ref:`providing_img` for a step-by-step example on how to handle images in HTML.
|
||||
|
||||
.. warning::
|
||||
|
||||
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
|
||||
|
||||
Composite Types
|
||||
---------------
|
||||
|
||||
With PostgREST, you have two options to handle `composite type columns <https://www.postgresql.org/docs/current/rowtypes.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type dimension as (
|
||||
length decimal(6,2),
|
||||
width decimal (6,2),
|
||||
height decimal (6,2),
|
||||
unit text
|
||||
);
|
||||
|
||||
create table products (
|
||||
id int primary key,
|
||||
size dimension
|
||||
);
|
||||
|
||||
insert into products (id, size)
|
||||
values (1, '(5.0,5.0,10.0,"cm")');
|
||||
|
||||
On one hand you can insert values using string representation.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{ "id": 2, "size": "(0.7,0.5,1.8,\"m\")" }
|
||||
EOF
|
||||
|
||||
Or you could insert the same data in JSON format.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 2,
|
||||
"size": {
|
||||
"length": 0.7,
|
||||
"width": 0.5,
|
||||
"height": 1.8,
|
||||
"unit": "m"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
You can also query the data using arrow operators. See :ref:`composite_array_columns`.
|
||||
|
||||
Enums
|
||||
-----
|
||||
|
||||
You can handle `Enumerated Types <https://www.postgresql.org/docs/current/datatype-enum.html>`_ using string representations:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type letter_size as enum ('s','m','l','xl');
|
||||
|
||||
create table products (
|
||||
id int primary key generated always as identity,
|
||||
name text,
|
||||
size letter_size
|
||||
);
|
||||
|
||||
To insert or update the value use a string:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -X POST "http://localhost:3000/products" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{ "name": "t-shirt", "size": "l" }
|
||||
EOF
|
||||
|
||||
You can then query and filter the enum using the compatible :ref:`operators <operators>`.
|
||||
For example, to get all the products larger than `m` and ordering them by their size:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/products?select=name,size&size=gt.m&order=size"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"name": "t-shirt",
|
||||
"size": "l"
|
||||
},
|
||||
{
|
||||
"name": "hoodie",
|
||||
"size": "xl"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
hstore
|
||||
------
|
||||
|
||||
You can work with data types belonging to additional supplied modules such as `hstore <https://www.postgresql.org/docs/current/hstore.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- Activate the hstore module in the current database
|
||||
create extension if not exists hstore;
|
||||
|
||||
create table countries (
|
||||
id int primary key,
|
||||
name hstore unique
|
||||
);
|
||||
|
||||
The ``name`` column will have the name of the country in different formats. You can insert values using the string representation for that data type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/countries" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
[
|
||||
{ "id": 1, "name": "common => Egypt, official => \"Arab Republic of Egypt\", native => مصر" },
|
||||
{ "id": 2, "name": "common => Germany, official => \"Federal Republic of Germany\", native => Deutschland" }
|
||||
]
|
||||
EOF
|
||||
|
||||
Notice that the use of ``"`` in the value of the ``name`` column needs to be escaped using a backslash ``\``.
|
||||
|
||||
You can also query and filter the value of a ``hstore`` column using the arrow operators, as you would do for a :ref:`JSON column<json_columns>`. For example, if you want to get the native name of Egypt:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/countries?select=name->>native&name->>common=like.Egypt"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{ "native": "مصر" }]
|
||||
|
||||
JSON
|
||||
----
|
||||
|
||||
To work with a ``json`` type column, you can handle the value as a JSON object.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table products (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
extra_info json
|
||||
);
|
||||
|
||||
You can insert a new product using a JSON object for the ``extra_info`` column:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Canned fish",
|
||||
"extra_info": {
|
||||
"expiry_date": "2025-12-31",
|
||||
"exportable": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
To query and filter the data see :ref:`json_columns` for a complete reference.
|
||||
|
||||
Ranges
|
||||
------
|
||||
|
||||
PostgREST allows you to handle `ranges <https://www.postgresql.org/docs/current/rangetypes.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table events (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
duration tsrange
|
||||
);
|
||||
|
||||
To insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/events" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
|
||||
}
|
||||
EOF
|
||||
|
||||
You can use range :ref:`operators <operators>` to filter the data. But, in this case, requesting a filter like ``events?duration=cs.2023-01-01`` will return an error, because PostgreSQL needs an explicit cast from string to timestamp. A workaround is to use a range starting and ending in the same date:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/events?duration=cs.\[2023-01-01,2023-01-01\]"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": "[\"2022-12-31 11:00:00\",\"2023-01-01 06:00:00\"]"
|
||||
}
|
||||
]
|
||||
|
||||
.. _casting_range_to_json:
|
||||
|
||||
Casting a Range to a JSON Object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As you may have noticed, the ``tsrange`` value is returned as a string literal. To return it as a JSON value, first you need to create a function that will do the conversion from a ``tsrange`` type:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function tsrange_to_json(tsrange) returns json as $$
|
||||
select json_build_object(
|
||||
'lower', lower($1)
|
||||
, 'upper', upper($1)
|
||||
, 'lower_inc', lower_inc($1)
|
||||
, 'upper_inc', upper_inc($1)
|
||||
);
|
||||
$$ language sql;
|
||||
|
||||
Then, create the cast using this function:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create cast (tsrange as json) with function tsrange_to_json(tsrange) as assignment;
|
||||
|
||||
Finally, do the request :ref:`casting the range column <casting_columns>`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/events?select=id,name,duration::json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": {
|
||||
"lower": "2022-12-31T11:00:00",
|
||||
"upper": "2023-01-01T06:00:00",
|
||||
"lower_inc": true,
|
||||
"upper_inc": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
.. note::
|
||||
|
||||
If you don't want to modify casts for built-in types, an option would be to `create a custom type <https://www.postgresql.org/docs/current/sql-createtype.html>`_
|
||||
for your own ``tsrange`` and add its own cast.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type mytsrange as range (subtype = timestamp, subtype_diff = tsrange_subdiff);
|
||||
|
||||
-- define column types and casting function analogously to the above example
|
||||
-- ...
|
||||
|
||||
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
|
||||
|
||||
Timestamps
|
||||
----------
|
||||
|
||||
You can use the **time zone** to filter or send data if needed.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table reports (
|
||||
id int primary key
|
||||
, due_date timestamptz
|
||||
);
|
||||
|
||||
Suppose you are located in Sydney and want create a report with the date in the local time zone. Your request should look like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/reports" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]'
|
||||
|
||||
Someone located in Cairo can retrieve the data using their local time, too:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"due_date": "2022-02-23T19:10:15-05:00"
|
||||
}
|
||||
]
|
||||
|
||||
The response has the date in the time zone configured by the server: ``UTC -05:00`` (see :ref:`prefer_timezone`).
|
||||
|
||||
You can use other comparative filters and also all the `PostgreSQL special date/time input values <https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE>`_ as illustrated in this example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch))"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"due_date": "2022-02-27T06:00:00-05:00"
|
||||
}
|
||||
]
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript">
|
||||
let hash = window.location.hash;
|
||||
|
||||
const redirects = {
|
||||
// PostGIS
|
||||
'#postgis': '../integrations/postgis.html#postgis',
|
||||
};
|
||||
|
||||
let willRedirectTo = redirects[hash];
|
||||
|
||||
if (willRedirectTo) {
|
||||
window.location.href = willRedirectTo;
|
||||
}
|
||||
</script>
|
||||
@@ -1,291 +0,0 @@
|
||||
.. title:: PostgREST Documentation
|
||||
|
||||
PostgREST Documentation
|
||||
=======================
|
||||
|
||||
.. container:: image-container
|
||||
|
||||
.. figure:: ../static/postgrest.png
|
||||
|
||||
.. image:: https://img.shields.io/github/stars/postgrest/postgrest.svg?style=social
|
||||
:target: https://github.com/PostgREST/postgrest
|
||||
|
||||
.. image:: https://img.shields.io/github/v/release/PostgREST/postgrest.svg
|
||||
:target: https://github.com/PostgREST/postgrest/releases
|
||||
|
||||
.. image:: https://img.shields.io/docker/pulls/postgrest/postgrest.svg
|
||||
:target: https://hub.docker.com/r/postgrest/postgrest/
|
||||
|
||||
.. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854
|
||||
:target: https://www.patreon.com/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.
|
||||
|
||||
Sponsors
|
||||
--------
|
||||
|
||||
.. container:: image-container
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/cybertec-dark.svg
|
||||
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/cybertec.svg
|
||||
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/supabase-dark.svg
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/supabase.svg
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/euronodes.svg
|
||||
:target: https://www.euronodes.com/postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/euronodes.svg
|
||||
:target: https://www.euronodes.com/postgrest
|
||||
|
||||
|
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/neon-dark.jpg
|
||||
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/neon.jpg
|
||||
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/bytebase-dark.svg
|
||||
:target: https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/bytebase.svg
|
||||
:target: https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. The static/empty.png(created with `convert -size 320x95 xc:#fcfcfc empty.png`) is an ugly workaround
|
||||
to create space and center the logos. It's not easy to layout with restructuredText.
|
||||
|
||||
.. image:: _static/empty.png
|
||||
:target: #sponsors
|
||||
|
||||
|
|
||||
|
||||
Database as Single Source of Truth
|
||||
----------------------------------
|
||||
|
||||
Using PostgREST is an alternative to manual CRUD programming. Custom API servers suffer problems. Writing business logic often duplicates, ignores or hobbles database structure. Object-relational mapping is a leaky abstraction leading to slow imperative code. The PostgREST philosophy establishes a single declarative source of truth: the data itself.
|
||||
|
||||
Declarative Programming
|
||||
-----------------------
|
||||
|
||||
It's easier to ask PostgreSQL to join data for you and let its query planner figure out the details than to loop through rows yourself. It's easier to assign permissions to database objects than to add guards in controllers. (This is especially true for cascading permissions in data dependencies.) It's easier to set constraints than to litter code with sanity checks.
|
||||
|
||||
Leak-proof Abstraction
|
||||
----------------------
|
||||
|
||||
There is no ORM involved. Creating new views happens in SQL with known performance implications. A database administrator can now create an API from scratch with no custom programming.
|
||||
|
||||
One Thing Well
|
||||
--------------
|
||||
|
||||
PostgREST has a focused scope. It works well with other tools like Nginx. This forces you to cleanly separate the data-centric CRUD operations from other concerns. Use a collection of sharp tools rather than building a big ball of mud.
|
||||
|
||||
Getting Support
|
||||
----------------
|
||||
|
||||
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.
|
||||
|
||||
Releases
|
||||
--------
|
||||
|
||||
PostgREST follows ``MAJOR.PATCH`` two-part versioning:
|
||||
|
||||
- ``MAJOR``: feature release, may deprecate or remove things.
|
||||
- ``PATCH``: fix/security release only; no features, no behavior changes.
|
||||
|
||||
Starting from ``v14.0``, only even-numbered MAJOR versions will be released, reserving odd-numbered MAJOR versions for development.
|
||||
|
||||
All the releases are published on `PostgREST's GitHub release page <https://github.com/PostgREST/postgrest/releases>`_.
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
Are you new to PostgREST? This is the place to start!
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Tutorials
|
||||
:maxdepth: 1
|
||||
|
||||
tutorials/*
|
||||
|
||||
Also have a look at :ref:`install` and :ref:`community_tutorials`.
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
Technical references for PostgREST's functionality.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: References
|
||||
:name: references
|
||||
:maxdepth: 1
|
||||
|
||||
references/auth.rst
|
||||
references/api.rst
|
||||
references/cli.rst
|
||||
references/transactions.rst
|
||||
references/connection_pool.rst
|
||||
references/schema_cache.rst
|
||||
references/errors.rst
|
||||
references/configuration.rst
|
||||
references/observability.rst
|
||||
references/*
|
||||
|
||||
Explanations
|
||||
------------
|
||||
|
||||
Key concepts in PostgREST.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Explanations
|
||||
:name: explanations
|
||||
:maxdepth: 1
|
||||
|
||||
explanations/*
|
||||
|
||||
How-tos
|
||||
-------
|
||||
|
||||
Recipes that'll help you address specific use-cases.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: How-to guides
|
||||
:name: how-tos
|
||||
:maxdepth: 1
|
||||
|
||||
how-tos/sql-user-*
|
||||
how-tos/working-*
|
||||
how-tos/*
|
||||
|
||||
.. _intgrs:
|
||||
|
||||
Integrations
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Integrations
|
||||
:name: integrations
|
||||
:maxdepth: 1
|
||||
|
||||
integrations/*
|
||||
|
||||
Ecosystem
|
||||
---------
|
||||
|
||||
PostgREST has a growing ecosystem of examples, libraries, and experiments. Here is a selection.
|
||||
|
||||
.. toctree::
|
||||
:caption: Ecosystem
|
||||
:name: ecosystem
|
||||
:maxdepth: 1
|
||||
|
||||
ecosystem.rst
|
||||
|
||||
In Production
|
||||
-------------
|
||||
|
||||
Here are some companies that use PostgREST in production.
|
||||
|
||||
* `Catarse <https://www.catarse.me>`_
|
||||
* `Drip Depot <https://www.dripdepot.com>`_
|
||||
* `Image-charts <https://www.image-charts.com>`_
|
||||
* `Netwo <https://www.netwo.io>`_
|
||||
* `Nimbus <https://www.nimbusfacility.com/sg/home>`_
|
||||
- See how Nimbus uses PostgREST in `Paul Copplestone's blog post <https://paul.copplest.one/blog/nimbus-tech-2019-04.html>`_.
|
||||
* `OpenBooking <https://openbooking.ch>`_
|
||||
* `Supabase <https://supabase.com>`_
|
||||
|
||||
Testimonials
|
||||
------------
|
||||
|
||||
"It's so fast to develop, it feels like cheating!"
|
||||
|
||||
-- François-Guillaume Ribreau
|
||||
|
||||
"I just have to say that, the CPU/Memory usage compared to our
|
||||
Node.js/Waterline ORM based API is ridiculous. It's hard to even push
|
||||
it over 60/70 MB while our current API constantly hits 1GB running on 6
|
||||
instances (dynos)."
|
||||
|
||||
-- Louis Brauer
|
||||
|
||||
"I really enjoyed the fact that all of a sudden I was writing
|
||||
microservices in SQL DDL (and v8 JavaScript functions). I dodged so
|
||||
much boilerplate. The next thing I knew, we pulled out a full rewrite
|
||||
of a Spring+MySQL legacy app in 6 months. Literally 10x faster, and
|
||||
code was super concise. The old one took 3 years and a team of 4
|
||||
people to develop."
|
||||
|
||||
-- Simone Scarduzio
|
||||
|
||||
"I like the fact that PostgREST does one thing, and one thing well.
|
||||
While PostgREST takes care of bridging the gap between our HTTP server
|
||||
and PostgreSQL database, we can focus on the development of our API in
|
||||
a single language: SQL. This puts the database in the center of our
|
||||
architecture, and pushed us to improve our skills in SQL programming
|
||||
and database design."
|
||||
|
||||
-- Eric Bréchemier, Data Engineer, eGull SAS
|
||||
|
||||
"PostgREST is performant, stable, and transparent. It allows us to
|
||||
bootstrap projects really fast, and to focus on our data and application
|
||||
instead of building out the ORM layer. In our k8s cluster, we run a few
|
||||
pods per schema we want exposed, and we scale up/down depending on demand.
|
||||
Couldn't be happier."
|
||||
|
||||
-- Anupam Garg, Datrium, Inc.
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Please see the `Contributing guidelines <https://github.com/PostgREST/postgrest/blob/main/CONTRIBUTING.md>`_ in the main PostgREST repository.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript">
|
||||
let hash = window.location.hash;
|
||||
|
||||
const redirects = {
|
||||
// Tables and Views
|
||||
'#release-notes': '#releases',
|
||||
};
|
||||
|
||||
let willRedirectTo = redirects[hash];
|
||||
|
||||
if (willRedirectTo) {
|
||||
window.location.href = willRedirectTo;
|
||||
}
|
||||
</script>
|
||||
@@ -1,30 +0,0 @@
|
||||
pg-safeupdate
|
||||
#############
|
||||
|
||||
.. _block_fulltable:
|
||||
|
||||
Block Full-Table Operations
|
||||
---------------------------
|
||||
|
||||
If the :ref:`active role <user_impersonation>` can delete table rows then the DELETE verb is allowed for clients. Here's an API request to delete old rows from a hypothetical logs table:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE
|
||||
|
||||
Note that it's very easy to delete the **entire table** by omitting the query parameter!
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/logs" -X DELETE
|
||||
|
||||
This can happen accidentally such as by switching a request from a GET to a DELETE. To protect against accidental operations use the `pg-safeupdate <https://github.com/eradman/pg-safeupdate>`_ PostgreSQL extension. It raises an error if UPDATE or DELETE are executed without specifying conditions. To install it you can use the `PGXN <https://pgxn.org/>`_ network:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -E pgxn install safeupdate
|
||||
|
||||
# then add this to postgresql.conf:
|
||||
# shared_preload_libraries='safeupdate';
|
||||
|
||||
This does not protect against malicious actions, since someone can add a url parameter that does not affect the result set. To prevent this you must turn to database permissions, forbidding the wrong people from deleting rows, and using `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ if finer access control is required.
|
||||
@@ -1,154 +0,0 @@
|
||||
.. _ww_postgis:
|
||||
|
||||
PostGIS
|
||||
=======
|
||||
|
||||
To work with `PostGIS <https://postgis.net/>`_ data types such as ``geometry`` or ``geography``, you'll need to `install PostGIS <https://postgis.net/documentation/getting_started/>`_ first.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- Activate the postgis module in the current database
|
||||
create extension if not exists postgis;
|
||||
|
||||
create table coverage (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
area geometry
|
||||
);
|
||||
|
||||
insert into coverage (id, name, area) values
|
||||
(1, 'small', ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))',4326)),
|
||||
(2, 'big', ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', 4326);
|
||||
|
||||
.. _application/geo+json:
|
||||
|
||||
``application/geo+json``
|
||||
------------------------
|
||||
|
||||
PostgREST supports the `standard <https://www.iana.org/assignments/media-types/application/geo+json>`_ ``application/geo+json`` media type which can be used to get the output in `GeoJSON <https://geojson.org/>`_ format. This will work for PostGIS versions 3.0.0 and up and will return the output as a `FeatureCollection Object <https://www.rfc-editor.org/rfc/rfc7946#section-3.3>`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-H "Accept: application/geo+json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[1,0],[1,1],[0,1],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 1,
|
||||
"name": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[10,0],[10,10],[0,10],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"name": "big"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Using generated columns
|
||||
-----------------------
|
||||
|
||||
If you need to add an extra property, like the area in square units by using ``st_area(area)``, you could add a generated column to the table and it will appear in the ``properties`` key of each ``Feature``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
alter table coverage
|
||||
add square_units double precision generated always as ( st_area(area) ) stored;
|
||||
|
||||
In the case that you are using older PostGIS versions, then creating a function is your best option:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function coverage_geo_collection() returns json as $$
|
||||
select
|
||||
json_build_object(
|
||||
'type', 'FeatureCollection',
|
||||
'features', json_agg(
|
||||
json_build_object(
|
||||
'type', 'Feature',
|
||||
'geometry', st_AsGeoJSON(c.area)::json,
|
||||
'properties', json_build_object('id', c.id, 'name', c.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
from coverage c;
|
||||
$$ language sql;
|
||||
|
||||
Now this query will return the same results:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/coverage_geo_collection"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[1,0],[1,1],[0,1],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 1,
|
||||
"name": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[10,0],[10,10],[0,10],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"name": "big"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Using string representation
|
||||
---------------------------
|
||||
|
||||
To insert areas in polygon format, you can use string representation:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
[
|
||||
{ "id": 3, "name": "strip", "area": "SRID=4326;POLYGON((0 0, 50 0, 50 2, 0 2, 0 0))" },
|
||||
{ "id": 4, "name": "diamond", "area": "SRID=4326;POLYGON((5 0, 10 5, 5 10, 0 5, 5 0))" }
|
||||
]
|
||||
EOF
|
||||
|
||||
PostgREST will automatically cast the ``area`` column into a ``Polygon`` geometry type.
|
||||
@@ -1,59 +0,0 @@
|
||||
systemd
|
||||
=======
|
||||
|
||||
For Linux distributions that use **systemd** (Ubuntu, Debian, Arch Linux) you can create a daemon in the following way.
|
||||
|
||||
First, create postgrest configuration in ``/etc/postgrest/config``
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
db-uri = "postgres://<your_user>:<your_password>@localhost:5432/<your_db>"
|
||||
db-schemas = "<your_exposed_schema>"
|
||||
db-anon-role = "<your_anon_role>"
|
||||
jwt-secret = "<your_secret>"
|
||||
|
||||
Create a dedicated ``postgrest`` user with:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
sudo useradd -M -U -d /nonexistent -s /usr/sbin/nologin postgrest
|
||||
|
||||
Then create the systemd service file in ``/etc/systemd/system/postgrest.service``
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[Unit]
|
||||
Description=REST API for any PostgreSQL database
|
||||
After=postgresql.service
|
||||
|
||||
[Service]
|
||||
User=postgrest
|
||||
Group=postgrest
|
||||
ExecStart=/bin/postgrest /etc/postgrest/config
|
||||
ExecReload=/bin/kill -SIGUSR1 $MAINPID
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
After that, you can enable the service at boot time and start it with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl enable postgrest
|
||||
systemctl start postgrest
|
||||
|
||||
## For reloading the service
|
||||
## systemctl restart postgrest
|
||||
|
||||
.. _file_descriptors:
|
||||
|
||||
File Descriptors
|
||||
----------------
|
||||
|
||||
File descriptors are kernel resources that are used by HTTP connections (among others). File descriptors are limited per process. The kernel default limit is 1024, which is increased in some Linux distributions.
|
||||
When under heavy traffic, PostgREST can reach this limit and start showing ``No file descriptors available`` errors. To clear these errors, you can increase the process' file descriptor limit.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[Service]
|
||||
LimitNOFILE=10000
|
||||
@@ -1,198 +0,0 @@
|
||||
personal_ws-1.1 en 0 utf-8
|
||||
api
|
||||
API's
|
||||
APIs
|
||||
APISIX
|
||||
AST
|
||||
aud
|
||||
Auth
|
||||
auth
|
||||
authenticator
|
||||
backoff
|
||||
booleans
|
||||
BOM
|
||||
Bytea
|
||||
Cardano
|
||||
cd
|
||||
centric
|
||||
CLI
|
||||
CMS
|
||||
coercible
|
||||
conf
|
||||
Cloudflare
|
||||
config
|
||||
cors
|
||||
CORS
|
||||
cryptographically
|
||||
CSV
|
||||
durations
|
||||
DDL
|
||||
DOM
|
||||
DSL
|
||||
DevOps
|
||||
dockerize
|
||||
enum
|
||||
Enums
|
||||
Entra
|
||||
eq
|
||||
ETH
|
||||
Ethereum
|
||||
EveryLayout
|
||||
filename
|
||||
FreeBSD
|
||||
fts
|
||||
GeoJSON
|
||||
Github
|
||||
Google
|
||||
grantor
|
||||
GraphQL
|
||||
gte
|
||||
GUC
|
||||
Haskell
|
||||
HMAC
|
||||
htmx
|
||||
Htmx
|
||||
Homebrew
|
||||
hstore
|
||||
HTTP
|
||||
HTTPS
|
||||
HV
|
||||
Inlining
|
||||
inlined
|
||||
Integrations
|
||||
idletime
|
||||
IDLETIME
|
||||
ilike
|
||||
imatch
|
||||
io
|
||||
IP
|
||||
isdistinct
|
||||
JS
|
||||
js
|
||||
JSON
|
||||
JSPath
|
||||
JWK
|
||||
JWT
|
||||
jwt
|
||||
Keycloak
|
||||
Kubernetes
|
||||
localhost
|
||||
login
|
||||
lookups
|
||||
Logins
|
||||
LIBPQ
|
||||
logins
|
||||
lon
|
||||
lt
|
||||
lte
|
||||
macOS
|
||||
misprediction
|
||||
multi
|
||||
namespace
|
||||
namespaced
|
||||
Nanos
|
||||
neq
|
||||
nginx
|
||||
nixpkgs
|
||||
npm
|
||||
nxl
|
||||
nxr
|
||||
OAuth
|
||||
ORM
|
||||
Observability
|
||||
Okta
|
||||
OpenAPI
|
||||
openapi
|
||||
ov
|
||||
parametrized
|
||||
passphrase
|
||||
PBKDF
|
||||
PgBouncer
|
||||
pgcrypto
|
||||
pgjwt
|
||||
pgrst
|
||||
pgrstX
|
||||
PGRSTX
|
||||
pgSQL
|
||||
authid
|
||||
phfts
|
||||
phraseto
|
||||
plainto
|
||||
plfts
|
||||
poolers
|
||||
PostGIS
|
||||
PostgreSQL
|
||||
PostgreSQL's
|
||||
PostgREST
|
||||
postgres
|
||||
postgrest
|
||||
PostgREST's
|
||||
pre
|
||||
preflight
|
||||
plpgsql
|
||||
psql
|
||||
RabbitMQ
|
||||
RDS
|
||||
reallyreallyreallyreallyverysafe
|
||||
Redux
|
||||
refactor
|
||||
reloadable
|
||||
Reloadable
|
||||
requester's
|
||||
RESTful
|
||||
RLS
|
||||
RPC
|
||||
RSA
|
||||
safeupdate
|
||||
savepoint
|
||||
schemas
|
||||
schema's
|
||||
SHA
|
||||
signup
|
||||
SIGUSR
|
||||
sl
|
||||
SQL
|
||||
sql
|
||||
SQLSTATE
|
||||
sr
|
||||
SSL
|
||||
stateful
|
||||
stdout
|
||||
supervisees
|
||||
SvelteKit
|
||||
systemd
|
||||
todo
|
||||
todos
|
||||
tos
|
||||
tsquery
|
||||
tx
|
||||
TypeScript
|
||||
UI
|
||||
ui
|
||||
unicode
|
||||
unikernel
|
||||
unix
|
||||
updatable
|
||||
unfulfillable
|
||||
unselected
|
||||
Untyped
|
||||
UPSERT
|
||||
Upsert
|
||||
upsert
|
||||
uri
|
||||
url
|
||||
urlencoded
|
||||
urls
|
||||
variadic
|
||||
verifier
|
||||
versioning
|
||||
Vondra
|
||||
Vue
|
||||
webhooks
|
||||
websearch
|
||||
Websockets
|
||||
webuser
|
||||
wfts
|
||||
www
|
||||
debouncing
|
||||
deduplicates
|
||||
@@ -1,76 +0,0 @@
|
||||
.. _admin_server:
|
||||
|
||||
Admin Server
|
||||
############
|
||||
|
||||
PostgREST provides an admin server that can be enabled by setting :ref:`admin-server-port`.
|
||||
|
||||
.. _health_check:
|
||||
|
||||
Health Check
|
||||
============
|
||||
|
||||
You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
|
||||
|
||||
Two endpoints ``live`` and ``ready`` will then be available. Both these endpoints reply with a status code and empty response body.
|
||||
|
||||
.. important::
|
||||
|
||||
If you have a machine with multiple network interfaces and multiple PostgREST instances in the same port, you need to specify a unique :ref:`hostname <server-host>`
|
||||
in the configuration of each PostgREST instance for the health check to work correctly. Don't use the special values(``!4``, ``*``, etc) in this case because the health check
|
||||
could report a false positive.
|
||||
|
||||
Live
|
||||
----
|
||||
|
||||
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``500`` otherwise.
|
||||
|
||||
For instance, to verify if PostgREST is running while the ``admin-server-port`` is set to ``3001``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -I "http://localhost:3001/live"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
Ready
|
||||
-----
|
||||
|
||||
Additionally to the ``live`` check, the ``ready`` endpoint checks the state of the :ref:`connection_pool` and the :ref:`schema_cache`. A request will return ``200 OK`` if both are good or ``503`` if not.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -I "http://localhost:3001/ready"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
PostgREST will try to recover from the ``503`` state with :ref:`automatic_recovery`.
|
||||
|
||||
Metrics
|
||||
=======
|
||||
|
||||
Provides :ref:`metrics`.
|
||||
|
||||
Runtime Schema Cache
|
||||
====================
|
||||
|
||||
Provides the ``schema_cache`` endpoint that prints the runtime :ref:`schema_cache`.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3001/schema_cache"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"dbMediaHandlers": ["..."],
|
||||
"dbRelationships": ["..."],
|
||||
"dbRepresentations": ["..."],
|
||||
"dbRoutines": ["..."],
|
||||
"dbTables": ["..."],
|
||||
"dbTimezones": ["..."]
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
.. _api:
|
||||
|
||||
API
|
||||
###
|
||||
|
||||
PostgREST exposes three database objects of a schema as resources: tables, views and functions.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
|
||||
api/tables_views.rst
|
||||
api/functions.rst
|
||||
api/schemas.rst
|
||||
api/computed_fields.rst
|
||||
api/domain_representations.rst
|
||||
api/pagination_count.rst
|
||||
api/resource_embedding.rst
|
||||
api/resource_representation.rst
|
||||
api/media_type_handlers.rst
|
||||
api/aggregate_functions.rst
|
||||
api/openapi.rst
|
||||
api/preferences.rst
|
||||
api/*
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript">
|
||||
let hash = window.location.hash;
|
||||
|
||||
const redirects = {
|
||||
// Tables and Views
|
||||
'#horizontal-filtering-rows': 'api/tables_views.html#horizontal-filtering',
|
||||
'#operators': 'api/tables_views.html#operators',
|
||||
'#logical-operators': 'api/tables_views.html#logical-operators',
|
||||
'#pattern-matching': 'api/tables_views.html#pattern-matching',
|
||||
'#full-text-search': 'api/tables_views.html#full-text-search',
|
||||
'#vertical-filtering-columns': 'api/tables_views.html#vertical-filtering',
|
||||
'#renaming-columns': 'api/tables_views.html#renaming-columns',
|
||||
'#casting-columns': 'api/tables_views.html#casting-columns',
|
||||
'#json-columns': 'api/tables_views.html#json-columns',
|
||||
'#composite-array-columns': 'api/tables_views.html#composite-array-columns',
|
||||
'#computed-virtual-columns': 'api/computed_fields.html',
|
||||
'#ordering': 'api/tables_views.html#ordering',
|
||||
'#limits-and-pagination': 'api/pagination_count.html',
|
||||
'#exact-count': 'api/pagination_count.html#exact-count',
|
||||
'#planned-count': 'api/pagination_count.html#planned-count',
|
||||
'#estimated-count': 'api/pagination_count.html#estimated-count',
|
||||
'#updates': 'api/tables_views.html#update',
|
||||
'#insertions': 'api/tables_views.html#insert',
|
||||
'#bulk-insert': 'api/tables_views.html#bulk-insert',
|
||||
'#specifying-columns': 'api/tables_views.html#specifying-columns',
|
||||
'#upsert': 'api/tables_views.html#upsert',
|
||||
'#on-conflict': 'api/tables_views.html#on-conflict',
|
||||
'#put': 'api/tables_views.html#put',
|
||||
'#deletions': 'api/tables_views.html#delete',
|
||||
'#limited-updates-deletions': 'api/tables_views.html#limited-update-delete',
|
||||
// Functions
|
||||
'#stored-procedures': 'api/functions.html',
|
||||
'#calling-functions-with-a-single-json-parameter': 'api/functions.html#functions-with-a-single-json-parameter',
|
||||
'#calling-functions-with-a-single-unnamed-parameter': 'api/functions.html#functions-with-a-single-unnamed-parameter',
|
||||
'#calling-functions-with-array-parameters': 'api/functions.html#functions-with-array-parameters',
|
||||
'#calling-variadic-functions': 'api/functions.html#variadic-functions',
|
||||
'#scalar-functions': 'api/functions.html#scalar-functions',
|
||||
'#function-filters': 'api/functions.html#table-valued-functions',
|
||||
'#overloaded-functions': 'api/functions.html#overloaded-functions',
|
||||
// Schemas
|
||||
'#switching-schemas': 'api/schemas.html',
|
||||
// Resource Embedding
|
||||
'#resource-embedding': 'api/resource_embedding.html#resource-embedding',
|
||||
'#many-to-one-relationships': 'api/resource_embedding.html#many-to-one-relationships',
|
||||
'#one-to-many-relationships': 'api/resource_embedding.html#one-to-many-relationships',
|
||||
'#many-to-many-relationships': 'api/resource_embedding.html#many-to-many-relationships',
|
||||
'#one-to-one-relationships': 'api/resource_embedding.html#one-to-one-relationships',
|
||||
'#computed-relationships': 'api/resource_embedding.html#computed-relationships',
|
||||
'#nested-embedding': 'api/resource_embedding.html#nested-embedding',
|
||||
'#embedded-filters': 'api/resource_embedding.html#embedded-filters',
|
||||
'#embedding-with-top-level-filtering': 'api/resource_embedding.html#top-level-filtering',
|
||||
'#embedding-partitioned-tables': 'api/resource_embedding.html#foreign-key-joins-on-partitioned-tables',
|
||||
'#embedding-views': 'api/resource_embedding.html#foreign-key-joins-on-views',
|
||||
'#embedding-chains-of-views': 'api/resource_embedding.html#foreign-key-joins-on-chains-of-views',
|
||||
'#embedding-on-stored-procedures': 'api/resource_embedding.html#foreign-key-joins-on-table-valued-functions',
|
||||
'#embedding-after-insertions-updates-deletions': 'api/resource_embedding.html#foreign-key-joins-on-writes',
|
||||
'#embedding-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships',
|
||||
'#target-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships',
|
||||
'#hint-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships',
|
||||
"#embedding-through-join-tables": "api/resource_embedding.html#many-to-many-relationships",
|
||||
// OpenAPI
|
||||
'#openapi-support': 'api/openapi.html',
|
||||
// Resource Representation
|
||||
'#response-format': 'api/resource_representation.html#response-format',
|
||||
'#singular-or-plural': 'api/resource_representation.html#singular-or-plural',
|
||||
'#response-formats-for-scalar-responses': 'api/functions.html#scalar-functions',
|
||||
// CORS
|
||||
'#cors': 'api/cors.html',
|
||||
// OPTIONS
|
||||
'#options': 'api/options.html',
|
||||
// URL Grammar
|
||||
'#custom-queries': 'api/url_grammar.html#custom-queries',
|
||||
'#unicode-support': 'api/url_grammar.html#unicode-support',
|
||||
'#table-columns-with-spaces': 'api/url_grammar.html#table-columns-with-spaces',
|
||||
'#reserved-characters': 'api/url_grammar.html#reserved-characters',
|
||||
// Transactions
|
||||
'#immutable-and-stable-functions': 'transactions.html#access-mode',
|
||||
'#http-context': 'transactions.html#transaction-scoped-settings',
|
||||
'#accessing-request-headers-cookies-and-jwt-claims': 'transactions.html#request-headers-cookies-and-jwt-claims',
|
||||
'#legacy-guc-variable-names': 'transactions.html#transaction-scoped-settings',
|
||||
'#accessing-request-path-and-method': 'transactions.html#request-path-and-method',
|
||||
'#setting-response-headers': 'transactions.html#response-headers',
|
||||
'#setting-headers-via-pre-request': 'transactions.html#setting-headers-via-pre-request',
|
||||
'#setting-response-status-code': 'transactions.html#response-status-code',
|
||||
'#raise-errors-with-http-status-codes': 'errors.html#raise-errors-with-http-status-codes',
|
||||
// Admin
|
||||
'#execution-plan': 'observability.html#execution-plan',
|
||||
// Deprecated
|
||||
'#bulk-call': '../releases/v11.0.1.html#breaking-changes',
|
||||
};
|
||||
|
||||
let willRedirectTo = redirects[hash];
|
||||
|
||||
if (willRedirectTo) {
|
||||
window.location.href = willRedirectTo;
|
||||
}
|
||||
</script>
|
||||
@@ -1,257 +0,0 @@
|
||||
.. _aggregate_functions:
|
||||
|
||||
Aggregate Functions
|
||||
###################
|
||||
|
||||
PostgREST supports the following aggregate functions: ``avg()``, ``count()``, ``max()``, ``min()``, and ``sum()``.
|
||||
Please refer to the `section on aggregate functions in the PostgreSQL documentation <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ for a detailed explanation of these functions.
|
||||
|
||||
.. note::
|
||||
Aggregate functions are *disabled* by default in PostgREST, because they can create performance problems without appropriate safeguards.
|
||||
See :ref:`db-aggregates-enabled` for further details.
|
||||
|
||||
To use an aggregate function, append it to a column in the ``select`` parameter, like so:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum()"
|
||||
|
||||
This will return a ``sum`` of all the values of the ``amount`` column in a single row:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"sum": 1234.56
|
||||
}
|
||||
]
|
||||
|
||||
You can ``select`` multiple aggregate functions at the same time (you may need to :ref:`rename them <renaming_columns>` to disambiguate).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=total_amount:amount.sum(),avg_amount:amount.avg(),total_quantity:quantity.sum()"
|
||||
|
||||
.. note::
|
||||
Aggregate functions work alongside other PostgREST features, like :ref:`h_filter`, :ref:`json_columns`, and :ref:`ordering`.
|
||||
However they are not compatible with :ref:`domain_reps` for the moment.
|
||||
Additionally, PostgreSQL's ``HAVING`` clause and ordering by aggregated columns are not yet supported.
|
||||
|
||||
Automatic ``GROUP BY``
|
||||
======================
|
||||
|
||||
In SQL, a ``GROUP BY`` clause is required to aggregate the selected columns.
|
||||
However, PostgREST handles grouping automatically if the columns are already present in the ``select`` parameter.
|
||||
For instance:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
|
||||
|
||||
This will get the sum and average of the amounts grouped by each unique value in the ``order_date`` column:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"sum": 1234.56,
|
||||
"avg": 123.45,
|
||||
"order_date": "2023-01-01"
|
||||
},
|
||||
{
|
||||
"sum": 2345.67,
|
||||
"avg": 234.56,
|
||||
"order_date": "2023-01-02"
|
||||
}
|
||||
]
|
||||
|
||||
The ``count()`` Aggregate
|
||||
=========================
|
||||
|
||||
.. note::
|
||||
Before the addition of aggregate functions, it was possible to count by adding ``count`` (without parentheses) to the ``select`` parameter.
|
||||
While this is still supported, it may be deprecated in the future, and thus use of this legacy feature is **not recommended**.
|
||||
Please use ``count()`` (with parentheses) instead.
|
||||
|
||||
``count()`` is a special case because it can be used with or without an aggregated column. For example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=count(),observation_count:observation.count(),order_date"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"count": 4,
|
||||
"observation_count": 2,
|
||||
"order_date": "2023-01-01"
|
||||
},
|
||||
{
|
||||
"count": 2,
|
||||
"observation_count": 1,
|
||||
"order_date": "2023-01-02"
|
||||
}
|
||||
]
|
||||
|
||||
Note that there is a difference between the result of ``count()`` and ``observation.count()``.
|
||||
The former counts the whole row, while the latter counts the non ``NULL`` values of the ``observation`` column (both grouped by ``order_date``).
|
||||
This is due to how PostgreSQL itself implements the ``count()`` function.
|
||||
|
||||
Casting Aggregates
|
||||
==================
|
||||
|
||||
It is :ref:`possible to cast <casting_columns>` the aggregated column or the aggregate itself, or both at the same time.
|
||||
|
||||
Casting the Aggregated Column
|
||||
-----------------------------
|
||||
|
||||
For example, let's say that ``orders`` has an ``order_details`` :ref:`JSON column <json_columns>` with a ``tax_amount`` key.
|
||||
We cannot sum ``tax_amount`` directly because using ``->`` or ``->>`` will return the data in ``json`` or ``text`` format.
|
||||
So we need to cast it to a compatible type (e.g. ``numeric``) right before the aggregate function:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"sum": 1234.56
|
||||
}
|
||||
]
|
||||
|
||||
Casting the Aggregate
|
||||
---------------------
|
||||
|
||||
For instance, if we wanted to round the average of the ``amount`` column, we could do so by casting ``avg()`` to an ``int``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.avg()::int"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"avg": 201
|
||||
}
|
||||
]
|
||||
|
||||
Aggregates and Resource Embedding
|
||||
=================================
|
||||
|
||||
You can group an aggregate function by an :ref:`embedded resource <resource_embedding>` and also use the aggregates inside them.
|
||||
|
||||
Grouping by an Embedded Resource
|
||||
--------------------------------
|
||||
|
||||
Similar to grouping by columns, aggregate functions can also be grouped by embedded resources.
|
||||
For example, let's say that the ``orders`` table is related to a ``customers`` table.
|
||||
To get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, we would do the following:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"sum": 100,
|
||||
"customers": {
|
||||
"name": "Customer A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"sum": 200,
|
||||
"customers": {
|
||||
"name": "Customer B"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
Using Aggregates Inside Embedded Resources
|
||||
------------------------------------------
|
||||
|
||||
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
|
||||
|
||||
curl "http://localhost:3000/customers?select=name,city,state,orders(amount.sum(),order_date)"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"name": "Customer A",
|
||||
"city": "New York",
|
||||
"state": "NY",
|
||||
"orders": [
|
||||
{
|
||||
"sum": 215.22,
|
||||
"order_date": "2023-09-01"
|
||||
},
|
||||
{
|
||||
"sum": 905.73,
|
||||
"order_date": "2023-09-02"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Customer B",
|
||||
"city": "Los Angeles",
|
||||
"state": "CA",
|
||||
"orders": [
|
||||
{
|
||||
"sum": 329.71,
|
||||
"order_date": "2023-09-01"
|
||||
},
|
||||
{
|
||||
"sum": 425.87,
|
||||
"order_date": "2023-09-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
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``.
|
||||
|
||||
Aggregates in To-One Spreads
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All the aggregates inside a :ref:`one-to-one or many-to-one spread embedded resource <spread_to_one_embed>` will be hoisted to the top-level relationship.
|
||||
In other words, it will behave as if the aggregate was done in the top-level relationship itself. For example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/orders?select=order_date,...customers(subscription_date.max(),subscription_date.min())
|
||||
|
||||
This will take the ``max`` and ``min`` subscription date of every customer and group it by the ``order_date`` column:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"order_date": "2023-11-01",
|
||||
"max": "2023-10-15",
|
||||
"min": "2013-10-01"
|
||||
},
|
||||
{
|
||||
"order_date": "2023-11-02",
|
||||
"max": "2023-10-30",
|
||||
"min": "2016-02-11"
|
||||
}
|
||||
]
|
||||
|
||||
.. note::
|
||||
|
||||
Aggregates inside to-many spreads are not supported
|
||||
@@ -1,75 +0,0 @@
|
||||
.. _computed_cols:
|
||||
|
||||
Computed Fields
|
||||
###############
|
||||
|
||||
Computed fields are virtual columns that are not stored in a table. PostgreSQL makes it possible to implement them using functions on table types.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE TABLE people (
|
||||
first_name text
|
||||
, last_name text
|
||||
, job text
|
||||
);
|
||||
|
||||
-- a computed field that combines data from two columns
|
||||
CREATE FUNCTION full_name(people)
|
||||
RETURNS text AS $$
|
||||
SELECT $1.first_name || ' ' || $1.last_name;
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
Horizontal Filtering on Computed Fields
|
||||
=======================================
|
||||
|
||||
:ref:`h_filter` can be applied to computed fields. For example, we can do a :ref:`fts` on :code:`full_name`:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- (optional) you can add an index on the computed field to speed up the query
|
||||
CREATE INDEX people_full_name_idx ON people
|
||||
USING GIN (to_tsvector('english', full_name(people)));
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/people?full_name=fts.Beckett"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{"first_name": "Samuel", "last_name": "Beckett", "job": "novelist"}
|
||||
]
|
||||
|
||||
Vertical Filtering on Computed Fields
|
||||
=====================================
|
||||
|
||||
Computed fields won't appear on the response by default but you can use :ref:`v_filter` to include them:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/people?select=full_name,job"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{"full_name": "Samuel Beckett", "job": "novelist"}
|
||||
]
|
||||
|
||||
Ordering on Computed Fields
|
||||
===========================
|
||||
|
||||
:ref:`ordering` on computed fields is also possible:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/people?order=full_name.desc"
|
||||
|
||||
.. important::
|
||||
|
||||
Computed fields must be created in the :ref:`exposed schema <db-schemas>` or in a schema in the :ref:`extra search path <db-extra-search-path>` to be used in this way. When placing the computed field in the :ref:`exposed schema <db-schemas>` you can use an **unnamed** parameter, as in the example above, to prevent it from being exposed as an :ref:`RPC <functions>` under ``/rpc``.
|
||||
|
||||
.. note::
|
||||
|
||||
- PostgreSQL 12 introduced `generated columns <https://www.postgresql.org/docs/12/ddl-generated-columns.html>`_, which can also compute a value based on other columns. However they're stored, not virtual.
|
||||
- "computed fields" are documented on https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-USAGE (search for "computed fields")
|
||||
- On previous PostgREST versions this feature was documented with the name of "computed columns".
|
||||
@@ -1,40 +0,0 @@
|
||||
.. _cors:
|
||||
|
||||
CORS
|
||||
####
|
||||
|
||||
By default, PostgREST sets highly permissive cross origin resource sharing, that is why it accepts Ajax requests from any domain. This behavior can be configured by using :ref:`server_cors_allowed_origins`.
|
||||
|
||||
|
||||
It also handles `preflight requests <https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request>`_ done by the browser, which are cached using the returned ``Access-Control-Max-Age: 86400`` header (86400 seconds = 24 hours). This is useful to reduce the latency of the subsequent requests.
|
||||
|
||||
A ``POST`` preflight request would look like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://localhost:3000/items" \
|
||||
-X OPTIONS \
|
||||
-H "Origin: http://example.com" \
|
||||
-H "Access-Control-Request-Method: POST" \
|
||||
-H "Access-Control-Request-Headers: Content-Type"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Access-Control-Allow-Origin: http://example.com
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD
|
||||
Access-Control-Allow-Headers: Authorization, Content-Type, Accept, Accept-Language, Content-Language
|
||||
Access-Control-Max-Age: 86400
|
||||
|
||||
.. _allowed_origins:
|
||||
|
||||
Allowed Origins
|
||||
===============
|
||||
|
||||
With the following config setting, PostgREST will accept CORS requests from domains :code:`http://example.com` and :code:`http://example2.com`.
|
||||
|
||||
|
||||
.. code-block::
|
||||
|
||||
server-cors-allowed-origins="http://example.com, http://example2.com"
|
||||
@@ -1,176 +0,0 @@
|
||||
.. _domain_reps:
|
||||
|
||||
Domain Representations
|
||||
######################
|
||||
|
||||
Domain Representations separates "how the data is presented" from "how the data is stored". It works by creating `domains <https://www.postgresql.org/docs/current/sql-createdomain.html>`_ and `casts <https://www.postgresql.org/docs/current/sql-createcast.html>`_, the latter act on the former to present and receive the data in different formats.
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
:backlinks: none
|
||||
|
||||
Custom Domain
|
||||
=============
|
||||
|
||||
Suppose you want to use a ``uuid`` type for a primary key and want to present it shortened to web users.
|
||||
|
||||
For this, let's create a domain based on ``uuid``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain app_uuid as uuid;
|
||||
|
||||
-- and use it as our table PK.
|
||||
create table profiles(
|
||||
id app_uuid
|
||||
, name text
|
||||
);
|
||||
|
||||
-- some data for the example
|
||||
insert into profiles values ('846c4ffd-92ce-4de7-8d11-8e29929f4ec4', 'John Doe');
|
||||
|
||||
Domain Response Format
|
||||
======================
|
||||
|
||||
We can shorten the ``uuid`` with ``base64`` encoding. Let's use JSON as our response format for this example.
|
||||
|
||||
To change the domain format for JSON, create a function that converts ``app_uuid`` to ``json``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- the name of the function is arbitrary
|
||||
CREATE OR REPLACE FUNCTION json(app_uuid) RETURNS json AS $$
|
||||
select to_json(encode(uuid_send($1),'base64'));
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
-- check it works
|
||||
select json('846c4ffd-92ce-4de7-8d11-8e29929f4ec4'::app_uuid);
|
||||
json
|
||||
----------------------------
|
||||
"hGxP/ZLOTeeNEY4pkp9OxA=="
|
||||
|
||||
Then create a CAST to tell PostgREST to convert it automatically whenever a JSON response is requested.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE CAST (app_uuid AS json) WITH FUNCTION json(app_uuid) AS IMPLICIT;
|
||||
|
||||
With this you can obtain the data in the shortened format.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/profiles" \
|
||||
-H "Accept: application/json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{"id":"hGxP/ZLOTeeNEY4pkp9OxA==","name":"John Doe"}]
|
||||
|
||||
.. note::
|
||||
|
||||
- Casts on domains are ignored by PostgreSQL, their interpretation is left to the application. We're discussing the possibility of including the Domain Representations behavior on `pgsql-hackers <https://www.postgresql.org/message-id/flat/CAGRrpzZKa%2BGu91j1SOvN3tM1f-7Gh_w441c5nAX1QqdH3Q31Lg%40mail.gmail.com>`_.
|
||||
- It would make more sense to use ``base58`` encoding as it's URL friendly but for simplicity we use ``base64`` (supported natively in PostgreSQL).
|
||||
|
||||
.. important::
|
||||
|
||||
After creating a cast over a domain, you must refresh PostgREST schema cache. See :ref:`schema_reloading`.
|
||||
|
||||
Domain Filter Format
|
||||
====================
|
||||
|
||||
For :ref:`h_filter` to work with the shortened format, you need a different conversion.
|
||||
|
||||
PostgREST considers the URL query string to be, in the most generic sense, ``text``. So let's create a function that converts ``text`` to ``app_uuid``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- the name of the function is arbitrary
|
||||
CREATE OR REPLACE FUNCTION app_uuid(text) RETURNS app_uuid AS $$
|
||||
select substring(decode($1,'base64')::text from 3)::uuid;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
-- plus a CAST to tell PostgREST to use this function
|
||||
CREATE CAST (text AS app_uuid) WITH FUNCTION app_uuid(text) AS IMPLICIT;
|
||||
|
||||
Now you can filter as usual.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
|
||||
-H "Accept: application/json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{"id":"hGxP/ZLOTeeNEY4pkp9OxA==","name":"John Doe"}]
|
||||
|
||||
.. note::
|
||||
|
||||
If there's no CAST from ``text`` to ``app_uuid`` defined, the filter will still work with the native uuid format (``846c4ffd-92ce-4de7-8d11-8e29929f4ec4``).
|
||||
|
||||
Domain Request Body Format
|
||||
==========================
|
||||
|
||||
To accept the shortened format in a JSON request body, for example when creating a new record, define a ``json`` to ``app_uuid`` conversion.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- the name of the function is arbitrary
|
||||
CREATE OR REPLACE FUNCTION app_uuid(json) RETURNS public.app_uuid AS $$
|
||||
-- here we reuse the previous app_uuid(text) function
|
||||
select app_uuid($1 #>> '{}');
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
CREATE CAST (json AS public.app_uuid) WITH FUNCTION app_uuid(json) AS IMPLICIT;
|
||||
|
||||
Now we can :ref:`insert` (or :ref:`update`) as usual.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/profiles" \
|
||||
-H "Prefer: return=representation" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- <<JSON
|
||||
|
||||
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
|
||||
|
||||
JSON
|
||||
|
||||
The response:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}]
|
||||
|
||||
Note that on the database side we have our regular ``uuid`` format.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
select * from profiles;
|
||||
|
||||
id | name
|
||||
--------------------------------------+----------
|
||||
846c4ffd-92ce-4de7-8d11-8e29929f4ec4 | John Doe
|
||||
cc7ec76c-5254-4dfc-bf19-9a70ba2ae9b9 | Jane Doe
|
||||
(2 rows)
|
||||
|
||||
.. note::
|
||||
|
||||
If there's no CAST from ``json`` to ``app_uuid`` defined, the request body will still work with the native uuid format (``cc7ec76c-5254-4dfc-bf19-9a70ba2ae9b9``).
|
||||
|
||||
Advantages over Views
|
||||
=====================
|
||||
|
||||
`Views <https://www.postgresql.org/docs/current/sql-createview.html>`_ also allow us to change the format of the underlying type. However they come with drawbacks that increase complexity.
|
||||
|
||||
1) Formatting the column in the view makes it `non-updatable <https://www.postgresql.org/docs/current/sql-createview.html#SQL-CREATEVIEW-UPDATABLE-VIEWS>`_ since Postgres doesn't know how to reverse the transform. This can be worked around using INSTEAD OF triggers.
|
||||
2) When filtering by this column, we get full table scans for the same reason (also applies to :ref:`computed_cols`) . The performance loss here can be avoided with a computed index, or using a materialized generated column.
|
||||
3) If the formatted column is used as a foreign key, PostgREST can no longer detect that relationship and :ref:`resource_embedding` breaks. This can be worked around with :ref:`computed_relationships`.
|
||||
|
||||
Domain Representations avoid all the above drawbacks. Their only drawback is that for existing tables, you have to change the column types. But this should be a fast operation since domains are binary coercible with their underlying types. A table rewrite won't be required.
|
||||
|
||||
.. note::
|
||||
|
||||
Why not create a `base type <https://www.postgresql.org/docs/current/sql-createtype.html#id-1.9.3.94.5.8>`_ instead? ``CREATE TYPE app_uuid (INTERNALLENGTH = 22, INPUT = app_uuid_parser, OUTPUT = app_uuid_formatter)``.
|
||||
|
||||
Creating base types need superuser, which is restricted on cloud hosted databases. Additionally this way lets "how the data is presented" dictate "how the data is stored" which would be backwards.
|
||||
@@ -1,391 +0,0 @@
|
||||
.. _functions:
|
||||
|
||||
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 <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.
|
||||
|
||||
If they return table types, functions can:
|
||||
|
||||
- Use all the same :ref:`read filters as Tables and Views <read>` (horizontal/vertical filtering, counts, limits, etc.).
|
||||
- Use :ref:`Resource Embedding <function_embed>`, if the returned table type has relationships to other tables.
|
||||
|
||||
.. note::
|
||||
|
||||
Why the ``/rpc`` prefix? PostgreSQL allows a table or view to have the same name as a function. The prefix allows us to avoid routes collisions.
|
||||
|
||||
.. warning::
|
||||
|
||||
`Stored Procedures <https://www.postgresql.org/docs/current/xproc.html>`_ are not supported.
|
||||
|
||||
Calling with POST
|
||||
-----------------
|
||||
|
||||
To supply arguments in an API call, include a JSON object in the request payload. Each key/value of the object will become an argument.
|
||||
|
||||
For instance, assume we have created this function in the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION add_them(a integer, b integer)
|
||||
RETURNS integer AS $$
|
||||
SELECT a + b;
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
||||
.. important::
|
||||
|
||||
Whenever you create or change a function you must refresh PostgREST's schema cache. See the section :ref:`schema_reloading`.
|
||||
|
||||
The client can call it by posting an object like
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/add_them" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "a": 1, "b": 2 }'
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
3
|
||||
|
||||
.. note::
|
||||
|
||||
PostgreSQL converts identifier names to lowercase unless you quote them like:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION "someFunc"("someParam" text) ...
|
||||
|
||||
Calling with GET
|
||||
----------------
|
||||
|
||||
If the function doesn't modify the database, it will also run under the GET method (see :ref:`access_mode`).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
|
||||
|
||||
The function parameter names match the JSON object keys in the POST case, for the GET case they match the query parameters ``?a=1&b=2``.
|
||||
|
||||
.. _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
|
||||
----------------------------------------------
|
||||
|
||||
If you want the JSON request body to be sent as a single argument, you can create a function with a single unnamed ``json`` or ``jsonb`` parameter.
|
||||
For this the ``Content-Type: application/json`` header must be included in the request.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION mult_them(json) RETURNS int AS $$
|
||||
SELECT ($1->>'x')::int * ($1->>'y')::int
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/mult_them" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "x": 4, "y": 2 }'
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
8
|
||||
|
||||
.. note::
|
||||
|
||||
If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
|
||||
|
||||
.. _function_single_unnamed:
|
||||
|
||||
Functions with a single unnamed parameter
|
||||
-----------------------------------------
|
||||
|
||||
You can make a POST request to a function with a single unnamed parameter to send raw ``bytea``, ``text`` or ``xml`` data.
|
||||
|
||||
To send raw XML, the parameter type must be ``xml`` and the header ``Content-Type: text/xml`` must be included in the request.
|
||||
|
||||
To send raw binary, the parameter type must be ``bytea`` and the header ``Content-Type: application/octet-stream`` must be included in the request.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE TABLE files(blob bytea);
|
||||
|
||||
CREATE FUNCTION upload_binary(bytea) RETURNS void AS $$
|
||||
INSERT INTO files(blob) VALUES ($1);
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/upload_binary" \
|
||||
-X POST -H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@file_name.ext"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
[ ... ]
|
||||
|
||||
To send raw text, the parameter type must be ``text`` and the header ``Content-Type: text/plain`` must be included in the request.
|
||||
|
||||
.. _functions_array:
|
||||
|
||||
Functions with array parameters
|
||||
-------------------------------
|
||||
|
||||
You can call a function that takes an array parameter:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function plus_one(arr int[]) returns int[] as $$
|
||||
SELECT array_agg(n + 1) FROM unnest($1) AS n;
|
||||
$$ language sql;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/plus_one" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{"arr": [1,2,3,4]}'
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[2,3,4,5]
|
||||
|
||||
For calling the function with GET, you can pass the array as an `array literal <https://www.postgresql.org/docs/current/arrays.html#ARRAYS-INPUT>`_,
|
||||
as in ``{1,2,3,4}``. Note that the curly brackets have to be urlencoded(``{`` is ``%7B`` and ``}`` is ``%7D``).
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D'"
|
||||
|
||||
.. note::
|
||||
|
||||
For versions prior to PostgreSQL 10, to pass a PostgreSQL native array on a POST payload, you need to quote it and use an array literal:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/plus_one" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "arr": "{1,2,3,4}" }'
|
||||
|
||||
In these versions we recommend using function parameters of type JSON to accept arrays from the client.
|
||||
|
||||
.. _functions_variadic:
|
||||
|
||||
Variadic functions
|
||||
------------------
|
||||
|
||||
You can call a variadic function by passing a JSON array in a POST request:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function plus_one(variadic v int[]) returns int[] as $$
|
||||
SELECT array_agg(n + 1) FROM unnest($1) AS n;
|
||||
$$ language sql;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/plus_one" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{"v": [1,2,3,4]}'
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[2,3,4,5]
|
||||
|
||||
In a GET request, you can repeat the same parameter name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4"
|
||||
|
||||
Repeating also works in POST requests with ``Content-Type: application/x-www-form-urlencoded``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/plus_one" \
|
||||
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d 'v=1&v=2&v=3&v=4'
|
||||
|
||||
.. _table_functions:
|
||||
|
||||
Table-Valued Functions
|
||||
----------------------
|
||||
|
||||
A function that returns a table type can be filtered using the same filters as :ref:`tables and views <tables_views>`. They can also use :ref:`Resource Embedding <function_embed>`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION best_films_2017() RETURNS SETOF films ..
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)"
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc"
|
||||
|
||||
.. _function_inlining:
|
||||
|
||||
Function Inlining
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
A function that follows the `rules for inlining <https://wiki.postgresql.org/wiki/Inlining_of_SQL_functions#Inlining_conditions_for_table_functions>`_ will also inline :ref:`filters <h_filter>`, :ref:`order <ordering>` and :ref:`limits <limits>`.
|
||||
|
||||
For example, for the following function:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function getallprojects() returns setof projects
|
||||
language sql stable
|
||||
as $$
|
||||
select * from projects;
|
||||
$$;
|
||||
|
||||
Let's get its :ref:`explain_plan` when calling it with filters applied:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/getallprojects?id=eq.1" \
|
||||
-H "Accept: application/vnd.pgrst.plan"
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
Aggregate (cost=8.18..8.20 rows=1 width=112)
|
||||
-> Index Scan using projects_pkey on projects (cost=0.15..8.17 rows=1 width=40)
|
||||
Index Cond: (id = 1)
|
||||
|
||||
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
|
||||
----------------
|
||||
|
||||
PostgREST will detect if the function is scalar or table-valued and will shape the response format accordingly:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
3
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/best_films_2017"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{ "title": "Okja", "rating": 7.4},
|
||||
{ "title": "Call me by your name", "rating": 8},
|
||||
{ "title": "Blade Runner 2049", "rating": 8.1}
|
||||
]
|
||||
|
||||
To manually choose a return format such as binary, see :ref:`custom_media`.
|
||||
|
||||
.. _untyped_functions:
|
||||
|
||||
Untyped functions
|
||||
-----------------
|
||||
|
||||
Functions that return ``record`` or ``SETOF record`` are supported:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function projects_setof_record() returns setof record as $$
|
||||
select * from projects;
|
||||
$$ language sql;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/projects_setof_record"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{"id":1,"name":"Windows 7","client_id":1},
|
||||
{"id":2,"name":"Windows 10","client_id":1},
|
||||
{"id":3,"name":"IOS","client_id":2}]
|
||||
|
||||
However note that they will fail when trying to use :ref:`v_filter` and :ref:`h_filter` on them.
|
||||
|
||||
So while they can be used for quick tests, it's recommended to always choose a strict return type for the function.
|
||||
|
||||
Overloaded functions
|
||||
--------------------
|
||||
|
||||
You can call overloaded functions with different number of arguments.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION rental_duration(customer_id integer) ..
|
||||
|
||||
CREATE FUNCTION rental_duration(customer_id integer, from_date date) ..
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/rental_duration?customer_id=232"
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01"
|
||||
|
||||
.. important::
|
||||
|
||||
Overloaded functions with the same argument names but different types are not supported.
|
||||