Compare commits
@@ -1,42 +1,16 @@
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-1
|
||||
image_family: freebsd-14-0
|
||||
|
||||
build_task:
|
||||
# Don't change this name without adjusting .github/workflows/build.yaml
|
||||
# Don't change this name without adjusting .github/workflows/ci.yaml
|
||||
name: Build FreeBSD (Stack)
|
||||
install_script: pkg install -y postgresql16-client hs-stack git
|
||||
|
||||
only_if: |
|
||||
$CIRRUS_TAG != '' || $CIRRUS_BRANCH == 'main' || $CIRRUS_BRANCH =~ 'v*' ||
|
||||
changesInclude(
|
||||
'.github/workflows/build.yaml',
|
||||
'.github/actions/artifact-from-cirrus/**',
|
||||
'.cirrus.yml',
|
||||
'postgrest.cabal',
|
||||
'stack.yaml*',
|
||||
'**.hs'
|
||||
)
|
||||
install_script: pkg install -y postgresql13-client hs-stack git
|
||||
|
||||
stack_cache:
|
||||
folders: /.stack
|
||||
fingerprint_script:
|
||||
- echo $CIRRUS_OS
|
||||
- stack --version
|
||||
- md5sum postgrest.cabal
|
||||
- md5sum stack.yaml.lock
|
||||
|
||||
stack_work_cache:
|
||||
folders: .stack-work
|
||||
fingerprint_script:
|
||||
- echo $CIRRUS_OS
|
||||
- stack --version
|
||||
- md5sum postgrest.cabal
|
||||
- md5sum stack.yaml.lock
|
||||
- find main src -type f -iname '*.hs' -exec md5sum "{}" +
|
||||
|
||||
build_script: |
|
||||
stack build -j 1 --local-bin-path . --copy-bins --stack-yaml stack-21.7.yaml
|
||||
strip postgrest
|
||||
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
|
||||
|
||||
@@ -7,14 +7,12 @@ When submitting a new feature or fix:
|
||||
+ `fix`, bug fixes
|
||||
+ `feat`, new features added
|
||||
+ `perf`, performance improvements
|
||||
+ `docs`, updating the documentation
|
||||
+ `nix`, related to the Nix development environment
|
||||
+ `ci`, related to the Continuous Integration modules
|
||||
+ `test`, related to the testing modules
|
||||
+ `refactor`, refactoring code
|
||||
+ `deprecate`, deprecating a feature
|
||||
+ `changelog`, updating the CHANGELOG
|
||||
+ `chore`, maintenance (build process, updating sponsors, etc.)
|
||||
+ `chore`, maintenance (changelog, build process, etc.)
|
||||
+ Other prefixes may be used if necessary
|
||||
- If there's a breaking change, add `BREAKING CHANGE` and an explanation to your commit message
|
||||
-->
|
||||
|
||||
@@ -1,5 +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:
|
||||
- ubuntu-24.04-arm
|
||||
@@ -112,7 +112,7 @@ runs:
|
||||
echo "artifacts=${artifacts}" >> "$GITHUB_OUTPUT"
|
||||
- name: Save artifact to GitHub Actions
|
||||
if: steps.find-task.outputs.task_found
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: ${{ inputs.upload }}
|
||||
path: ${{ steps.download.outputs.artifacts }}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Cache on main
|
||||
|
||||
description: Stores caches on main and release branches only, but restores them on all branches.
|
||||
|
||||
inputs:
|
||||
path:
|
||||
description: Path(s) to cache
|
||||
required: true
|
||||
save-prs:
|
||||
description: Whether to additionally store the cache in a pull request, too. Should only be used for very small caches.
|
||||
type: boolean
|
||||
prefix:
|
||||
description: Cache key prefix to be used in both primary key and restore-keys.
|
||||
required: true
|
||||
suffix:
|
||||
description: Cache key suffix to be used only in primary key.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ inputs.prefix }}-
|
||||
- uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ inputs.prefix }}-
|
||||
@@ -11,16 +11,15 @@ inputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: nixbuild/nix-quick-install-action@25aff27c252e0c8cdda3264805f7b6bcd92c8718 # v29
|
||||
- uses: nixbuild/nix-quick-install-action@60e9c39264d4714139af3cdf15f691b19eec3530 # v28
|
||||
with:
|
||||
nix_conf: |-
|
||||
always-allow-substitutes = true
|
||||
max-jobs = auto
|
||||
- uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15
|
||||
- uses: cachix/cachix-action@18cf96c7c98e048e10a83abd92116114cd8504be # v14
|
||||
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,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /.github/actions/setup-nix
|
||||
schedule:
|
||||
interval: weekly
|
||||
@@ -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,25 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices"
|
||||
],
|
||||
"baseBranches": [
|
||||
"main",
|
||||
"/^v[0-9]+/"
|
||||
],
|
||||
"rebaseWhen": "conflicted",
|
||||
"packageRules": [
|
||||
{
|
||||
"matchBaseBranches": [ "/^v[0-9]+/" ],
|
||||
"groupName": "all dependencies"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["docutils"],
|
||||
"allowedVersions": "<0.21.0"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["macos"],
|
||||
"allowedVersions": "<13"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# This script builds PostgREST in a remote ARM server
|
||||
|
||||
[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; }
|
||||
[ -z "$2" ] && { echo "Missing 2nd argument: Build environment directory name"; exit 1; }
|
||||
[ -z "$3" ] && { echo "Missing 3rd argument: GHC version"; exit 1; }
|
||||
|
||||
PGRST_GITHUB_COMMIT="$1"
|
||||
SCRIPT_DIR="$2"
|
||||
|
||||
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
|
||||
# latest is a shortcut documented on https://www.haskell.org/ghcup/guide/#tags-and-shortcuts
|
||||
CABAL_VERSION="latest"
|
||||
GHC_VERSION="$3"
|
||||
|
||||
install_packages() {
|
||||
sudo apt-get update -y
|
||||
sudo apt-get upgrade -y
|
||||
sudo apt-get install -y git build-essential curl libffi-dev libffi7 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 llvm libnuma-dev zlib1g-dev libpq-dev jq gcc
|
||||
sudo apt-get clean
|
||||
}
|
||||
|
||||
install_ghcup() {
|
||||
export BOOTSTRAP_HASKELL_NONINTERACTIVE=1
|
||||
export BOOTSTRAP_HASKELL_MINIMAL=1
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
|
||||
source ~/.ghcup/env
|
||||
}
|
||||
|
||||
install_cabal() {
|
||||
ghcup upgrade
|
||||
ghcup install cabal $CABAL_VERSION
|
||||
ghcup set cabal $CABAL_VERSION
|
||||
}
|
||||
|
||||
install_ghc() {
|
||||
ghcup upgrade
|
||||
ghcup install ghc $GHC_VERSION
|
||||
ghcup set ghc $GHC_VERSION
|
||||
}
|
||||
|
||||
install_packages
|
||||
|
||||
# Add ghcup to the PATH for this session
|
||||
[ -f ~/.ghcup/env ] && source ~/.ghcup/env
|
||||
|
||||
ghcup --version || install_ghcup
|
||||
ghcup set cabal $CABAL_VERSION || install_cabal
|
||||
ghcup set ghc $GHC_VERSION || install_ghc
|
||||
|
||||
cd ~/$SCRIPT_DIR
|
||||
|
||||
# Clone the repository and build the project
|
||||
git clone https://github.com/PostgREST/postgrest.git
|
||||
cd postgrest
|
||||
git checkout $PGRST_GITHUB_COMMIT
|
||||
cabal v2-update && cabal v2-build
|
||||
|
||||
# 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
|
||||
@@ -1,8 +1,6 @@
|
||||
# PostgREST Docker Hub image for aarch64.
|
||||
# The x86-64 is a single-static-binary image built via Nix, see:
|
||||
# nix/tools/docker/README.md
|
||||
# PostgREST docker hub image
|
||||
|
||||
FROM ubuntu:noble@sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab AS postgrest
|
||||
FROM ubuntu:noble@sha256:3f85b7caad41a95462cf5b787d8a04604c8262cdcdf9a472b8c52ef83375fe15 AS postgrest
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt install -y --no-install-recommends libpq-dev zlib1g-dev jq gcc libnuma-dev \
|
||||
@@ -10,7 +8,6 @@ RUN apt-get update -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY postgrest /usr/bin/postgrest
|
||||
RUN chmod +x /usr/bin/postgrest
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# This script publishes the Docker ARM images to Docker Hub.
|
||||
|
||||
[ -z "$1" ] && { echo "Missing 1st argument: PostgREST github commit SHA"; exit 1; }
|
||||
[ -z "$2" ] && { echo "Missing 2nd argument: Docker repo"; exit 1; }
|
||||
[ -z "$3" ] && { echo "Missing 3rd argument: Docker username"; exit 1; }
|
||||
[ -z "$4" ] && { echo "Missing 4th argument: Docker password"; exit 1; }
|
||||
[ -z "$5" ] && { echo "Missing 5th argument: Build environment directory name"; exit 1; }
|
||||
[ -z "$6" ] && { echo "Missing 6th argument: PostgREST version"; exit 1; }
|
||||
|
||||
PGRST_GITHUB_COMMIT="$1"
|
||||
DOCKER_REPO="$2"
|
||||
DOCKER_USER="$3"
|
||||
DOCKER_PASS="$4"
|
||||
SCRIPT_DIR="$5"
|
||||
PGRST_VERSION="$6"
|
||||
|
||||
DOCKER_BUILD_DIR="$SCRIPT_DIR/docker-env"
|
||||
|
||||
clean_env()
|
||||
{
|
||||
sudo docker logout
|
||||
}
|
||||
|
||||
# Login to Docker
|
||||
sudo docker logout
|
||||
{ echo $DOCKER_PASS | sudo docker login -u $DOCKER_USER --password-stdin; } || { echo "Couldn't login to docker"; exit 1; }
|
||||
|
||||
trap clean_env sigint sigterm exit
|
||||
|
||||
# Move to the docker build environment
|
||||
cd ~/$DOCKER_BUILD_DIR
|
||||
|
||||
# Push final images to Docker hub
|
||||
# NOTE: This command publishes a separate ARM image because the builds cannot
|
||||
# be added to the manifest if they are not in the registry beforehand.
|
||||
# This image must be manually deleted from Docker Hub at the end of the process.
|
||||
sudo docker buildx build --build-arg PGRST_GITHUB_COMMIT=$PGRST_GITHUB_COMMIT \
|
||||
-t $DOCKER_REPO/postgrest:$PGRST_VERSION-arm \
|
||||
--push .
|
||||
|
||||
# Add the arm images to the manifest
|
||||
# NOTE: This assumes that there already is a `postgrest:<version>` image
|
||||
# for the amd64 architecture pushed to Docker Hub
|
||||
sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:$PGRST_VERSION $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
|
||||
[ "$PGRST_VERSION" != "devel" ] && sudo docker buildx imagetools create --append -t $DOCKER_REPO/postgrest:latest $DOCKER_REPO/postgrest:$PGRST_VERSION-arm
|
||||
|
||||
sudo docker logout
|
||||
@@ -1,201 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
paths:
|
||||
- .github/workflows/build.yaml
|
||||
- .github/actions/**
|
||||
- .github/scripts/**
|
||||
- .github/*
|
||||
- '*.nix'
|
||||
- nix/**
|
||||
- .cirrus.yml
|
||||
- cabal.project*
|
||||
- postgrest.cabal
|
||||
- stack.yaml*
|
||||
- '**.hs'
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: build-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
static:
|
||||
name: Nix - Linux x86-64 static
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build static executable
|
||||
run: nix-build -A postgrestStatic
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: postgrest-linux-static-x86-64
|
||||
path: result/bin/postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build Docker image
|
||||
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
|
||||
- name: Save built Docker image as artifact
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: postgrest-docker-x86-64
|
||||
path: postgrest-docker.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
macos:
|
||||
name: Nix - MacOS
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Install gnu sed
|
||||
run: brew install gnu-sed
|
||||
|
||||
- name: Build everything
|
||||
run: |
|
||||
# The --dry-run will give us a list of derivations to download from cachix and
|
||||
# derivations to build. We only take those that would have to be built and then build
|
||||
# those explicitly. This has the advantage that pure verification will not include
|
||||
# a download anymore, making it much faster. If something needs to be built, only
|
||||
# the dependencies required to do so will be downloaded, but not everything.
|
||||
nix-build --dry-run 2>&1 \
|
||||
| gsed -e '1,/derivations will be built:$/d' -e '/paths will be fetched/Q' \
|
||||
| xargs nix-build
|
||||
|
||||
|
||||
stack:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux aarch64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
cache: |
|
||||
~/.stack/pantry
|
||||
~/.stack/snapshots
|
||||
~/.stack/stack.sqlite3
|
||||
artifact: postgrest-ubuntu-aarch64
|
||||
deps: sudo apt-get update && sudo apt-get install libpq-dev
|
||||
|
||||
- name: MacOS
|
||||
runs-on: macos-14
|
||||
cache: |
|
||||
~/.stack/pantry
|
||||
~/.stack/snapshots
|
||||
~/.stack/stack.sqlite3
|
||||
artifact: postgrest-macos-aarch64
|
||||
deps: brew link --force libpq
|
||||
|
||||
- name: Windows
|
||||
runs-on: windows-2022
|
||||
cache: |
|
||||
C:\sr\pantry
|
||||
C:\sr\snapshots
|
||||
C:\sr\stack.sqlite3
|
||||
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
|
||||
artifact: postgrest-windows-x86-64
|
||||
|
||||
name: Stack - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: haskell-actions/setup@bbd90a29996ac33b1c644a42206e312fc0379748 # v2.7.9
|
||||
with:
|
||||
# This must match the version in stack.yaml's resolver
|
||||
ghc-version: 9.6.5
|
||||
enable-stack: true
|
||||
stack-no-global: true
|
||||
stack-setup-ghc: true
|
||||
- name: Cache ~/.stack
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: ${{ matrix.cache }}
|
||||
prefix: stack
|
||||
suffix: ${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
|
||||
- name: Cache .stack-work
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: .stack-work
|
||||
save-prs: true
|
||||
prefix: stack-work-${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
|
||||
suffix: ${{ hashFiles('main/**/*.hs', 'src/**/*.hs') }}
|
||||
- name: Install dependencies
|
||||
if: matrix.deps
|
||||
run: ${{ matrix.deps }}
|
||||
- name: Build with Stack
|
||||
run: stack build --lock-file error-on-write --local-bin-path result --copy-bins
|
||||
- name: Strip Executable
|
||||
run: strip result/postgrest*
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: |
|
||||
result/postgrest
|
||||
result/postgrest.exe
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
freebsd:
|
||||
name: Stack - FreeBSD from CirrusCI
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: ./.github/actions/artifact-from-cirrus
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
task: Build FreeBSD (Stack)
|
||||
download: bin
|
||||
upload: postgrest-freebsd-x86-64
|
||||
|
||||
|
||||
cabal:
|
||||
strategy:
|
||||
matrix:
|
||||
ghc: ['9.6.4', '9.8.2']
|
||||
fail-fast: false
|
||||
name: Cabal - Linux x86-64 - GHC ${{ matrix.ghc }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: haskell-actions/setup@bbd90a29996ac33b1c644a42206e312fc0379748 # v2.7.9
|
||||
with:
|
||||
ghc-version: ${{ matrix.ghc }}
|
||||
- name: Cache .cabal
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/packages
|
||||
~/.cabal/store
|
||||
prefix: cabal-${{ matrix.ghc }}
|
||||
suffix: ${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
|
||||
- name: Cache dist-newstyle
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: dist-newstyle
|
||||
save-prs: true
|
||||
prefix: cabal-${{ matrix.ghc }}-dist-newstyle-${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
|
||||
suffix: ${{ hashFiles('**/*.hs') }}
|
||||
- name: Install dependencies
|
||||
run: cabal build --only-dependencies --enable-tests --enable-benchmarks
|
||||
- name: Build
|
||||
run: cabal build --enable-tests --enable-benchmarks all
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Cachix
|
||||
|
||||
# This workflow serves to
|
||||
# - keep cachix up to date with the main branch
|
||||
# - incrementally update cachix for large dependency
|
||||
# updates, e.g. after running postgrest-nixpkgs-upgrade,
|
||||
# which can cause the main CI workflow to time out
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- rel-*
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
Seed-Cachix:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: Linux
|
||||
runs-on: ubuntu-22.04
|
||||
- os: MacOS
|
||||
runs-on: macos-12
|
||||
name: Seed ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Install cachix tooling
|
||||
run: |
|
||||
nix-env -f default.nix -iA devTools.pushCachix.bin
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Seed dynamic postgrest build
|
||||
run: |
|
||||
nix-build -A postgrestPackage
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Seed style tools
|
||||
run: |
|
||||
nix-build -A style
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Seed test tools
|
||||
run: |
|
||||
nix-build -A tests
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Seed static toolchain
|
||||
if: matrix.os == 'Linux'
|
||||
run: |
|
||||
nix-build -A packagesStatic.haskellPackages.hello
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Seed static postgresql build (for libpq)
|
||||
if: matrix.os == 'Linux'
|
||||
run: |
|
||||
nix-build -A packagesStatic.pkgs.postgresql
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Seed static postgrest build
|
||||
if: matrix.os == 'Linux'
|
||||
run: |
|
||||
nix-build -A postgrestStatic
|
||||
postgrest-push-cachix
|
||||
|
||||
- name: Build and push everything to Cachix
|
||||
run: |
|
||||
nix-build
|
||||
postgrest-push-cachix
|
||||
@@ -1,32 +0,0 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: style-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-style:
|
||||
name: Lint & Style
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: style.lint.bin style.styleCheck.bin
|
||||
- name: Run linter (check locally with `nix-shell --run postgrest-lint`)
|
||||
run: postgrest-lint
|
||||
- name: Run style check (auto-format with `nix-shell --run postgrest-style`)
|
||||
run: postgrest-style-check
|
||||
@@ -5,52 +5,329 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
tags:
|
||||
- devel
|
||||
- v*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
cancel-in-progress: "${{ github.event_name == 'pull_request' }}"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
uses: ./.github/workflows/check.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
uses: ./.github/workflows/docs.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
test:
|
||||
name: Test
|
||||
uses: ./.github/workflows/test.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
tag:
|
||||
name: Tag
|
||||
concurrency:
|
||||
# Never tag outdated commits on the main branch by skipping superseded commits
|
||||
group: ci-tag-${{ (github.ref == 'refs/heads/main' && github.ref) || github.run_id }}
|
||||
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
|
||||
cancel-in-progress: false
|
||||
if: vars.RELEASE_ENABLED
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- docs
|
||||
- test
|
||||
- build
|
||||
Lint-Style:
|
||||
name: Lint & check code style
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
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-22.04
|
||||
defaults:
|
||||
run:
|
||||
# Hack for enabling color output, see:
|
||||
# https://github.com/actions/runner/issues/241#issuecomment-842566950
|
||||
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: tests
|
||||
|
||||
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
|
||||
run: postgrest-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1
|
||||
with:
|
||||
files: ./coverage/codecov.json
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- 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, 15, 16]
|
||||
name: Test PG ${{ matrix.pgVersion }} (Nix)
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
# Hack for enabling color output, see:
|
||||
# https://github.com/actions/runner/issues/241#issuecomment-842566950
|
||||
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- 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 -vv
|
||||
|
||||
|
||||
Test-Memory-Nix:
|
||||
name: Test memory (Nix)
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- 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-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: tests
|
||||
|
||||
- name: Build static executable
|
||||
run: nix-build -A postgrestStatic
|
||||
- name: Check static executable
|
||||
run: postgrest-check-static result/bin/postgrest
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: postgrest-linux-static-x64
|
||||
path: result/bin/postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build Docker image
|
||||
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
|
||||
- name: Save built Docker image as artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: postgrest-docker-x64
|
||||
path: postgrest-docker.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
Build-Macos-Nix:
|
||||
name: Build MacOS (Nix)
|
||||
runs-on: macos-12
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
|
||||
- name: Build everything
|
||||
run: |
|
||||
nix-build
|
||||
|
||||
|
||||
Build-Stack:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux
|
||||
runs-on: ubuntu-22.04
|
||||
cache: |
|
||||
~/.stack
|
||||
.stack-work
|
||||
artifact: postgrest-ubuntu-x64
|
||||
|
||||
- name: MacOS
|
||||
runs-on: macos-12
|
||||
cache: |
|
||||
~/.stack
|
||||
.stack-work
|
||||
artifact: postgrest-macos-x64
|
||||
|
||||
- name: Windows
|
||||
runs-on: windows-2022
|
||||
cache: |
|
||||
~\AppData\Roaming\stack
|
||||
~\AppData\Local\Programs\stack
|
||||
.stack-work
|
||||
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
|
||||
artifact: postgrest-windows-x64
|
||||
|
||||
name: Build ${{ matrix.name }} (Stack)
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Stack working files cache
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: ${{ matrix.cache }}
|
||||
key: cache-stack-${{ 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: Save built executable as artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
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-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- uses: ./.github/actions/artifact-from-cirrus
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
task: Build FreeBSD (Stack)
|
||||
download: bin
|
||||
upload: postgrest-freebsd-x64
|
||||
|
||||
Build-Cabal:
|
||||
strategy:
|
||||
matrix:
|
||||
ghc: ['9.0.2', '9.2.4']
|
||||
fail-fast: false
|
||||
name: Build Linux (Cabal, GHC ${{ matrix.ghc }})
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Workaround runner image issue
|
||||
# https://github.com/actions/runner-images/issues/7061
|
||||
run: sudo chown -R "$USER" /usr/local/.ghcup
|
||||
- name: ghcup
|
||||
run: |
|
||||
ghcup install ghc ${{ matrix.ghc }}
|
||||
ghcup set ghc ${{ matrix.ghc }}
|
||||
- name: Copy cabal.project & fix caching
|
||||
run: |
|
||||
mkdir ~/.cabal
|
||||
cp cabal.project.non-nix cabal.project
|
||||
- name: Cache
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/packages
|
||||
~/.cabal/store
|
||||
dist-newstyle
|
||||
key: cache-cabal-${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('**/*.cabal', '**/cabal.project') }}
|
||||
restore-keys: |
|
||||
cache-cabal-${{ runner.os }}-${{ matrix.ghc }}-
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cabal update
|
||||
cabal build --only-dependencies --enable-tests --enable-benchmarks
|
||||
- name: Build
|
||||
run: cabal build --enable-tests --enable-benchmarks all
|
||||
|
||||
Build-Cabal-Arm:
|
||||
strategy:
|
||||
matrix:
|
||||
ghc: ['9.2.4']
|
||||
fail-fast: false
|
||||
name: Build aarch64 (Cabal, GHC ${{ matrix.ghc }})
|
||||
if: "${{ github.event_name == 'push' }}"
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
remotepath: ${{ steps.Remote-Dir.outputs.remotepath }}
|
||||
env:
|
||||
GITHUB_COMMIT: ${{ github.sha }}
|
||||
GHC_VERSION: ${{ matrix.ghc }}
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- id: Remote-Dir
|
||||
name: Unique directory name for the remote build
|
||||
run: echo "remotepath=postgrest-build-$(uuidgen)" >> "$GITHUB_OUTPUT"
|
||||
- name: Copy script files to the remote server
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.SSH_ARM_HOST }}
|
||||
username: ubuntu
|
||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
||||
source: ".github/scripts/arm/*"
|
||||
target: ${{ steps.Remote-Dir.outputs.remotepath }}
|
||||
strip_components: 3
|
||||
- name: Build ARM
|
||||
uses: appleboy/ssh-action@master
|
||||
env:
|
||||
REMOTE_DIR: ${{ steps.Remote-Dir.outputs.remotepath }}
|
||||
with:
|
||||
host: ${{ secrets.SSH_ARM_HOST }}
|
||||
username: ubuntu
|
||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
||||
command_timeout: 120m
|
||||
script_stop: true
|
||||
envs: GITHUB_COMMIT,REMOTE_DIR,GHC_VERSION
|
||||
script: bash ~/$REMOTE_DIR/build.sh "$GITHUB_COMMIT" "$REMOTE_DIR" "$GHC_VERSION"
|
||||
- name: Download binaries from remote server
|
||||
uses: nicklasfrahm/scp-action@main
|
||||
with:
|
||||
direction: download
|
||||
host: ${{ secrets.SSH_ARM_HOST }}
|
||||
username: ubuntu
|
||||
key: ${{ secrets.SSH_ARM_PRIVATE_KEY }}
|
||||
fingerprint: ${{ secrets.SSH_ARM_FINGERPRINT }}
|
||||
source: "${{ steps.Remote-Dir.outputs.remotepath }}/result.tar.xz"
|
||||
target: "result.tar.xz"
|
||||
- name: Extract downloaded binaries
|
||||
run: tar -xvf result.tar.xz && rm result.tar.xz
|
||||
- name: Save aarch64 executable as artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: postgrest-ubuntu-aarch64
|
||||
path: result/postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
Tag-Release:
|
||||
name: Tag Release
|
||||
if: startsWith(github.ref, 'refs/heads/')
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- Lint-Style
|
||||
- Test-Nix
|
||||
- Test-Pg-Nix
|
||||
- Test-Memory-Nix
|
||||
- Build-Static-Nix
|
||||
- Build-Stack
|
||||
- Get-FreeBSD-CirrusCI
|
||||
- Build-Cabal-Arm
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
with:
|
||||
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
|
||||
- name: Tag latest commit
|
||||
@@ -68,3 +345,210 @@ jobs:
|
||||
git tag -f "devel"
|
||||
git push -f origin "devel"
|
||||
fi
|
||||
|
||||
|
||||
Prepare-Release:
|
||||
name: Prepare release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- Lint-Style
|
||||
- Test-Nix
|
||||
- Test-Pg-Nix
|
||||
- Test-Memory-Nix
|
||||
- Build-Static-Nix
|
||||
- Build-Stack
|
||||
- Get-FreeBSD-CirrusCI
|
||||
- Build-Cabal-Arm
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Check the version to be released
|
||||
run: |
|
||||
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
|
||||
if [ "${GITHUB_REF_NAME}" != "devel" ] && [ "${GITHUB_REF_NAME}" != "v$cabal_version" ]; then
|
||||
echo "Tagged version ($GITHUB_REF_NAME) does not match the one in postgrest.cabal (v$cabal_version). Aborting release..."
|
||||
exit 1
|
||||
fi
|
||||
- name: Identify changes from CHANGELOG.md
|
||||
run: |
|
||||
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
|
||||
echo "Getting unreleased changes..."
|
||||
sed -n "1,/## Unreleased/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
else
|
||||
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
echo "Propper release, getting changes for version $version ..."
|
||||
sed -n "1,/## \[$version\]/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
fi
|
||||
|
||||
echo "Relevant extract from CHANGELOG.md:"
|
||||
cat CHANGES.md
|
||||
- name: Save CHANGES.md as artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: release-changes
|
||||
path: CHANGES.md
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
Release-GitHub:
|
||||
name: Release on GitHub
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
needs: Prepare-Release
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Create release bundle with archives for all builds
|
||||
run: |
|
||||
find artifacts -type f -iname postgrest -exec chmod +x {} \;
|
||||
|
||||
mkdir -p release-bundle
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x64.tar.xz" \
|
||||
-C artifacts/postgrest-linux-static-x64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-x64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-x64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x64.tar.xz" \
|
||||
-C artifacts/postgrest-freebsd-x64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-ubuntu-aarch64 postgrest
|
||||
|
||||
zip "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x64.zip" \
|
||||
artifacts/postgrest-windows-x64/postgrest.exe
|
||||
|
||||
- name: Save release bundle
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: release-bundle
|
||||
path: release-bundle
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish release on GitHub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Releasing version ${GITHUB_REF_NAME} on GitHub..."
|
||||
|
||||
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
|
||||
# To replace the existing release, we must first delete the old assets,
|
||||
# then modify the release, then add the new assets.
|
||||
gh release view devel --json assets \
|
||||
| jq -r '.assets[] | .name' \
|
||||
| xargs -rn1 \
|
||||
gh release delete-asset -y devel
|
||||
gh release edit devel \
|
||||
-t devel \
|
||||
--verify-tag \
|
||||
-F artifacts/release-changes/CHANGES.md \
|
||||
--prerelease
|
||||
gh release upload --clobber devel release-bundle/*
|
||||
else
|
||||
gh release create "${GITHUB_REF_NAME}" \
|
||||
-t "${GITHUB_REF_NAME}" \
|
||||
--verify-tag \
|
||||
-F artifacts/release-changes/CHANGES.md \
|
||||
release-bundle/*
|
||||
fi
|
||||
|
||||
|
||||
Release-Docker:
|
||||
name: Release on Docker Hub
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- Prepare-Release
|
||||
env:
|
||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
||||
DOCKER_USER: ${{ vars.DOCKER_USER }}
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- name: Download Docker image
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
name: postgrest-docker-x64
|
||||
- name: Publish images on Docker Hub
|
||||
run: |
|
||||
docker login -u "$DOCKER_USER" -p "$DOCKER_PASS"
|
||||
docker load -i postgrest-docker.tar.gz
|
||||
|
||||
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
|
||||
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
|
||||
|
||||
# Only tag 'latest' for full releases
|
||||
if [ "${GITHUB_REF_NAME}" != "devel" ]; then
|
||||
echo "Pushing to 'latest' tag for full release of ${GITHUB_REF_NAME} ..."
|
||||
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
|
||||
docker push "$DOCKER_REPO"/postgrest:latest
|
||||
else
|
||||
echo "Skipping push to 'latest' tag for pre-release..."
|
||||
fi
|
||||
# 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
|
||||
|
||||
Release-Docker-Arm:
|
||||
name: Release Arm Builds on Docker Hub
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- Build-Cabal-Arm
|
||||
- Release-Docker
|
||||
env:
|
||||
GITHUB_COMMIT: ${{ github.sha }}
|
||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
||||
DOCKER_USER: ${{ vars.DOCKER_USER }}
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- 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,GITHUB_REF_NAME
|
||||
script: bash ~/$REMOTE_DIR/docker-publish.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" "$GITHUB_REF_NAME"
|
||||
|
||||
Clean-Arm-Server:
|
||||
name: Remove copied files from server
|
||||
needs:
|
||||
- Build-Cabal-Arm
|
||||
- Release-Docker-Arm
|
||||
if: success() ||
|
||||
needs.Build-Cabal-Arm.result == 'failure' ||
|
||||
needs.Build-Cabal-Arm.result == 'cancelled' ||
|
||||
(needs.Build-Cabal-Arm.result == 'success' && !startsWith(github.ref, 'refs/tags/v'))
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }}
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- 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,67 +1,50 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
paths:
|
||||
- .github/workflows/docs.yaml
|
||||
- .github/actions/setup-nix/**
|
||||
- default.nix
|
||||
- nix/**
|
||||
- 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
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-24.04
|
||||
name: Build docs
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.build.bin
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
|
||||
- run: nix-env -f docs/default.nix -iA build
|
||||
- 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
|
||||
name: Run spellcheck
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.spellcheck.bin docs.dictcheck.bin
|
||||
- name: Run spellcheck
|
||||
run: postgrest-docs-spellcheck
|
||||
- name: Run dictcheck
|
||||
run: postgrest-docs-dictcheck
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
|
||||
- run: nix-env -f docs/default.nix -iA spellcheck
|
||||
- run: postgrest-docs-spellcheck
|
||||
|
||||
dictcheck:
|
||||
name: Run dictcheck
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
|
||||
- run: nix-env -f docs/default.nix -iA dictcheck
|
||||
- run: postgrest-docs-dictcheck
|
||||
|
||||
linkcheck:
|
||||
name: Linkcheck
|
||||
name: Run linkcheck
|
||||
if: github.base_ref == 'main'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.linkcheck.bin
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
|
||||
- run: nix-env -f docs/default.nix -iA linkcheck
|
||||
- run: postgrest-docs-linkcheck
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Loadtest
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
Loadtest-PR-Nix:
|
||||
name: Loadtest PR (Nix)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
runs-on: ubuntu-22.04
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: loadtest
|
||||
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
|
||||
id: get-latest-tag
|
||||
with:
|
||||
prefix: v
|
||||
- name: Run loadtest
|
||||
run: |
|
||||
postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }}
|
||||
postgrest-loadtest-report > loadtest/loadtest.md
|
||||
- name: Upload report
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: loadtest.md
|
||||
path: loadtest/loadtest.md
|
||||
if-no-files-found: error
|
||||
|
||||
Loadtest-Merge-Nix:
|
||||
name: Loadtest Merge (Nix)
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
|
||||
id: get-latest-tag
|
||||
with:
|
||||
prefix: v
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
tools: loadtest
|
||||
- name: Run loadtest
|
||||
run: |
|
||||
postgrest-loadtest-against ${{ steps.get-latest-tag.outputs.tag }}
|
||||
postgrest-loadtest-report > loadtest/loadtest.md
|
||||
- name: Upload report
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: loadtest.md
|
||||
path: loadtest/loadtest.md
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- devel
|
||||
- v*
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for the same tag.
|
||||
group: release-${{ github.ref }}
|
||||
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Check the version to be released
|
||||
run: |
|
||||
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
|
||||
if [ "${GITHUB_REF_NAME}" != "devel" ] && [ "${GITHUB_REF_NAME}" != "v$cabal_version" ]; then
|
||||
echo "Tagged version ($GITHUB_REF_NAME) does not match the one in postgrest.cabal (v$cabal_version). Aborting release..."
|
||||
exit 1
|
||||
fi
|
||||
- name: Identify changes from CHANGELOG.md
|
||||
run: |
|
||||
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
|
||||
echo "Getting unreleased changes..."
|
||||
sed -n "1,/## Unreleased/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
else
|
||||
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
echo "Propper release, getting changes for version $version ..."
|
||||
sed -n "1,/## \[$version\]/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
fi
|
||||
|
||||
echo "Relevant extract from CHANGELOG.md:"
|
||||
cat CHANGES.md
|
||||
- name: Save CHANGES.md as artifact
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: release-changes
|
||||
path: CHANGES.md
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
github:
|
||||
name: GitHub
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Create release bundle with archives for all builds
|
||||
run: |
|
||||
find artifacts -type f -iname postgrest -exec chmod +x {} \;
|
||||
|
||||
mkdir -p release-bundle
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-linux-static-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-aarch64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-freebsd-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-ubuntu-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-ubuntu-aarch64 postgrest
|
||||
|
||||
zip --junk-paths "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x86-64.zip" \
|
||||
artifacts/postgrest-windows-x86-64/postgrest.exe
|
||||
|
||||
- name: Save release bundle
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: release-bundle
|
||||
path: release-bundle
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish release on GitHub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Releasing version ${GITHUB_REF_NAME} on GitHub..."
|
||||
|
||||
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
|
||||
# To replace the existing release, we must first delete the old assets,
|
||||
# then modify the release, then add the new assets.
|
||||
gh release view devel --json assets \
|
||||
| jq -r '.assets[] | .name' \
|
||||
| xargs -rn1 \
|
||||
gh release delete-asset -y devel
|
||||
gh release edit devel \
|
||||
-t devel \
|
||||
--verify-tag \
|
||||
-F artifacts/release-changes/CHANGES.md \
|
||||
--prerelease
|
||||
gh release upload --clobber devel release-bundle/*
|
||||
else
|
||||
gh release create "${GITHUB_REF_NAME}" \
|
||||
-t "${GITHUB_REF_NAME}" \
|
||||
--verify-tag \
|
||||
-F artifacts/release-changes/CHANGES.md \
|
||||
release-bundle/*
|
||||
fi
|
||||
|
||||
|
||||
docker:
|
||||
name: Docker Hub
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs:
|
||||
- prepare
|
||||
if: |
|
||||
vars.DOCKER_REPO && vars.DOCKER_USER
|
||||
env:
|
||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Download x86-64 Docker image
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: postgrest-docker-x86-64
|
||||
- name: Download aarch64 binary
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: postgrest-ubuntu-aarch64
|
||||
- uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
- name: Build aarch64 Docker image
|
||||
run: |
|
||||
# This only pushes the image via digest, not a tag. This will not appear
|
||||
# in the image list on Docker Hub, yet. It will be later added to the main
|
||||
# tag's manifest.
|
||||
docker buildx build \
|
||||
-t "$DOCKER_REPO/postgrest" \
|
||||
--platform linux/arm64 \
|
||||
--output push-by-digest=true,type=image,push=true \
|
||||
--metadata-file metadata.json \
|
||||
.
|
||||
echo "SHA256_ARM=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_ENV"
|
||||
- name: Publish images on Docker Hub
|
||||
run: |
|
||||
docker load -i postgrest-docker.tar.gz
|
||||
|
||||
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
|
||||
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}"
|
||||
docker buildx imagetools create --append \
|
||||
-t "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
|
||||
"$DOCKER_REPO/postgrest@$SHA256_ARM"
|
||||
|
||||
# Only tag 'latest' for full releases
|
||||
if [ "${GITHUB_REF_NAME}" != "devel" ]; then
|
||||
echo "Pushing to 'latest' tag for full release of ${GITHUB_REF_NAME} ..."
|
||||
docker tag postgrest:latest "$DOCKER_REPO"/postgrest:latest
|
||||
docker push "$DOCKER_REPO"/postgrest:latest
|
||||
docker buildx imagetools create --append \
|
||||
-t "$DOCKER_REPO/postgrest:latest" \
|
||||
"$DOCKER_REPO/postgrest@$SHA256_ARM"
|
||||
else
|
||||
echo "Skipping push to 'latest' tag for pre-release..."
|
||||
fi
|
||||
|
||||
|
||||
docker-description:
|
||||
name: Docker Hub Description
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
vars.DOCKER_REPO && vars.DOCKER_USER &&
|
||||
github.ref == 'refs/tags/devel'
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
repository: ${{ vars.DOCKER_REPO }}/postgrest
|
||||
short-description: ${{ github.event.repository.description }}
|
||||
readme-filepath: ./docker-hub-readme.md
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Upload Reports
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Loadtest"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: Loadtest
|
||||
permissions:
|
||||
checks: write
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: Download from Artifacts
|
||||
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
name: loadtest.md
|
||||
path: artifacts
|
||||
- name: Upload to GitHub Checks
|
||||
uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 # v2.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sha: ${{ github.event.workflow_run.head_sha }}
|
||||
name: Loadtest Results
|
||||
conclusion: neutral
|
||||
output: |
|
||||
{"summary":""}
|
||||
output_text_description_file: artifacts/loadtest.md
|
||||
@@ -1,132 +0,0 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
CODECOV_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
paths:
|
||||
- .github/workflows/test.yaml
|
||||
- .github/workflows/report.yaml
|
||||
- .github/actions/setup-nix/**
|
||||
- default.nix
|
||||
- nix/**
|
||||
- .stylish-haskell.yaml
|
||||
- cabal.project
|
||||
- postgrest.cabal
|
||||
- '**.hs'
|
||||
- test/**
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: test-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
# Hack for enabling color output, see:
|
||||
# https://github.com/actions/runner/issues/241#issuecomment-842566950
|
||||
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.coverage.bin tests.testDoctests.bin tests.testSpecIdempotence.bin
|
||||
|
||||
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
|
||||
run: postgrest-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1
|
||||
with:
|
||||
files: ./coverage/codecov.json
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Run doctests
|
||||
if: always()
|
||||
run: postgrest-test-doctests
|
||||
|
||||
- name: Check the spec tests for idempotence
|
||||
if: always()
|
||||
run: postgrest-test-spec-idempotence
|
||||
|
||||
|
||||
postgres:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pgVersion: ["9_6", 10, 11, 12, 13, 14, 15, 16]
|
||||
name: PG ${{ matrix.pgVersion }}
|
||||
runs-on: ubuntu-24.04
|
||||
defaults:
|
||||
run:
|
||||
# Hack for enabling color output, see:
|
||||
# https://github.com/actions/runner/issues/241#issuecomment-842566950
|
||||
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.testSpec.bin tests.testIO.bin tests.testBigSchema.bin withTools.postgresql-${{ matrix.pgVersion }}.bin
|
||||
|
||||
- name: Run spec tests
|
||||
if: always()
|
||||
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
|
||||
|
||||
- name: Run IO tests
|
||||
if: always()
|
||||
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io -vv
|
||||
|
||||
- name: Run IO tests on a big schema
|
||||
if: always()
|
||||
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
|
||||
|
||||
|
||||
memory:
|
||||
name: Memory
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: memory.test.bin
|
||||
- name: Run memory tests
|
||||
run: postgrest-test-memory
|
||||
|
||||
|
||||
loadtest:
|
||||
name: Loadtest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: loadtest.loadtestAgainst.bin loadtest.report.bin
|
||||
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
|
||||
id: get-latest-tag
|
||||
with:
|
||||
prefix: v
|
||||
- name: Run loadtest
|
||||
run: |
|
||||
postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }}
|
||||
postgrest-loadtest-report >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -0,0 +1,68 @@
|
||||
# Architecture
|
||||
|
||||
This document describes the high-level architecture of PostgREST.
|
||||
|
||||
## Bird's Eye View
|
||||
|
||||
```haskell
|
||||
postgrest :: Request -> Either Error SQLStatement -> Response
|
||||
```
|
||||
|
||||
On the highest level, PostgREST processes an HTTP request, if it's accepted it builds a SQL statement for it, executes it, and produces a response.
|
||||
|
||||
## Code Map
|
||||
|
||||
This section talks briefly about various important modules.
|
||||
|
||||
The starting point of the program is `main/Main.hs`, which calls `src/PostgREST/CLI.hs` which then calls `src/PostgREST/App.hs`.
|
||||
|
||||
`App.hs` is then in charge of composing the different modules.
|
||||
|
||||
### ApiRequest.hs
|
||||
|
||||
PostgREST operates over two types of resources: database relations(tables or views) and database functions; providing different representations(depending on the media type)
|
||||
for them.
|
||||
|
||||
This module is in charge of representing the operation over an `ApiRequest` type. It parses the URL querystring following PostgREST syntax, the request headers, and the request body
|
||||
(if possible it avoids parsing the body and sends it directly to the db).
|
||||
|
||||
A request might be rejected at this level if it's invalid, e.g. providing an unknown media type to PostgREST or using an unknown HTTP method.
|
||||
|
||||
### Plan.hs
|
||||
|
||||
Using the Schema Cache, this module enables more complex functionality(like resource embedding) by enriching the ApiRequest. It generates Plan types(`ReadPlan`, `MutatePlan`)
|
||||
that then will be used to generate a SQL statement.
|
||||
|
||||
A request might be rejected at this level if it's invalid, e.g. by doing resource embedding on a nonexistent resource.
|
||||
|
||||
An OPTIONS request doesn't require a plan to be generated.
|
||||
|
||||
### Query.hs
|
||||
|
||||
This module constructs single SQL statements that can be parametrized and prepared. Only at this stage a PostgreSQL connection from the pool is used.
|
||||
|
||||
A query might fail(and be rollbacked) at this level if it doesn't comply to certain conditions, e.g. by not returning a single row when a ``Accept: application/vnd.pgrst.object`` header is specified.
|
||||
|
||||
An OPTIONS request doesn't require a query to be executed.
|
||||
|
||||
### Response.hs
|
||||
|
||||
This module constructs the HTTP response body with the right headers.
|
||||
|
||||
It builds the OpenAPI response using the schema cache.
|
||||
|
||||
### Auth.hs
|
||||
|
||||
This module provides functions to deal with JWT authorization.
|
||||
|
||||
### SchemaCache.hs
|
||||
|
||||
This queries the PostgreSQL system catalogs and caches the metadata into a SchemaCache type,
|
||||
|
||||
### AppState.hs
|
||||
|
||||
The state of the App which is kept across requests.
|
||||
|
||||
This spawns threads which are used to execute concurrent jobs.
|
||||
|
||||
Jobs include connection recover and a listener for the PostgreSQL LISTEN command.
|
||||
@@ -9,7 +9,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
<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="296px" src="static/cybertec-new.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
@@ -32,7 +32,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.png">
|
||||
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
@@ -75,12 +75,12 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.timescale.com?utm_campaign=postgrest&utm_source=sponsor&utm_medium=referral&utm_content=github" target="_blank">
|
||||
<img width="222px" src="static/timescaledb.png">
|
||||
<img width="222px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/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">
|
||||
<img max-width="222px" height="88" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
|
||||
@@ -5,116 +5,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [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
|
||||
@@ -182,7 +72,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- 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
|
||||
|
||||
@@ -486,11 +375,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- #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)]'
|
||||
```
|
||||
|
||||
## [9.0.1] - 2022-06-03
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -18,7 +20,7 @@ API than you are likely to write from scratch.
|
||||
<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="296px" src="static/cybertec-new.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
@@ -41,7 +43,7 @@ API than you are likely to write from scratch.
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.png">
|
||||
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
@@ -66,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
|
||||
|
||||
@@ -143,7 +145,11 @@ 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.
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
packages: postgrest.cabal
|
||||
tests: true
|
||||
package *
|
||||
ghc-options: -split-sections
|
||||
@@ -1 +1 @@
|
||||
index-state: hackage.haskell.org 2025-02-01T14:59:33Z
|
||||
index-state: hackage.haskell.org 2023-10-13T13:54:33Z
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Settings to allow building with plain cabal. If this was
|
||||
-- named just cabal.project, it would interfere with the default
|
||||
-- nix build.
|
||||
|
||||
packages: .
|
||||
|
||||
-- Example of depending on a forked repository (the same dependency
|
||||
-- would be mentioned in nix/overlays/haskell-packages.nix and
|
||||
-- stack.yaml, and should refer to a main branch commit of the
|
||||
-- repository.
|
||||
--
|
||||
-- source-repository-package
|
||||
-- type: git
|
||||
-- location: https://github.com/PostgREST/hasql-pool.git
|
||||
-- tag: 4d462c4d47d762effefc7de6c85eaed55f144f1d
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/PostgREST/postgresql-libpq.git
|
||||
tag: 890a0a16cf57dd401420fdc6c7d576fb696003bc
|
||||
@@ -1,23 +1,12 @@
|
||||
{ system ? builtins.currentSystem
|
||||
|
||||
, compiler ? "ghc948"
|
||||
|
||||
, # 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 ? let inherit (nixpkgsVersion) owner repo rev tarballHash; in
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
|
||||
sha256 = tarballHash;
|
||||
}
|
||||
}:
|
||||
{ system ? builtins.currentSystem }:
|
||||
|
||||
let
|
||||
name =
|
||||
"postgrest";
|
||||
|
||||
compiler =
|
||||
"ghc924";
|
||||
|
||||
# PostgREST source files, filtered based on the rules in the .gitignore 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
|
||||
@@ -27,6 +16,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;
|
||||
|
||||
@@ -35,10 +36,10 @@ let
|
||||
allOverlays.build-toolbox
|
||||
allOverlays.checked-shell-script
|
||||
allOverlays.gitignore
|
||||
allOverlays.postgresql-libpq
|
||||
allOverlays.postgis
|
||||
(allOverlays.postgresql-default { inherit patches; })
|
||||
allOverlays.postgresql-legacy
|
||||
allOverlays.postgresql-future
|
||||
allOverlays.postgis
|
||||
(allOverlays.haskell-packages { inherit compiler; })
|
||||
allOverlays.slocat
|
||||
];
|
||||
@@ -49,28 +50,52 @@ let
|
||||
|
||||
postgresqlVersions =
|
||||
[
|
||||
{ name = "postgresql-16"; postgresql = pkgs.postgresql_16.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{
|
||||
name = "postgresql-16";
|
||||
postgresql = pkgs.postgresql_16.withPackages (p: [
|
||||
p.postgis
|
||||
(p.pg_safeupdate.overrideAttrs (old: {
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp safeupdate.dylib safeupdate.so || true
|
||||
install -D safeupdate.so -t $out/lib
|
||||
'';
|
||||
}))
|
||||
]);
|
||||
}
|
||||
{ name = "postgresql-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-10"; postgresql = pkgs.postgresql_10.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-9_6"; postgresql = pkgs.postgresql_9_6.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "postgresql-9.6"; postgresql = pkgs.postgresql_9_6.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
];
|
||||
|
||||
patches =
|
||||
pkgs.callPackage nix/patches { };
|
||||
|
||||
# Dynamic derivation for PostgREST
|
||||
postgrest =
|
||||
pkgs.haskell.packages."${compiler}".callCabal2nix name src { };
|
||||
|
||||
staticHaskellPackage = import nix/static.nix { inherit compiler name pkgs src; };
|
||||
# Functionality that derives a fully static Haskell package based on
|
||||
# nh2/static-haskell-nix
|
||||
staticHaskellPackage =
|
||||
import nix/static-haskell-package.nix { inherit nixpkgs system compiler patches allOverlays; };
|
||||
|
||||
# Static executable.
|
||||
postgrestStatic =
|
||||
lib.justStaticExecutables (lib.dontCheck (staticHaskellPackage name src).package);
|
||||
|
||||
packagesStatic = (staticHaskellPackage name src).survey;
|
||||
|
||||
# Options passed to cabal in dev tools and tests
|
||||
devCabalOptions =
|
||||
"-f dev --test-show-detail=direct";
|
||||
|
||||
profiledHaskellPackages =
|
||||
pkgs.haskell.packages."${compiler}".extend (_: super:
|
||||
pkgs.haskell.packages."${compiler}".extend (self: super:
|
||||
{
|
||||
mkDerivation =
|
||||
args:
|
||||
@@ -111,16 +136,12 @@ rec {
|
||||
pkgs.callPackage nix/tools/cabalTools.nix { inherit devCabalOptions postgrest; };
|
||||
|
||||
withTools =
|
||||
pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions postgrest; };
|
||||
pkgs.callPackage nix/tools/withTools.nix { inherit cabalTools devCabalOptions 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 { };
|
||||
|
||||
# Load testing tools.
|
||||
loadtest =
|
||||
pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; };
|
||||
@@ -151,8 +172,8 @@ rec {
|
||||
};
|
||||
} // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux rec {
|
||||
# Static executable.
|
||||
inherit (staticHaskellPackage) postgrestStatic;
|
||||
inherit (staticHaskellPackage) packagesStatic;
|
||||
inherit postgrestStatic;
|
||||
inherit packagesStatic;
|
||||
|
||||
# Docker images and loading script.
|
||||
docker =
|
||||
|
||||
@@ -5,4 +5,4 @@ Pipfile.lock
|
||||
_diagrams/db.pdf
|
||||
misspellings
|
||||
unuseddict
|
||||
*.mo
|
||||
.history
|
||||
|
||||
@@ -2,27 +2,19 @@
|
||||
|
||||
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).
|
||||
To build the docs locally, use [nix](https://nixos.org/nix/):
|
||||
|
||||
```bash
|
||||
nix-shell
|
||||
```
|
||||
|
||||
Once in the nix-shell you have the following commands available:
|
||||
|
||||
- `postgrest-docs-build`: Build the docs.
|
||||
- `postgrest-docs-serve`: Build the docs and start a livereload server on `http://localhost:5500`.
|
||||
- `postgrest-docs-spellcheck`: Run aspell.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -5,20 +5,38 @@ 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
|
||||
./erd_static-x86-64 -i 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
|
||||
## LaTeX
|
||||
|
||||
The UML diagrams are created with https://plantuml.com/.
|
||||
The schema structure diagram is done with LaTeX. You can use a GUI like https://www.mathcha.io/editor to create the .tex file.
|
||||
|
||||
PlantUML only creates one diagram per file.
|
||||
That's why we need to create another one for dark mode.
|
||||
For example, for the file [uml/arch.uml](uml/arch.uml) there's [uml/dark/arch-dark.uml](uml/dark/arch-dark.uml) which includes the first one:
|
||||
Then use this command to generate the png file.
|
||||
|
||||
```bash
|
||||
plantuml -tsvg uml/arch.uml -o ../../_static
|
||||
plantuml -tsvg -darkmode uml/dark/arch-dark.uml -o ../../../_static
|
||||
pdflatex --shell-escape -halt-on-error db.tex
|
||||
|
||||
## and move it to the static folder(it's not easy to do it in one go with the pdflatex)
|
||||
mv db.png ../_static/
|
||||
```
|
||||
|
||||
LaTeX is used because it's a tweakable plain text format.
|
||||
|
||||
You can install the full latex suite with `nix`:
|
||||
|
||||
```
|
||||
nix-env -iA texlive.combined.scheme-full
|
||||
```
|
||||
|
||||
To tweak the file with a live reload environment use:
|
||||
|
||||
```bash
|
||||
# open the pdf(zathura used as an example)
|
||||
zathura db.pdf &
|
||||
|
||||
# live reload with entr
|
||||
echo db.tex | entr pdflatex --shell-escape -halt-on-error db.tex
|
||||
```
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
\documentclass[convert]{standalone}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{tikz}
|
||||
\usepackage{mathdots}
|
||||
\usepackage{yhmath}
|
||||
\usepackage{cancel}
|
||||
\usepackage{color}
|
||||
\usepackage{siunitx}
|
||||
\usepackage{array}
|
||||
\usepackage{multirow}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{gensymb}
|
||||
\usepackage{tabularx}
|
||||
\usepackage{booktabs}
|
||||
\usetikzlibrary{fadings}
|
||||
\usetikzlibrary{patterns}
|
||||
\usetikzlibrary{shadows.blur}
|
||||
\usetikzlibrary{shapes}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\newcommand\customScale{0.35}
|
||||
|
||||
\begin{tikzpicture}[x=0.75pt,y=0.75pt,yscale=-1,xscale=1, scale=\customScale, every node/.style={scale=\customScale}]
|
||||
|
||||
%Shape: Can [id:dp7234864758664346]
|
||||
\draw [fill={rgb, 255:red, 47; green, 97; blue, 144 } ,fill opacity=1 ] (497.5,51.5) -- (497.5,255.5) .. controls (497.5,275.66) and (423.18,292) .. (331.5,292) .. controls (239.82,292) and (165.5,275.66) .. (165.5,255.5) -- (165.5,51.5) .. controls (165.5,31.34) and (239.82,15) .. (331.5,15) .. controls (423.18,15) and (497.5,31.34) .. (497.5,51.5) .. controls (497.5,71.66) and (423.18,88) .. (331.5,88) .. controls (239.82,88) and (165.5,71.66) .. (165.5,51.5) ;
|
||||
%Shape: Rectangle [id:dp7384065579958246]
|
||||
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (189,115) -- (252.5,115) -- (252.5,155) -- (189,155) -- cycle ;
|
||||
%Shape: Rectangle [id:dp24763906430298177]
|
||||
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (292,118) -- (362,118) -- (362,158) -- (292,158) -- cycle ;
|
||||
%Shape: Rectangle [id:dp3775601612537265]
|
||||
\draw [fill={rgb, 255:red, 236; green, 227; blue, 227 } ,fill opacity=1 ] (397,114) -- (467,114) -- (467,154) -- (397,154) -- cycle ;
|
||||
%Shape: Rectangle [id:dp7071457022893852]
|
||||
\draw [fill={rgb, 255:red, 248; green, 231; blue, 28 } ,fill opacity=1 ] (269,199) -- (397.5,199) -- (397.5,273) -- (269,273) -- cycle ;
|
||||
%Straight Lines [id:da8846759047437789]
|
||||
\draw (268,234) -- (226.44,155.77) ;
|
||||
\draw [shift={(225.5,154)}, rotate = 422.02] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
|
||||
%Straight Lines [id:da6908444738113828]
|
||||
\draw (309.5,198) -- (307.6,161) ;
|
||||
\draw [shift={(307.5,159)}, rotate = 447.06] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
|
||||
%Straight Lines [id:da7168757864413169]
|
||||
\draw (398.5,233) -- (431.72,154.84) ;
|
||||
\draw [shift={(432.5,153)}, rotate = 473.03] [color={rgb, 255:red, 0; green, 0; blue, 0 } ][line width=0.75] (10.93,-3.29) .. controls (6.95,-1.4) and (3.31,-0.3) .. (0,0) .. controls (3.31,0.3) and (6.95,1.4) .. (10.93,3.29) ;
|
||||
%Up Down Arrow [id:dp14059754167108496]
|
||||
\draw [fill={rgb, 255:red, 126; green, 211; blue, 33 } ,fill opacity=1 ] (312.5,288.5) -- (330,273) -- (347.5,288.5) -- (338.75,288.5) -- (338.75,319.5) -- (347.5,319.5) -- (330,335) -- (312.5,319.5) -- (321.25,319.5) -- (321.25,288.5) -- cycle ;
|
||||
|
||||
% Text Node
|
||||
\draw (201,129) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
|
||||
% Text Node
|
||||
\draw (307,130) node [anchor=north west][inner sep=0.75pt] [align=left ] {tables};
|
||||
% Text Node
|
||||
\draw (414,127) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
|
||||
% Text Node
|
||||
\draw (272,203) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 0; green, 0; blue, 0 } ,opacity=1 ] [align=center] { \\ views \\ + \\ \ \ stored procedures};
|
||||
|
||||
% Text Node
|
||||
\draw (322,178) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{api}};
|
||||
% Text Node
|
||||
\draw (190,97) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{internal}};
|
||||
% Text Node
|
||||
\draw (300,99) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{private}};
|
||||
% Text Node
|
||||
\draw (417,101) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 255; green, 255; blue, 255 } ,opacity=1 ] [align=left] {\large\textbf{core}};
|
||||
% Text Node
|
||||
\draw (358,306) node [anchor=north west][inner sep=0.75pt] [align=left] {REST};
|
||||
|
||||
\end{tikzpicture}
|
||||
|
||||
|
||||
\end{document}
|
||||
@@ -1,84 +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
|
||||
HTTPAPI <.l- Proxy
|
||||
|
||||
:operator: .d-> HTTPADMIN
|
||||
:operator: .d-> CLI
|
||||
|
||||
|
||||
PostgreSQL <.developer : "\t"
|
||||
Listener -r.> "PostgreSQL"
|
||||
"Connection Pool" -r.> "PostgreSQL" : "\t\t"
|
||||
|
||||
|
||||
note bottom of Auth
|
||||
Authenticates the user request
|
||||
end note
|
||||
|
||||
note bottom of ApiRequest
|
||||
Parses the URL syntax
|
||||
end note
|
||||
|
||||
note bottom of Query
|
||||
Generates the SQL
|
||||
end note
|
||||
|
||||
note top of Listener
|
||||
LISTEN session
|
||||
end note
|
||||
|
||||
url of Admin is [[../references/admin_server.html#admin-server]]
|
||||
url of API is [[../explanations/schema_isolation.html]]
|
||||
url of Auth is [[../references/auth.html#authn]]
|
||||
url of Authorization is [[../explanations/db_authz.html]]
|
||||
url of CLI is [[../references/cli.html#cli]]
|
||||
url of "Connection Pool" is [[../references/connection_pool.html]]
|
||||
url of Config is [[../references/configuration.html#configuration]]
|
||||
url of HTTPADMIN is [[https://aosabook.org/en/posa/warp.html]]
|
||||
url of HTTPAPI is [[https://aosabook.org/en/posa/warp.html]]
|
||||
url of Listener is [[../references/listener.html#listener]]
|
||||
url of Proxy is [[../explanations/nginx.html]]
|
||||
url of "Schema Cache" is [[../references/schema_cache.html#schema-cache]]
|
||||
|
||||
@enduml
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -28,7 +28,6 @@ div.line-block {
|
||||
|
||||
#sponsors img{
|
||||
margin: 10px;
|
||||
width: 13em; /* ".. image::" does not apply width properly to SVGs */
|
||||
}
|
||||
|
||||
#thanks{
|
||||
@@ -94,62 +93,3 @@ div.line-block {
|
||||
#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;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 62 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" encoding="us-ascii" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="411px" preserveAspectRatio="none" style="width:315px;height:411px;" version="1.1" viewBox="0 0 315 411" width="315px" zoomAndPan="magnify"><defs/><g><!--cluster PostgreSQL--><g id="cluster_PostgreSQL"><path d="M6,16 C6,6 152,6 152,6 C152,6 298,6 298,16 L298,309.14 C298,319.14 152,319.14 152,319.14 C152,319.14 6,319.14 6,309.14 L6,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><path d="M6,16 C6,26 152,26 152,26 C152,26 298,26 298,16 " fill="none" style="stroke:#E7E7E7;stroke-width:1.0;"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="81.375" x="111.3125" y="42.9659">PostgreSQL</text></g><!--cluster public--><g id="cluster_public"><polygon fill="none" points="30,77,40,67,150,67,150,155.07,140,165.07,30,165.07,30,77" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="140" x2="150" y1="77" y2="67"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="30" x2="140" y1="77" y2="77"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="140" x2="140" y1="77" y2="165.07"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="44.625" x="63.6875" y="94.9659">public</text></g><!--cluster API--><g id="cluster_API"><polygon fill="none" points="68,199.07,78,189.07,236,189.07,236,285.14,226,295.14,68,295.14,68,199.07" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="226" x2="236" y1="199.07" y2="189.07"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="68" x2="226" y1="199.07" y2="199.07"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="226" x2="226" y1="199.07" y2="295.14"/><text fill="#FFFFFF" font-family="sans-serif" font-size="20" font-weight="bold" lengthAdjust="spacing" textLength="29.875" x="133.0625" y="223.45">api</text></g><!--entity tables_public--><g id="elem_tables_public"><rect fill="#313139" height="39.0679" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="61" x="69.5" y="110"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="41" x="79.5" y="134.9659">tables</text></g><!--entity extensions--><g id="elem_extensions"><polygon fill="none" points="165.81,115,175.81,105,282.185,105,282.185,144.0679,272.185,154.0679,165.81,154.0679,165.81,115" style="stroke:#E7E7E7;stroke-width:1.0;"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="272.185" x2="282.185" y1="115" y2="105"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="165.81" x2="272.185" y1="115" y2="115"/><line style="stroke:#E7E7E7;stroke-width:1.0;" x1="272.185" x2="272.185" y1="115" y2="154.0679"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="76.375" x="180.81" y="139.9659">extensions</text></g><!--entity vf_api--><g id="elem_vf_api"><rect fill="#313139" height="39.0679" rx="2.5" ry="2.5" style="stroke:#E7E7E7;stroke-width:0.5;" width="136" x="84" y="240.07"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="116" x="94" y="265.0359">views + functions</text></g><!--entity PostgREST--><g id="elem_PostgREST"><ellipse cx="152" cy="371.14" fill="#313139" rx="8" ry="8" style="stroke:#E7E7E7;stroke-width:0.5;"/><text fill="#FFFFFF" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="70" x="117" y="403.1059">PostgREST</text></g><!--reverse link tables_public to vf_api--><g id="link_tables_public_vf_api"><path d="M107.25,155.29 C107.25,179.74 107.25,215.13 107.25,239.67 " fill="none" id="tables_public-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="107.25,149.29,103.25,158.29,107.25,154.29,111.25,158.29,107.25,149.29" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--reverse link extensions to vf_api--><g id="link_extensions_vf_api"><path d="M192.91,160.31 C192.91,185.07 192.91,216.68 192.91,239.61 " fill="none" id="extensions-backto-vf_api" style="stroke:#E7E7E7;stroke-width:1.0;"/><polygon fill="#E7E7E7" points="192.91,154.31,188.91,163.31,192.91,159.31,196.91,163.31,192.91,154.31" style="stroke:#E7E7E7;stroke-width:1.0;"/></g><!--link vf_api to PostgREST--><g id="link_vf_api_PostgREST"><path d="M152,285.55 C152,309.98 152,338.34 152,356.18 " fill="none" id="vf_api-PostgREST" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="152,279.55,148,288.55,152,284.55,156,288.55,152,279.55" style="stroke:#E7E7E7;stroke-width:3.0;"/><polygon fill="#E7E7E7" points="152,362.18,156,353.18,152,357.18,148,353.18,152,362.18" style="stroke:#E7E7E7;stroke-width:3.0;"/></g><!--SRC=[KypCIyufJKbLqDFJBqxEqCqipjShpSq10000]--></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 89 KiB |
@@ -32,7 +32,6 @@ extensions = [
|
||||
"sphinx_tabs.tabs",
|
||||
"sphinx_copybutton",
|
||||
"sphinxext.opengraph",
|
||||
"sphinx_rtd_dark_mode",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
@@ -50,7 +49,7 @@ source_suffix = ".rst"
|
||||
master_doc = "index"
|
||||
|
||||
# This is overriden by readthedocs with the version tag anyway
|
||||
version = "12.2"
|
||||
version = "12.0"
|
||||
# To avoid repetition in <title> we set this to an empty string.
|
||||
release = ""
|
||||
|
||||
@@ -295,17 +294,9 @@ def setup(app):
|
||||
# taken from https://github.com/sphinx-doc/sphinx/blob/82dad44e5bd3776ecb6fd8ded656bc8151d0e63d/sphinx/util/requests.py#L42
|
||||
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0"
|
||||
|
||||
linkcheck_ignore = [
|
||||
r"https://www.patreon.com/postgrest",
|
||||
r"https://blog.frankel.ch/poor-man-api",
|
||||
]
|
||||
|
||||
# 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"
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
let
|
||||
# Commit of the Nixpkgs repository that we want to use.
|
||||
nixpkgsVersion = {
|
||||
date = "2023-03-25";
|
||||
rev = "dbf5322e93bcc6cfc52268367a8ad21c09d76fea";
|
||||
tarballHash = "0lwk4v9dkvd28xpqch0b0jrac4xl9lwm6snrnzx8k5lby72kmkng";
|
||||
};
|
||||
|
||||
# Nix files that describe the Nixpkgs repository. We evaluate the expression
|
||||
# using `import` below.
|
||||
pkgs = import
|
||||
(fetchTarball {
|
||||
url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
|
||||
sha256 = nixpkgsVersion.tarballHash;
|
||||
})
|
||||
{ };
|
||||
|
||||
python = pkgs.python3.withPackages (ps: [ ps.sphinx ps.sphinx_rtd_theme ps.livereload ps.sphinx-tabs ps.sphinx-copybutton ps.sphinxext-opengraph ]);
|
||||
in
|
||||
rec {
|
||||
inherit pkgs;
|
||||
|
||||
build =
|
||||
pkgs.writeShellScriptBin "postgrest-docs-build"
|
||||
''
|
||||
set -euo pipefail
|
||||
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
|
||||
|
||||
# clean previous build, otherwise some errors might be supressed
|
||||
rm -rf _build
|
||||
|
||||
${python}/bin/sphinx-build --color -W -b html -a -n . _build
|
||||
'';
|
||||
|
||||
serve =
|
||||
pkgs.writeShellScriptBin "postgrest-docs-serve"
|
||||
''
|
||||
set -euo pipefail
|
||||
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
|
||||
|
||||
# livereload_docs.py needs to find "sphinx-build"
|
||||
PATH=${python}/bin:$PATH
|
||||
|
||||
${python}/bin/python livereload_docs.py
|
||||
'';
|
||||
|
||||
spellcheck =
|
||||
pkgs.writeShellScriptBin "postgrest-docs-spellcheck"
|
||||
''
|
||||
set -euo pipefail
|
||||
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
|
||||
|
||||
FILES=$(find . -type f -iname '*.rst' | tr '\n' ' ')
|
||||
|
||||
cat $FILES \
|
||||
| grep -v '^\(\.\.\| \)' \
|
||||
| sed 's/`.*`//g' \
|
||||
| ${pkgs.aspell}/bin/aspell -d ${pkgs.aspellDicts.en}/lib/aspell/en_US -p ./postgrest.dict list \
|
||||
| sort -f \
|
||||
| tee misspellings
|
||||
test ! -s misspellings
|
||||
'';
|
||||
|
||||
# dictcheck detects obsolete entries in postgrest.dict, that are not used anymore
|
||||
dictcheck =
|
||||
pkgs.writeShellScriptBin "postgrest-docs-dictcheck"
|
||||
''
|
||||
set -euo pipefail
|
||||
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
|
||||
|
||||
FILES=$(find . -type f -iname '*.rst' | tr '\n' ' ')
|
||||
|
||||
cat postgrest.dict \
|
||||
| tail -n+2 \
|
||||
| tr '\n' '\0' \
|
||||
| xargs -0 -n 1 -i \
|
||||
sh -c "grep \"{}\" $FILES > /dev/null || echo \"{}\"" \
|
||||
| tee unuseddict
|
||||
test ! -s unuseddict
|
||||
'';
|
||||
|
||||
linkcheck =
|
||||
pkgs.writeShellScriptBin "postgrest-docs-linkcheck"
|
||||
''
|
||||
set -euo pipefail
|
||||
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
|
||||
|
||||
${python}/bin/sphinx-build --color -b linkcheck . _build
|
||||
'';
|
||||
|
||||
check =
|
||||
pkgs.writeShellScriptBin "postgrest-docs-check"
|
||||
''
|
||||
set -euo pipefail
|
||||
${build}/bin/postgrest-docs-build
|
||||
${dictcheck}/bin/postgrest-docs-dictcheck
|
||||
${linkcheck}/bin/postgrest-docs-linkcheck
|
||||
${spellcheck}/bin/postgrest-docs-spellcheck
|
||||
'';
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Community Tutorials
|
||||
* `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
||||
|
||||
* `PostgREST + PostGIS API tutorial in 5 minutes <https://gis-ops.com/postgrest-postgis-api-tutorial-geospatial-api-in-5-minutes/>`_ -
|
||||
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`functions` interface.
|
||||
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`s_procs` interface.
|
||||
|
||||
* `"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).
|
||||
@@ -20,9 +20,7 @@ Community Tutorials
|
||||
|
||||
* `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/>`_
|
||||
.. * `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.
|
||||
|
||||
.. _templates:
|
||||
|
||||
@@ -51,18 +49,17 @@ 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
|
||||
* `Limezest/postgrest-cloud-run <https://github.com/Limezest/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
|
||||
|
||||
.. _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.
|
||||
These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows stored procedures 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
|
||||
@@ -85,8 +82,8 @@ 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-dart <https://github.com/supabase-community/postgrest-dart>`_ - Dart
|
||||
* `postgrest-ex <https://github.com/J0/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
|
||||
|
||||
@@ -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>`_ fills in 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`.
|
||||
@@ -156,7 +156,7 @@ You can also grant execute on all functions in a schema to a higher privileged r
|
||||
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.
|
||||
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 procedure 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.
|
||||
@@ -166,7 +166,7 @@ Another option is to define the function with the :code:`SECURITY DEFINER` optio
|
||||
-- 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 $$
|
||||
create or replace function login(email text, pass text) returns jwt_token as $$
|
||||
begin
|
||||
-- access to a private schema called 'auth'
|
||||
select auth.user_role(email, pass) into _role;
|
||||
@@ -180,7 +180,7 @@ Note the ``SECURITY DEFINER`` keywords at the end of the function. See `PostgreS
|
||||
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.
|
||||
Views are invoked with the privileges of the view owner, much like stored procedures 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.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Installation
|
||||
############
|
||||
|
||||
The release page has `pre-compiled binaries for macOS, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_.
|
||||
The release page has `pre-compiled binaries for macOS, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_ .
|
||||
The Linux binary is a static executable that can be run on any Linux distribution.
|
||||
|
||||
You can also use your OS package manager.
|
||||
@@ -21,12 +21,6 @@ Supported PostgreSQL versions
|
||||
|
||||
PostgREST works with all PostgreSQL versions starting from 9.6.
|
||||
|
||||
.. note::
|
||||
|
||||
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/.
|
||||
|
||||
Running PostgREST
|
||||
=================
|
||||
|
||||
@@ -169,15 +163,14 @@ If you want to have a visual overview of your API in your browser you can add sw
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# in services:
|
||||
swagger:
|
||||
image: swaggerapi/swagger-ui
|
||||
ports:
|
||||
- "8080:8080"
|
||||
expose:
|
||||
- "8080"
|
||||
environment:
|
||||
API_URL: http://localhost:3000/
|
||||
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.
|
||||
|
||||
@@ -188,9 +181,13 @@ Building from Source
|
||||
|
||||
When a pre-built binary does not exist for your system you can build the project from source.
|
||||
|
||||
.. note::
|
||||
|
||||
We discourage building and using PostgREST on **Alpine Linux** because of a reported GHC memory leak on that platform.
|
||||
|
||||
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 Stack <https://docs.haskellstack.org/en/stable/README/#how-to-install-stack>`_ for your platform
|
||||
* Install Library Dependencies
|
||||
|
||||
===================== =======================================
|
||||
|
||||
@@ -42,7 +42,7 @@ The first step is to create an Nginx configuration file that proxies requests to
|
||||
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>`_.
|
||||
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>`_. Note that some Platforms as a Service like Heroku also add SSL automatically in their load balancer.
|
||||
|
||||
Rate Limiting
|
||||
-------------
|
||||
@@ -55,7 +55,7 @@ Nginx supports "leaky bucket" rate limiting (see `official docs <https://nginx.o
|
||||
|
||||
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`.
|
||||
Next we apply the zone to certain routes, like a hypothetical stored procedure called :code:`login`.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
@@ -73,10 +73,17 @@ 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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people?id=eq.1" \
|
||||
-H "Accept: application/vnd.pgrst.object+json"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /people?id=eq.1 HTTP/1.1
|
||||
Accept: application/vnd.pgrst.object+json
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
.. note::
|
||||
|
||||
This page is a work in progress.
|
||||
|
||||
.. _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.
|
||||
A PostgREST instance exposes all the tables, views, and stored procedures 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.
|
||||
It is recommended that you don't expose tables on your API schema. Instead expose views and stored procedures 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>
|
||||
.. image:: ../_static/db.png
|
||||
|
||||
@@ -191,7 +191,7 @@ References
|
||||
|
||||
For more information concerning PostgREST, cf.
|
||||
|
||||
- :ref:`function_single_unnamed`
|
||||
- :ref:`s_proc_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>`
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ and return a raw HTML document file.
|
||||
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.
|
||||
Let's create a function that returns a basic HTML file, using `Tailwind CSS <https://v2.tailwindcss.com/>`_ for styling.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
@@ -51,20 +50,15 @@ Let's create a function that returns a basic HTML file, using `Pico CSS <https:/
|
||||
<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" />
|
||||
<!-- Tailwind for CSS styling -->
|
||||
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.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 class="bg-gray-900">
|
||||
<div class="flex justify-center">
|
||||
<div class="max-w-lg mt-5 p-6 bg-gray-800 border border-gray-800 rounded-lg shadow-xl">
|
||||
<h5 class="mb-3 text-2xl font-bold tracking-tight text-white">PostgREST + HTMX To-Do List</h5>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
$html$;
|
||||
@@ -90,22 +84,24 @@ For that, we'll also need a function to help us sanitize the HTML content that m
|
||||
|
||||
create or replace function api.html_todo(api.todos) returns text as $$
|
||||
select format($html$
|
||||
<div>
|
||||
<%2$s>
|
||||
<li class="py-3">
|
||||
<span class="ml-2 %2$s">
|
||||
%3$s
|
||||
</%2$s>
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 's' else 'span' end,
|
||||
case when $1.done then 'line-through text-gray-400' else '' 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>'
|
||||
'<ul id="todo-list" role="list" class="divide-y divide-gray-700 text-gray-100">'
|
||||
|| string_agg(api.html_todo(t), '' order by t.id) ||
|
||||
'</ul>',
|
||||
'<p class="text-gray-100">There is nothing else to do.</p>'
|
||||
)
|
||||
from api.todos t;
|
||||
$$ language sql;
|
||||
@@ -130,44 +126,39 @@ Next, let's add an endpoint to register a to-do in the database and modify the `
|
||||
$$ 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>
|
||||
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>
|
||||
<!-- Tailwind for CSS styling -->
|
||||
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<!-- htmx for AJAX requests -->
|
||||
<script src="https://unpkg.com/htmx.org"></script>
|
||||
</head>
|
||||
<body class="bg-gray-900"
|
||||
hx-headers='{"Accept": "text/html"}'>
|
||||
<div class="flex justify-center">
|
||||
<div class="max-w-lg mt-5 p-6 bg-gray-800 border border-gray-800 rounded-lg shadow-xl">
|
||||
<h5 class="mb-3 text-2xl font-bold tracking-tight text-white">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 class="bg-gray-50 border text-sm rounded-lg block w-full p-2.5 mb-3 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
type="text" name="_task" placeholder="Add a todo...">
|
||||
</form>
|
||||
<div id="todo-list-area">
|
||||
$html$
|
||||
|| api.html_all_todos() ||
|
||||
$html$
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
$html$;
|
||||
$$ language sql;
|
||||
|
||||
@@ -199,46 +190,49 @@ 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}'
|
||||
select format($html$
|
||||
<li class="py-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<div id="todo-edit-area-%1$s" class="pr-5">
|
||||
<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">
|
||||
<span class="ml-2 %2$s cursor-pointer">
|
||||
%3$s
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<button class="p-1.5 rounded-full hover:bg-gray-700 focus:ring-gray-800"
|
||||
hx-get="/rpc/html_editable_task"
|
||||
hx-vals='{"_id": "%1$s"}'
|
||||
hx-target="#todo-edit-area-%1$s"
|
||||
hx-trigger="click">
|
||||
<svg class="w-4 h-4 text-blue-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
|
||||
<path d="M12.687 14.408a3.01 3.01 0 0 1-1.533.821l-3.566.713a3 3 0 0 1-3.53-3.53l.713-3.566a3.01 3.01 0 0 1 .821-1.533L10.905 2H2.167A2.169 2.169 0 0 0 0 4.167v11.666A2.169 2.169 0 0 0 2.167 18h11.666A2.169 2.169 0 0 0 16 15.833V11.1l-3.313 3.308Zm5.53-9.065.546-.546a2.518 2.518 0 0 0 0-3.56 2.576 2.576 0 0 0-3.559 0l-.547.547 3.56 3.56Z"/>
|
||||
<path d="M13.243 3.2 7.359 9.081a.5.5 0 0 0-.136.256L6.51 12.9a.5.5 0 0 0 .59.59l3.566-.713a.5.5 0 0 0 .255-.136L16.8 6.757 13.243 3.2Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="p-1.5 rounded-full hover:bg-gray-700 focus:ring-gray-800"
|
||||
hx-post="/rpc/delete_todo"
|
||||
hx-vals='{"_id": %1$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>
|
||||
<svg class="w-4 h-4 text-red-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 's' else 'span' end,
|
||||
api.sanitize_html($1.task),
|
||||
(not $1.done)::text
|
||||
);
|
||||
</div>
|
||||
</li>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 'line-through text-gray-400' else '' end,
|
||||
api.sanitize_html($1.task),
|
||||
(not $1.done)::text
|
||||
);
|
||||
$$ language sql stable;
|
||||
|
||||
Let's deconstruct the new htmx features added:
|
||||
@@ -273,21 +267,22 @@ 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;
|
||||
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 class="bg-gray-50 border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500"
|
||||
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.
|
||||
|
||||
@@ -81,14 +81,12 @@ First, in addition to the minimal example, we need to store the media types and
|
||||
.. code-block:: postgres
|
||||
|
||||
alter table files
|
||||
add column type text generated always as (byteamagic_mime(substr(blob, 0, 4100))) stored,
|
||||
add column type text,
|
||||
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>`.
|
||||
For production, you probably want to configure additional caches, e.g. on the :ref:`reverse proxy <admin>`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ As in :ref:`sql_user_management`, we create a :code:`basic_auth` schema:
|
||||
-- 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;
|
||||
CREATE SCHEMA IF NOT EXISTS 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:
|
||||
@@ -40,7 +40,7 @@ As in :ref:`sql_user_management`, we create the :code:`pgcrypto` and :code:`pgjw
|
||||
|
||||
CREATE SCHEMA ext_pgcrypto;
|
||||
ALTER SCHEMA ext_pgcrypto OWNER TO postgres;
|
||||
CREATE EXTENSION pgcrypto WITH SCHEMA ext_pgcrypto;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA ext_pgcrypto;
|
||||
|
||||
|
||||
Concerning the `pgjwt extension <https://github.com/michelp/pgjwt>`_, please cf. to :ref:`client_auth`.
|
||||
@@ -49,12 +49,12 @@ Concerning the `pgjwt extension <https://github.com/michelp/pgjwt>`_, please cf.
|
||||
|
||||
CREATE SCHEMA ext_pgjwt;
|
||||
ALTER SCHEMA ext_pgjwt OWNER TO postgres;
|
||||
CREATE EXTENSION pgjwt WITH SCHEMA ext_pgjwt;
|
||||
CREATE EXTENSION IF NOT EXISTS 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>`_:
|
||||
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/q/47162200/2337147>`_:
|
||||
|
||||
.. code-block:: postgres
|
||||
.. code-block:: plpgsql
|
||||
|
||||
CREATE FUNCTION basic_auth.pbkdf2(salt bytea, pw text, count integer, desired_length integer, algorithm text) RETURNS bytea
|
||||
LANGUAGE plpgsql IMMUTABLE
|
||||
@@ -117,10 +117,10 @@ In order to be able to work with postgres' SCRAM-SHA-256 password hashes, we als
|
||||
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).
|
||||
Analogous to :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
|
||||
.. code-block:: plpgsql
|
||||
|
||||
CREATE FUNCTION basic_auth.check_user_pass(username text, password text) RETURNS name
|
||||
LANGUAGE sql
|
||||
@@ -160,17 +160,22 @@ Logins
|
||||
As described in :ref:`client_auth`, 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
|
||||
.. code-block:: plpgsql
|
||||
|
||||
CREATE TYPE basic_auth.jwt_token AS (
|
||||
token text
|
||||
);
|
||||
|
||||
-- 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)
|
||||
CREATE FUNCTION public.login(username text, password text) RETURNS basic_auth.jwt_token
|
||||
LANGUAGE plpgsql security definer
|
||||
AS $$
|
||||
DECLARE
|
||||
_role name;
|
||||
result basic_auth.jwt_token;
|
||||
BEGIN
|
||||
-- check email and password
|
||||
SELECT basic_auth.check_user_pass(username, password) INTO _role;
|
||||
@@ -185,7 +190,8 @@ As described in :ref:`client_auth`, we'll create a JWT token inside our login fu
|
||||
SELECT login.username as role,
|
||||
extract(epoch FROM now())::integer + 60*60 AS exp
|
||||
) r
|
||||
INTO token;
|
||||
INTO result;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -253,11 +259,19 @@ Test at the REST level
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
An API request to call this function would look like:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/rpc/login" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "username": "foo", "password": "bar" }'
|
||||
.. code-tab:: http
|
||||
|
||||
POST /rpc/login HTTP/1.1
|
||||
|
||||
{ "username": "foo", "password": "bar" }
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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!)
|
||||
|
||||
@@ -282,18 +296,31 @@ Let's add a table, intended for the :code:`foo` user:
|
||||
|
||||
Now try to get the table's contents with:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/foobar"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /foobar HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/foobar" \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /foobar HTTP/1.1
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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:
|
||||
|
||||
@@ -17,8 +17,9 @@ First we'll need a table to keep track of our users:
|
||||
-- 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 if not exists basic_auth;
|
||||
|
||||
create table
|
||||
create table if not exists
|
||||
basic_auth.users (
|
||||
email text primary key check ( email ~* '^.+@.+\..+$' ),
|
||||
pass text not null check (length(pass) < 512),
|
||||
@@ -27,9 +28,9 @@ First we'll need a table to keep track of our users:
|
||||
|
||||
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
|
||||
.. code-block:: plpgsql
|
||||
|
||||
create function
|
||||
create or replace 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
|
||||
@@ -41,6 +42,7 @@ We would like the role to be a foreign key to actual database roles, however Pos
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
drop trigger if exists ensure_user_role_exists on basic_auth.users;
|
||||
create constraint trigger ensure_user_role_exists
|
||||
after insert or update on basic_auth.users
|
||||
for each row
|
||||
@@ -48,11 +50,11 @@ We would like the role to be a foreign key to actual database roles, however Pos
|
||||
|
||||
Next we'll use the pgcrypto extension and a trigger to keep passwords safe in the :code:`users` table.
|
||||
|
||||
.. code-block:: postgres
|
||||
.. code-block:: plpgsql
|
||||
|
||||
create extension pgcrypto;
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
create function
|
||||
create or replace function
|
||||
basic_auth.encrypt_pass() returns trigger as $$
|
||||
begin
|
||||
if tg_op = 'INSERT' or new.pass <> old.pass then
|
||||
@@ -62,6 +64,7 @@ Next we'll use the pgcrypto extension and a trigger to keep passwords safe in th
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
drop trigger if exists encrypt_pass on basic_auth.users;
|
||||
create trigger encrypt_pass
|
||||
before insert or update on basic_auth.users
|
||||
for each row
|
||||
@@ -69,9 +72,9 @@ Next we'll use the pgcrypto extension and a trigger to keep passwords safe in th
|
||||
|
||||
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
|
||||
.. code-block:: plpgsql
|
||||
|
||||
create function
|
||||
create or replace function
|
||||
basic_auth.user_role(email text, pass text) returns name
|
||||
language plpgsql
|
||||
as $$
|
||||
@@ -115,11 +118,15 @@ 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.
|
||||
Next write a stored procedure 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 $$
|
||||
CREATE TYPE jwt_token AS (
|
||||
token text
|
||||
);
|
||||
|
||||
CREATE FUNCTION jwt_test() RETURNS public.jwt_token AS $$
|
||||
SELECT public.sign(
|
||||
row_to_json(r), 'reallyreallyreallyreallyverysafe'
|
||||
) AS token
|
||||
@@ -134,7 +141,7 @@ 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.
|
||||
To avoid hard-coding the secret in stored procedures, save it as a property of the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
@@ -154,11 +161,17 @@ As described in `JWT from SQL`_, we'll create a JWT inside our login function. N
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- add type
|
||||
CREATE TYPE basic_auth.jwt_token AS (
|
||||
token text
|
||||
);
|
||||
|
||||
-- login should be on your exposed schema
|
||||
create function
|
||||
login(email text, pass text, out token text) as $$
|
||||
create or replace function
|
||||
login(email text, pass text) returns basic_auth.jwt_token as $$
|
||||
declare
|
||||
_role name;
|
||||
result basic_auth.jwt_token;
|
||||
begin
|
||||
-- check email and password
|
||||
select basic_auth.user_role(email, pass) into _role;
|
||||
@@ -173,7 +186,8 @@ As described in `JWT from SQL`_, we'll create a JWT inside our login function. N
|
||||
select _role as role, login.email as email,
|
||||
extract(epoch from now())::integer + 60*60 as exp
|
||||
) r
|
||||
into token;
|
||||
into result;
|
||||
return result;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
@@ -185,11 +199,19 @@ the anonymous user :code:`anon` doesn't need permission to read the :code:`basic
|
||||
|
||||
An API request to call this function would look like:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/rpc/login" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "email": "foo@bar.com", "pass": "foobar" }'
|
||||
.. code-tab:: http
|
||||
|
||||
POST /rpc/login HTTP/1.1
|
||||
|
||||
{ "email": "foo@bar.com", "pass": "foobar" }
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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!)
|
||||
|
||||
|
||||
@@ -5,13 +5,133 @@ 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.
|
||||
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 to perform operations on different PostgreSQL data types.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
|
||||
.. NOTE: Titles are ordered alphabetically. New entries should respect this order.
|
||||
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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /reports HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },
|
||||
{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch)) HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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"
|
||||
}
|
||||
]
|
||||
|
||||
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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /products HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Canned fish",
|
||||
"extra_info": {
|
||||
"expiry_date": "2025-12-31",
|
||||
"exportable": true
|
||||
}
|
||||
}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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.
|
||||
|
||||
Arrays
|
||||
------
|
||||
@@ -29,33 +149,61 @@ To handle `array types <https://www.postgresql.org/docs/current/arrays.html>`_ y
|
||||
|
||||
You can insert a new value using string representation.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /movies HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /movies HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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`.
|
||||
|
||||
@@ -72,21 +220,38 @@ Similarly to one-dimensional arrays, both the string representation and JSON arr
|
||||
|
||||
You can now update the item using JSON array format:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
PATCH /movies?id=eq.1 HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1 HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -97,57 +262,6 @@ Then, for example, to query the auditoriums that are located in the first cinema
|
||||
}
|
||||
]
|
||||
|
||||
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
|
||||
---------------
|
||||
|
||||
@@ -172,79 +286,253 @@ With PostgREST, you have two options to handle `composite type columns <https://
|
||||
|
||||
On one hand you can insert values using string representation.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /products HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
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"
|
||||
.. code-tab:: http
|
||||
|
||||
POST /products HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 2,
|
||||
"size": {
|
||||
"length": 0.7,
|
||||
"width": 0.5,
|
||||
"height": 1.8,
|
||||
"unit": "m"
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
-----
|
||||
Ranges
|
||||
------
|
||||
|
||||
You can handle `Enumerated Types <https://www.postgresql.org/docs/current/datatype-enum.html>`_ using string representations:
|
||||
PostgREST allows you to handle `ranges <https://www.postgresql.org/docs/current/rangetypes.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type letter_size as enum ('s','m','l','xl');
|
||||
create table events (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
duration tsrange
|
||||
);
|
||||
|
||||
create table products (
|
||||
id int primary key generated always as identity,
|
||||
name text,
|
||||
size letter_size
|
||||
);
|
||||
To insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type:
|
||||
|
||||
To insert or update the value use a string:
|
||||
.. tabs::
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-tab:: http
|
||||
|
||||
curl -X POST "http://localhost:3000/products" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{ "name": "t-shirt", "size": "l" }
|
||||
EOF
|
||||
POST /events HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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:
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
|
||||
}
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/products?select=name,size&size=gt.m&order=size"
|
||||
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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /events?duration=cs.[2023-01-01,2023-01-01] HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/events?duration=cs.\[2023-01-01,2023-01-01\]"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"name": "t-shirt",
|
||||
"size": "l"
|
||||
},
|
||||
{
|
||||
"name": "hoodie",
|
||||
"size": "xl"
|
||||
"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>`:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /events?select=id,name,duration::json HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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;
|
||||
|
||||
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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /rpc/upload_binary HTTP/1.1
|
||||
Content-Type: application/octet-stream
|
||||
|
||||
postgrest-logo.png
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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;
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /get_image?id=1 HTTP/1.1
|
||||
Accept: image/png
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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>`_.
|
||||
|
||||
hstore
|
||||
------
|
||||
@@ -263,67 +551,53 @@ You can work with data types belonging to additional supplied modules such as `h
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /countries HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/countries?select=name->>native&name->>common=like.Egypt"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /countries?select=name->>native&name->>common=like.Egypt HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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.
|
||||
|
||||
.. _ww_postgis:
|
||||
|
||||
PostGIS
|
||||
-------
|
||||
|
||||
You can use the string representation for `PostGIS <https://postgis.net/>`_ data types such as ``geometry`` or ``geography`` (you need to `install PostGIS <https://postgis.net/documentation/getting_started/>`_ first).
|
||||
You can use the string representation for `PostGIS <https://postgis.net/>`_ data types such as ``geometry`` or ``geography`` (you need to `install PostGIS <https://postgis.net/install/>`_ first).
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
@@ -338,23 +612,42 @@ You can use the string representation for `PostGIS <https://postgis.net/>`_ data
|
||||
|
||||
To add areas in polygon format, you can use string representation:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /coverage HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
[
|
||||
{ "id": 1, "name": "small", "area": "SRID=4326;POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))" },
|
||||
{ "id": 2, "name": "big", "area": "SRID=4326;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))" }
|
||||
]
|
||||
EOF
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
[
|
||||
{ "id": 1, "name": "small", "area": "SRID=4326;POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))" },
|
||||
{ "id": 2, "name": "big", "area": "SRID=4326;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))" }
|
||||
]
|
||||
EOF
|
||||
|
||||
Now, when you request the information, PostgREST will automatically cast the ``area`` column into a ``Polygon`` geometry type. Although this is useful, you may need the whole output to be in `GeoJSON <https://geojson.org/>`_ format out of the box, which can be done by including the ``Accept: application/geo+json`` in the request. 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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-H "Accept: application/geo+json"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /coverage HTTP/1.1
|
||||
Accept: application/geo+json
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-H "Accept: application/geo+json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -418,9 +711,15 @@ In the case that you are using older PostGIS versions, then creating a function
|
||||
|
||||
Now this query will return the same results:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/rpc/coverage_geo_collection"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /rpc/coverage_geo_collection HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/rpc/coverage_geo_collection"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -455,157 +754,3 @@ Now this query will return the same results:
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,7 +5,7 @@ PostgREST Documentation
|
||||
|
||||
.. container:: image-container
|
||||
|
||||
.. figure:: ../static/postgrest.png
|
||||
.. figure:: _static/logo.png
|
||||
|
||||
.. image:: https://img.shields.io/github/stars/postgrest/postgrest.svg?style=social
|
||||
:target: https://github.com/PostgREST/postgrest
|
||||
@@ -16,9 +16,15 @@ PostgREST Documentation
|
||||
.. image:: https://img.shields.io/docker/pulls/postgrest/postgrest.svg
|
||||
:target: https://hub.docker.com/r/postgrest/postgrest/
|
||||
|
||||
.. image:: https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg
|
||||
:target: https://gitter.im/begriffs/postgrest
|
||||
|
||||
.. image:: https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854
|
||||
:target: https://www.patreon.com/postgrest
|
||||
|
||||
.. image:: https://img.shields.io/badge/Donate-PayPal-green.svg
|
||||
:target: https://www.paypal.com/paypalme/postgrest
|
||||
|
||||
|
|
||||
|
||||
PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. The structural constraints and permissions in the database determine the API endpoints and operations.
|
||||
@@ -28,59 +34,38 @@ Sponsors
|
||||
|
||||
.. container:: image-container
|
||||
|
||||
.. container:: img-dark
|
||||
.. image:: _static/cybertec-new.png
|
||||
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
.. 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
|
||||
|
||||
.. image:: ../static/gnuhost.png
|
||||
.. image:: _static/gnuhost.png
|
||||
:target: https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/neon-dark.jpg
|
||||
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/neon.jpg
|
||||
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
|
||||
.. image:: _static/neon.jpg
|
||||
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
|
|
||||
|
||||
.. container:: img-dark
|
||||
.. image:: _static/code-build.webp
|
||||
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
.. image:: ../static/code-build-dark.png
|
||||
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
|
||||
.. image:: _static/supabase.png
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
:width: 13em
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/code-build.png
|
||||
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/supabase-dark.png
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/supabase.png
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
|
||||
.. image:: ../static/tembo.png
|
||||
.. image:: _static/tembo.png
|
||||
:target: https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
.. 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
|
||||
:width: 13em
|
||||
|
||||
|
|
||||
|
||||
@@ -107,7 +92,7 @@ PostgREST has a focused scope. It works well with other tools like Nginx. This f
|
||||
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.
|
||||
The project has a friendly and growing community. For discussions, use the Github `discussions page <https://github.com/PostgREST/postgrest/discussions>`_ or join our `chat room <https://gitter.im/begriffs/postgrest>`_. You can also report or search for bugs/features on the Github `issues <https://github.com/PostgREST/postgrest/issues>`_ page.
|
||||
|
||||
Release Notes
|
||||
-------------
|
||||
@@ -141,13 +126,11 @@ Technical references for PostgREST's functionality.
|
||||
|
||||
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
|
||||
@@ -212,12 +195,13 @@ Here are some companies that use PostgREST in production.
|
||||
* `Datrium <https://www.datrium.com>`_
|
||||
* `Drip Depot <https://www.dripdepot.com>`_
|
||||
* `Image-charts <https://www.image-charts.com>`_
|
||||
* `Moat <https://www.oracle.com/advertising/>`_
|
||||
* `Moat <https://www.moat.com>`_
|
||||
* `Netwo <https://www.netwo.io>`_
|
||||
* `Nimbus <https://www.nimbusfacility.com/sg/home>`_
|
||||
* `Nimbus <https://www.nimbusforwork.com>`_
|
||||
- See how Nimbus uses PostgREST in `Paul Copplestone's blog post <https://paul.copplest.one/blog/nimbus-tech-2019-04.html>`_.
|
||||
* `OpenBooking <https://openbooking.ch>`_
|
||||
* `OpenBooking <https://www.openbooking.ch>`_
|
||||
* `Redsmin <https://www.redsmin.com>`_
|
||||
* `Sompani <https://www.sompani.com>`_
|
||||
* `Supabase <https://supabase.com>`_
|
||||
|
||||
.. Failing links
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
.. _deploy_heroku:
|
||||
|
||||
Heroku
|
||||
======
|
||||
|
||||
1. Log into Heroku using the `Heroku CLI <https://devcenter.heroku.com/articles/heroku-cli>`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# If you have multiple Heroku accounts, use flag '--interactive' to switch between them
|
||||
heroku login --interactive
|
||||
|
||||
|
||||
2. Create a new Heroku app using the PostgREST buildpack:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
mkdir ${YOUR_APP_NAME}
|
||||
cd ${YOUR_APP_NAME}
|
||||
git init .
|
||||
|
||||
heroku apps:create ${YOUR_APP_NAME} --buildpack https://github.com/PostgREST/postgrest-heroku.git
|
||||
heroku git:remote -a ${YOUR_APP_NAME}
|
||||
|
||||
3. Create a new Heroku PostgreSQL add-on attached to the app and keep notes of the assigned add-on name (e.g. :code:`postgresql-curly-58902`) referred later as ${HEROKU_PG_DB_NAME}
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
heroku addons:create heroku-postgresql:standard-0 -a ${YOUR_APP_NAME}
|
||||
# wait until the add-on is available
|
||||
heroku pg:wait -a ${YOUR_APP_NAME}
|
||||
|
||||
4. Create the necessary user roles according to the
|
||||
`PostgREST documentation <https://postgrest.org/en/stable/auth.html>`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
heroku pg:credentials:create --name api_user -a ${YOUR_APP_NAME}
|
||||
# use the following command to ensure the new credential state is active before attaching it
|
||||
heroku pg:credentials -a ${YOUR_APP_NAME}
|
||||
|
||||
heroku addons:attach ${HEROKU_PG_DB_NAME} --credential api_user -a ${YOUR_APP_NAME}
|
||||
|
||||
5. Connect to the PostgreSQL database and create some sample data:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
heroku psql -a ${YOUR_APP_NAME}
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
# from the psql command prompt execute the following commands:
|
||||
create schema api;
|
||||
|
||||
create table api.todos (
|
||||
id serial primary key,
|
||||
done boolean not null default false,
|
||||
task text not null,
|
||||
due timestamptz
|
||||
);
|
||||
|
||||
insert into api.todos (task) values
|
||||
('finish tutorial 0'), ('pat self on back');
|
||||
|
||||
grant usage on schema api to api_user;
|
||||
grant select on api.todos to api_user;
|
||||
|
||||
6. Create the :code:`Procfile`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
web: PGRST_SERVER_HOST=0.0.0.0 PGRST_SERVER_PORT=${PORT} PGRST_DB_URI=${PGRST_DB_URI:-${DATABASE_URL}} ./postgrest-${POSTGREST_VER}
|
||||
..
|
||||
|
||||
Set the following environment variables on Heroku:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
heroku config:set POSTGREST_VER=10.0.0
|
||||
heroku config:set PGRST_DB_SCHEMA=api
|
||||
heroku config:set PGRST_DB_ANON_ROLE=api_user
|
||||
..
|
||||
|
||||
PGRST_DB_URI can be set if an external database is used or if it's different from the default Heroku DATABASE_URL. This latter is used if nothing is provided.
|
||||
POSTGREST_VER is mandatory to select and build the required PostgREST release.
|
||||
|
||||
See https://postgrest.org/en/stable/configuration.html#environment-variables for the full list of environment variables.
|
||||
|
||||
7. Build and deploy your app:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git add Procfile
|
||||
git commit -m "PostgREST on Heroku"
|
||||
git push heroku master
|
||||
..
|
||||
|
||||
Your Heroku app should be live at :code:`${YOUR_APP_NAME}.herokuapp.com`
|
||||
|
||||
8. Test your app
|
||||
|
||||
From a terminal display the application logs:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
heroku logs -t
|
||||
..
|
||||
|
||||
From a different terminal retrieve with curl the records previously created:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl https://${YOUR_APP_NAME}.herokuapp.com/todos
|
||||
..
|
||||
|
||||
and test that any attempt to modify the table via a read-only user is not allowed:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl https://${YOUR_APP_NAME}.herokuapp.com/todos -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"task": "do bad thing"}'
|
||||
@@ -9,3 +9,23 @@ 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.
|
||||
|
||||
.. note::
|
||||
|
||||
Our code requires a database role in the JWT. To add it you need to save the database role in Auth0 `app metadata <https://auth0.com/docs/manage-users/user-accounts/metadata/manage-metadata-rules>`_. Then, you will need to write `a rule <https://auth0.com/docs/customize/rules>`_ that will extract the role from the user's app_metadata and set it as a `custom claim <https://auth0.com/docs/get-started/apis/scopes/sample-use-cases-scopes-and-claims#add-custom-claims-to-a-token>`_ in the access token. Note that, you may use Auth0's `core authorization feature <https://auth0.com/docs/manage-users/access-control/rbac>`_ for more complex use cases. Metadata solution is mentioned here for simplicity.
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
function (user, context, callback) {
|
||||
|
||||
// Follow the documentations at
|
||||
// https://postgrest.org/en/latest/configuration.html#db-role-claim-key
|
||||
// to set a custom role claim on PostgREST
|
||||
// and use it as custom claim attribute in this rule
|
||||
const myRoleClaim = 'https://myapp.com/role';
|
||||
|
||||
user.app_metadata = user.app_metadata || {};
|
||||
context.accessToken[myRoleClaim] = user.app_metadata.role;
|
||||
callback(null, user, context);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,27 @@ 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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE
|
||||
.. code-tab:: http
|
||||
|
||||
DELETE /logs?time=lt.1991-08-06 HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/logs" -X DELETE
|
||||
.. code-tab:: http
|
||||
|
||||
DELETE /logs HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
systemd
|
||||
=======
|
||||
|
||||
For Linux distributions that use **systemd** (Ubuntu, Debian, Arch Linux) you can create a daemon in the following way.
|
||||
For Linux distributions that use **systemd** (Ubuntu, Debian, Archlinux) you can create a daemon in the following way.
|
||||
|
||||
First, create postgrest configuration in ``/etc/postgrest/config``
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
from livereload import Server, shell
|
||||
from subprocess import call
|
||||
|
||||
## Build docs at startup
|
||||
call(["sphinx-build", "-b", "html", "-a", "-n", ".", "_build"])
|
||||
server = Server()
|
||||
server.watch("**/*.rst", shell("sphinx-build -b html -a -n . _build"))
|
||||
# For custom port and host
|
||||
# server.serve(root='_build/', host='192.168.1.2')
|
||||
server.serve(root="_build/")
|
||||
@@ -1,21 +1,22 @@
|
||||
personal_ws-1.1 en 0 utf-8
|
||||
api
|
||||
API's
|
||||
APIs
|
||||
APISIX
|
||||
Archlinux
|
||||
aud
|
||||
Auth
|
||||
auth
|
||||
authenticator
|
||||
backoff
|
||||
balancer
|
||||
booleans
|
||||
buildpack
|
||||
BOM
|
||||
Bytea
|
||||
Cardano
|
||||
casted
|
||||
cd
|
||||
centric
|
||||
CLI
|
||||
coercible
|
||||
conf
|
||||
Cloudflare
|
||||
@@ -29,8 +30,6 @@ DDL
|
||||
DOM
|
||||
DevOps
|
||||
dockerize
|
||||
enum
|
||||
Enums
|
||||
eq
|
||||
ETH
|
||||
Ethereum
|
||||
@@ -39,6 +38,7 @@ filename
|
||||
FreeBSD
|
||||
fts
|
||||
GeoJSON
|
||||
GHC
|
||||
Github
|
||||
Google
|
||||
grantor
|
||||
@@ -47,6 +47,7 @@ Greenplum
|
||||
gte
|
||||
GUC
|
||||
Haskell
|
||||
Heroku
|
||||
HMAC
|
||||
htmx
|
||||
Htmx
|
||||
@@ -94,12 +95,10 @@ npm
|
||||
nxl
|
||||
nxr
|
||||
OAuth
|
||||
Observability
|
||||
OpenAPI
|
||||
openapi
|
||||
ORM
|
||||
ov
|
||||
parametrized
|
||||
passphrase
|
||||
PBKDF
|
||||
PgBouncer
|
||||
@@ -150,13 +149,13 @@ spreaded
|
||||
Spreaded
|
||||
SQL
|
||||
sql
|
||||
SQLSTATE
|
||||
sr
|
||||
SSL
|
||||
stateful
|
||||
stdout
|
||||
supervisees
|
||||
SvelteKit
|
||||
syslog
|
||||
systemd
|
||||
todo
|
||||
todos
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
.. _observability:
|
||||
.. _admin:
|
||||
|
||||
Observability
|
||||
#############
|
||||
|
||||
Observability allows measuring a system's current state based on the data it generates, such as logs, metrics, and traces.
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
:backlinks: none
|
||||
Admin
|
||||
#####
|
||||
|
||||
.. _pgrst_logging:
|
||||
|
||||
Logs
|
||||
====
|
||||
Logging
|
||||
-------
|
||||
|
||||
PostgREST logs basic request information to ``stdout``, including the authenticated user if available, the requesting IP address and user agent, the URL requested, and HTTP response status.
|
||||
|
||||
@@ -22,30 +15,33 @@ PostgREST logs basic request information to ``stdout``, including the authentica
|
||||
127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 - "" "curl/7.64.0"
|
||||
127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 - "" "curl/7.64.0"
|
||||
|
||||
For diagnostic information about the server itself, PostgREST logs to ``stderr``:
|
||||
|
||||
- The full version of the connected PostgreSQL database.
|
||||
- :ref:`schema_cache` statistics.
|
||||
- The messages received by the :ref:`listener`.
|
||||
For diagnostic information about the server itself, PostgREST logs to ``stderr``.
|
||||
|
||||
.. code::
|
||||
|
||||
06/May/2024:08:16:11 -0500: Starting PostgREST 12.1...
|
||||
06/May/2024:08:16:11 -0500: Attempting to connect to the database...
|
||||
06/May/2024:08:16:11 -0500: Successfully connected to PostgreSQL 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit
|
||||
06/May/2024:08:16:11 -0500: Listening on port 3000
|
||||
06/May/2024:08:16:11 -0500: Listening for notifications on the "pgrst" channel
|
||||
06/May/2024:08:16:11 -0500: Config reloaded
|
||||
06/May/2024:08:16:11 -0500: Schema cache queried in 3.8 milliseconds
|
||||
06/May/2024:08:16:11 -0500: Schema cache loaded 15 Relations, 8 Relationships, 8 Functions, 0 Domain Representations, 4 Media Type Handlers
|
||||
06/May/2024:14:11:27 -0500: Received a config reload message on the "pgrst" channel
|
||||
06/May/2024:14:11:27 -0500: Config reloaded
|
||||
12/Jun/2021:17:47:39 -0500: Starting PostgREST 11.1.0...
|
||||
12/Jun/2021:17:47:39 -0500: Attempting to connect to the database...
|
||||
12/Jun/2021:17:47:39 -0500: Listening on port 3000
|
||||
12/Jun/2021:17:47:39 -0500: Connection successful
|
||||
12/Jun/2021:17:47:39 -0500: Config re-loaded
|
||||
12/Jun/2021:17:47:40 -0500: Schema cache loaded
|
||||
|
||||
Database Logs
|
||||
-------------
|
||||
.. note::
|
||||
|
||||
When running it in an SSH session you must detach it from stdout or it will be terminated when the session closes. The easiest technique is redirecting the output to a log file or to the syslog:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ssh foo@example.com \
|
||||
'postgrest foo.conf </dev/null >/var/log/postgrest.log 2>&1 &'
|
||||
|
||||
# another option is to pipe the output into "logger -t postgrest"
|
||||
|
||||
Currently PostgREST doesn't log the SQL commands executed against the underlying database.
|
||||
|
||||
Database Logs
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
To find the SQL operations, you can watch the database logs. By default PostgreSQL does not keep these logs, so you'll need to make the configuration changes below.
|
||||
|
||||
Find :code:`postgresql.conf` inside your PostgreSQL data directory (to find that, issue the command :code:`show data_directory;`). Either find the settings scattered throughout the file and change them to the following values, or append this block of code to the end of the configuration file.
|
||||
@@ -85,97 +81,12 @@ Restart the database and watch the log file in real-time to understand how HTTP
|
||||
docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres
|
||||
docker logs -f <container-id>
|
||||
|
||||
.. _metrics:
|
||||
Server Version
|
||||
--------------
|
||||
|
||||
Metrics
|
||||
=======
|
||||
When debugging a problem it's important to verify the running PostgREST version. There are three ways to do this:
|
||||
|
||||
The ``metrics`` endpoint on the :ref:`admin_server` endpoint provides metrics in `Prometheus text format <https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format>`_.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3001/metrics"
|
||||
|
||||
# HELP pgrst_schema_cache_query_time_seconds The query time in seconds of the last schema cache load
|
||||
# TYPE pgrst_schema_cache_query_time_seconds gauge
|
||||
pgrst_schema_cache_query_time_seconds 1.5937927e-2
|
||||
# HELP pgrst_schema_cache_loads_total The total number of times the schema cache was loaded
|
||||
# TYPE pgrst_schema_cache_loads_total counter
|
||||
pgrst_schema_cache_loads_total 1.0
|
||||
...
|
||||
|
||||
Schema Cache Metrics
|
||||
--------------------
|
||||
|
||||
Metrics related to the :ref:`schema_cache`.
|
||||
|
||||
pgrst_schema_cache_query_time_seconds
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
======== =======
|
||||
**Type** Gauge
|
||||
======== =======
|
||||
|
||||
The query time in seconds of the last schema cache load.
|
||||
|
||||
pgrst_schema_cache_loads_total
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
========== ==========================
|
||||
**Type** Counter
|
||||
**Labels** ``status``: SUCCESS | FAIL
|
||||
========== ==========================
|
||||
|
||||
The total number of times the schema cache was loaded.
|
||||
|
||||
Connection Pool Metrics
|
||||
-----------------------
|
||||
|
||||
Metrics related to the :ref:`connection_pool`.
|
||||
|
||||
pgrst_db_pool_timeouts_total
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
======== =======
|
||||
**Type** Counter
|
||||
======== =======
|
||||
|
||||
The total number of pool connection timeouts.
|
||||
|
||||
pgrst_db_pool_available
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
======== =======
|
||||
**Type** Gauge
|
||||
======== =======
|
||||
|
||||
Available connections in the pool.
|
||||
|
||||
pgrst_db_pool_waiting
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
======== =======
|
||||
**Type** Gauge
|
||||
======== =======
|
||||
|
||||
Requests waiting to acquire a pool connection
|
||||
|
||||
pgrst_db_pool_max
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
======== =======
|
||||
**Type** Gauge
|
||||
======== =======
|
||||
|
||||
Max pool connections.
|
||||
|
||||
Traces
|
||||
======
|
||||
|
||||
Server Version Header
|
||||
---------------------
|
||||
|
||||
When debugging a problem it's important to verify the running PostgREST version. For this you can look at the :code:`Server` HTTP response header that is returned on every request.
|
||||
- Look for the :code:`Server` HTTP response header that is returned on every request.
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -183,6 +94,26 @@ When debugging a problem it's important to verify the running PostgREST version.
|
||||
|
||||
Server: postgrest/11.0.1
|
||||
|
||||
- Query ``application_name`` on `pg_stat_activity <https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW>`_.
|
||||
|
||||
.. code-block:: psql
|
||||
|
||||
select distinct application_name
|
||||
from pg_stat_activity
|
||||
where application_name ilike '%postgrest%';
|
||||
|
||||
application_name
|
||||
------------------------------
|
||||
PostgREST 11.1.0
|
||||
|
||||
.. important::
|
||||
|
||||
- The server sets the `fallback_application_name <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-FALLBACK-APPLICATION-NAME>`_ to the connection URI for this query to work. To override the value set ``application_name`` on the connection string.
|
||||
- The version will only be set if it's a valid URI (`RFC 3986 <https://datatracker.ietf.org/doc/html/rfc3986>`_). This means any special characters must be urlencoded.
|
||||
- The version will not be set if the connection string is in `keyword/value format <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-KEYWORD-VALUE>`_.
|
||||
|
||||
- The ``stderr`` logs also contain the version, as noted on :ref:`pgrst_logging`.
|
||||
|
||||
.. _trace_header:
|
||||
|
||||
Trace Header
|
||||
@@ -194,10 +125,18 @@ You can enable tracing HTTP requests by setting :ref:`server-trace-header`. Spec
|
||||
|
||||
server-trace-header = "X-Request-Id"
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/users" \
|
||||
-H "X-Request-Id: 123"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /users HTTP/1.1
|
||||
|
||||
X-Request-Id: 123
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/users" \
|
||||
-H "X-Request-Id: 123"
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -212,9 +151,15 @@ Server-Timing Header
|
||||
You can enable the `Server-Timing <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing>`_ header by setting :ref:`server-timing-enabled` on.
|
||||
This header communicates metrics of the different phases in the request-response cycle.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/users" -i
|
||||
.. code-tab:: http
|
||||
|
||||
GET /users HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/users" -i
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -241,22 +186,36 @@ Execution plan
|
||||
You can get the `EXPLAIN execution plan <https://www.postgresql.org/docs/current/sql-explain.html>`_ of a request by adding the ``Accept: application/vnd.pgrst.plan`` header.
|
||||
This is enabled by :ref:`db-plan-enabled` (false by default).
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/users?select=name&order=id" \
|
||||
-H "Accept: application/vnd.pgrst.plan"
|
||||
.. code-tab:: http
|
||||
|
||||
.. code-block:: postgres
|
||||
GET /users?select=name&order=id HTTP/1.1
|
||||
Accept: application/vnd.pgrst.plan
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/users?select=name&order=id" \
|
||||
-H "Accept: application/vnd.pgrst.plan"
|
||||
|
||||
.. code-block:: psql
|
||||
|
||||
Aggregate (cost=73.65..73.68 rows=1 width=112)
|
||||
-> Index Scan using users_pkey on users (cost=0.15..60.90 rows=850 width=36)
|
||||
|
||||
The output of the plan is generated in ``text`` format by default but you can change it to JSON by using the ``+json`` suffix.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/users?select=name&order=id" \
|
||||
-H "Accept: application/vnd.pgrst.plan+json"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /users?select=name&order=id HTTP/1.1
|
||||
Accept: application/vnd.pgrst.plan+json
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/users?select=name&order=id" \
|
||||
-H "Accept: application/vnd.pgrst.plan+json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -306,7 +265,7 @@ However, if you choose to use it in production you can add a :ref:`db-pre-reques
|
||||
|
||||
For example, to only allow requests from an IP address to get the execution plans:
|
||||
|
||||
.. code-block:: postgres
|
||||
.. code-block:: postgresql
|
||||
|
||||
-- Assuming a proxy(Nginx, Cloudflare, etc) passes an "X-Forwarded-For" header(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
|
||||
create or replace function filter_plan_requests()
|
||||
@@ -325,19 +284,34 @@ For example, to only allow requests from an IP address to get the execution plan
|
||||
-- set this function on your postgrest.conf
|
||||
-- db-pre-request = filter_plan_requests
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript">
|
||||
let hash = window.location.hash;
|
||||
.. _health_check:
|
||||
|
||||
const redirects = {
|
||||
'#health_check': 'health_check.html',
|
||||
'#server-version': '#server-version-header',
|
||||
};
|
||||
Health Check
|
||||
------------
|
||||
|
||||
let willRedirectTo = redirects[hash];
|
||||
You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
|
||||
|
||||
if (willRedirectTo) {
|
||||
window.location.href = willRedirectTo;
|
||||
}
|
||||
</script>
|
||||
To do this, set the configuration variable :ref:`admin-server-port` to the port number of your preference. Two endpoints ``live`` and ``ready`` will then be available.
|
||||
|
||||
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``503`` otherwise.
|
||||
|
||||
The ``ready`` endpoint also checks the state of both the Database Connection and the :ref:`schema_cache`. A request will return ``200 OK`` if it is ready or ``503`` if not.
|
||||
|
||||
For instance, to verify if PostgREST is running at ``localhost:3000`` while the ``admin-server-port`` is set to ``3001``:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET localhost:3001/live HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -I "http://localhost:3001/live"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
|
||||
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.
|
||||
@@ -1,93 +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.
|
||||
|
||||
.. 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 Configuration
|
||||
=====================
|
||||
|
||||
Provides a ``config`` endpoint that returns the runtime :ref:`configuration`.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3001/config"
|
||||
|
||||
.. code-block::
|
||||
|
||||
db-aggregates-enabled = false
|
||||
db-anon-role = "web_anon"
|
||||
db-channel = "pgrst"
|
||||
db-channel-enabled = false
|
||||
...
|
||||
|
||||
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": ["..."]
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
API
|
||||
###
|
||||
|
||||
PostgREST exposes three database objects of a schema as resources: tables, views and functions.
|
||||
PostgREST exposes three database objects of a schema as resources: tables, views and stored procedures.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
|
||||
api/tables_views.rst
|
||||
api/functions.rst
|
||||
api/stored_procedures.rst
|
||||
api/schemas.rst
|
||||
api/computed_fields.rst
|
||||
api/domain_representations.rst
|
||||
@@ -30,22 +30,22 @@ PostgREST exposes three database objects of a schema as resources: tables, views
|
||||
|
||||
const redirects = {
|
||||
// Tables and Views
|
||||
'#horizontal-filtering-rows': 'api/tables_views.html#horizontal-filtering',
|
||||
'#horizontal-filtering-rows': 'api/tables_views.html#horizontal-filtering-rows',
|
||||
'#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',
|
||||
'#vertical-filtering-columns': 'api/tables_views.html#vertical-filtering-columns',
|
||||
'#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',
|
||||
'#computed-virtual-columns': 'api/computed_fields.html#computed-fields',
|
||||
'#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',
|
||||
'#limits-and-pagination': 'api/tables_views.html#limits-and-pagination',
|
||||
'#exact-count': 'api/tables_views.html#exact-count',
|
||||
'#planned-count': 'api/tables_views.html#planned-count',
|
||||
'#estimated-count': 'api/tables_views.html#estimated-count',
|
||||
'#updates': 'api/tables_views.html#update',
|
||||
'#insertions': 'api/tables_views.html#insert',
|
||||
'#bulk-insert': 'api/tables_views.html#bulk-insert',
|
||||
@@ -55,15 +55,15 @@ PostgREST exposes three database objects of a schema as resources: tables, views
|
||||
'#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',
|
||||
// Stored procedures
|
||||
'#stored-procedures': 'api/stored_procedures.html#stored-procedures',
|
||||
'#calling-functions-with-a-single-json-parameter': 'api/stored_procedures.html#functions-with-a-single-json-parameter',
|
||||
'#calling-functions-with-a-single-unnamed-parameter': 'api/stored_procedures.html#functions-with-a-single-unnamed-parameter',
|
||||
'#calling-functions-with-array-parameters': 'api/stored_procedures.html#functions-with-array-parameters',
|
||||
'#calling-variadic-functions': 'api/stored_procedures.html#variadic-functions',
|
||||
'#scalar-functions': 'api/stored_procedures.html#scalar-functions',
|
||||
'#function-filters': 'api/stored_procedures.html#table-valued-functions',
|
||||
'#overloaded-functions': 'api/stored_procedures.html#overloaded-functions',
|
||||
// Schemas
|
||||
'#switching-schemas': 'api/schemas.html',
|
||||
// Resource Embedding
|
||||
@@ -76,21 +76,21 @@ PostgREST exposes three database objects of a schema as resources: tables, views
|
||||
'#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-partitioned-tables': 'api/resource_embedding.html#embedding-partitioned-tables',
|
||||
'#embedding-views': 'api/resource_embedding.html#embedding-views',
|
||||
'#embedding-chains-of-views': 'api/resource_embedding.html#embedding-chains-of-views',
|
||||
'#embedding-on-stored-procedures': 'api/resource_embedding.html#embedding-on-stored-procedures',
|
||||
'#embedding-after-insertions-updates-deletions': 'api/resource_embedding.html#embedding-after-insertions-updates-deletions',
|
||||
'#embedding-disambiguation': 'api/resource_embedding.html#embedding-disambiguation',
|
||||
'#target-disambiguation': 'api/resource_embedding.html#target-disambiguation',
|
||||
'#hint-disambiguation': 'api/resource_embedding.html#hint-disambiguation',
|
||||
"#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',
|
||||
'#response-formats-for-scalar-responses': 'api/resource_representation.html#scalar-function-response-format',
|
||||
// CORS
|
||||
'#cors': 'api/cors.html',
|
||||
// OPTIONS
|
||||
@@ -109,9 +109,9 @@ PostgREST exposes three database objects of a schema as resources: tables, views
|
||||
'#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',
|
||||
'#raise-errors-with-http-status-codes': 'transactions.html#raise-errors-with-http-status-codes',
|
||||
// Admin
|
||||
'#execution-plan': 'observability.html#execution-plan',
|
||||
'#execution-plan': 'admin.html#execution-plan',
|
||||
// Deprecated
|
||||
'#bulk-call': '../releases/v11.0.1.html#breaking-changes',
|
||||
};
|
||||
|
||||
@@ -12,9 +12,15 @@ PostgREST supports the following aggregate functions: ``avg()``, ``count()``, ``
|
||||
|
||||
To use an aggregate function, you append the function to a value in the ``select`` parameter, like so:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum()"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=amount.sum() HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum()"
|
||||
|
||||
With the above query, PostgREST will return a single row with a single column named ``sum`` that contains the sum of all the values in the ``amount`` column:
|
||||
|
||||
@@ -30,9 +36,15 @@ You can use multiple aggregate functions by just adding more columns with aggreg
|
||||
|
||||
To group by other columns, you simply add those columns to the ``select`` parameter. For instance:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=amount.sum(),amount.avg(),order_date HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
|
||||
|
||||
This will return a row for each unique value in the ``order_date`` column, with the sum and average of the ``amount`` column for all rows that share the same ``order_date``:
|
||||
|
||||
@@ -63,9 +75,15 @@ The Case of ``count()``
|
||||
|
||||
``count()`` is treated specially, as it can be used without an associated column. Take for example the following query:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=count(),order_date"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=count(),order_date HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=count(),order_date"
|
||||
|
||||
This would return a row for each unique value in the ``order_date`` column, with the count of all rows that share the same ``order_date``:
|
||||
|
||||
@@ -82,7 +100,7 @@ This would return a row for each unique value in the ``order_date`` column, with
|
||||
}
|
||||
]
|
||||
|
||||
When ``count()`` is used with an associated column, its behavior is slightly different: It will return the count of all values that are not ``NULL``. This is due to how PostgreSQL itself implements the ``count()`` function.
|
||||
When ``count()`` is used with an associated column, its behaviour is slightly different: It will return the count of all values that are not ``NULL``. This is due to how PostgreSQL itself implements the ``count()`` function.
|
||||
|
||||
Renaming and Casting
|
||||
====================
|
||||
@@ -106,9 +124,15 @@ For instance, imagine that the ``orders`` table has a JSON column, ``order_detai
|
||||
|
||||
Therefore, you will need to first cast the input value to a type that is compatible with ``sum()`` (e.g. ``numeric``). Casting the input value is done in exactly the same way as casting any other value:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=order_details->tax_amount::numeric.sum() HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
|
||||
|
||||
With this, you will receive the sum of the casted ``tax_amount`` value:
|
||||
|
||||
@@ -125,9 +149,15 @@ Casting the Value of the Output
|
||||
|
||||
Now let's return to an example involving the ``amount`` column of the ``orders`` table. Imagine that we want to get the rounded average of the ``amount`` column. One way to do this is to use the ``avg()`` aggregate function and then to cast the output value of the function to ``int``. To cast the value of the output of the function, we simply place the cast *after* the aggregate function:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.avg()::int"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=amount.avg()::int HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.avg()::int"
|
||||
|
||||
You will then receive the rounded average as the result:
|
||||
|
||||
@@ -154,9 +184,15 @@ Using an embedded resource as a grouping column allows you to use data from an a
|
||||
|
||||
For example, imagine that the ``orders`` table from the examples above is related to a ``customers`` table. If you want to get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, you can include the customer name, using the standard :ref:`resource_embedding` syntax, and perform a sum on the ``amount`` column.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=amount.sum(),customers(name) HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
|
||||
|
||||
You will then get the summed amount, along with the embedded customer resource:
|
||||
|
||||
@@ -187,9 +223,15 @@ When embedding a resource, you can apply aggregate functions to columns from the
|
||||
|
||||
Continuing with the example relationship between ``orders`` and ``customers`` from the previous section, imagine that you want to fetch the ``name``, ``city``, and ``state`` for each customer, along with the sum of amount of the customer's orders, grouped by the order date. This can be done in the following way:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/customers?select=name,city,state,orders(amount.sum(),order_date)"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /customers?select=name,city,state,orders(amount.sum(),order_date) HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/customers?select=name,city,state,orders(amount.sum(),order_date)"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -238,9 +280,15 @@ Grouping with Columns from a Spreaded Resource
|
||||
|
||||
For instance, assume you want to sum the ``amount`` column from the ``orders`` table, using the ``city`` and ``state`` columns from the ``customers`` table as grouping columns. To achieve this, you may select these two columns from the ``customers`` table and spread them; they will then be used as grouping columns:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),...customers(city,state)
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=amount.sum(),...customers(city,state) HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=amount.sum(),...customers(city,state)
|
||||
|
||||
The result will be the same as if ``city`` and ``state`` were columns from the ``orders`` table:
|
||||
|
||||
@@ -264,9 +312,15 @@ Aggregate Functions with Columns from a Spreaded Resource
|
||||
|
||||
Now imagine that the ``customers`` table has a ``joined_date`` column that represents the date that the customer joined. You want to get both the most recent and the oldest ``joined_date`` for customers that placed an order on every distinct order date. This can be expressed as follows:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/orders?select=order_date,...customers(joined_date.max(),joined_date.min())
|
||||
.. code-tab:: http
|
||||
|
||||
GET /orders?select=order_date,...customers(joined_date.max(),joined_date.min()) HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/orders?select=order_date,...customers(joined_date.max(),joined_date.min())
|
||||
|
||||
As columns from a spreaded resource are treated as if they were columns from the top-level resource, the ``max()`` and ``min()`` are applied *within* the context of the top-level, rather than within the context of the embedded resource, as in the previous section.
|
||||
|
||||
|
||||
@@ -30,9 +30,15 @@ Horizontal Filtering on Computed Fields
|
||||
CREATE INDEX people_full_name_idx ON people
|
||||
USING GIN (to_tsvector('english', full_name(people)));
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people?full_name=fts.Beckett"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /people?full_name=fts.Beckett HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/people?full_name=fts.Beckett"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -45,9 +51,15 @@ 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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people?select=full_name,job"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /people?select=full_name,job HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/people?select=full_name,job"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -60,13 +72,19 @@ Ordering on Computed Fields
|
||||
|
||||
:ref:`ordering` on computed fields is also possible:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people?order=full_name.desc"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /people?order=full_name.desc HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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``.
|
||||
Computed columns 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 column 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 <s_procs>` under ``/rpc``.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -10,13 +10,22 @@ It also handles `preflight requests <https://developer.mozilla.org/en-US/docs/Gl
|
||||
|
||||
A ``POST`` preflight request would look like this:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
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-tab:: http
|
||||
|
||||
OPTIONS /items HTTP/1.1
|
||||
Origin: http://example.com
|
||||
Access-Control-Allow-Method: POST
|
||||
Access-Control-Allow-Headers: Content-Type
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
|
||||
@@ -38,3 +47,4 @@ With the following config setting, PostgREST will accept CORS requests from doma
|
||||
.. code-block::
|
||||
|
||||
server-cors-allowed-origins="http://example.com, http://example2.com"
|
||||
|
||||
|
||||
@@ -58,10 +58,17 @@ Then create a CAST to tell PostgREST to convert it automatically whenever a JSON
|
||||
|
||||
With this you can obtain the data in the shortened format.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/profiles" \
|
||||
-H "Accept: application/json"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /profiles HTTP/1.1
|
||||
Accept: application/json
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/profiles" \
|
||||
-H "Accept: application/json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -95,10 +102,17 @@ PostgREST considers the URL query string to be, in the most generic sense, ``tex
|
||||
|
||||
Now you can filter as usual.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
|
||||
-H "Accept: application/json"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /profiles?id=eq.hGxP/ZLOTeeNEY4pkp9OxA== HTTP/1.1
|
||||
Accept: application/json
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
|
||||
-H "Accept: application/json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -125,16 +139,26 @@ To accept the shortened format in a JSON request body, for example when creating
|
||||
|
||||
Now we can :ref:`insert` (or :ref:`update`) as usual.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/profiles" \
|
||||
-H "Prefer: return=representation" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- <<JSON
|
||||
.. code-tab:: http
|
||||
|
||||
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
|
||||
POST /profiles HTTP/1.1
|
||||
Content-Type: application/json
|
||||
Prefer: return=representation
|
||||
|
||||
JSON
|
||||
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Media types are expressed as type aliases using `domains <https://www.postgresql
|
||||
|
||||
CREATE DOMAIN "application/json" AS json;
|
||||
|
||||
Using these domains, :ref:`functions <functions>` can become handlers and `user-defined aggregates <https://www.postgresql.org/docs/current/xaggr.html>`_ can serve as handlers for :ref:`tables_views` and :ref:`table_functions`.
|
||||
Using these domains, :ref:`functions <s_procs>` can become handlers and `user-defined aggregates <https://www.postgresql.org/docs/current/xaggr.html>`_ can serve as handlers for :ref:`tables_views` and :ref:`table_functions`.
|
||||
|
||||
.. important::
|
||||
|
||||
@@ -95,18 +95,16 @@ Now we'll use it on a new aggregate defined for the ``lines`` table.
|
||||
, sfunc = twkb_handler_transition
|
||||
);
|
||||
|
||||
.. note::
|
||||
Make a quick test on SQL to see it working.
|
||||
|
||||
You can test see this aggregate working with:
|
||||
.. code-block:: psql
|
||||
|
||||
.. code-block:: psql
|
||||
SELECT twkb_agg(l) from lines l;
|
||||
|
||||
SELECT twkb_agg(l) from lines l;
|
||||
|
||||
twkb_agg
|
||||
---------------------------------------------------------------
|
||||
\xa20002c09a0cc09a0c80ea3080ea30a2000280b51880b51880ea3080ea30
|
||||
(1 row)
|
||||
twkb_agg
|
||||
---------------------------------------------------------------
|
||||
\xa20002c09a0cc09a0c80ea3080ea30a2000280b51880b51880ea3080ea30
|
||||
(1 row)
|
||||
|
||||
Now you can request the table endpoint with the ``twkb`` media type:
|
||||
|
||||
@@ -186,20 +184,16 @@ Now use the transition and final function as part of the new aggregate.
|
||||
, finalfunc = bom_csv_final
|
||||
);
|
||||
|
||||
.. note::
|
||||
.. code-block:: psql
|
||||
|
||||
You can test this with:
|
||||
select bom_csv_agg(l) from lines l;
|
||||
bom_csv_agg
|
||||
-----------------------------------------------------------------------------------------------------
|
||||
id,name,geom +
|
||||
1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440+
|
||||
2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840+
|
||||
|
||||
.. code-block:: psql
|
||||
|
||||
select bom_csv_agg(l) from lines l;
|
||||
bom_csv_agg
|
||||
-----------------------------------------------------------------------------------------------------
|
||||
id,name,geom +
|
||||
1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440+
|
||||
2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840+
|
||||
|
||||
(1 row)
|
||||
(1 row)
|
||||
|
||||
And request it like:
|
||||
|
||||
@@ -221,11 +215,11 @@ And request it like:
|
||||
The "Any" Handler
|
||||
=================
|
||||
|
||||
For more flexibility, you can also define a catch-all handler by using a domain named ``*/*`` (any media type). This handler obeys the following rules:
|
||||
For more flexibility, you can also define a catch-all handler by using a domain named ``*/*`` (any media type). This obeys to the following rules:
|
||||
|
||||
- It responds to all media types and even to requests that don't include an ``Accept`` header.
|
||||
- It sets the ``Content-Type`` header to ``application/octet-stream`` by default, but this can be overridden inside the function with :ref:`guc_resp_hdrs`.
|
||||
- It overrides all other handlers (:ref:`builtin <builtin_media>` or custom), so it's better to do it for an isolated function or view.
|
||||
- Responds to all media types and even to requests that don't include an ``Accept`` header.
|
||||
- Sets the ``Content-Type`` header to ``application/octet-stream`` by default, but this can be overridden inside the function with :ref:`guc_resp_hdrs`.
|
||||
- This overrides all other handlers (:ref:`builtin <builtin_media>` or custom), so it's better to do it for an isolated function or view.
|
||||
|
||||
Let's define an any handler for a view that will always respond with ``XML`` output. It will accept ``text/xml``, ``application/xml``, ``*/*`` and reject other media types.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ PostgREST automatically serves a full `OpenAPI <https://www.openapis.org/>`_ des
|
||||
|
||||
For extra customization, the OpenAPI output contains a "description" field for every `SQL comment <https://www.postgresql.org/docs/current/sql-comment.html>`_ on any database object. For instance,
|
||||
|
||||
.. code-block:: postgres
|
||||
.. code-block:: sql
|
||||
|
||||
COMMENT ON SCHEMA mammals IS
|
||||
'A warm-blooded vertebrate animal of a class that is distinguished by the secretion of milk by females for the nourishment of the young';
|
||||
@@ -26,7 +26,7 @@ These unsavory comments will appear in the generated JSON as the fields, ``info.
|
||||
|
||||
Also if you wish to generate a ``summary`` field you can do it by having a multiple line comment, the ``summary`` will be the first line and the ``description`` the lines that follow it:
|
||||
|
||||
.. code-block:: postgres
|
||||
.. code-block:: plpgsql
|
||||
|
||||
COMMENT ON TABLE entities IS
|
||||
$$Entities summary
|
||||
@@ -37,7 +37,7 @@ Also if you wish to generate a ``summary`` field you can do it by having a multi
|
||||
|
||||
Similarly, you can override the API title by commenting the schema.
|
||||
|
||||
.. code-block:: postgres
|
||||
.. code-block:: plpgsql
|
||||
|
||||
COMMENT ON SCHEMA api IS
|
||||
$$FooBar API
|
||||
@@ -81,9 +81,15 @@ You can override the whole default response with a function result. To do this,
|
||||
end
|
||||
$_$ language plpgsql;
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl http://localhost:3000
|
||||
.. code-tab:: http
|
||||
|
||||
GET / HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl http://localhost:3000
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
|
||||
@@ -7,9 +7,15 @@ You can verify which HTTP methods are allowed on endpoints for tables and views
|
||||
|
||||
For a table named ``people``, OPTIONS would show:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people" -X OPTIONS -i
|
||||
.. code-tab:: http
|
||||
|
||||
OPTIONS /people HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/people" -X OPTIONS -i
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
|
||||
@@ -23,9 +23,15 @@ Query Parameters
|
||||
|
||||
One way to request limits and offsets is by using query parameters. For example:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people?limit=15&offset=30"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /people?limit=15&offset=30 HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/people?limit=15&offset=30"
|
||||
|
||||
This method is also useful for embedded resources, which we will cover in another section. The server always responds with range headers even if you use query parameters to limit the query.
|
||||
|
||||
@@ -35,11 +41,19 @@ Range Header
|
||||
You can use headers to specify the range of rows desired.
|
||||
This request gets the first twenty people:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/people" -i \
|
||||
-H "Range-Unit: items" \
|
||||
-H "Range: 0-19"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /people HTTP/1.1
|
||||
Range-Unit: items
|
||||
Range: 0-19
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/people" -i \
|
||||
-H "Range-Unit: items" \
|
||||
-H "Range: 0-19"
|
||||
|
||||
Note that the server may respond with fewer if unable to meet your request:
|
||||
|
||||
@@ -68,12 +82,21 @@ Exact Count
|
||||
|
||||
To get the exact count, use ``Prefer: count=exact``.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/bigtable" -I \
|
||||
-H "Range-Unit: items" \
|
||||
-H "Range: 0-24" \
|
||||
-H "Prefer: count=exact"
|
||||
.. code-tab:: http
|
||||
|
||||
HEAD /bigtable HTTP/1.1
|
||||
Range-Unit: items
|
||||
Range: 0-24
|
||||
Prefer: count=exact
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/bigtable" -I \
|
||||
-H "Range-Unit: items" \
|
||||
-H "Range: 0-24" \
|
||||
-H "Prefer: count=exact"
|
||||
|
||||
Note that the larger the table the slower this query runs in the database. The server will respond with the selected range and total
|
||||
|
||||
@@ -91,10 +114,17 @@ Planned Count
|
||||
To avoid the shortcomings of :ref:`exact count <exact_count>`, PostgREST can leverage PostgreSQL statistics and get a fairly accurate and fast count.
|
||||
To do this, specify the ``Prefer: count=planned`` header.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/bigtable?limit=25" -I \
|
||||
-H "Prefer: count=planned"
|
||||
.. code-tab:: http
|
||||
|
||||
HEAD /bigtable?limit=25 HTTP/1.1
|
||||
Prefer: count=planned
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/bigtable?limit=25" -I \
|
||||
-H "Prefer: count=planned"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -121,10 +151,17 @@ defined by :ref:`db-max-rows`.
|
||||
|
||||
Here's an example. Suppose we set ``db-max-rows=1000`` and ``smalltable`` has 321 rows, then we'll get the exact count:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/smalltable?limit=25" -I \
|
||||
-H "Prefer: count=estimated"
|
||||
.. code-tab:: http
|
||||
|
||||
HEAD /smalltable?limit=25 HTTP/1.1
|
||||
Prefer: count=estimated
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/smalltable?limit=25" -I \
|
||||
-H "Prefer: count=estimated"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -133,10 +170,17 @@ Here's an example. Suppose we set ``db-max-rows=1000`` and ``smalltable`` has 32
|
||||
|
||||
If we make a similar request on ``bigtable``, which has 3573458 rows, we would get the planned count:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/bigtable?limit=25" -I \
|
||||
-H "Prefer: count=estimated"
|
||||
.. code-tab:: http
|
||||
|
||||
HEAD /bigtable?limit=25 HTTP/1.1
|
||||
Prefer: count=estimated
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/bigtable?limit=25" -I \
|
||||
-H "Prefer: count=estimated"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ The following preferences are supported.
|
||||
- ``Prefer: count``. See :ref:`prefer_count`.
|
||||
- ``Prefer: resolution``. See :ref:`prefer_resolution`.
|
||||
- ``Prefer: missing``. See :ref:`bulk_insert_default`.
|
||||
- ``Prefer: max-affected``, See :ref:`prefer_max_affected`.
|
||||
- ``Prefer: tx``. See :ref:`prefer_tx`.
|
||||
- ``Prefer: params``. See :ref:`prefer_params`.
|
||||
|
||||
.. _prefer_handling:
|
||||
|
||||
@@ -26,10 +23,17 @@ The server ignores unrecognized or unfulfillable preferences by default. You can
|
||||
|
||||
``handling=strict`` will throw an error if you specify invalid preferences. For instance:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/projects" \
|
||||
-H "Prefer: handling=strict, foo, bar"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /projects HTTP/1.1
|
||||
Prefer: handling=strict, foo, bar
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/projects" \
|
||||
-H "Prefer: handling=strict, foo, bar"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -48,10 +52,17 @@ The server ignores unrecognized or unfulfillable preferences by default. You can
|
||||
|
||||
``handling=lenient`` ignores invalid preferences.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/projects" \
|
||||
-H "Prefer: handling=lenient, foo, bar"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /projects HTTP/1.1
|
||||
Prefer: handling=lenient, foo, bar
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/projects" \
|
||||
-H "Prefer: handling=lenient, foo, bar"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -63,13 +74,20 @@ The server ignores unrecognized or unfulfillable preferences by default. You can
|
||||
Timezone
|
||||
========
|
||||
|
||||
The ``timezone`` preference allows you to change the `PostgreSQL timezone <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-TIMEZONE>`_. It accepts all time zones in `pg_timezone_names <https://www.postgresql.org/docs/current/view-pg-timezone-names.html>`_.
|
||||
The ``timezone`` preference allows you to change the `PostgreSQL timezone <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-TIMEZONE>`_. It accepts all timezones in `pg_timezone_names <https://www.postgresql.org/docs/current/view-pg-timezone-names.html>`_.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/timestamps" \
|
||||
-H "Prefer: timezone=America/Los_Angeles"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /timestamps HTTP/1.1
|
||||
Prefer: timezone=America/Los_Angeles
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/timestamps" \
|
||||
-H "Prefer: timezone=America/Los_Angeles"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -85,12 +103,19 @@ The ``timezone`` preference allows you to change the `PostgreSQL timezone <https
|
||||
{"t":"2023-10-18T09:37:59.611-07:00"}
|
||||
]
|
||||
|
||||
For an invalid time zone, PostgREST returns values with the default time zone (configured on ``postgresql.conf`` or as a setting on the :ref:`authenticator <roles>`).
|
||||
For an invalid timezone, PostgREST returns values with the default timezone (configured on ``postgresql.conf`` or as a setting on the :ref:`authenticator <roles>`).
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/timestamps" \
|
||||
-H "Prefer: timezone=Jupiter/Red_Spot"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /timestamps HTTP/1.1
|
||||
Prefer: timezone=Jupiter/Red_Spot
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/timestamps" \
|
||||
-H "Prefer: timezone=Jupiter/Red_Spot"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -107,12 +132,19 @@ For an invalid time zone, PostgREST returns values with the default time zone (c
|
||||
|
||||
Note that there's no ``Preference-Applied`` in the response.
|
||||
|
||||
However, with ``handling=strict``, an invalid time zone preference will throw an :ref:`error <pgrst122>`.
|
||||
However, with ``handling=strict``, an invalid timezone preference will throw an :ref:`error <pgrst122>`.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/timestamps" \
|
||||
-H "Prefer: handling=strict, timezone=Jupiter/Red_Spot"
|
||||
.. code-tab:: http
|
||||
|
||||
GET /timestamps HTTP/1.1
|
||||
Prefer: handling=strict, timezone=Jupiter/Red_Spot
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/timestamps" \
|
||||
-H "Prefer: handling=strict, timezone=Jupiter/Red_Spot"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -136,12 +168,21 @@ Headers Only
|
||||
|
||||
If the table has a primary key, the response can contain a :code:`Location` header describing where to find the new object by including the header :code:`Prefer: return=headers-only` in the request. Make sure that the table is not write-only, otherwise constructing the :code:`Location` header will cause a permissions error.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/projects" -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Prefer: return=headers-only" \
|
||||
-d '{"id":33, "name": "x"}'
|
||||
.. code-tab:: http
|
||||
|
||||
POST /projects HTTP/1.1
|
||||
Prefer: return=headers-only
|
||||
|
||||
{"id":33, "name": "x"}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/projects" -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Prefer: return=headers-only" \
|
||||
-d '{"id":33, "name": "x"}'
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
@@ -154,101 +195,31 @@ Full
|
||||
|
||||
On the other end of the spectrum you can get the full created object back in the response to your request by including the header :code:`Prefer: return=representation`. That way you won't have to make another HTTP call to discover properties that may have been filled in on the server side. You can also apply the standard :ref:`v_filter` to these results.
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl -i "http://localhost:3000/projects" -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Prefer: return=representation" \
|
||||
-d '{"id":33, "name": "x"}'
|
||||
.. code-tab:: http
|
||||
|
||||
.. code-block:: http
|
||||
POST /projects HTTP/1.1
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Prefer: return=representation
|
||||
|
||||
{"id":33, "name": "x"}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl -i "http://localhost:3000/projects" -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Prefer: return=representation" \
|
||||
-d '{"id":33, "name": "x"}'
|
||||
|
||||
.. code::
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Preference-Applied: return=representation
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 33,
|
||||
"name": "x"
|
||||
}
|
||||
]
|
||||
|
||||
.. _prefer_tx:
|
||||
|
||||
Transaction End Preference
|
||||
==========================
|
||||
|
||||
The ``tx`` preference can be set to specify if the :ref:`transaction <transactions>` will end in a COMMIT or ROLLBACK. This preference is not enabled by default but can be activated with :ref:`db-tx-end`.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://localhost:3000/projects" -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Prefer: tx=rollback, return=representation" \
|
||||
-d '{"name": "Project X"}'
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Preference-Applied: tx=rollback, return=representation
|
||||
|
||||
{"id": 35, "name": "Project X"}
|
||||
|
||||
|
||||
.. _prefer_max_affected:
|
||||
|
||||
Max Affected
|
||||
============
|
||||
|
||||
You can set a limit to the amount of resources affected in a request by sending ``max-affected`` preference. This feature works in combination with ``handling=strict`` preference. ``max-affected`` would be ignored with lenient handling. The "affected resources" are the number of rows returned by ``DELETE`` and ``PATCH`` requests. This is also supported through ``RPC`` calls.
|
||||
|
||||
To illustrate the use of this preference, consider the following scenario where the ``items`` table contains 14 rows.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -i "http://localhost:3000/items?id=lt.15 -X DELETE \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Prefer: handling=strict, max-affected=10"
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 400 Bad Request
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"code": "PGRST124",
|
||||
"message": "Query result exceeds max-affected preference constraint",
|
||||
"details": "The query affects 14 rows",
|
||||
"hint": null
|
||||
}
|
||||
|
||||
.. _prefer_params:
|
||||
|
||||
Single JSON object as Function Parameter
|
||||
----------------------------------------
|
||||
|
||||
.. warning::
|
||||
|
||||
Using this preference is **deprecated** in favor of :ref:`function_single_json`.
|
||||
|
||||
:code:`Prefer: params=single-object` allows sending the JSON request body as the single argument of a :ref:`function <functions>`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION mult_them(param json) RETURNS int AS $$
|
||||
SELECT (param->>'x')::int * (param->>'y')::int
|
||||
$$ LANGUAGE SQL;
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/mult_them" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-H "Prefer: params=single-object" \
|
||||
-d '{ "x": 4, "y": 2 }'
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
8
|
||||
|
||||