Compare commits
@@ -1,9 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -0,0 +1,55 @@
|
||||
# Contributing to PostgREST
|
||||
|
||||
**First:** if you're unsure or afraid of _anything_, just ask or
|
||||
submit the issue or pull request anyways. You won't be yelled at
|
||||
for giving your best effort. The worst that can happen is that
|
||||
you'll be politely asked to change something. We appreciate any
|
||||
sort of contributions, and don't want a wall of rules to get in the
|
||||
way of that.
|
||||
|
||||
However, for those individuals who want a bit more guidance on the
|
||||
best way to contribute to the project, read on. This document will
|
||||
cover what we're looking for. By addressing all the points we're
|
||||
looking for, it raises the chances we can quickly merge or address
|
||||
your contributions.
|
||||
|
||||
## Issues
|
||||
|
||||
For questions on how to use PostgREST, please use
|
||||
[GitHub discussions](https://github.com/PostgREST/postgrest/discussions).
|
||||
|
||||
### Reporting an Issue
|
||||
|
||||
* Make sure you test against the latest [stable release](https://github.com/PostgREST/postgrest/releases/latest)
|
||||
and also against the latest [nightly release](https://github.com/PostgREST/postgrest/releases/tag/nightly).
|
||||
It is possible we already fixed the bug you're experiencing.
|
||||
|
||||
* Provide steps to reproduce the issue, including your OS version and
|
||||
the specific database schema that you are using.
|
||||
|
||||
* Please include SQL logs for issues involving runtime problems. To obtain logs first
|
||||
[enable logging all statements](http://www.microhowto.info/howto/log_all_queries_to_a_postgresql_server.html),
|
||||
then [find your logs](http://blog.endpoint.com/2014/11/dear-postgresql-where-are-my-logs.html).
|
||||
|
||||
* If your database schema has changed while the PostgREST server is running,
|
||||
[send the server a `SIGUSR1` signal](http://postgrest.org/en/latest/admin.html#schema-reloading) or restart it to ensure the schema cache
|
||||
is not stale. This sometimes fixes apparent bugs.
|
||||
|
||||
## Code
|
||||
|
||||
We have a fully nix-based development environment with many tools for a smooth development workflow available.
|
||||
Check the [development docs](https://github.com/PostgREST/postgrest/blob/main/nix/README.md) on how to set it up and use it.
|
||||
|
||||
### Haskell Conventions
|
||||
|
||||
* All contributions must pass the tests before being merged. When
|
||||
you create a pull request your code will automatically be tested.
|
||||
|
||||
* All code must also pass [hlint](http://community.haskell.org/~ndm/hlint/) and [stylish-haskell](https://github.com/jaspervdj/stylish-haskell)
|
||||
with no warnings. This helps enforce a uniform style for all committers. Continuous integration will check this as well on every
|
||||
pull request. There are useful tools in the nix-shell that help with checking this locally. You can run `postgrest-check` to do this manually but
|
||||
we recommend adding it to `.git/hooks/pre-commit` as `nix-shell --run postgrest-check` to automatically check this before doing a commit.
|
||||
|
||||
### Running Tests
|
||||
|
||||
For instructions on running tests, see the [development docs](https://github.com/PostgREST/postgrest/blob/main/nix/README.md#testing).
|
||||
@@ -0,0 +1,17 @@
|
||||
<!--
|
||||
Before reporting a bug:
|
||||
If your database schema has changed while the PostgREST server is running,
|
||||
send the server a SIGUSR1 signal or restart it(http://postgrest.org/en/stable/admin.html#schema-reloading)
|
||||
to ensure the schema cache is not stale. This sometimes fixes apparent bugs.
|
||||
-->
|
||||
### Environment
|
||||
|
||||
* PostgreSQL version: (if using docker, specify the image)
|
||||
* PostgREST version: (if using docker, specify the image)
|
||||
* Operating system:
|
||||
|
||||
### Description of issue
|
||||
|
||||
(Expected behavior vs actual behavior)
|
||||
|
||||
(Steps to reproduce: Include a minimal SQL definition plus how you make the request to PostgREST and the response body)
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report to help us improve
|
||||
type: Bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Before reporting a bug:
|
||||
If your database schema has changed while the PostgREST server is running,
|
||||
send the server a SIGUSR1 signal or restart it (http://postgrest.org/en/stable/admin.html#schema-reloading) to ensure the schema cache is not stale. This sometimes fixes apparent bugs.
|
||||
-->
|
||||
### Environment
|
||||
|
||||
* PostgreSQL version: (if using docker, specify the image)
|
||||
* PostgREST version: (if using docker, specify the image)
|
||||
* Operating system:
|
||||
|
||||
### Description of issue
|
||||
|
||||
Describe the behavior you expected vs the actual behavior. Include:
|
||||
|
||||
- A minimal SQL definition.
|
||||
- How you make the request to PostgREST (curl command preferred).
|
||||
- The PostgREST response.
|
||||
@@ -1 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an enhancement for this project
|
||||
type: Feature
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
## Solution
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
@@ -3,19 +3,4 @@ When submitting a new feature or fix:
|
||||
|
||||
- Add a new entry to the CHANGELOG - https://github.com/PostgREST/postgrest/blob/main/CHANGELOG.md#unreleased
|
||||
- If relevant, update the docs
|
||||
- Use a prefix for the PR title or commits, e.g. "fix: description of the fix".
|
||||
+ `add`, Add a new feature
|
||||
+ `amend`, To amend an unrealease commit
|
||||
+ `change`, Breaking changes
|
||||
+ `chore`, Maintenance, update sponsors, changelog, readme etc
|
||||
+ `ci`, CI configuration files and scripts
|
||||
+ `docs`, Documentation
|
||||
+ `fix`, Bug fix
|
||||
+ `nix`, Related to Nix
|
||||
+ `perf`, Performance improvements
|
||||
+ `refactor`, Refactoring code
|
||||
+ `remove`, Remove a feature or fix
|
||||
+ `test`, Adding tests
|
||||
+ Other prefixes may be used if necessary
|
||||
- If there's a breaking change, add `BREAKING CHANGE` and an explanation to your commit message
|
||||
-->
|
||||
|
||||
@@ -1,34 +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.
|
||||
prefix:
|
||||
description: Cache key prefix to be used in both primary key and restore-keys.
|
||||
required: true
|
||||
suffix:
|
||||
description: Cache key suffix to be used only in primary key.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-
|
||||
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
|
||||
with:
|
||||
path: ${{ inputs.path }}
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-${{ inputs.suffix }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-${{ inputs.prefix }}-
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Run anywhere
|
||||
|
||||
description: Runs the same code either in a VM or on the bare machine
|
||||
|
||||
inputs:
|
||||
vm:
|
||||
description: Which VM to run on.
|
||||
envs:
|
||||
description: List of relevant environment variables, which might need to be copied into the VM.
|
||||
prepare:
|
||||
description: Code to run in a prepare step, e.g. installing dependencies.
|
||||
run:
|
||||
description: Code to run as the main action.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- if: ${{ inputs.vm == 'freebsd' }}
|
||||
uses: vmactions/freebsd-vm@83b151f58c6047089f4c80eb5ba2039d158ce093 # v1.5.3
|
||||
with:
|
||||
envs: ${{ inputs.envs }}
|
||||
prepare: ${{ inputs.prepare }}
|
||||
# Work around https://github.com/vmactions/freebsd-vm/issues/59
|
||||
run: |
|
||||
pw user add -n action -m
|
||||
su action -c '${{ inputs.run }}'
|
||||
- if: ${{ inputs.vm == '' }}
|
||||
name: Prepare
|
||||
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
|
||||
run: ${{ inputs.prepare }}
|
||||
- if: ${{ inputs.vm == '' }}
|
||||
name: Run
|
||||
shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }}
|
||||
run: ${{ inputs.run }}
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Setup Nix
|
||||
|
||||
description: Installs nix, sets up cachix and installs a subset of tooling.
|
||||
|
||||
inputs:
|
||||
authToken:
|
||||
description: Token to pass to cachix
|
||||
tools:
|
||||
description: Tools to install with nix-env -iA <tools>
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: nixbuild/nix-quick-install-action@9f63be77f412a248c9d9a65a4c82cf066cdf8f0c # v35
|
||||
with:
|
||||
nix_conf: |-
|
||||
always-allow-substitutes = true
|
||||
max-jobs = auto
|
||||
- uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
|
||||
with:
|
||||
name: postgrest
|
||||
authToken: ${{ inputs.authToken }}
|
||||
skipPush: ${{ inputs.authToken == '' }}
|
||||
- if: ${{ inputs.tools }}
|
||||
run: nix-env -f default.nix -iA ${{ inputs.tools }}
|
||||
shell: bash
|
||||
@@ -1,21 +0,0 @@
|
||||
codecov:
|
||||
branch: main
|
||||
require_ci_to_pass: false
|
||||
|
||||
comment: false
|
||||
|
||||
github_checks:
|
||||
annotations: true
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 1%
|
||||
only_pulls: false
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 1%
|
||||
only_pulls: true
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:best-practices"
|
||||
],
|
||||
"baseBranchPatterns": [
|
||||
"main",
|
||||
"/^v[0-9]+/"
|
||||
],
|
||||
"rebaseWhen": "conflicted",
|
||||
"pip_requirements": {
|
||||
"enabled": false
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"haskell-cabal"
|
||||
],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"matchBaseBranches": [
|
||||
"/^v[0-9]+/"
|
||||
],
|
||||
"groupName": "all dependencies"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
name: Backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-slim
|
||||
# It triggers only when PR is already merged on either:
|
||||
#
|
||||
# - The merge event itself (action != labeled) or
|
||||
# - A label event with the right label (backport ...).
|
||||
#
|
||||
# The result will be that we can add the label before or after merge,
|
||||
# but the workflow will only run once the PR had been merged.
|
||||
if: >
|
||||
github.event.pull_request.merged &&
|
||||
(
|
||||
github.event.action != 'labeled' ||
|
||||
startsWith(github.event.label.name, 'backport')
|
||||
)
|
||||
steps:
|
||||
|
||||
# This actions creates the github token using the postgrest app secrets
|
||||
- name: Create Github App Token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.POSTGREST_CI_APP_ID }}
|
||||
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
permission-workflows: write # required when backporting CI changes
|
||||
|
||||
# This is required for backport action to cherry-pick the PR
|
||||
- name: Fetch PR ref
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
allow-unsafe-pr-checkout: true
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
# Backport action that creates the PR with given settings
|
||||
- name: Create backport PR
|
||||
uses: korthout/backport-action@2e830a1d0b8269505846ddd407a70876913ad1f8 # v4.6
|
||||
with:
|
||||
github_token: ${{ steps.app-token.outputs.token }}
|
||||
pull_description: 'Backport for #${pull_number}.'
|
||||
pull_title: '${target_branch}: ${pull_title}'
|
||||
@@ -1,199 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
paths:
|
||||
- .github/workflows/build.yaml
|
||||
- .github/actions/**
|
||||
- .github/scripts/**
|
||||
- .github/*
|
||||
- '*.nix'
|
||||
- nix/**
|
||||
- flake.lock
|
||||
- .cirrus.yml
|
||||
- cabal.project*
|
||||
- postgrest.cabal
|
||||
- stack.yaml*
|
||||
- '**.hs'
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: build-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
static:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Linux aarch64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
artifact: aarch64
|
||||
- name: Linux x86-64
|
||||
runs-on: ubuntu-24.04
|
||||
artifact: x86-64
|
||||
name: Nix - ${{ matrix.name }} static
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build static executable
|
||||
run: nix-build -A postgrestStatic -A postgrestStatic.tests
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: postgrest-linux-static-${{ matrix.artifact }}
|
||||
path: result/bin/postgrest
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build Docker image
|
||||
run: nix-build -A docker.image --out-link postgrest-docker-${{ matrix.artifact }}.tar.gz
|
||||
- name: Save built Docker image as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: postgrest-docker-${{ matrix.artifact }}
|
||||
path: postgrest-docker-${{ matrix.artifact }}.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Test static executable with NixOS' VM test
|
||||
# GHA's ARM runner does not support KVM
|
||||
if: runner.arch == 'X64'
|
||||
run: nix-build -A nixpkgs-nixos-test
|
||||
|
||||
|
||||
macos:
|
||||
name: Nix - MacOS
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Install nix-build-uncached
|
||||
run: nix-env -f default.nix -iA nix-build-uncached
|
||||
|
||||
- name: Build everything (default.nix)
|
||||
run: nix-build-uncached
|
||||
|
||||
- name: Build everything (shell.nix)
|
||||
run: nix-build-uncached shell.nix
|
||||
|
||||
|
||||
stack:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: FreeBSD x86-64
|
||||
runs-on: ubuntu-24.04
|
||||
vm: freebsd
|
||||
artifact: postgrest-freebsd-x86-64
|
||||
deps: pkg install -y git postgresql16-client hs-stack
|
||||
|
||||
- name: MacOS aarch64
|
||||
runs-on: macos-14
|
||||
artifact: postgrest-macos-aarch64
|
||||
deps: brew link --force libpq
|
||||
|
||||
- name: MacOS x86-64
|
||||
runs-on: macos-15-intel
|
||||
artifact: postgrest-macos-x86-64
|
||||
deps: brew link --force libpq
|
||||
|
||||
- name: Windows
|
||||
runs-on: windows-2022
|
||||
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
|
||||
artifact: postgrest-windows-x86-64
|
||||
|
||||
name: Stack - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
# Putting .stack in the working directory helps with moving this in and out of the FreeBSD VM.
|
||||
STACK_ROOT: ${{ github.workspace }}/.stack
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- if: ${{ !matrix.vm }}
|
||||
uses: haskell-actions/setup@6037f33647c3f17758a2356c80fc4a53d7e0685d # v2.12.0
|
||||
with:
|
||||
# This must match the version in stack.yaml's resolver
|
||||
ghc-version: 9.10.3
|
||||
enable-stack: true
|
||||
stack-no-global: true
|
||||
stack-setup-ghc: true
|
||||
- name: Cache .stack
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: .stack
|
||||
prefix: ${{ matrix.vm }}${{ matrix.vm && '-' }}stack
|
||||
suffix: ${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
|
||||
- name: Cache .stack-work
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: .stack-work
|
||||
save-prs: true
|
||||
prefix: ${{ matrix.vm }}${{ matrix.vm && '-' }}stack-work-${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
|
||||
suffix: ${{ hashFiles('main/**/*.hs', 'src/**/*.hs') }}
|
||||
- name: Build with Stack
|
||||
uses: ./.github/actions/run-anywhere
|
||||
with:
|
||||
vm: ${{ matrix.vm }}
|
||||
envs: STACK_ROOT
|
||||
prepare: ${{ matrix.deps }}
|
||||
run: |
|
||||
stack build --lock-file error-on-write --local-bin-path result --copy-bins
|
||||
strip result/postgrest*
|
||||
- name: Save built executable as artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: |
|
||||
result/postgrest
|
||||
result/postgrest.exe
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
cabal:
|
||||
strategy:
|
||||
matrix:
|
||||
ghc: ['9.10.3', '9.12.3']
|
||||
fail-fast: false
|
||||
name: Cabal - Linux x86-64 - GHC ${{ matrix.ghc }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: haskell-actions/setup@6037f33647c3f17758a2356c80fc4a53d7e0685d # v2.12.0
|
||||
with:
|
||||
ghc-version: ${{ matrix.ghc }}
|
||||
- name: Cache .cabal
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/packages
|
||||
~/.cabal/store
|
||||
prefix: cabal-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
|
||||
suffix: ${{ hashFiles('postgrest.cabal', 'cabal.project') }}
|
||||
- name: Cache dist-newstyle
|
||||
uses: ./.github/actions/cache-on-main
|
||||
with:
|
||||
path: dist-newstyle
|
||||
save-prs: true
|
||||
prefix: cabal-${{ matrix.ghc }}-dist-newstyle-${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
|
||||
suffix: ${{ hashFiles('**/*.hs') }}
|
||||
- name: Install dependencies
|
||||
run: cabal build --only-dependencies --enable-tests --enable-benchmarks
|
||||
- name: Build
|
||||
run: cabal build --enable-tests --enable-benchmarks all
|
||||
@@ -1,53 +0,0 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: style-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint-style:
|
||||
name: Lint & Style
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: style.lint.bin style.styleCheck.bin
|
||||
- name: Run linter (check locally with `nix-shell --run postgrest-lint`)
|
||||
run: postgrest-lint
|
||||
- name: Run style check (auto-format with `nix-shell --run postgrest-style`)
|
||||
run: postgrest-style-check
|
||||
|
||||
commit:
|
||||
if: github.event_name != 'push' # we don't run this on a push, a failure on push disrupts the release workflow
|
||||
name: Commit
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 100 # fetch history (last 100 commits) instead of default shallow clone history, this is deemed enough for a PR history
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: gitTools.commitCheck.bin
|
||||
- name: Run commitlint (check locally with `nix-shell --run postgrest-commitlint`)
|
||||
run: |
|
||||
# Fetch target branch explicitly
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
|
||||
# Run commitlint
|
||||
postgrest-commitlint --from origin/${{ github.base_ref }} --to HEAD
|
||||
@@ -1,69 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
uses: ./.github/workflows/check.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
docs:
|
||||
name: Docs
|
||||
uses: ./.github/workflows/docs.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
test:
|
||||
name: Test
|
||||
uses: ./.github/workflows/test.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
tag:
|
||||
name: Tag
|
||||
concurrency:
|
||||
# Never tag outdated commits on the main branch by skipping superseded commits
|
||||
group: ci-tag-${{ (github.ref == 'refs/heads/main' && github.ref) || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
if: vars.RELEASE_ENABLED
|
||||
runs-on: ubuntu-slim
|
||||
needs:
|
||||
- docs
|
||||
- test
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
|
||||
- name: Tag latest commit
|
||||
run: |
|
||||
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
|
||||
if [[ "$cabal_version" == *.* ]]; then
|
||||
git fetch --tags
|
||||
|
||||
if [ -z "$(git tag --list "v$cabal_version")" ]; then
|
||||
git tag "v$cabal_version"
|
||||
git push origin "v$cabal_version"
|
||||
fi
|
||||
else
|
||||
git tag -f "devel"
|
||||
git push -f origin "devel"
|
||||
fi
|
||||
@@ -1,54 +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/**
|
||||
- flake.lock
|
||||
- docs/**
|
||||
- '!**.md'
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for pull requests
|
||||
group: docs-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
- main
|
||||
- v[0-9]+
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
name: Build docs
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.build.bin
|
||||
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
|
||||
- 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
|
||||
name: Run spellcheck
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- 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: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
|
||||
- run: nix-env -f docs/default.nix -iA spellcheck
|
||||
- run: postgrest-docs-spellcheck
|
||||
|
||||
dictcheck:
|
||||
name: Run dictcheck
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
|
||||
- run: nix-env -f docs/default.nix -iA dictcheck
|
||||
- run: postgrest-docs-dictcheck
|
||||
|
||||
linkcheck:
|
||||
name: Run linkcheck
|
||||
if: github.base_ref == 'main'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31
|
||||
- run: nix-env -f docs/default.nix -iA linkcheck
|
||||
- run: postgrest-docs-linkcheck
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
name: Linkcheck
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '1 2 * * 3'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linkcheck:
|
||||
name: Linkcheck
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: docs.linkcheck.bin
|
||||
|
||||
- name: Run Linkcheck
|
||||
id: linkcheck
|
||||
run: postgrest-docs-linkcheck
|
||||
|
||||
# This actions creates the github token using the postgrest app secrets
|
||||
- name: Create Github App Token (Runs only on linkcheck failure)
|
||||
id: app-token
|
||||
if: ${{ failure() && steps.linkcheck.outcome == 'failure' }} # only create the token on linkcheck failure
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
||||
with:
|
||||
client-id: ${{ vars.POSTGREST_CI_APP_ID }}
|
||||
private-key: ${{ secrets.POSTGREST_CI_PRIVATE_KEY }}
|
||||
permission-issues: write # required for commenting on issues
|
||||
|
||||
- name: Notify on linkcheck failure by commenting on GH Issue 4106
|
||||
if: ${{ failure() && steps.linkcheck.outcome == 'failure' }}
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
issue-number: 4106
|
||||
body: |
|
||||
**Linkcheck Job Failed!**
|
||||
|
||||
A broken link was detected in the docs. Please check the [failed run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
|
||||
@@ -1,226 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- devel
|
||||
- v*
|
||||
|
||||
concurrency:
|
||||
# Terminate all previous runs of the same workflow for the same tag.
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
uses: ./.github/workflows/build.yaml
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
||||
|
||||
github:
|
||||
name: GitHub
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-slim
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Check the version to be released
|
||||
run: |
|
||||
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
|
||||
if [ "${GITHUB_REF_NAME}" != "devel" ] && [ "${GITHUB_REF_NAME}" != "v$cabal_version" ]; then
|
||||
echo "Tagged version ($GITHUB_REF_NAME) does not match the one in postgrest.cabal (v$cabal_version). Aborting release..."
|
||||
exit 1
|
||||
fi
|
||||
- name: Identify changes from CHANGELOG.md
|
||||
run: |
|
||||
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
|
||||
echo "Getting unreleased changes..."
|
||||
sed -n "1,/## Unreleased/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
else
|
||||
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
|
||||
echo "Propper release, getting changes for version $version ..."
|
||||
sed -n "1,/## \[$version\]/d;/## \[/q;p" CHANGELOG.md > CHANGES.md
|
||||
fi
|
||||
|
||||
echo "Relevant extract from CHANGELOG.md:"
|
||||
cat CHANGES.md
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Create release bundle with archives for all builds
|
||||
run: |
|
||||
find artifacts -type f -iname postgrest -exec chmod +x {} \;
|
||||
|
||||
mkdir -p release-bundle
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-linux-static-aarch64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-linux-static-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-linux-static-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-aarch64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-aarch64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-macos-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-macos-x86-64 postgrest
|
||||
|
||||
tar cJvf "release-bundle/postgrest-${GITHUB_REF_NAME}-freebsd-x86-64.tar.xz" \
|
||||
-C artifacts/postgrest-freebsd-x86-64 postgrest
|
||||
|
||||
zip --junk-paths "release-bundle/postgrest-${GITHUB_REF_NAME}-windows-x86-64.zip" \
|
||||
artifacts/postgrest-windows-x86-64/postgrest.exe
|
||||
|
||||
- name: Save release bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: release-bundle
|
||||
path: release-bundle
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish release on GitHub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Releasing version ${GITHUB_REF_NAME} on GitHub..."
|
||||
|
||||
if [ "${GITHUB_REF_NAME}" == "devel" ]; then
|
||||
# To replace the existing release, we must first delete the old assets,
|
||||
# then modify the release, then add the new assets.
|
||||
gh release view devel --json assets \
|
||||
| jq -r '.assets[] | .name' \
|
||||
| xargs -rn1 \
|
||||
gh release delete-asset -y devel
|
||||
gh release edit devel \
|
||||
-t devel \
|
||||
--verify-tag \
|
||||
-F CHANGES.md \
|
||||
--prerelease
|
||||
gh release upload --clobber devel release-bundle/*
|
||||
else
|
||||
gh release create "${GITHUB_REF_NAME}" \
|
||||
-t "${GITHUB_REF_NAME}" \
|
||||
--verify-tag \
|
||||
-F CHANGES.md \
|
||||
release-bundle/*
|
||||
fi
|
||||
|
||||
|
||||
docker:
|
||||
name: Docker Hub
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs:
|
||||
- github
|
||||
if: |
|
||||
vars.DOCKER_REPO && vars.DOCKER_USER
|
||||
env:
|
||||
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Download aarch64 Docker image
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: postgrest-docker-aarch64
|
||||
- name: Download x86-64 Docker image
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: postgrest-docker-x86-64
|
||||
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
- name: Publish images on Docker Hub
|
||||
run: |
|
||||
docker load -i postgrest-docker-aarch64.tar.gz
|
||||
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64"
|
||||
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64"
|
||||
|
||||
docker load -i postgrest-docker-x86-64.tar.gz
|
||||
docker tag postgrest:latest "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
|
||||
docker push "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
|
||||
|
||||
docker manifest create "$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}" \
|
||||
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64" \
|
||||
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
|
||||
docker manifest 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 manifest create "$DOCKER_REPO/postgrest:latest" \
|
||||
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-arm64" \
|
||||
"$DOCKER_REPO/postgrest:${GITHUB_REF_NAME}-linux-amd64"
|
||||
docker manifest push "$DOCKER_REPO/postgrest:latest"
|
||||
else
|
||||
echo "Skipping push to 'latest' tag for pre-release..."
|
||||
fi
|
||||
|
||||
- uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
|
||||
if: github.ref == 'refs/tags/devel'
|
||||
name: Docker Hub Description
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASS }}
|
||||
repository: ${{ vars.DOCKER_REPO }}/postgrest
|
||||
short-description: ${{ github.event.repository.description }}
|
||||
readme-filepath: ./docker-hub-readme.md
|
||||
|
||||
|
||||
ghcr:
|
||||
name: GitHub Container Registry
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs:
|
||||
- github
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Download aarch64 Docker image
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: postgrest-docker-aarch64
|
||||
- name: Download x86-64 Docker image
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: postgrest-docker-x86-64
|
||||
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Publish images on Docker Hub
|
||||
run: |
|
||||
docker load -i postgrest-docker-aarch64.tar.gz
|
||||
docker tag postgrest:latest "ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-arm64"
|
||||
docker push "ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-arm64"
|
||||
|
||||
docker load -i postgrest-docker-x86-64.tar.gz
|
||||
docker tag postgrest:latest "ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-amd64"
|
||||
docker push "ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-amd64"
|
||||
|
||||
docker manifest create "ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}" \
|
||||
"ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-arm64" \
|
||||
"ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-amd64"
|
||||
docker manifest push "ghcr.io/${GITHUB_REPOSITORY,,}:${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 manifest create "ghcr.io/${GITHUB_REPOSITORY,,}:latest" \
|
||||
"ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-arm64" \
|
||||
"ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_REF_NAME}-linux-amd64"
|
||||
docker manifest push "ghcr.io/${GITHUB_REPOSITORY,,}:latest"
|
||||
else
|
||||
echo "Skipping push to 'latest' tag for pre-release..."
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
required: false
|
||||
CODECOV_TOKEN:
|
||||
required: false
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- v[0-9]+
|
||||
paths:
|
||||
- .github/workflows/test.yaml
|
||||
- .github/workflows/report.yaml
|
||||
- .github/actions/setup-nix/**
|
||||
- default.nix
|
||||
- nix/**
|
||||
- flake.lock
|
||||
- .stylish-haskell.yaml
|
||||
- cabal.project
|
||||
- postgrest.cabal
|
||||
- '**.hs'
|
||||
- test/**
|
||||
- '!**.md'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.coverage.bin tests.testDoctests.bin tests.testSpecIdempotence.bin cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run coverage (IO tests and Spec tests against latest supported PostgreSQL)
|
||||
run: postgrest-coverage
|
||||
- name: Upload coverage to codecov
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
files: ./coverage/codecov.json
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Run doctests
|
||||
if: always()
|
||||
run: nix-shell --run postgrest-test-doctests
|
||||
|
||||
- name: Check the spec tests for idempotence
|
||||
if: always()
|
||||
run: postgrest-test-spec-idempotence
|
||||
|
||||
|
||||
postgres:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Latest version is tested via `coverage` above.
|
||||
pgVersion: [pg-14, pg-15, pg-16, pg-17, oriole-18, pg-18]
|
||||
name: ${{ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.testSpec.bin tests.testObservability.bin tests.testIO.bin tests.testBigSchema.bin withTools.${{ matrix.pgVersion }}.bin cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run spec tests
|
||||
if: always()
|
||||
run: postgrest-with-${{ matrix.pgVersion }} postgrest-test-spec
|
||||
|
||||
- name: Run observability tests
|
||||
if: always()
|
||||
run: postgrest-with-${{ matrix.pgVersion }} postgrest-test-observability
|
||||
|
||||
- name: Run IO tests
|
||||
if: always()
|
||||
run: postgrest-with-${{ matrix.pgVersion }} postgrest-test-io -vv
|
||||
|
||||
- name: Run IO tests on a big schema
|
||||
if: always()
|
||||
run: postgrest-with-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
|
||||
|
||||
|
||||
memory:
|
||||
name: Memory
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
tools: tests.testMemory.bin cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run memory tests
|
||||
run: postgrest-test-memory
|
||||
|
||||
|
||||
loadtest:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
kind: ['mixed', 'jwt-cache']
|
||||
name: Loadtest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
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 loadtest.report-load.bin cabalTools.update.bin
|
||||
|
||||
- run: postgrest-cabal-update
|
||||
|
||||
- name: Run loadtest
|
||||
env:
|
||||
TARGET_BRANCH: ${{ github.base_ref || github.ref_name }}
|
||||
run: |
|
||||
if [ "$TARGET_BRANCH" = "main" ]; then
|
||||
latest_tag=$(git tag --sort=-creatordate --list "v*" | head -n1)
|
||||
else
|
||||
latest_tag=$(git tag --merged HEAD --sort=-creatordate "v*" | head -n1)
|
||||
fi
|
||||
postgrest-loadtest-against -k ${{ matrix.kind }} "$TARGET_BRANCH" "$latest_tag"
|
||||
|
||||
- name: Report P50
|
||||
# This step checks whether any red cross indicators (:x:) are present in the step summary.
|
||||
# The loadtest reporter writes them when any of individual steps fails the performance
|
||||
# regression threshold.
|
||||
run: |
|
||||
! (postgrest-loadtest-report -g ${{ matrix.kind }} -p 50 \
|
||||
| tee "$GITHUB_STEP_SUMMARY" \
|
||||
| grep ':x:')
|
||||
|
||||
- name: Report P0
|
||||
if: always()
|
||||
run: |
|
||||
postgrest-loadtest-report -g ${{ matrix.kind }} -p 0 >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Report P90
|
||||
if: always()
|
||||
run: |
|
||||
postgrest-loadtest-report -g ${{ matrix.kind }} -p 90 >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Report P95
|
||||
if: always()
|
||||
run: |
|
||||
postgrest-loadtest-report -g ${{ matrix.kind }} -p 95 >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Report CPU/MEM
|
||||
if: always()
|
||||
run: |
|
||||
postgrest-loadtest-report-load -g ${{ matrix.kind }} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
flake:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runs-on:
|
||||
- macos-14 # aarch64-darwin
|
||||
- ubuntu-24.04 # x86_64-linux
|
||||
- ubuntu-24.04-arm # aarch64-linux
|
||||
name: Flake Check
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Nix Environment
|
||||
uses: ./.github/actions/setup-nix
|
||||
with:
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Run flake check
|
||||
run: |
|
||||
nix flake check
|
||||
@@ -14,7 +14,7 @@ site
|
||||
.#*
|
||||
*.swp
|
||||
result*
|
||||
dist-*
|
||||
dist-newstyle
|
||||
postgrest.hp
|
||||
postgrest.prof
|
||||
__pycache__
|
||||
@@ -24,10 +24,3 @@ coverage
|
||||
loadtest
|
||||
.history
|
||||
.docs-build
|
||||
gen_targets.http
|
||||
gen_jwks.json
|
||||
gen_private.json
|
||||
.pytest_cache
|
||||
.ruff_cache
|
||||
postgrest-module-graph.png
|
||||
.ghc.environment.*
|
||||
|
||||
@@ -5,6 +5,6 @@ python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
build:
|
||||
os: ubuntu-24.04
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
python: "3.11"
|
||||
|
||||
@@ -200,7 +200,7 @@ steps:
|
||||
|
||||
# A common setting is the number of columns (parts of) code will be wrapped
|
||||
# to. Different steps take this into account. Default: 80.
|
||||
columns: 80
|
||||
columns: 70
|
||||
|
||||
# By default, line endings are converted according to the OS. You can override
|
||||
# preferred format here.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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.
|
||||
|
||||
### Workers.hs
|
||||
|
||||
This spawns threads which are used to execute concurrent jobs.
|
||||
|
||||
Jobs include connection recovery, a listener for the PostgreSQL LISTEN command, and an admin server.
|
||||
|
||||
### 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.
|
||||
@@ -4,35 +4,40 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
|
||||
## Sponsors
|
||||
|
||||
<table align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/cybertec.svg">
|
||||
<img width="222px" src="static/cybertec-new.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.svg">
|
||||
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
|
||||
<img width="296px" src="static/2ndquadrant.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.euronodes.com/postgrest" target="_blank">
|
||||
<img width="296px" src="static/euronodes.svg">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/neon.jpg">
|
||||
<a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/gnuhost.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/bytebase.svg">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/oblivious.jpg">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -41,14 +46,12 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
|
||||
## Lead Backers
|
||||
|
||||
- [Roboflow](https://github.com/roboflow)
|
||||
- Evans Fernandes
|
||||
- [Jan Sommer](https://github.com/nerfpops)
|
||||
- [Franz Gusenbauer](https://www.igutech.at/)
|
||||
|
||||
## Backers
|
||||
|
||||
- Zac Miller
|
||||
- Tsingson Qin
|
||||
- Michel Pelletier
|
||||
- Jay Hannah
|
||||
@@ -73,31 +76,6 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
|
||||
<img width="222px" src="static/timescaledb.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img max-width="222px" height="88" src="static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
|
||||
<img width="222px" src="static/2ndquadrant.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="222px" src="static/oblivious.jpg">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://code.build/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="222px" src="static/code-build.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/tembo.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,734 +1,7 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. From version `14.0` onwards PostgREST follows a `MAJOR.PATCH` two-part versioning. Only even-numbered MAJOR versions will be released, reserving odd-numbered MAJOR versions for development.
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [16.1] - 2026-08-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- JWT validation uses wrong current time due to a bug in auto-update by @mkleczek in #5159
|
||||
|
||||
## [16.0] - 2026-08-07
|
||||
|
||||
### Changes
|
||||
|
||||
#### HTTP Server
|
||||
|
||||
- [Graceful shutdown](https://docs.postgrest.org/en/v16/references/http_server.html#graceful-shutdown) by @mkleczek, @Vlix in #4702
|
||||
|
||||
- [server-reuseport](https://docs.postgrest.org/en/v16/references/configuration.html#server-reuseport) allows starting multiple PostgREST instances using the same port on supported platforms by @mkleczek in #4703, #4694
|
||||
|
||||
#### Performance
|
||||
|
||||
- Optimize schema cache domain type resolution by using [pg_basetype](https://www.postgresql.org/docs/current/functions-info.html#FUNCTIONS-INFO-CATALOG) on PostgreSQL 17+ by @joelonsql in #4567
|
||||
|
||||
- [Prefer: count=exact](https://docs.postgrest.org/en/v16/references/api/pagination_count.html#exact-count) no longer does a double count on requests that do not use ranges or `db-max-rows` by @laurenceisla in #3957
|
||||
|
||||
- [Prefer: timezone](https://docs.postgrest.org/en/v16/references/api/preferences.html#prefer-timezone) no longer requires the schema cache by @steve-chavez in #5100
|
||||
+ Previously this required caching [pg_timezone_names](https://www.postgresql.org/docs/current/view-pg-timezone-names.html) which was slow in some systems
|
||||
|
||||
#### Integrations
|
||||
|
||||
- PostgREST is now tested to work with [OrioleDB](https://github.com/orioledb/orioledb/) in #4845 by @wolfgangwalther
|
||||
+ See [our guide for running OrioleDB on NixOS](https://docs.postgrest.org/en/v16/integrations/nixos.html)
|
||||
|
||||
#### JWT
|
||||
|
||||
- [JWT Role Extraction](https://docs.postgrest.org/en/v16/references/auth.html#jwt-role-extract) is now more flexible, supporting the standard JSON Path defined in RFC 9535 by @taimoorzaeem in #4984
|
||||
|
||||
#### API
|
||||
|
||||
- [Prefer: timezone](https://docs.postgrest.org/en/v16/references/api/preferences.html#timezone) now supports numeric offsets like `05:00` or `-4` by @steve-chavez in #5100
|
||||
|
||||
- Fix unexpected results when embedding and filtering the same table more than once by @laurenceisla in #4075
|
||||
+ You need to set [url-use-legacy-target-names](https://docs.postgrest.org/en/v16/references/configuration.html#url-use-legacy-target-names) to `false`.
|
||||
|
||||
- Deprecate filters, orders and limits with the name of an embedded table when it has an alias by @steve-chavez, @laurenceisla in #4075
|
||||
+ e.g. `?select=alias:table(*)&table.id=eq.1` will not be possible anymore, use `?select=alias:table(*)&alias.id=eq.1` instead.
|
||||
+ You will see a warning in the logs and a `Warning` header on the client response when this happens.
|
||||
+ You can disable this behavior now by setting `url-use-legacy-target-names = false`.
|
||||
|
||||
- Add `Vary` header to responses by @develop7 in #4609
|
||||
|
||||
- Fix automatic transaction retries on `40001 (serialization_failure)` errors to prevent replication lag by @laurenceisla in #3673
|
||||
|
||||
#### Observability
|
||||
|
||||
- [GHC runtime metrics](https://docs.postgrest.org/en/v16/references/observability.html#ghc-runtime-metrics) by @mkleczek in #4862
|
||||
- [client-error-verbosity](https://docs.postgrest.org/en/v16/references/configuration.html#client-error-verbosity) to customize responses error verbosity by @taimoorzaeem in #4088, #3980, #3824
|
||||
- [log-level](https://docs.postgrest.org/en/v16/references/configuration.html#log-level) config is now reloadable by @taimoorzaeem in #5113
|
||||
- Log error when `db-schemas` config contains schema `pg_catalog` or `information_schema` by @taimoorzaeem in #4359
|
||||
- Log schema cache queries timings on `log-level=debug` by @steve-chavez in #4805
|
||||
|
||||
#### Admin Server
|
||||
|
||||
- [admin-server-unix-socket](https://docs.postgrest.org/en/v16/references/configuration.html#admin-server-unix-socket)/[admin-server-unix-socket-mode](https://docs.postgrest.org/en/v16/references/configuration.html#admin-server-unix-socket-mode) to run the admin server on a unix socket by @wolfgangwalther in #5003
|
||||
- Fix responding with `Something went wrong` on Admin server when under EMFILE by @mkleczek in #5077
|
||||
|
||||
#### Deployment
|
||||
|
||||
- Make executable for aarch64-linux static instead of Ubuntu-based by @wolfgangwalther in #4193
|
||||
- Docker image for aarch64-linux is now built from scratch instead of being Ubuntu-based by @wolfgangwalther in #4193
|
||||
- Besides Docker Hub, docker images are now published to Github Container Registry by @wolfgangwalther in #2836
|
||||
|
||||
#### Schema Cache
|
||||
|
||||
- Fix requests failing when the schema cache fails to reload, when this happens PostgREST will continue serving requests in "best effort" by @mkleczek in #4873 #4869
|
||||
- Fix reporting 503s errors unnecessarily while the schema cache is loading at startup by @mkleczek in #4880
|
||||
- Fix schema cache dump missing RPC transaction isolation level by @taimoorzaeem in #5079
|
||||
|
||||
#### Listener
|
||||
|
||||
- Fix config `db-channel-enabled` not reloading by @taimoorzaeem in #4894
|
||||
|
||||
### Migration to v16
|
||||
|
||||
- Drop support for PostgreSQL EOL version 13 by @wolfgangwalther in #4193
|
||||
+ PostgreSQL 13 end of life was on 2025 ([ref](https://www.postgresql.org/support/versioning/))
|
||||
+ Upgrade your PostgreSQL version to at least 14 to use this new PostgREST version.
|
||||
|
||||
- Fail at startup when `db-schemas` contains schema `pg_catalog` or `information_schema` by @taimoorzaeem in #4359
|
||||
+ Previously it failed at runtime with `PGRST205` on requests related to these schemas.
|
||||
+ Remove `pg_catalog` and `information_schema` from `db-schemas`.
|
||||
|
||||
- `Prefer: timezone` no longer complies with `handling=lenient` and instead always fails by @steve-chavez in #5128
|
||||
+ Supporting this required caching `pg_timezone_names`, which was expensive.
|
||||
+ Ensure your requests always have a valid timezone.
|
||||
|
||||
- `jwt-role-claim-key` no longer uses the JSPath DSL and instead uses JSON Path by @taimoorzaeem in #4984
|
||||
+ Now all config values must start with `$` character.
|
||||
Example: `.roles.read` -> `$.roles.read`
|
||||
+ Keys with special characters, with the exception of `_` char must be quoted.
|
||||
Example: `.roles.write-role` -> `$.roles["write-role"]`
|
||||
+ String comparison operators (`^==`, `==^` and `*==`) are replaced with regular expression search.
|
||||
Example: `.roles[?(@ ^== "postgrest_test_")]` -> `$.roles[?search(@, "^postgrest_test_")]`
|
||||
+ Update the `jwt-role-claim-key` value accoring to the above rules. Also see the syntax reference: [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html#name-jsonpath-syntax-and-semanti).
|
||||
|
||||
## [14.16] - 2026-07-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix admin server crashing without a way to recover by @taimoorzaeem in #5096
|
||||
|
||||
## [14.15] - 2026-07-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix admin server dying silently by @Vlix, @mkleczek, @steve-chavez in #5012
|
||||
|
||||
## [14.14] - 2026-06-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix admin server not logging cause of failure by @taimoorzaeem in #5012
|
||||
|
||||
## [14.13] - 2026-06-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix connection retrying message in `PGRST000` error by @netqo in #4980
|
||||
+ Remove redundant "Retrying the connection." from message because it is logged separately
|
||||
- Fix request failures when `work_mem` is set on a role by @laurenceisla in #4955
|
||||
|
||||
## [14.12] - 2026-05-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix race condition in pool_available metric causing negative values during network instability by @mkleczek in #4622
|
||||
|
||||
## [14.11] - 2026-05-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix login with uppercase and mixed case role names by @taimoorzaeem in #4678
|
||||
- Restore Listener query shape so it can be found in `pg_stat_activity` by @mkleczek in #4857 #4859
|
||||
- The LISTEN channel now automatically recovers when it stops working due to a PostgreSQL bug @laurenceisla in #3147
|
||||
- Fix misleading "Functions" name on schema cache summary in startup logs by @taimoorzaeem in #4821
|
||||
|
||||
## [14.10] - 2026-04-16
|
||||
|
||||
### Added
|
||||
|
||||
- Log when the pool is released during schema cache reload on `log-level=debug` by @mkleczek in #4668
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix unnecessary connection pool flushes during schema cache reloading by @mkleczek in #4645
|
||||
|
||||
## [14.9] - 2026-04-10
|
||||
|
||||
### Added
|
||||
|
||||
- Log host, port and pg version of listener database connection by @mkleczek in #4617 #4618
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove red herring warp logs on default log-level, only emit them on `log-level=debug` by @steve-chavez in #4799
|
||||
|
||||
## [14.8] - 2026-04-03
|
||||
|
||||
### Added
|
||||
|
||||
- Log a `HINT` when the LISTEN channel stops working due to a PostgreSQL bug by @laurenceisla in #4581
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix invalid OpenAPI 2.0 format for integer types (`smallint`, `integer`, `bigint`) by @arturbent0 in #4641
|
||||
|
||||
## [14.7] - 2026-03-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix not logging SIGTERM and SIGINT by @steve-chavez in #4728
|
||||
|
||||
## [14.6] - 2026-03-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix leaking table and function names when calculating error hint by @taimoorzaeem in #4675
|
||||
|
||||
## [14.5] - 2026-02-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't hide async exceptions in logs by @stevechavez in #4646
|
||||
|
||||
## [14.4] - 2026-01-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure Listener connections are released by @mkleczek in #4614
|
||||
- Fix incorrectly filtering the returned representation for PATCH requests when using `or/and` filters by @laurenceisla in #3707
|
||||
- Fix listener running with exception masked after first failure by @mkleczek in #4615
|
||||
|
||||
## [14.3] - 2026-01-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix performance and high memory usage of relation hint calculation by @mkleczek in #4462, #4463
|
||||
|
||||
## [14.2] - 2025-12-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix `hasSingleUnnamedParam` incorrectly matching functions with named parameters by @joelonsql in #4553
|
||||
+ Functions with a single named parameter (e.g., `foo(data json)`) no longer incorrectly match the single-param fallback, returning a clean `PGRST202` error instead of a confusing PostgreSQL `42883` error.
|
||||
- Fix misleading logs on unsupported PostgreSQL versions by @taimoorzaeem in #4519
|
||||
- Fix regression where the `PGRST103` error response was truncated by @laurenceisla in #4455
|
||||
+ Happened when an `offset` was greater than the rows requested and `Prefer: count=exact` was sent.
|
||||
- Fix not returning `Content-Length` on empty HTTP `201` responses by @laurenceisla in #4518
|
||||
- Fix inaccurate Server-Timing header durations by @steve-chavez in #4522
|
||||
- Fix inaccurate "Schema cache queried" logs by @steve-chavez in #4522
|
||||
|
||||
## [14.1] - 2025-11-05
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fix `db-pre-config` function failing when function names are pg reserved words by @taimoorzaeem in #4380
|
||||
- Fix `server-host=!6` incorrectly binds to IPv4 address by @taimoorzaeem in #3202
|
||||
|
||||
## [14.0] - 2025-10-24
|
||||
|
||||
### Added
|
||||
|
||||
- Bounded JWT cache using the SIEVE algorithm by @mkleczek in #4084
|
||||
+ It now uses a fixed size cache instead of arbitrary sized cache.
|
||||
- Add `--ready` flag for postgrest healthcheck by @taimoorzaeem in #4239
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix not logging OpenAPI queries when `log-query=main-query` is enabled by @steve-chavez in #4226
|
||||
- Fix not logging explain query when `log-query=main-query` is enabled by @steve-chavez in #4319
|
||||
- Fix not logging transaction variables and db-pre-request function when `log-query=main-query` is enabled by @steve-chavez in #3934
|
||||
- Fix not logging the JSON message to stderr on a `PGRST002` error by @laurenceisla in #4129
|
||||
- Fix reloading the Schema Cache unnecessarily on a `PGRST002` error by @laurenceisla in #4367
|
||||
- Fix schema cache loading taking a long time for large schemas by @mkleczek in #4360, #3704
|
||||
|
||||
### Changed
|
||||
|
||||
- Drop support for PostgreSQL EOL version 12 by @wolfgangwalther in #3865
|
||||
- From now on PostgREST will follow a `MAJOR.PATCH` two-part versioning. Only even-numbered MAJOR versions will be released, reserving odd-numbered MAJOR versions for development.
|
||||
- Replaced `jwt-cache-max-lifetime` config with `jwt-cache-max-entries` by @mkleczek in #4084
|
||||
- `log-query` config now takes a boolean instead of a string value by @steve-chavez in #3934
|
||||
|
||||
## [13.0.8] - 2025-10-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix loading utf-8 config files with `ASCII` locale set by @taimoorzaeem in #4386
|
||||
|
||||
## [13.0.7] - 2025-09-14
|
||||
|
||||
### Added
|
||||
|
||||
- Improve the `PGRST106` error when the requested schema is invalid by @laurenceisla in #4089
|
||||
+ It now shows the invalid schema in the `message` field.
|
||||
+ The exposed schemas are now listed in the `hint` instead of the `message` field.
|
||||
- Improve error details of `PGRST301` error by @taimoorzaeem in #4051
|
||||
|
||||
## [13.0.6] - 2025-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix logging the Haskell type instead of the listener error message directly by @laurenceisla in #3588
|
||||
- Fix format of `IPv6` address logged at PostgREST startup by @taimoorzaeem in #4291
|
||||
- Fix empty enum in `preferParams` OpenAPI parameter by @laurenceisla in #4292
|
||||
|
||||
## [13.0.5] - 2025-08-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix OpenAPI broken docs link by @taimoorzaeem in #4080
|
||||
- Fix OpenAPI specification incorrectly exposing GET methods for volatile functions by @joelonsql in #4174
|
||||
- Fix empty spread embeddings return unexpected SQL error by @taimoorzaeem in #3887
|
||||
- Fix `/metrics` endpoint not responding with `Content-Type` header by @taimoorzaeem in #4271
|
||||
|
||||
## [13.0.4] - 2025-06-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix regression that makes full-text search not work on domain types based on `tsvector` by @laurenceisla in #4135
|
||||
- Fix `jwt-aud` config not failing when set to an invalid URI by @taimoorzaeem in #4132
|
||||
|
||||
## [13.0.3] - 2025-06-16
|
||||
|
||||
- Fix `max-affected` preference not failing with RPC when `handling=strict` by @taimoorzaeem in #4100
|
||||
- Fix a property definition's type in OpenAPI not showing the correct base type of a recursive domain by @laurenceisla in #4136
|
||||
|
||||
### Fixed
|
||||
|
||||
## [13.0.2] - 2025-06-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix regression that makes `ORDER BY` with nulls-order not work alongside limits by @laurenceisla in #4109
|
||||
|
||||
## [13.0.1] - 2025-06-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix jwt error returning HTTP status `400` for invalid role by @taimoorzaeem in #3601
|
||||
- Fix `db-extra-search-path` cannot be set to nothing by @taimoorzaeem in #4074
|
||||
+ It can now be disabled by setting it to empty string.
|
||||
+ Schema Cache load error is now logged including `db-schemas` and `db-extra-search-path` config values.
|
||||
|
||||
## [13.0.0] - 2025-05-08
|
||||
|
||||
### Added
|
||||
|
||||
- #3558, Add the `admin-server-host` config to set the host for the admin server - @develop7
|
||||
- #3607, Log to stderr when the JWT secret is less than 32 characters long - @laurenceisla
|
||||
- #2858, Performance improvements when calling RPCs via GET using indexes in more cases - @wolfgangwalther
|
||||
- #3560, Log resolved host in "Listening on ..." messages - @develop7
|
||||
- #3727, Log maximum pool size - @steve-chavez
|
||||
- #1536, Add string comparison feature for jwt-role-claim-key - @taimoorzaeem
|
||||
- #3747, Allow `not_null` value for the `is` operator - @taimoorzaeem
|
||||
- #2255, Apply `to_tsvector()` explicitly to the full-text search filtered column (excluding `tsvector` types) - @laurenceisla
|
||||
- #1578, Log the main SQL query to stderr at the current `log-level` when `log-query=main-query` - @laurenceisla
|
||||
- #3903, Log connection pool borrows on `log-level=debug` - @taimoorzaeem
|
||||
- #3041, Allow spreading one-to-many and many-to-many embedded resources - @laurenceisla
|
||||
+ The selected columns in the embedded resources are aggregated into arrays
|
||||
+ Aggregates are not supported
|
||||
- #2967, Add `Proxy-Status` header for better error response - @taimoorzaeem
|
||||
- #4016, Add `Content-Length` response header - @laurenceisla
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3693, Prevent spread embedding to allow aggregates when they are disabled - @laurenceisla
|
||||
- #3693, A nested spread embedding now correctly groups by the fields of its top parent relationship - @laurenceisla
|
||||
- #3693, Fix spread embedding errors when using the `count()` aggregate without a field - @laurenceisla
|
||||
+ Fixed `"column reference <col> is ambiguous"` error when selecting `?select=...table(col,count())`
|
||||
+ Fixed `"column <json_aggregate>.<alias> does not exist"` error when selecting `?select=...table(aias:count())`
|
||||
- #3727, Clarify "listening" logs - @steve-chavez
|
||||
- #3795, Clarify `Accept: vnd.pgrst.object` error message - @steve-chavez
|
||||
- #3697, #3602, Handle queries on non-existing table gracefully - @taimoorzaeem
|
||||
- #3600, #3926, Improve JWT errors - @taimoorzaeem
|
||||
- #3013, Fix `order=` with POST, PATCH, PUT and DELETE requests - @taimoorzaeem
|
||||
- #3965, Fix filter on unselected columns in a table-valued function - @taimoorzaeem
|
||||
- #4052, Fix schema cache load duplicate objects with different object type but same oid - @taimoorzaeem
|
||||
|
||||
### Changed
|
||||
|
||||
- #2052, Dropped support for PostgreSQL 9.6 - @wolfgangwalther
|
||||
- #2052, Dropped support for PostgreSQL 10 - @wolfgangwalther
|
||||
- #2052, Dropped support for PostgreSQL 11 - @wolfgangwalther
|
||||
- #3508, PostgREST now fails to start when `server-port` and `admin-server-port` config options are the same - @develop7
|
||||
- #3607, PostgREST now fails to start when the JWT secret is less than 32 characters long - @laurenceisla
|
||||
- #3644, Fail schema cache lookup with invalid `db-schemas` or `db-extra-search-path` config - @wolfgangwalther
|
||||
- Previously, this would silently return 200 - OK on the root endpoint, but don't provide any usable endpoints.
|
||||
- Note: This also applies when deleting the `public` schema - both config options default to that.
|
||||
- #3757, Remove support for `Prefer: params=single-object` - @joelonsql
|
||||
+ This preference was deprecated in favor of Functions with an array of JSON objects
|
||||
- #3013, Drop support for Limited updates/deletes
|
||||
+ The feature was complicated and largely unused.
|
||||
- #3956, Drop `/config` endpoint of admin server - @steve-chavez
|
||||
+ The endpoint was at risk of being left unprotected when exposing it.
|
||||
+ The accompanying `admin-server-config-enabled` config was also dropped.
|
||||
- #3598, PostgREST now validates the `kid` parameter of the JWT - @wolfgangwalther
|
||||
+ If the JWT contains a ``kid`` parameter, then PostgREST will look for the JSON Web Key in the `jwt-secret`.
|
||||
+ If the JWT doesn't contain a `kid`, the behavior should be backwards compatible. PostgREST will try each key in the `jwt-secret` one by one until it finds one that works.
|
||||
- #3697, #3602, Querying non-existent table now returns `PGRST205` error instead of empty json - @taimoorzaeem
|
||||
- #3600, #3926, Improve JWT errors - @taimoorzaeem
|
||||
+ Return `PGRST301` error when `Bearer` in auth header is sent empty
|
||||
+ Diagnostic error messages instead of exposed internals
|
||||
+ Return new `PGRST303` error when jwt claims decoding fails
|
||||
- #3906, Return `PGRST125` and `PGRST126` errors instead of empty json - @taimoorzaeem
|
||||
|
||||
## [12.2.12] - 2025-05-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3956, Fix exposing admin server `/config` by default - @steve-chavez
|
||||
+ The above endpoint is now disabled unless the `admin-server-config-enabled` config is set to `true`
|
||||
|
||||
## [12.2.11] - 2025-04-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- #4030, Fix regression with parameter `charset=utf-8` in mediatype - @taimoorzaeem
|
||||
|
||||
## [12.2.10] - 2025-04-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3889, Fix: JWT cache purging on every request decreases performance - @mkleczek
|
||||
|
||||
## [12.2.9] - 2025-04-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3498, Fix incorrect parsing of the `for` parameter of the `application/vnd.pgrst.plan` media type - @taimoorzaeem
|
||||
- #4014, Fix JWT cache allows old tokens after the jwt-secret is changed in a config reload - @taimoorzaeem
|
||||
|
||||
## [12.2.8] - 2025-02-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3841, Log `503` client error to stderr - @taimoorzaeem
|
||||
|
||||
## [12.2.7] - 2025-02-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2524, Fix schema reloading notice on windows - @diogob
|
||||
|
||||
## [12.2.6] - 2025-01-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3788, Fix jwt cache does not remove expired entries - @taimoorzaeem
|
||||
|
||||
## [12.2.5] - 2025-01-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3867, Fix startup for arm64 docker image - @wolfgangwalther
|
||||
|
||||
## [12.2.4] - 2025-01-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3779, Always log the schema cache load time - @steve-chavez
|
||||
- #3706, Fix insert with `missing=default` uses default value of domain instead of column - @taimoorzaeem
|
||||
|
||||
## [12.2.3] - 2024-08-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3091, Broken link in OpenAPI description `externalDocs` - @salim-b
|
||||
- #3659, Embed One-to-One relationship with different column order properly - @wolfgangwalther
|
||||
- #3504, Remove `format` from `rowFilter` parameters in OpenAPI - @dantheman2865
|
||||
- #3660, Fix regression that loaded the schema cache before the in-database configuration - @steve-chavez, @laurenceisla
|
||||
|
||||
## [12.2.2] - 2024-07-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3093, Nested empty embeds no longer show empty values and are correctly omitted - @laurenceisla
|
||||
- #3644, Make --dump-schema work with in-database pgrst.db_schemas setting - @wolfgangwalther
|
||||
- #3644, Show number of timezones in schema cache load report - @wolfgangwalther
|
||||
- #3644, List correct enum options in OpenApi output when multiple types with same name are present - @wolfgangwalther
|
||||
- #3523, Fix schema cache loading retry without backoff - @steve-chavez
|
||||
|
||||
## [12.2.1] - 2024-06-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3147, Don't reload schema cache on every listener failure - @steve-chavez
|
||||
|
||||
### Documentation
|
||||
|
||||
- #3592, Architecture diagram now supports dark mode and has links - @laurenceisla
|
||||
- #3616, The schema isolation diagram now supports dark mode and uses well-known schemas - @laurenceisla
|
||||
|
||||
## [12.2.0] - 2024-06-11
|
||||
|
||||
### Added
|
||||
|
||||
- #2887, Add Preference `max-affected` to limit affected resources - @taimoorzaeem
|
||||
- #3171, Add an ability to dump config via admin API - @skywriter
|
||||
- #3171, #3046, Log schema cache stats to stderr - @steve-chavez
|
||||
- #3210, Dump schema cache through admin API - @taimoorzaeem
|
||||
- #2676, Performance improvement on bulk json inserts, around 10% increase on requests per second by removing `json_typeof` from write queries - @steve-chavez
|
||||
- #3435, Add log-level=debug, for development purposes - @steve-chavez
|
||||
- #1526, Add `/metrics` endpoint on admin server - @steve-chavez
|
||||
- Exposes connection pool metrics, schema cache metrics
|
||||
- #3404, Show the failed MESSAGE or DETAIL in the `details` field of the `PGRST121` (could not parse RAISE 'PGRST') error - @laurenceisla
|
||||
- #3404, Show extra information in the `PGRST121` (could not parse RAISE 'PGRST') error - @laurenceisla
|
||||
+ Shows the failed MESSAGE or DETAIL in the `details` field
|
||||
+ Shows the correct JSON format in the `hints` field
|
||||
- #3340, Log when the LISTEN channel gets a notification - @steve-chavez
|
||||
- #3184, Log full pg version to stderr on connection - @steve-chavez
|
||||
- #3242. Add config `db-hoisted-tx-settings` to apply only hoisted function settings - @taimoorzaeem
|
||||
- #3214, #3229 Log connection pool events on log-level=debug - @steve-chavez, @laurenceisla
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3237, Dump media handlers and timezones with --dump-schema - @wolfgangwalther
|
||||
- #3323, #3324, Don't hide error on LISTEN channel failure - @steve-chavez
|
||||
- #3330, Incorrect admin server `/ready` response on slow schema cache loads - @steve-chavez
|
||||
- #3345, Fix in-database configuration values not loading for `pgrst.server_trace_header` and `pgrst.server_cors_allowed_origins` - @laurenceisla
|
||||
- #3404, Clarify the `PGRST121` (could not parse RAISE 'PGRST') error message - @laurenceisla
|
||||
- #3267, Fix wrong `503 Service Unavailable` on pg error `53400` - @taimoorzaeem
|
||||
- #2985, Fix not adding `application_name` on all connection strings - @steve-chavez
|
||||
- #3424, Admin `/live` and `/ready` now differentiates a failure as 500 status - @steve-chavez
|
||||
+ 503 status is still given when postgREST is in a recovering state
|
||||
- #3478, Media Types are parsed case insensitively - @develop7
|
||||
- #3533, #3536, Fix listener silently failing on read replica - @steve-chavez
|
||||
+ If the LISTEN connection fails, it's retried with exponential backoff
|
||||
- #3414, Force listener to connect to read-write instances using `target_session_attrs` - @steve-chavez
|
||||
- #3255, Fix incorrect `413 Request Entity Too Large` on pg errors `54*` - @taimoorzaeem
|
||||
- #3549, Remove verbosity from error logs starting with "An error occurred..." and replacing it with "Failed to..." - @laurenceisla
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Support for PostgreSQL versions 9.6, 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions. See https://www.postgresql.org/support/versioning/.
|
||||
- `Prefer: params=single-object` is deprecated. Use [a function with a single unnamed JSON parameter](https://postgrest.org/en/latest/references/api/functions.html#function-single-json) instead. - @steve-chavez
|
||||
|
||||
### Documentation
|
||||
|
||||
- #3289, Add dark mode. Can be toggled by a button in the bottom right corner. - @laurenceisla
|
||||
- #3384, Add architecture diagram and documentation - @steve-chavez
|
||||
|
||||
## [12.0.3] - 2024-05-09
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3149, Misleading "Starting PostgREST.." logs on schema cache reloading - @steve-chavez
|
||||
- #3205, Fix wrong subquery error returning a status of 400 Bad Request - @steve-chavez
|
||||
- #3224, Return status code 406 for non-accepted media type instead of code 415 - @wolfgangwalther
|
||||
- #3160, Fix using select= query parameter for custom media type handlers - @wolfgangwalther
|
||||
- #3361, Clarify PGRST204(column not found) error message - @steve-chavez
|
||||
- #3373, Remove rejected mediatype `application/vnd.pgrst.object+json` from response - @taimoorzaeem
|
||||
- #3418, Fix OpenAPI not tagging a FK column correctly on O2O relationships - @laurenceisla
|
||||
- #3256, Fix wrong http status for pg error `42P17 infinite recursion` - @taimoorzaeem
|
||||
|
||||
## [12.0.2] - 2023-12-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3124, Fix table's media type handlers not working for all schemas - @steve-chavez
|
||||
- #3126, Fix empty row on media type handler function - @steve-chavez
|
||||
|
||||
## [12.0.1] - 2023-12-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3054, Fix not allowing special characters in JSON keys - @laurenceisla
|
||||
- #2344, Replace JSON parser error with a clearer generic message - @develop7
|
||||
- #3100, Add missing in-database configuration option for `jwt-cache-max-lifetime` - @laurenceisla
|
||||
- #3089, The any media type handler now sets `Content-Type: application/octet-stream` by default instead of `Content-Type: application/json` - @steve-chavez
|
||||
|
||||
## [12.0.0] - 2023-12-01
|
||||
|
||||
### Added
|
||||
|
||||
- #1614, Add `db-pool-automatic-recovery` configuration to disable connection retrying - @taimoorzaeem
|
||||
- #2492, Allow full response control when raising exceptions - @taimoorzaeem, @laurenceisla
|
||||
- #2771, #2983, #3062, #3055 Add `Server-Timing` response header - @taimoorzaeem, @develop7, @laurenceisla
|
||||
- #2698, Add config `jwt-cache-max-lifetime` and implement JWT caching - @taimoorzaeem
|
||||
- #2943, Add `handling=strict/lenient` for Prefer header - @taimoorzaeem
|
||||
- #2441, Add config `server-cors-allowed-origins` to specify CORS origins - @taimoorzaeem
|
||||
- #2825, SQL handlers for custom media types - @steve-chavez
|
||||
+ Solves #1548, #2699, #2763, #2170, #1462, #1102, #1374, #2901
|
||||
- #2799, Add timezone in Prefer header - @taimoorzaeem
|
||||
- #3001, Add `statement_timeout` set on functions - @taimoorzaeem
|
||||
- #3045, Apply superuser settings on impersonated roles if they have PostgreSQL 15 `GRANT SET ON PARAMETER` privilege - @steve-chavez
|
||||
- #915, Add support for aggregate functions - @timabdulla
|
||||
+ The aggregate functions SUM(), MAX(), MIN(), AVG(), and COUNT() are now supported.
|
||||
+ It's disabled by default, you can enable it with `db-aggregates-enabled`.
|
||||
- #3057, Log all internal database errors to stderr - @laurenceisla
|
||||
|
||||
### Fixed
|
||||
|
||||
- #3015, Fix unnecessary count() on RPC returning single - @steve-chavez
|
||||
- #1070, Fix HTTP status responses for upserts - @taimoorzaeem
|
||||
+ `PUT` returns `201` instead of `200` when rows are inserted
|
||||
+ `POST` with `Prefer: resolution=merge-duplicates` returns `200` instead of `201` when no rows are inserted
|
||||
- #3019, Transaction-Scoped Settings are now shown clearly in the Postgres logs - @laurenceisla
|
||||
+ Shows `set_config('pgrst.setting_name', $1)` instead of `setconfig($1, $2)`
|
||||
+ Does not apply to role settings and `app.settings.*`
|
||||
- #2420, Fix bogus message when listening on port 0 - @develop7
|
||||
- #3067, Fix Acquision Timeout errors logging to stderr when `log-level=crit` - @laurenceisla
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
|
||||
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
|
||||
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
|
||||
- Removed [db-use-legacy-gucs](https://postgrest.org/en/v11.2/references/configuration.html#db-use-legacy-gucs) - @laurenceisla
|
||||
+ All PostgreSQL versions now use GUCs in JSON format for [Headers, Cookies and JWT claims](https://postgrest.org/en/v12/references/transactions.html#request-headers-cookies-and-jwt-claims).
|
||||
|
||||
## [11.2.2] - 2023-10-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2824, Fix regression by reverting fix that returned 206 when first position = length in a `Range` header - @laurenceisla, @strengthless
|
||||
|
||||
## [11.2.1] - 2023-10-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2899, Fix `application/vnd.pgrst.array` not accepted as a valid mediatype - @taimoorzaeem
|
||||
- #2524, Fix schema cache and configuration reloading with `NOTIFY` not working on Windows - @diogob, @laurenceisla
|
||||
- #2915, Fix duplicate headers in response - @taimoorzaeem
|
||||
- #2824, Fix range request with first position same as length return status 206 - @taimoorzaeem
|
||||
- #2939, Fix wrong `Preference-Applied` with `Prefer: tx=commit` when transaction is rollbacked - @steve-chavez
|
||||
- #2939, Fix `count=exact` not being included in `Preference-Applied` - @steve-chavez
|
||||
- #2800, Fix not including to-one embed resources that had a `NULL` value in any of the selected fields when doing null filtering on them - @laurenceisla
|
||||
- #2846, Fix error when requesting `Prefer: count=<type>` and doing null filtering on embedded resources - @laurenceisla
|
||||
- #2959, Fix setting `default_transaction_isolation` unnecessarily - @steve-chavez
|
||||
- #2929, Fix arrow filtering on RPC returning dynamic TABLE with composite type - @steve-chavez
|
||||
- #2963, Fix RPCs not embedding correctly when using overloaded functions for computed relationships - @laurenceisla
|
||||
- #2970, Fix regression that rejects URI connection strings with certain unescaped characters in the password - @laurenceisla, @steve-chavez
|
||||
|
||||
## [11.2.0] - 2023-08-10
|
||||
|
||||
### Added
|
||||
|
||||
- #2523, Data representations - @aljungberg
|
||||
+ Allows for flexible API output formatting and input parsing on a per-column type basis using regular SQL functions configured in the database
|
||||
+ Enables greater flexibility in the form and shape of your APIs, both for output and input, making PostgREST a more versatile general-purpose API server
|
||||
+ Examples include base64 encode/decode your binary data (like a `bytea` column containing an image), choose whether to present a timestamp column as seconds since the Unix epoch or as an ISO 8601 string, or represent fixed precision decimals as strings, not doubles, to preserve precision
|
||||
+ ...and accept the same in `POST/PUT/PATCH` by configuring the reverse transformation(s)
|
||||
+ Other use-cases include custom representation of enums, arrays, nested objects, CSS hex colour strings, gzip compressed fields, metric to imperial conversions, and much more
|
||||
+ Works when using the `select` parameter to select only a subset of columns, embedding through complex joins, renaming fields, with views and computed columns
|
||||
+ Works when filtering on a formatted column without extra indexes by parsing to the canonical representation
|
||||
+ Works for data `RETURNING` operations, such as requesting the full body in a POST/PUT/PATCH with `Prefer: return=representation`
|
||||
+ Works for batch updates and inserts
|
||||
+ Completely optional, define the functions in the database and they will be used automatically everywhere
|
||||
+ Data representations preserve the ability to write to the original column and require no extra storage or complex triggers (compared to using `GENERATED ALWAYS` columns)
|
||||
+ Note: data representations require Postgres 10 (Postgres 11 if using `IN` predicates); data representations are not implemented for RPC
|
||||
- #2647, Allow to verify the PostgREST version in SQL: `select distinct application_name from pg_stat_activity`. - @laurenceisla
|
||||
- #2856, Add the `--version` CLI option that prints the version information - @laurenceisla
|
||||
- #1655, Improve `details` field of the singular error response - @taimoorzaeem
|
||||
- #740, Add `Preference-Applied` in response for `Prefer: return=representation/headers-only/minimal` - @taimoorzaeem
|
||||
- #1601, Add optional `nulls=stripped` parameter for mediatypes `application/vnd.pgrst.array+json` and `application/vnd.pgrst.object+json` - @taimoorzaeem
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2821, Fix OPTIONS not accepting all available media types - @steve-chavez
|
||||
- #2834, Fix compilation on Ubuntu by being compatible with GHC 9.0.2 - @steve-chavez
|
||||
- #2840, Fix `Prefer: missing=default` with DOMAIN default values - @steve-chavez
|
||||
- #2849, Fix HEAD unnecessarily executing aggregates - @steve-chavez
|
||||
- #2594, Fix unused index on jsonb/jsonb arrow filter and order (``/bets?data->>contractId=eq.1`` and ``/bets?order=data->>contractId``) - @steve-chavez
|
||||
- #2861, Fix character and bit columns with fixed length not inserting/updating properly - @laurenceisla
|
||||
+ Fixes the error "value too long for type character(1)" when the char length of the column was bigger than one.
|
||||
- #2862, Fix null filtering on embedded resource when using a column name equal to the relation name - @steve-chavez
|
||||
- #1586, Fix function parameters of type character and bit not ignoring length - @laurenceisla
|
||||
+ Fixes the error "value too long for type character(1)" when the char length of the parameter was bigger than one.
|
||||
- #2881, Fix error when a function returns `RECORD` or `SET OF RECORD` - @laurenceisla
|
||||
- #2896, Fix applying superuser settings for impersonated role - @steve-chavez
|
||||
|
||||
### Deprecated
|
||||
|
||||
- #2863, Deprecate resource embedding target disambiguation - @steve-chavez
|
||||
+ The `/table?select=*,other!fk(*)` must be used to disambiguate
|
||||
+ The server aids in choosing the `!fk` by sending a `hint` on the error whenever an ambiguous request happens.
|
||||
|
||||
## [11.1.0] - 2023-06-07
|
||||
|
||||
### Added
|
||||
|
||||
- #2786, Limit idle postgresql connection lifetime - @robx
|
||||
+ New option `db-pool-max-idletime` (default 30s).
|
||||
+ This is equivalent to the old option `db-pool-timeout` of PostgREST 10.0.0.
|
||||
+ A config alias for `db-pool-timeout` is included.
|
||||
- #2703, Add pre-config function - @steve-chavez
|
||||
+ New config option `db-pre-config`(empty by default)
|
||||
+ Allows using the in-database configuration without SUPERUSER
|
||||
- #2781, When `db-channel-enabled` is false, start automatic connection recovery on a new request when pool connections are closed with `pg_terminate_backend` - @steve-chavez
|
||||
+ Mitigates the lack of LISTEN/NOTIFY for schema cache reloading on read replicas.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2791, Fix dropping schema cache reload notifications - @steve-chavez
|
||||
- #2801, Stop retrying connection when "no password supplied" - @steve-chavez
|
||||
|
||||
## [11.0.1] - 2023-04-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2762, Fixes "permission denied for schema" error during schema cache load - @steve-chavez
|
||||
- #2756, Fix bad error message on generated columns when using `Prefer: missing=default` - @steve-chavez
|
||||
- #1139, Allow a 30 second skew for JWT validation - @steve-chavez
|
||||
+ It used to be 1 second, which was too strict
|
||||
|
||||
## [11.0.0] - 2023-04-16
|
||||
|
||||
### Added
|
||||
|
||||
- #1414, Add related orders - @steve-chavez
|
||||
+ On a many-to-one or one-to-one relationship, you can order a parent by a child column `/projects?select=*,clients(*)&order=clients(name).desc.nullsfirst`
|
||||
- #1233, #1907, #2566, Allow spreading embedded resources - @steve-chavez
|
||||
+ On a many-to-one or one-to-one relationship, you can unnest a json object with `/projects?select=*,...clients(client_name:name)`
|
||||
+ Allows including the join table columns when resource embedding
|
||||
+ Allows disambiguating a recursive m2m embed
|
||||
+ Allows disambiguating an embed that has a many-to-many relationship using two foreign keys on a junction
|
||||
- #2340, Allow embedding without selecting any column - @steve-chavez
|
||||
- #2563, Allow `is.null` or `not.is.null` on an embedded resource - @steve-chavez
|
||||
+ Offers a more flexible replacement for `!inner`, e.g. `/projects?select=*,clients(*)&clients=not.is.null`
|
||||
+ Allows doing an anti join, e.g. `/projects?select=*,clients(*)&clients=is.null`
|
||||
+ Allows using or across related tables conditions
|
||||
- #1100, Customizable OpenAPI title - @AnthonyFisi
|
||||
- #2506, Add `server-trace-header` for tracing HTTP requests. - @steve-chavez
|
||||
+ When the client sends the request header specified in the config it will be included in the response headers.
|
||||
- #2694, Make `db-root-spec` stable. - @steve-chavez
|
||||
+ This can be used to override the OpenAPI spec with a custom database function
|
||||
- #1567, On bulk inserts, missing values can get the column DEFAULT by using the `Prefer: missing=default` header - @steve-chavez
|
||||
- #2501, Allow filtering by`IS DISTINCT FROM` using the `isdistinct` operator, e.g. `/people?alias=isdistinct.foo`
|
||||
- #1569, Allow `any/all` modifiers on the `eq,like,ilike,gt,gte,lt,lte,match,imatch` operators, e.g. `/tbl?id=eq(any).{1,2,3}` - @steve-chavez
|
||||
- This converts the input into an array type
|
||||
- #2561, Configurable role settings - @steve-chavez
|
||||
- Database roles that are members of the connection role get their settings applied, e.g. doing
|
||||
`ALTER ROLE anon SET statement_timeout TO '5s'` will result in that `statement_timeout` getting applied for that role.
|
||||
- Works when switching roles when a JWT is sent
|
||||
- Settings can be reloaded with `NOTIFY pgrst, 'reload config'`.
|
||||
- #2468, Configurable transaction isolation level with `default_transaction_isolation` - @steve-chavez
|
||||
- Can be set per function `create function .. set default_transaction_isolation = 'repeatable read'`
|
||||
- Or per role `alter role .. set default_transaction_isolation = 'serializable'`
|
||||
|
||||
### Fixed
|
||||
|
||||
- #2651, Add the missing `get` path item for RPCs to the OpenAPI output - @laurenceisla
|
||||
- #2648, Fix inaccurate error codes with new ones - @laurenceisla
|
||||
+ `PGRST204`: Column is not found
|
||||
+ `PGRST003`: Timed out when acquiring connection to db
|
||||
- #1652, Fix function call with arguments not inlining - @steve-chavez
|
||||
- #2705, Fix bug when using the `Range` header on `PATCH/DELETE` - @laurenceisla
|
||||
+ Fix the`"message": "syntax error at or near \"RETURNING\""` error
|
||||
+ Fix doing a limited update/delete when an `order` query parameter was present
|
||||
- #2742, Fix db settings and pg version queries not getting prepared - @steve-chavez
|
||||
- #2618, Fix `PATCH` requests not recognizing embedded filters and using the top-level resource instead - @steve-chavez
|
||||
|
||||
### Changed
|
||||
|
||||
- #2705, The `Range` header is now only considered on `GET` requests and is ignored for any other method - @laurenceisla
|
||||
+ Other methods should use the `limit/offset` query parameters for sub-ranges
|
||||
+ `PUT` requests no longer return an error when this header is present (using `limit/offset` still triggers the error)
|
||||
- #2733, Remove bulk RPC call with the `Prefer: params=multiple-objects` header. A function with a JSON array or object parameter should be used instead.
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [10.2.0] - 2023-04-12
|
||||
|
||||
@@ -763,7 +36,6 @@ All notable changes to this project will be documented in this file. From versio
|
||||
|
||||
- #2548, Fix regression when embedding views with partial references to multi column FKs - @wolfgangwalther
|
||||
- #2558, Fix regression when requesting limit=0 and `db-max-row` is set - @laurenceisla
|
||||
- #2542, Return a clear error without hitting the database when trying to update or insert an unknown column with `?columns` - @aljungberg
|
||||
|
||||
## [10.1.0] - 2022-10-28
|
||||
|
||||
@@ -805,7 +77,7 @@ All notable changes to this project will be documented in this file. From versio
|
||||
### Added
|
||||
|
||||
- #1933, #2109, Add a minimal health check endpoint - @steve-chavez
|
||||
+ For enabling this, the `admin-server-port` config must be set explicitly
|
||||
+ For enabling this, the `admin-server-port` config must be set explictly
|
||||
+ A `<host>:<admin_server_port>/live` endpoint is available for checking if postgrest is running on its port/socket. 200 OK = alive, 503 = dead.
|
||||
+ A `<host>:<admin_server_port>/ready` endpoint is available for checking a correct internal state(the database connection plus the schema cache). 200 OK = ready, 503 = not ready.
|
||||
- #1988, Add the current user to the request log on stdout - @DavidLindbom, @wolfgangwalther
|
||||
@@ -883,11 +155,6 @@ All notable changes to this project will be documented in this file. From versio
|
||||
- #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
|
||||
|
||||
@@ -1288,7 +555,7 @@ All notable changes to this project will be documented in this file. From versio
|
||||
- Customize content negotiation per route - @begriffs
|
||||
- Allow using nulls order without explicit order direction - @steve-chavez
|
||||
- Fatal error on postgres unsupported version, format supported version in error message - @steve-chavez
|
||||
- Prevent database memory consumption by prepared statements caches - @ruslantalpa
|
||||
- Prevent database memory cosumption by prepared statements caches - @ruslantalpa
|
||||
- Use specific columns in the RETURNING section - @ruslantalpa
|
||||
- Fix columns alias for RETURNING - @steve-chavez
|
||||
|
||||
|
||||
@@ -1,64 +1,3 @@
|
||||
# Contributing to PostgREST
|
||||
This repository follows the same contribution guidelines as the main PostgREST repository contribution guidelines:
|
||||
|
||||
## AI Policy
|
||||
|
||||
We adhere to [Gentoo's AI policy](https://wiki.gentoo.org/wiki/Project:Council/AI_policy):
|
||||
|
||||
> It is expressly forbidden to contribute [...] any content that has been created with the assistance of Natural Language Processing artificial intelligence tools. This motion can be revisited, should a case been made over such a tool that does not pose copyright, ethical and quality concerns.
|
||||
|
||||
You can find more about its rationale [here](https://wiki.gentoo.org/wiki/Project:Council/AI_policy#Rationale).
|
||||
|
||||
## Issues
|
||||
|
||||
For questions on how to use PostgREST, please use
|
||||
[GitHub discussions](https://github.com/PostgREST/postgrest/discussions).
|
||||
|
||||
### Reporting an Issue
|
||||
|
||||
* Make sure you test against the latest [stable release](https://github.com/PostgREST/postgrest/releases/latest)
|
||||
and also against the latest [devel release](https://github.com/PostgREST/postgrest/releases/tag/devel).
|
||||
It is possible we already fixed the bug you're experiencing.
|
||||
|
||||
* Provide steps to reproduce the issue, including your OS version and
|
||||
the specific database schema that you are using.
|
||||
|
||||
* Please include SQL logs for issues involving runtime problems. To obtain logs first
|
||||
[enable logging all statements](http://www.microhowto.info/howto/log_all_queries_to_a_postgresql_server.html),
|
||||
then [find your logs](http://blog.endpoint.com/2014/11/dear-postgresql-where-are-my-logs.html).
|
||||
|
||||
* If your database schema has changed while the PostgREST server is running,
|
||||
[send the server a `SIGUSR1` signal](http://postgrest.org/en/latest/admin.html#schema-reloading) or restart it to ensure the schema cache
|
||||
is not stale. This sometimes fixes apparent bugs.
|
||||
|
||||
## Code
|
||||
|
||||
We have a fully nix-based development environment with many tools for a smooth development workflow available.
|
||||
Check the [development docs](https://github.com/PostgREST/postgrest/blob/main/nix/README.md) on how to set it up and use it.
|
||||
|
||||
* All contributions must pass the tests before being merged. When
|
||||
you create a pull request your code will automatically be tested.
|
||||
|
||||
* All fixes or features must have a test proving the improvement.
|
||||
|
||||
* All features must document the new behavior. Critical fixes that introduce new behavior must be documented too.
|
||||
|
||||
* All code must also pass a [linter](http://community.haskell.org/~ndm/hlint/) and [styler](https://github.com/jaspervdj/stylish-haskell)
|
||||
with no warnings. This helps enforce a uniform style for all committers. Continuous integration will check this as well on every
|
||||
pull request. There are useful tools in the nix-shell that help with checking this locally. You can run `postgrest-check` to do this manually but
|
||||
we recommend adding it to `.git/hooks/pre-commit` as `nix-shell --run postgrest-check` to automatically check this before doing a commit.
|
||||
|
||||
### Running Tests
|
||||
|
||||
For instructions on running tests, see the [development docs](https://github.com/PostgREST/postgrest/blob/main/nix/README.md#testing).
|
||||
|
||||
### Structuring commits in pull requests
|
||||
|
||||
To simplify reviews, make it easy to split pull requests if deemed necessary, and to maintain clean and meaningful history of changes, you will be asked to update your PR if it does not follow the below rules:
|
||||
|
||||
* It must be possible to merge the PR branch into target using `git merge --ff-only`, ie. the source branch must be rebased on top of target.
|
||||
* No merge commits in the source branch.
|
||||
* All commits in the source branch must be self contained, meaning: it should be possible to treat each commit as a separate PR.
|
||||
* Commits in the source branch must contain only related changes (related means the changes target a single problem/goal). For example, any refactorings should be isolated from the actual change implementation into separate commits.
|
||||
* Tests, documentation, and changelog updates should be contained in the same commits as the actual code changes they relate to. An exception to this rule is when test or documentation changes are made in separate PR.
|
||||
* Commit messages must be prefixed with one of the prefixes defined in [the list used by commit verification scripts](https://github.com/PostgREST/postgrest/blob/main/nix/tools/gitTools.nix#L11).
|
||||
* Commit messages should contain a longer description of the purpose of the changes contained in the commit and, for non-trivial changes, a description of the changes themselves.
|
||||
https://github.com/PostgREST/postgrest/blob/main/.github/CONTRIBUTING.md
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Copyright (c) 2014-2026 The PostgREST contributors
|
||||
Copyright (c) 2014 Joe Nelson
|
||||
Copyright (c) 2019 Steve Chavez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
[](https://www.patreon.com/postgrest)
|
||||
[](http://postgrest.org)
|
||||
@@ -13,35 +13,40 @@ API than you are likely to write from scratch.
|
||||
|
||||
## Sponsors
|
||||
|
||||
<table align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/cybertec.svg">
|
||||
<img width="222px" src="static/cybertec-new.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.svg">
|
||||
<a href="https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo" target="_blank">
|
||||
<img width="296px" src="static/2ndquadrant.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.euronodes.com/postgrest" target="_blank">
|
||||
<img width="296px" src="static/euronodes.svg">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/retool.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/neon.jpg">
|
||||
<a href="https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/gnuhost.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/bytebase.svg">
|
||||
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
|
||||
<img width="296px" src="static/supabase.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://oblivious.ai/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
|
||||
<img width="296px" src="static/oblivious.jpg">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -52,8 +57,8 @@ Big thanks to our sponsors! You can join them by supporting PostgREST on [Patreo
|
||||
|
||||
## Usage
|
||||
|
||||
1. See the docs for [how to install PostgREST on your platform](https://docs.postgrest.org/en/stable/explanations/install.html). You can also [use Docker](https://docs.postgrest.org/en/stable/explanations/install.html#docker).
|
||||
|
||||
1. Download the binary ([latest release](https://github.com/PostgREST/postgrest/releases/latest))
|
||||
for your platform.
|
||||
2. Invoke for help:
|
||||
|
||||
```bash
|
||||
@@ -61,7 +66,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
|
||||
|
||||
@@ -125,7 +130,7 @@ and limited with - range headers. More about
|
||||
## Data Integrity
|
||||
|
||||
Rather than relying on an Object Relational Mapper and custom
|
||||
imperative coding, this system requires you to put declarative constraints
|
||||
imperative coding, this system requires you put declarative constraints
|
||||
directly into your database. Hence no application can corrupt your
|
||||
data (including your API server).
|
||||
|
||||
@@ -142,10 +147,6 @@ You can help PostgREST ongoing maintenance and development by making a regular d
|
||||
|
||||
Every donation will be spent on making PostgREST better for the whole community.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are always welcome and appreciated. Please see the [Contributing guidelines](https://github.com/PostgREST/postgrest/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## Thanks
|
||||
|
||||
The PostgREST organization is grateful to:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
packages: postgrest.cabal
|
||||
tests: true
|
||||
allow-newer:
|
||||
hasql:postgresql-libpq
|
||||
|
||||
-- https://github.com/martijnbastiaan/doctest-parallel/blob/main/example/README.md#cabalproject
|
||||
write-ghc-environment-files: always
|
||||
@@ -1 +0,0 @@
|
||||
index-state: hackage.haskell.org 2026-08-10T16:58:32Z
|
||||
@@ -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,33 +1,14 @@
|
||||
{ system ? builtins.currentSystem
|
||||
|
||||
, compiler ? "ghc9123"
|
||||
|
||||
, # Commit of the Nixpkgs repository that we want to use.
|
||||
# It defaults to reading the inputs from flake.lock, which serves
|
||||
# as a compatibility layer for non-flake builds / default.nix / shell.nix.
|
||||
nixpkgsVersion ? let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
in
|
||||
{
|
||||
inherit (lock.nodes.nixpkgs.locked) owner repo rev;
|
||||
tarballHash = lock.nodes.nixpkgs.locked.narHash;
|
||||
}
|
||||
|
||||
, # Nix files that describe the Nixpkgs repository. We evaluate the expression
|
||||
# using `import` below.
|
||||
nixpkgs ? let inherit (nixpkgsVersion) owner repo rev tarballHash; in
|
||||
builtins.fetchTarball {
|
||||
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
|
||||
sha256 = tarballHash;
|
||||
}
|
||||
}:
|
||||
{ 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 little as possible, as the files
|
||||
# and file extensions. We want to include as litte as possible, as the files
|
||||
# added here will increase the space used in the Nix store and trigger the
|
||||
# build of new Nix derivations when changed.
|
||||
src =
|
||||
@@ -35,6 +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;
|
||||
|
||||
@@ -43,6 +36,10 @@ let
|
||||
allOverlays.build-toolbox
|
||||
allOverlays.checked-shell-script
|
||||
allOverlays.gitignore
|
||||
allOverlays.postgis
|
||||
(allOverlays.postgresql-default { inherit patches; })
|
||||
allOverlays.postgresql-legacy
|
||||
allOverlays.postgresql-future
|
||||
(allOverlays.haskell-packages { inherit compiler; })
|
||||
];
|
||||
|
||||
@@ -52,71 +49,62 @@ let
|
||||
|
||||
postgresqlVersions =
|
||||
[
|
||||
{ name = "pg-19"; postgresql = pkgs.postgresql_19.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-18"; postgresql = pkgs.postgresql_18.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-17"; postgresql = pkgs.postgresql_17.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-16"; postgresql = pkgs.postgresql_16.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{ name = "pg-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
|
||||
{
|
||||
name = "oriole-18";
|
||||
postgresql = pkgs.orioledb.withPackages (p: [ p.postgis p.pg_safeupdate ]);
|
||||
config = "
|
||||
default_table_access_method = 'orioledb'
|
||||
shared_preload_libraries = 'orioledb, pg_stat_statements'
|
||||
";
|
||||
}
|
||||
{ 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 ]); }
|
||||
];
|
||||
|
||||
haskellPackages = pkgs.haskell.packages."${compiler}";
|
||||
patches =
|
||||
pkgs.callPackage nix/patches { };
|
||||
|
||||
# Dynamic derivation for PostgREST
|
||||
postgrest = pkgs.lib.pipe (haskellPackages.callCabal2nix name src { }) [
|
||||
# To allow ghc-datasize to be used.
|
||||
lib.disableLibraryProfiling
|
||||
# We are never going to use dynamic haskell libraries anyway. "Dynamic" refers to how
|
||||
# non-haskell deps are linked. All haskell dependencies are always statically linked.
|
||||
lib.disableSharedLibraries
|
||||
];
|
||||
postgrest =
|
||||
pkgs.haskell.packages."${compiler}".callCabal2nix name src { };
|
||||
|
||||
staticHaskellPackage = import nix/static.nix { inherit compiler name pkgs src; };
|
||||
# Function that derives a fully static Haskell package based on
|
||||
# nh2/static-haskell-nix
|
||||
staticHaskellPackage =
|
||||
import nix/static-haskell-package.nix { inherit nixpkgs system compiler patches allOverlays; };
|
||||
|
||||
# Options passed to cabal in dev tools and tests
|
||||
devCabalOptions =
|
||||
"-f dev --test-show-detail=direct";
|
||||
|
||||
inherit (pkgs.haskell) lib;
|
||||
profiledHaskellPackages =
|
||||
pkgs.haskell.packages."${compiler}".extend (self: super:
|
||||
{
|
||||
mkDerivation =
|
||||
args:
|
||||
super.mkDerivation (args // { enableLibraryProfiling = true; });
|
||||
}
|
||||
);
|
||||
|
||||
nixos-lib = import (pkgs.path + "/nixos/lib") { };
|
||||
runTest = postgrest: test: (nixos-lib.runTest {
|
||||
hostPkgs = pkgs;
|
||||
# Replace the top-level `pkgs.postgrest` attribute with our current version on this branch.
|
||||
defaults.nixpkgs.overlays = [ (_: _: { inherit postgrest; }) ];
|
||||
# Speeds up evaluation a little bit; documentation is really not required for tests.
|
||||
defaults.documentation.enable = pkgs.lib.mkDefault false;
|
||||
imports = [ test ];
|
||||
}).config.result;
|
||||
inherit (pkgs.haskell) lib;
|
||||
in
|
||||
rec {
|
||||
inherit nixpkgs pkgs;
|
||||
|
||||
# Derivation for the PostgREST Haskell package, including the executable,
|
||||
# libraries and documentation. We disable running the test suite on Nix
|
||||
# builds, as they require a database to be set up. We split the binary
|
||||
# into a separate output, so that the default distribution via flake.nix
|
||||
# has a much smaller closure size.
|
||||
postgrestPackage = pkgs.lib.pipe postgrest [
|
||||
lib.dontCheck
|
||||
lib.enableSeparateBinOutput
|
||||
(haskellPackages.generateOptparseApplicativeCompletions [ "postgrest" ])
|
||||
];
|
||||
# builds, as they require a database to be set up.
|
||||
postgrestPackage =
|
||||
lib.dontCheck postgrest;
|
||||
|
||||
# Static executable.
|
||||
postgrestStatic =
|
||||
lib.justStaticExecutables (lib.dontCheck (staticHaskellPackage name src));
|
||||
|
||||
# Profiled dynamic executable.
|
||||
postgrestProfiled = pkgs.lib.pipe postgrestPackage [
|
||||
lib.enableExecutableProfiling
|
||||
lib.enableLibraryProfiling
|
||||
lib.dontHaddock
|
||||
];
|
||||
postgrestProfiled =
|
||||
lib.enableExecutableProfiling (
|
||||
lib.dontHaddock (
|
||||
lib.dontCheck (profiledHaskellPackages.callCabal2nix name src { })
|
||||
)
|
||||
);
|
||||
|
||||
inherit (postgrest) env;
|
||||
|
||||
@@ -126,40 +114,34 @@ rec {
|
||||
inherit (pkgs.haskell.packages."${compiler}") ghcWithPackages;
|
||||
};
|
||||
|
||||
# Used by CI on MacOS
|
||||
inherit (pkgs) nix-build-uncached;
|
||||
|
||||
### Tools
|
||||
|
||||
cabalTools =
|
||||
pkgs.callPackage nix/tools/cabalTools.nix { inherit devCabalOptions postgrest; };
|
||||
|
||||
withTools =
|
||||
pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions postgrest; };
|
||||
|
||||
# Development tools.
|
||||
devTools =
|
||||
pkgs.callPackage nix/tools/devTools.nix { inherit tests style devCabalOptions hsie; };
|
||||
pkgs.callPackage nix/tools/devTools.nix { inherit tests style devCabalOptions hsie withTools; };
|
||||
|
||||
# Documentation tools.
|
||||
docs =
|
||||
pkgs.callPackage nix/tools/docs.nix { };
|
||||
|
||||
# Git tools.
|
||||
gitTools =
|
||||
pkgs.callPackage nix/tools/gitTools.nix { };
|
||||
# Docker images and loading script.
|
||||
docker =
|
||||
pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; };
|
||||
|
||||
# Load testing tools.
|
||||
loadtest =
|
||||
pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; };
|
||||
|
||||
# Script for running memory tests.
|
||||
memory =
|
||||
pkgs.callPackage nix/tools/memory.nix { inherit postgrestProfiled withTools; };
|
||||
|
||||
# Utility for updating the pinned version of Nixpkgs.
|
||||
nixpkgsTools =
|
||||
pkgs.callPackage nix/tools/nixpkgsTools.nix { };
|
||||
|
||||
# Scripts for publishing new releases.
|
||||
release =
|
||||
pkgs.callPackage nix/tools/release.nix { };
|
||||
pkgs.callPackage nix/tools/release { };
|
||||
|
||||
# Linting and styling tools.
|
||||
style =
|
||||
@@ -173,15 +155,7 @@ rec {
|
||||
inherit (pkgs.haskell.packages."${compiler}") hpc-codecov;
|
||||
inherit (pkgs.haskell.packages."${compiler}") weeder;
|
||||
};
|
||||
} // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux rec {
|
||||
# Static executable.
|
||||
inherit (staticHaskellPackage) postgrestStatic;
|
||||
inherit (staticHaskellPackage) packagesStatic;
|
||||
|
||||
# Docker images and loading script.
|
||||
docker =
|
||||
pkgs.callPackage nix/tools/docker { postgrest = postgrestStatic; };
|
||||
|
||||
# NixOS VM tests
|
||||
nixpkgs-nixos-test = runTest postgrestStatic (pkgs.path + "/nixos/tests/postgrest.nix");
|
||||
withTools =
|
||||
pkgs.callPackage nix/tools/withTools.nix { inherit devCabalOptions postgresqlVersions postgrest; };
|
||||
}
|
||||
|
||||
@@ -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,36 @@ 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/
|
||||
## LaTeX
|
||||
|
||||
## UML
|
||||
The schema structure diagram is done with LaTeX. You can use a GUI like https://www.mathcha.io/editor to create the .tex file.
|
||||
|
||||
The UML diagrams are created with https://plantuml.com/.
|
||||
|
||||
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,15 +0,0 @@
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Box_Office]
|
||||
*bo_date
|
||||
*+film_id
|
||||
gross_revenue
|
||||
|
||||
[Films]
|
||||
*id
|
||||
+director_id
|
||||
title
|
||||
`...`
|
||||
|
||||
Box_Office +--1 Films
|
||||
@@ -1,12 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Employees]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
+supervisor_id
|
||||
|
||||
Employees 1--* Employees
|
||||
@@ -1,16 +0,0 @@
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Premieres]
|
||||
*id
|
||||
location
|
||||
date
|
||||
+film_id
|
||||
|
||||
[Films]
|
||||
*id
|
||||
+director_id
|
||||
title
|
||||
`...`
|
||||
|
||||
Premieres *--1 Films
|
||||
@@ -1,12 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Presidents]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
+predecessor_id
|
||||
|
||||
Presidents 1--? Presidents
|
||||
@@ -1,18 +0,0 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Users]
|
||||
*id
|
||||
first_name
|
||||
last_name
|
||||
username
|
||||
|
||||
[Subscriptions]
|
||||
*+subscriber_id
|
||||
*+subscribed_id
|
||||
type
|
||||
|
||||
Users 1--* Subscriptions
|
||||
Subscriptions *--1 Users
|
||||
@@ -1,6 +1,3 @@
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeSerif"}
|
||||
|
||||
[Films]
|
||||
*id
|
||||
+director_id
|
||||
@@ -34,12 +31,6 @@ year
|
||||
*+film_id
|
||||
rank
|
||||
|
||||
[Technical_Specs]
|
||||
*+film_id
|
||||
runtime
|
||||
camera
|
||||
sound
|
||||
|
||||
Roles *--1 Actors
|
||||
Roles *--1 Films
|
||||
|
||||
@@ -47,5 +38,3 @@ Nominations *--1 Competitions
|
||||
Nominations *--1 Films
|
||||
|
||||
Films *--1 Directors
|
||||
|
||||
Films 1--1 Technical_Specs
|
||||
@@ -1,8 +1,3 @@
|
||||
# Build using: -e ortho
|
||||
|
||||
entity {font: "FreeSans"}
|
||||
relationship {font: "FreeMono"}
|
||||
|
||||
[Addresses]
|
||||
*id
|
||||
name
|
||||
@@ -1,96 +0,0 @@
|
||||
@startuml
|
||||
|
||||
skinparam backgroundColor transparent
|
||||
|
||||
package "PostgREST" {
|
||||
() HTTP as HTTPAPI
|
||||
HTTPAPI - [Auth]
|
||||
[Auth] -r.> [ApiRequest]
|
||||
[ApiRequest] -r.> [Plan]
|
||||
[Plan] -r.> [Query]
|
||||
[Query] - () "Connection Pool" : "\t"
|
||||
[Plan] -u-> [Schema Cache]:uses
|
||||
[Schema Cache] <- () Listener : reloads
|
||||
|
||||
() HTTP as HTTPADMIN
|
||||
[Admin] -r- () HTTPADMIN
|
||||
[Config] -l- () CLI
|
||||
|
||||
[Config] <-r~ Listener
|
||||
|
||||
HTTPADMIN -[hidden]r- CLI
|
||||
[Schema Cache] -l[hidden]- [Config]
|
||||
[Schema Cache] -l[hidden]- [Admin]
|
||||
[Schema Cache] -l[hidden]- CLI
|
||||
}
|
||||
|
||||
|
||||
database "PostgreSQL" {
|
||||
node Authorization {
|
||||
rectangle "Roles, GRANT, RLS"
|
||||
}
|
||||
node "API schema" as API {
|
||||
rectangle "Functions, Views"
|
||||
}
|
||||
rectangle "Tables, extensions" as tbs
|
||||
API -d- tbs
|
||||
|
||||
API -l[hidden]- Authorization
|
||||
}
|
||||
|
||||
:user:
|
||||
hexagon Proxy
|
||||
:user: .r-> Proxy : request with JWT
|
||||
HTTPAPI <.l- Proxy
|
||||
|
||||
hexagon ExternalAuth
|
||||
ExternalAuth -u[hidden]- Proxy
|
||||
:user: .r-> ExternalAuth : login
|
||||
:user: <.r- ExternalAuth : JWT
|
||||
|
||||
:operator: .d-> HTTPADMIN
|
||||
:operator: .d-> CLI
|
||||
|
||||
|
||||
PostgreSQL <.developer : "\t"
|
||||
Listener -r.> "PostgreSQL"
|
||||
"Connection Pool" -r.> "PostgreSQL" : "\t\t"
|
||||
|
||||
note bottom of Auth
|
||||
Validates the JWT
|
||||
end note
|
||||
|
||||
note bottom of ApiRequest
|
||||
Parses the URL syntax
|
||||
end note
|
||||
|
||||
note bottom of Plan
|
||||
Generates internal AST
|
||||
end note
|
||||
|
||||
note bottom of Query
|
||||
Generates the SQL
|
||||
end note
|
||||
|
||||
note top of Listener
|
||||
LISTEN session
|
||||
end note
|
||||
|
||||
url of ExternalAuth is [[../explanations/external_auth.html]]
|
||||
url of Admin is [[../references/admin_server.html#admin-server]]
|
||||
url of API is [[../explanations/schema_isolation.html]]
|
||||
url of Auth is [[../references/auth.html#authn]]
|
||||
url of ApiRequest is [[../explanations/architecture.html#api-request]]
|
||||
url of Plan is [[../explanations/architecture.html#plan]]
|
||||
url of Query is [[../explanations/architecture.html#query]]
|
||||
url of Authorization is [[../explanations/db_authz.html]]
|
||||
url of CLI is [[../references/cli.html#cli]]
|
||||
url of "Connection Pool" is [[../references/connection_pool.html]]
|
||||
url of Config is [[../references/configuration.html#configuration]]
|
||||
url of HTTPADMIN is [[../references/http_server.html]]
|
||||
url of HTTPAPI is [[../references/http_server.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: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@@ -28,7 +28,6 @@ div.line-block {
|
||||
|
||||
#sponsors img{
|
||||
margin: 10px;
|
||||
width: 13em; /* ".. image::" does not apply width properly to SVGs */
|
||||
}
|
||||
|
||||
#thanks{
|
||||
@@ -66,90 +65,3 @@ div.line-block {
|
||||
.wy-table-responsive {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
#tutorials span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#references span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#explanations span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#how-tos span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#ecosystem span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#integrations span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#api span.caption-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tweaks for dark mode from extension: sphinx-rtd-dark-theme */
|
||||
|
||||
html[data-theme="dark"] .highlight {
|
||||
background-color: #17181c !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .sphinx-tabs-tab {
|
||||
color: var(--dark-link-color);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .sphinx-tabs-panel {
|
||||
border: 1px solid #404040;
|
||||
border-top: 0;
|
||||
background: #141414;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
|
||||
border: 1px solid #404040;
|
||||
border-bottom: 1px solid #141414;
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] [role="tablist"] {
|
||||
border-bottom: 1px solid #404040;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .btn-neutral {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .img-dark {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
html:not([data-theme="dark"]) .img-dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .img-light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html:not([data-theme="dark"]) .img-light {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .img-translucent img {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
.img-translucent img {
|
||||
transition: background-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.svg-container-md {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 156 B After Width: | Height: | Size: 468 B |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,385 @@
|
||||
.. _admin:
|
||||
|
||||
Hardening PostgREST
|
||||
===================
|
||||
|
||||
PostgREST is a fast way to construct a RESTful API. Its default behavior is great for scaffolding in development. When it's time to go to production it works great too, as long as you take precautions. PostgREST is a small sharp tool that focuses on performing the API-to-database mapping. We rely on a reverse proxy like Nginx for additional safeguards.
|
||||
|
||||
The first step is to create an Nginx configuration file that proxies requests to an underlying PostgREST server.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
http {
|
||||
# ...
|
||||
# upstream configuration
|
||||
upstream postgrest {
|
||||
server localhost:3000;
|
||||
}
|
||||
# ...
|
||||
server {
|
||||
# ...
|
||||
# expose to the outside world
|
||||
location /api/ {
|
||||
default_type application/json;
|
||||
proxy_hide_header Content-Location;
|
||||
add_header Content-Location /api/$upstream_http_content_location;
|
||||
proxy_set_header Connection "";
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://postgrest/;
|
||||
}
|
||||
# ...
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
For ubuntu, if you already installed nginx through :code:`apt` you can add this to the config file in
|
||||
:code:`/etc/nginx/sites-enabled/default`.
|
||||
|
||||
.. _block_fulltable:
|
||||
|
||||
Block Full-Table Operations
|
||||
---------------------------
|
||||
|
||||
Each table in the admin-selected schema gets exposed as a top level route. Client requests are executed by certain database roles depending on their authentication. All HTTP verbs are supported that correspond to actions permitted to the role. For instance if the active role can drop rows of the table then the DELETE verb is allowed for clients. Here's an API request to delete old rows from a hypothetical logs table:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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
|
||||
|
||||
However it's very easy to delete the **entire table** by omitting the query parameter!
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -E pgxn install safeupdate
|
||||
|
||||
# then add this to postgresql.conf:
|
||||
# shared_preload_libraries='safeupdate';
|
||||
|
||||
This does not protect against malicious actions, since someone can add a url parameter that does not affect the result set. To prevent this you must turn to database permissions, forbidding the wrong people from deleting rows, and using `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ if finer access control is required.
|
||||
|
||||
Count-Header DoS
|
||||
----------------
|
||||
|
||||
For convenience to client-side pagination controls PostgREST supports counting and reporting total table size in its response. As described in :ref:`limits`, responses ordinarily include a range but leave the total unspecified like
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Range-Unit: items
|
||||
Content-Range: 0-14/*
|
||||
|
||||
However including the request header :code:`Prefer: count=exact` calculates and includes the full count:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 206 Partial Content
|
||||
Range-Unit: items
|
||||
Content-Range: 0-14/3573458
|
||||
|
||||
This is fine in small tables, but count performance degrades in big tables due to the MVCC architecture of PostgreSQL. For very large tables it can take a very long time to retrieve the results which allows a denial of service attack. The solution is to strip this header from all requests:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- Pending nginx config: Remove any prefer header which contains the word count
|
||||
|
||||
.. _https:
|
||||
|
||||
HTTPS
|
||||
-----
|
||||
|
||||
PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how <https://nginx.org/en/docs/http/configuring_https_servers.html>`_. Note that some Platforms as a Service like Heroku also add SSL automatically in their load balancer.
|
||||
|
||||
Rate Limiting
|
||||
-------------
|
||||
|
||||
Nginx supports "leaky bucket" rate limiting (see `official docs <https://nginx.org/en/docs/http/ngx_http_limit_req_module.html>`_). Using standard Nginx configuration, routes can be grouped into *request zones* for rate limiting. For instance we can define a zone for login attempts:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
|
||||
|
||||
This creates a shared memory zone called "login" to store a log of IP addresses that access the rate limited urls. The space reserved, 10 MB (:code:`10m`) will give us enough space to store a history of 160k requests. We have chosen to allow only allow one request per second (:code:`1r/s`).
|
||||
|
||||
Next we apply the zone to certain routes, like a hypothetical stored procedure called :code:`login`.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
location /rpc/login/ {
|
||||
# apply rate limiting
|
||||
limit_req zone=login burst=5;
|
||||
}
|
||||
|
||||
The burst argument tells Nginx to start dropping requests if more than five queue up from a specific IP.
|
||||
|
||||
Nginx rate limiting is general and indiscriminate. To rate limit each authenticated request individually you will need to add logic in a :ref:`Custom Validation <custom_validation>` function.
|
||||
|
||||
.. _external_connection_poolers:
|
||||
|
||||
Using External Connection Poolers
|
||||
---------------------------------
|
||||
|
||||
PostgREST manages its :ref:`own pool of connections <db-pool>` and uses prepared statements by default in order to increase performance. However, this setting is incompatible with external connection poolers such as PgBouncer working in transaction pooling mode. In this case, you need to set the :ref:`db-prepared-statements` config option to ``false``. On the other hand, session pooling is fully compatible with PostgREST, while statement pooling is not compatible at all.
|
||||
|
||||
.. note::
|
||||
|
||||
If prepared statements are enabled, PostgREST will quit after detecting that transaction or statement pooling is being used.
|
||||
|
||||
You should also set the :ref:`db-channel-enabled` config option to ``false``, due to the ``LISTEN`` command not being compatible with transaction pooling, although it should not give any errors if it's left enabled by default.
|
||||
|
||||
Debugging
|
||||
=========
|
||||
|
||||
Server Version
|
||||
--------------
|
||||
|
||||
When debugging a problem it's important to verify the PostgREST version. At any time you can make a request to the running server and determine exactly which version is deployed. Look for the :code:`Server` HTTP response header, which contains the version number.
|
||||
|
||||
Errors
|
||||
------
|
||||
|
||||
See the :doc:`Errors <errors>` reference page for detailed information on the errors that PostgREST returns.
|
||||
|
||||
.. _pgrst_logging:
|
||||
|
||||
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.
|
||||
|
||||
.. code::
|
||||
|
||||
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``.
|
||||
|
||||
.. code::
|
||||
|
||||
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
|
||||
|
||||
.. 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"
|
||||
|
||||
PostgREST logging provides limited information for debugging server errors. It's helpful to get full information about both client requests and the corresponding SQL commands executed against the underlying database.
|
||||
|
||||
HTTP Requests
|
||||
-------------
|
||||
|
||||
A great way to inspect incoming HTTP requests including headers and query parameters is to sniff the network traffic on the port where PostgREST is running. For instance on a development server bound to port 3000 on localhost, run this:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# sudo access is necessary for watching the network
|
||||
sudo ngrep -d lo0 port 3000
|
||||
|
||||
The options to ngrep vary depending on the address and host on which you've bound the server. The binding is described in the :ref:`configuration` section. The ngrep output isn't particularly pretty, but it's legible.
|
||||
|
||||
.. _automatic_recovery:
|
||||
|
||||
Automatic Connection Recovery
|
||||
-----------------------------
|
||||
|
||||
When PostgREST loses the connection to the database, it retries the connection using capped exponential backoff, with 32 seconds being the maximum backoff time.
|
||||
|
||||
This retry behavior is triggered immediately after the connection is lost if :ref:`db-channel-enabled` is set to true(the default), otherwise it will be activated once a request is made.
|
||||
|
||||
To notify the client when the next reconnection attempt will be, PostgREST responds with ``503 Service Unavailable`` and the ``Retry-After: x`` header, where ``x`` is the number of seconds programmed for the next retry.
|
||||
|
||||
Database Logs
|
||||
-------------
|
||||
|
||||
Once you've verified that requests are as you expect, you can get more information about the server operations by watching 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.
|
||||
|
||||
.. code:: sql
|
||||
|
||||
# send logs where the collector can access them
|
||||
log_destination = "stderr"
|
||||
|
||||
# collect stderr output to log files
|
||||
logging_collector = on
|
||||
|
||||
# save logs in pg_log/ under the pg data directory
|
||||
log_directory = "pg_log"
|
||||
|
||||
# (optional) new log file per day
|
||||
log_filename = "postgresql-%Y-%m-%d.log"
|
||||
|
||||
# log every kind of SQL statement
|
||||
log_statement = "all"
|
||||
|
||||
Restart the database and watch the log file in real-time to understand how HTTP requests are being translated into SQL commands.
|
||||
|
||||
.. note::
|
||||
|
||||
On Docker you can enable the logs by using a custom ``init.sh``:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
#!/bin/sh
|
||||
echo "log_statement = 'all'" >> /var/lib/postgresql/data/postgresql.conf
|
||||
|
||||
After that you can start the container and check the logs with ``docker logs``.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres
|
||||
docker logs -f <container-id>
|
||||
|
||||
Schema Reloading
|
||||
----------------
|
||||
|
||||
Changing the schema while the server is running can lead to errors due to a stale schema cache. To learn how to refresh the cache see :ref:`schema_reloading`.
|
||||
|
||||
.. _health_check:
|
||||
|
||||
Health Check
|
||||
------------
|
||||
|
||||
You can enable a minimal health check to verify if PostgREST is available for client requests and to check the status of its internal state.
|
||||
|
||||
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.
|
||||
|
||||
Daemonizing
|
||||
===========
|
||||
|
||||
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``
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
db-uri = "postgres://<your_user>:<your_password>@localhost:5432/<your_db>"
|
||||
db-schemas = "<your_exposed_schema>"
|
||||
db-anon-role = "<your_anon_role>"
|
||||
jwt-secret = "<your_secret>"
|
||||
|
||||
Then create the systemd service file in ``/etc/systemd/system/postgrest.service``
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[Unit]
|
||||
Description=REST API for any PostgreSQL database
|
||||
After=postgresql.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/postgrest /etc/postgrest/config
|
||||
ExecReload=/bin/kill -SIGUSR1 $MAINPID
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
After that, you can enable the service at boot time and start it with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl enable postgrest
|
||||
systemctl start postgrest
|
||||
|
||||
## For reloading the service
|
||||
## systemctl restart postgrest
|
||||
|
||||
.. _file_descriptors:
|
||||
|
||||
File Descriptors
|
||||
----------------
|
||||
|
||||
File descriptors are kernel resources that are used by HTTP connections (among others). File descriptors are limited per process. The kernel default limit is 1024, which is increased in some Linux distributions.
|
||||
When under heavy traffic, PostgREST can reach this limit and start showing ``No file descriptors available`` errors. To clear these errors, you can increase the process' file descriptor limit.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[Service]
|
||||
LimitNOFILE=10000
|
||||
|
||||
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
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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.
|
||||
|
||||
Nginx rewrite rules allow you to simulate the familiar URL convention. The following example adds a rewrite rule for all table endpoints, but you'll want to restrict it to those tables that have a numeric simple primary key named "id."
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
# support /endpoint/:id url style
|
||||
location ~ ^/([a-z_]+)/([0-9]+) {
|
||||
|
||||
# make the response singular
|
||||
proxy_set_header Accept 'application/vnd.pgrst.object+json';
|
||||
|
||||
# assuming an upstream named "postgrest"
|
||||
proxy_pass http://postgrest/$1?id=eq.$2;
|
||||
|
||||
}
|
||||
|
||||
.. TODO
|
||||
.. Administration
|
||||
.. API Versioning
|
||||
.. HTTP Caching
|
||||
.. Upgrading
|
||||
@@ -0,0 +1,496 @@
|
||||
.. _roles:
|
||||
|
||||
Overview of Role System
|
||||
=======================
|
||||
|
||||
PostgREST is designed to keep the database at the center of API security. All authorization happens through database roles and permissions. It is PostgREST's job to **authenticate** requests -- i.e. verify that a client is who they say they are -- and then let the database **authorize** client actions.
|
||||
|
||||
Authentication Sequence
|
||||
-----------------------
|
||||
|
||||
There are three types of roles used by PostgREST, the **authenticator**, **anonymous** and **user** roles. The database administrator creates these roles and configures PostgREST to use them.
|
||||
|
||||
.. image:: _static/security-roles.png
|
||||
|
||||
The authenticator should be created :code:`NOINHERIT` and configured in the database to have very limited access. It is a chameleon whose job is to "become" other users to service authenticated HTTP requests. The picture below shows how the server handles authentication. If auth succeeds, it switches into the user role specified by the request, otherwise it switches into the anonymous role (if it's set in :ref:`db-anon-role`).
|
||||
|
||||
.. image:: _static/security-anon-choice.png
|
||||
|
||||
Here are the technical details. We use `JSON Web Tokens <https://jwt.io/>`_ to authenticate API requests. As you'll recall a JWT contains a list of cryptographically signed claims. All claims are allowed but PostgREST cares specifically about a claim called role.
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"role": "user123"
|
||||
}
|
||||
|
||||
When a request contains a valid JWT with a role claim PostgREST will switch to the database role with that name for the duration of the HTTP request.
|
||||
|
||||
.. code:: sql
|
||||
|
||||
SET LOCAL ROLE user123;
|
||||
|
||||
Note that the database administrator must allow the authenticator role to switch into this user by previously executing
|
||||
|
||||
.. code:: sql
|
||||
|
||||
GRANT user123 TO authenticator;
|
||||
|
||||
If the client included no JWT (or one without a role claim) then PostgREST switches into the anonymous role whose actual database-specific name, like that of with the authenticator role, is specified in the PostgREST server configuration file. The database administrator must set anonymous role permissions correctly to prevent anonymous users from seeing or changing things they shouldn't.
|
||||
|
||||
Users and Groups
|
||||
----------------
|
||||
|
||||
PostgreSQL manages database access permissions using the concept of roles. A role can be thought of as either a database user, or a group of database users, depending on how the role is set up.
|
||||
|
||||
Roles for Each Web User
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
PostgREST can accommodate either viewpoint. If you treat a role as a single user then the JWT-based role switching described above does most of what you need. When an authenticated user makes a request PostgREST will switch into the role for that user, which in addition to restricting queries, is available to SQL through the :code:`current_user` variable.
|
||||
|
||||
You can use row-level security to flexibly restrict visibility and access for the current user. Here is an `example <https://www.2ndquadrant.com/en/blog/application-users-vs-row-level-security/>`_ from Tomas Vondra, a chat table storing messages sent between users. Users can insert rows into it to send messages to other users, and query it to see messages sent to them by other users.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE TABLE chat (
|
||||
message_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
message_time TIMESTAMP NOT NULL DEFAULT now(),
|
||||
message_from NAME NOT NULL DEFAULT current_user,
|
||||
message_to NAME NOT NULL,
|
||||
message_subject VARCHAR(64) NOT NULL,
|
||||
message_body TEXT
|
||||
);
|
||||
|
||||
ALTER TABLE chat ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
We want to enforce a policy that ensures a user can see only those messages sent by them or intended for them. Also we want to prevent a user from forging the message_from column with another person's name.
|
||||
|
||||
PostgreSQL allows us to set this policy with row-level security:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE POLICY chat_policy ON chat
|
||||
USING ((message_to = current_user) OR (message_from = current_user))
|
||||
WITH CHECK (message_from = current_user)
|
||||
|
||||
Anyone accessing the generated API endpoint for the chat table will see exactly the rows they should, without our needing custom imperative server-side coding.
|
||||
|
||||
.. warning::
|
||||
|
||||
Roles are namespaced per-cluster rather than per-database so they may be prone to collision.
|
||||
|
||||
Web Users Sharing Role
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Alternately database roles can represent groups instead of (or in addition to) individual users. You may choose that all signed-in users for a web app share the role webuser. You can distinguish individual users by including extra claims in the JWT such as email.
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"role": "webuser",
|
||||
"email": "john@doe.com"
|
||||
}
|
||||
|
||||
SQL code can access claims through GUC variables set by PostgREST per request. For instance to get the email claim, call this function:
|
||||
|
||||
For PostgreSQL server version >= 14
|
||||
|
||||
.. code:: sql
|
||||
|
||||
current_setting('request.jwt.claims', true)::json->>'email';
|
||||
|
||||
|
||||
For PostgreSQL server version < 14
|
||||
|
||||
.. code:: sql
|
||||
|
||||
current_setting('request.jwt.claim.email', true);
|
||||
|
||||
This allows JWT generation services to include extra information and your database code to react to it. For instance the RLS example could be modified to use this current_setting rather than current_user. The second 'true' argument tells current_setting to return NULL if the setting is missing from the current configuration.
|
||||
|
||||
Hybrid User-Group Roles
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can mix the group and individual role policies. For instance we could still have a webuser role and individual users which inherit from it:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE webuser NOLOGIN;
|
||||
-- grant this role access to certain tables etc
|
||||
|
||||
CREATE ROLE user000 NOLOGIN;
|
||||
GRANT webuser TO user000;
|
||||
-- now user000 can do whatever webuser can
|
||||
|
||||
GRANT user000 TO authenticator;
|
||||
-- allow authenticator to switch into user000 role
|
||||
-- (the role itself has nologin)
|
||||
|
||||
.. _custom_validation:
|
||||
|
||||
Custom Validation
|
||||
-----------------
|
||||
|
||||
PostgREST honors the :code:`exp` claim for token expiration, rejecting expired tokens. However it does not enforce any extra constraints. An example of an extra constraint would be to immediately revoke access for a certain user. The configuration file parameter :code:`db-pre-request` specifies a stored procedure to call immediately after the authenticator switches into a new role and before the main query itself runs.
|
||||
|
||||
Here's an example. In the config file specify a stored procedure:
|
||||
|
||||
.. code:: ini
|
||||
|
||||
db-pre-request = "public.check_user"
|
||||
|
||||
In the function you can run arbitrary code to check the request and raise an exception to block it if desired.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE OR REPLACE FUNCTION check_user() RETURNS void AS $$
|
||||
BEGIN
|
||||
IF current_user = 'evil_user' THEN
|
||||
RAISE EXCEPTION 'No, you are evil'
|
||||
USING HINT = 'Stop being so evil and maybe you can log in';
|
||||
END IF;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
.. _client_auth:
|
||||
|
||||
Client Auth
|
||||
===========
|
||||
|
||||
To make an authenticated request the client must include an :code:`Authorization` HTTP header with the value :code:`Bearer <jwt>`. For instance:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /foo HTTP/1.1
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/foo" \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4"
|
||||
|
||||
The ``Bearer`` header value can be used with or without capitalization(``bearer``).
|
||||
|
||||
JWT Generation
|
||||
--------------
|
||||
|
||||
You can create a valid JWT either from inside your database or via an external service. Each token is cryptographically signed with a secret key. In the case of symmetric cryptography the signer and verifier share the same secret passphrase. In asymmetric cryptography the signer uses the private key and the verifier the public key. PostgREST supports both symmetric and asymmetric cryptography.
|
||||
|
||||
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 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 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
|
||||
FROM (
|
||||
SELECT
|
||||
'my_role'::text as role,
|
||||
extract(epoch from now())::integer + 300 AS exp
|
||||
) r;
|
||||
$$ LANGUAGE sql;
|
||||
|
||||
PostgREST exposes this function to clients via a POST request to ``/rpc/jwt_test``.
|
||||
|
||||
.. note::
|
||||
|
||||
To avoid hard-coding the secret in stored procedures, save it as a property of the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- run this once
|
||||
ALTER DATABASE mydb SET "app.jwt_secret" TO 'reallyreallyreallyreallyverysafe';
|
||||
|
||||
-- then all functions can refer to app.jwt_secret
|
||||
SELECT sign(
|
||||
row_to_json(r), current_setting('app.jwt_secret')
|
||||
) AS token
|
||||
FROM ...
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
.. _asym_keys:
|
||||
|
||||
Asymmetric Keys
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
As described in the :ref:`configuration` section, PostgREST accepts a ``jwt-secret`` config file parameter. If it is set to a simple string value like "reallyreallyreallyreallyverysafe" then PostgREST interprets it as an HMAC-SHA256 passphrase. However you can also specify a literal JSON Web Key (JWK) or set. For example, you can use an RSA-256 public key encoded as a JWK:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"alg":"RS256",
|
||||
"e":"AQAB",
|
||||
"key_ops":["verify"],
|
||||
"kty":"RSA",
|
||||
"n":"9zKNYTaYGfGm1tBMpRT6FxOYrM720GhXdettc02uyakYSEHU2IJz90G_MLlEl4-WWWYoS_QKFupw3s7aPYlaAjamG22rAnvWu-rRkP5sSSkKvud_IgKL4iE6Y2WJx2Bkl1XUFkdZ8wlEUR6O1ft3TS4uA-qKifSZ43CahzAJyUezOH9shI--tirC028lNg767ldEki3WnVr3zokSujC9YJ_9XXjw2hFBfmJUrNb0-wldvxQbFU8RPXip-GQ_JPTrCTZhrzGFeWPvhA6Rqmc3b1PhM9jY7Dur1sjYWYVyXlFNCK3c-6feo5WlRfe1aCWmwZQh6O18eTmLeT4nWYkDzQ"
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
This could also be a JSON Web Key Set (JWKS) if it was contained within an array assigned to a `keys` member, e.g. ``{ keys: [jwk1, jwk2] }``.
|
||||
|
||||
Just pass it in as a single line string, escaping the quotes:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
jwt-secret = "{ \"alg\":\"RS256\", … }"
|
||||
|
||||
To generate such a public/private key pair use a utility like `latchset/jose <https://github.com/latchset/jose>`_.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
jose jwk gen -i '{"alg": "RS256"}' -o rsa.jwk
|
||||
jose jwk pub -i rsa.jwk -o rsa.jwk.pub
|
||||
|
||||
# now rsa.jwk.pub contains the desired JSON object
|
||||
|
||||
You can specify the literal value as we saw earlier, or reference a filename to load the JWK from a file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
jwt-secret = "@rsa.jwk.pub"
|
||||
|
||||
JWT security
|
||||
~~~~~~~~~~~~
|
||||
|
||||
There are at least three types of common critiques against using JWT: 1) against the standard itself, 2) against using libraries with known security vulnerabilities, and 3) against using JWT for web sessions. We'll briefly explain each critique, how PostgREST deals with it, and give recommendations for appropriate user action.
|
||||
|
||||
The critique against the `JWT standard <https://datatracker.ietf.org/doc/html/rfc7519>`_ is voiced in detail `elsewhere on the web <https://web.archive.org/web/20230123041631/https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid>`_. The most relevant part for PostgREST is the so-called :code:`alg=none` issue. Some servers implementing JWT allow clients to choose the algorithm used to sign the JWT. In this case, an attacker could set the algorithm to :code:`none`, remove the need for any signature at all and gain unauthorized access. The current implementation of PostgREST, however, does not allow clients to set the signature algorithm in the HTTP request, making this attack irrelevant. The critique against the standard is that it requires the implementation of the :code:`alg=none` at all.
|
||||
|
||||
Critiques against JWT libraries are only relevant to PostgREST via the library it uses. As mentioned above, not allowing clients to choose the signature algorithm in HTTP requests removes the greatest risk. Another more subtle attack is possible where servers use asymmetric algorithms like RSA for signatures. Once again this is not relevant to PostgREST since it is not supported. Curious readers can find more information in `this article <https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/>`_. Recommendations about high quality libraries for usage in API clients can be found on `jwt.io <https://jwt.io/>`_.
|
||||
|
||||
The last type of critique focuses on the misuse of JWT for maintaining web sessions. The basic recommendation is to `stop using JWT for sessions <http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/>`_ because most, if not all, solutions to the problems that arise when you do, `do not work <http://cryto.net/~joepie91/blog/2016/06/19/stop-using-jwt-for-sessions-part-2-why-your-solution-doesnt-work/>`_. The linked articles discuss the problems in depth but the essence of the problem is that JWT is not designed to be secure and stateful units for client-side storage and therefore not suited to session management.
|
||||
|
||||
PostgREST uses JWT mainly for authentication and authorization purposes and encourages users to do the same. For web sessions, using cookies over HTTPS is good enough and well catered for by standard web frameworks.
|
||||
|
||||
Schema Isolation
|
||||
================
|
||||
|
||||
You can isolate your api schema from internal implementation details, as explained in :ref:`schema_isolation`. For an example of wrapping a private table with a public view see the :ref:`public_ui` section below.
|
||||
|
||||
.. _sql_user_management:
|
||||
|
||||
SQL User Management
|
||||
===================
|
||||
|
||||
Storing Users and Passwords
|
||||
---------------------------
|
||||
|
||||
As mentioned, an external service can provide user management and coordinate with the PostgREST server using JWT. It's also possible to support logins entirely through SQL. It's a fair bit of work, so get ready.
|
||||
|
||||
The following table, functions, and triggers will live in a :code:`basic_auth` schema that you shouldn't expose publicly in the API. The public views and functions will live in a different schema which internally references this internal information.
|
||||
|
||||
First we'll need a table to keep track of our users:
|
||||
|
||||
.. code:: sql
|
||||
|
||||
-- We put things inside the basic_auth schema to hide
|
||||
-- them from public view. Certain public procs/views will
|
||||
-- refer to helpers and tables inside.
|
||||
create schema if not exists basic_auth;
|
||||
|
||||
create table if not exists
|
||||
basic_auth.users (
|
||||
email text primary key check ( email ~* '^.+@.+\..+$' ),
|
||||
pass text not null check (length(pass) < 512),
|
||||
role name not null check (length(role) < 512)
|
||||
);
|
||||
|
||||
We would like the role to be a foreign key to actual database roles, however PostgreSQL does not support these constraints against the :code:`pg_roles` table. We'll use a trigger to manually enforce it.
|
||||
|
||||
.. code-block:: plpgsql
|
||||
|
||||
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
|
||||
raise foreign_key_violation using message =
|
||||
'unknown database role: ' || new.role;
|
||||
return null;
|
||||
end if;
|
||||
return new;
|
||||
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
|
||||
execute procedure basic_auth.check_role_exists();
|
||||
|
||||
Next we'll use the pgcrypto extension and a trigger to keep passwords safe in the :code:`users` table.
|
||||
|
||||
.. code-block:: plpgsql
|
||||
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
create or replace function
|
||||
basic_auth.encrypt_pass() returns trigger as $$
|
||||
begin
|
||||
if tg_op = 'INSERT' or new.pass <> old.pass then
|
||||
new.pass = crypt(new.pass, gen_salt('bf'));
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
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
|
||||
execute procedure basic_auth.encrypt_pass();
|
||||
|
||||
With the table in place we can make a helper to check a password against the encrypted column. It returns the database role for a user if the email and password are correct.
|
||||
|
||||
.. code-block:: plpgsql
|
||||
|
||||
create or replace function
|
||||
basic_auth.user_role(email text, pass text) returns name
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
return (
|
||||
select role from basic_auth.users
|
||||
where users.email = user_role.email
|
||||
and users.pass = crypt(user_role.pass, users.pass)
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
.. _public_ui:
|
||||
|
||||
Public User Interface
|
||||
---------------------
|
||||
|
||||
In the previous section we created an internal table to store user information. Here we create a login function which takes an email address and password and returns JWT if the credentials match a user in the internal table.
|
||||
|
||||
Permissions
|
||||
~~~~~~~~~~~
|
||||
|
||||
Your database roles need access to the schema, tables, views and functions in order to service HTTP requests.
|
||||
Recall from the `Overview of Role System`_ that PostgREST uses special roles to process requests, namely the authenticator and
|
||||
anonymous roles. Below is an example of permissions that allow anonymous users to create accounts and attempt to log in.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- the names "anon" and "authenticator" are configurable and not
|
||||
-- sacred, we simply choose them for clarity
|
||||
create role anon noinherit;
|
||||
create role authenticator noinherit;
|
||||
grant anon to authenticator;
|
||||
|
||||
Then, add ``db-anon-role`` to the configuration file to allow anonymous requests.
|
||||
|
||||
.. code:: ini
|
||||
|
||||
db-anon-role = "anon"
|
||||
|
||||
Logins
|
||||
~~~~~~
|
||||
|
||||
As described in `JWT from SQL`_, we'll create a JWT inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- add type
|
||||
CREATE TYPE basic_auth.jwt_token AS (
|
||||
token text
|
||||
);
|
||||
|
||||
-- login should be on your exposed schema
|
||||
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;
|
||||
if _role is null then
|
||||
raise invalid_password using message = 'invalid user or password';
|
||||
end if;
|
||||
|
||||
select sign(
|
||||
row_to_json(r), 'reallyreallyreallyreallyverysafe'
|
||||
) as token
|
||||
from (
|
||||
select _role as role, login.email as email,
|
||||
extract(epoch from now())::integer + 60*60 as exp
|
||||
) r
|
||||
into result;
|
||||
return result;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
grant execute on function login(text,text) to anon;
|
||||
|
||||
Since the above :code:`login` function is defined as `security definer <https://www.postgresql.org/docs/current/sql-createfunction.html#id-1.9.3.67.10.2>`_,
|
||||
the anonymous user :code:`anon` doesn't need permission to read the :code:`basic_auth.users` table. It doesn't even need permission to access the :code:`basic_auth` schema.
|
||||
:code:`grant execute on function` is included for clarity but it might not be needed, see :ref:`func_privs` for more details.
|
||||
|
||||
An API request to call this function would look like:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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!)
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZvb0BiYXIuY29tIiwicGFzcyI6ImZvb2JhciJ9.37066TTRlh-1hXhnA9oO9Pj6lgL6zFuJU0iCHhuCFno"
|
||||
}
|
||||
|
||||
|
||||
Alternatives
|
||||
~~~~~~~~~~~~
|
||||
|
||||
See the how-to :ref:`sql-user-management-using-postgres-users-and-passwords` for a similar way that completely avoids the table :code:`basic_auth.users`.
|
||||
@@ -12,6 +12,7 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
@@ -27,12 +28,7 @@ import os
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx_tabs.tabs",
|
||||
"sphinx_copybutton",
|
||||
"sphinxext.opengraph",
|
||||
"sphinx_rtd_dark_mode",
|
||||
]
|
||||
extensions = ["sphinx_tabs.tabs", "sphinx_copybutton"]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
@@ -48,14 +44,14 @@ source_suffix = ".rst"
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# This is overridden by readthedocs with the version tag anyway
|
||||
version = "devel"
|
||||
# This is overriden by readthedocs with the version tag anyway
|
||||
version = "10.2"
|
||||
# To avoid repetition in <title> we set this to an empty string.
|
||||
release = ""
|
||||
|
||||
# General information about the project.
|
||||
project = "PostgREST " + version
|
||||
author = "The PostgREST contributors"
|
||||
author = "Joe Nelson, Steve Chavez"
|
||||
copyright = "2017, " + author
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
@@ -63,7 +59,7 @@ copyright = "2017, " + author
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = "en"
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
@@ -74,7 +70,7 @@ language = "en"
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "shared/*.rst"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
@@ -113,7 +109,7 @@ html_theme = "sphinx_rtd_theme"
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {}
|
||||
html_theme_options = {"display_version": False}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
@@ -291,39 +287,7 @@ def setup(app):
|
||||
app.add_css_file("css/custom.css")
|
||||
|
||||
|
||||
user_agent = (
|
||||
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
|
||||
)
|
||||
|
||||
linkcheck_ignore = [
|
||||
# 403 only in CI / GitHub Actions
|
||||
r"https://www.patreon.com/postgrest",
|
||||
r"https://blog.frankel.ch/poor-man-api",
|
||||
r"https://www.cybertec-postgresql.com/.*",
|
||||
r"https://stackoverflow.com/.*",
|
||||
# Odd SSL error
|
||||
r"https://www.dripdepot.com",
|
||||
r"https://www.euronodes.com",
|
||||
# New GitHub UI delays comment load, so anchor fails
|
||||
r"https://github.com/.*#issuecomment",
|
||||
# Random 500 Internal Server Error
|
||||
r"https://jwt.io",
|
||||
]
|
||||
user_agent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"
|
||||
|
||||
# sphinx-tabs configuration
|
||||
sphinx_tabs_disable_tab_closing = True
|
||||
|
||||
# sphinx_rtd_dark_mode configuration
|
||||
default_dark_mode = False
|
||||
|
||||
# sphinxext-opengraph configuration
|
||||
|
||||
ogp_image = "_images/logo.png"
|
||||
ogp_use_first_image = True
|
||||
ogp_enable_meta_description = True
|
||||
ogp_description_length = 300
|
||||
|
||||
## RTD sets html_baseurl, ensures we use the correct env for canonical URLs
|
||||
## Useful to generate correct meta tags for Open Graph
|
||||
## Refs: https://github.com/readthedocs/readthedocs.org/issues/10226, https://github.com/urllib3/urllib3/pull/3064
|
||||
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
.. _configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Without configuration, PostgREST won't be able to serve requests. At the minimum it needs either :ref:`a role to serve anonymous requests with <db-anon-role>` - or :ref:`a secret to use for JWT authentication <jwt-secret>`. Config parameters can be provided via :ref:`file_config`, via :ref:`env_variables_config` or through :ref:`in_db_config`.
|
||||
|
||||
To connect to a database it uses a `libpq connection string <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_. The connection string can be set in the configuration file or via environment variable or can be read from an external file. See :ref:`db-uri` for details. Any parameter that is not set in the connection string is read from `libpq environment variables <https://www.postgresql.org/docs/current/libpq-envars.html>`_. The default connection string is ``postgresql://``, which reads **all** parameters from the environment.
|
||||
|
||||
The user with whom PostgREST connects to the database is also known as the authenticator role. For more information about the anonymous vs authenticator roles see :ref:`roles`.
|
||||
|
||||
Config parameters are read in the following order:
|
||||
|
||||
1. From the config file.
|
||||
2. From environment variables, overriding values from the config file.
|
||||
3. From the database, overriding values from both the config file and environment variables.
|
||||
|
||||
.. _file_config:
|
||||
|
||||
Config File
|
||||
-----------
|
||||
|
||||
PostgREST can read a config file. There is no predefined location for this file, you must specify the file path as the one and only argument to the server:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
./postgrest /path/to/postgrest.conf
|
||||
|
||||
.. note::
|
||||
|
||||
Configuration can be reloaded without restarting the server. See :ref:`config_reloading`.
|
||||
|
||||
The configuration file must contain a set of key value pairs:
|
||||
|
||||
.. code::
|
||||
|
||||
# postgrest.conf
|
||||
|
||||
# The standard connection URI format, documented at
|
||||
# https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
|
||||
db-uri = "postgres://user:pass@host:5432/dbname"
|
||||
|
||||
# The database role to use when no client authentication is provided.
|
||||
# Should differ from authenticator
|
||||
db-anon-role = "anon"
|
||||
|
||||
# The secret to verify the JWT for authenticated requests with.
|
||||
# Needs to be 32 characters minimum.
|
||||
jwt-secret = "reallyreallyreallyreallyverysafe"
|
||||
jwt-secret-is-base64 = False
|
||||
|
||||
# Port the postgrest process is listening on for http requests
|
||||
server-port = 80
|
||||
|
||||
You can run ``postgrest --example`` to display all possible configuration parameters and how to use them in a configuration file.
|
||||
|
||||
.. _env_variables_config:
|
||||
|
||||
Environment Variables
|
||||
---------------------
|
||||
|
||||
You can also set these :ref:`configuration parameters <config_full_list>` using environment variables. They are capitalized, have a ``PGRST_`` prefix, and use underscores. For example: ``PGRST_DB_URI`` corresponds to ``db-uri`` and ``PGRST_APP_SETTINGS_*`` to ``app.settings.*``.
|
||||
|
||||
.. _in_db_config:
|
||||
|
||||
In-Database Configuration
|
||||
-------------------------
|
||||
|
||||
By adding settings to the **authenticator** role (see :ref:`roles`), you can make the database the single source of truth for PostgREST's configuration.
|
||||
This is enabled by :ref:`db-config`.
|
||||
|
||||
For example, you can configure :ref:`db-schemas` and :ref:`jwt-secret` like this:
|
||||
|
||||
.. code:: postgresql
|
||||
|
||||
ALTER ROLE authenticator SET pgrst.db_schemas = "tenant1, tenant2, tenant3"
|
||||
ALTER ROLE authenticator IN DATABASE <your_database_name> SET pgrst.jwt_secret = "REALLYREALLYREALLYREALLYVERYSAFE"
|
||||
|
||||
You can use both database-specific settings with `IN DATABASE` and cluster-wide settings without it. Database-specific settings will override cluster-wide settings if both are used for the same parameter.
|
||||
|
||||
Note that underscores(``_``) need to be used instead of dashes(``-``) for the in-database config parameters.
|
||||
|
||||
.. important::
|
||||
|
||||
For altering a role in this way, you need a SUPERUSER. You might not be able to use this configuration mode on cloud-hosted databases.
|
||||
|
||||
When using both the configuration file and the in-database configuration, the latter takes precedence.
|
||||
|
||||
.. danger::
|
||||
|
||||
If direct connections to the database are allowed, then it's not safe to use the in-db configuration for storing the :ref:`jwt-secret`.
|
||||
The settings of every role are PUBLIC - they can be viewed by any user that queries the ``pg_catalog.pg_db_role_setting`` table.
|
||||
In this case you should keep the :ref:`jwt-secret` in the configuration file or as environment variables.
|
||||
|
||||
.. _config_reloading:
|
||||
|
||||
Configuration Reloading
|
||||
=======================
|
||||
|
||||
It's possible to reload PostgREST's configuration without restarting the server. You can do this :ref:`via signal <config_reloading_signal>` or :ref:`via notification <config_reloading_notify>`.
|
||||
|
||||
It's not possible to change :ref:`env_variables_config` for a running process and reloading a Docker container configuration will not work. In these cases, you need to restart the PostgREST server or use :ref:`in_db_config` as an alternative.
|
||||
|
||||
.. important::
|
||||
|
||||
The following settings will not be reloaded. You will need to restart PostgREST to change those.
|
||||
|
||||
* :ref:`admin-server-port`
|
||||
* :ref:`db-uri`
|
||||
* :ref:`db-pool`
|
||||
* :ref:`db-pool-acquisition-timeout`
|
||||
* :ref:`db-pool-max-lifetime`
|
||||
* :ref:`server-host`
|
||||
* :ref:`server-port`
|
||||
* :ref:`server-unix-socket`
|
||||
* :ref:`server-unix-socket-mode`
|
||||
|
||||
.. _config_reloading_signal:
|
||||
|
||||
Reload with signal
|
||||
------------------
|
||||
|
||||
To reload the configuration via signal, send a SIGUSR2 signal to the server process.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
killall -SIGUSR2 postgrest
|
||||
|
||||
.. _config_reloading_notify:
|
||||
|
||||
Reload with NOTIFY
|
||||
------------------
|
||||
|
||||
To reload the configuration from within the database, you can use a NOTIFY command.
|
||||
|
||||
.. code:: postgresql
|
||||
|
||||
NOTIFY pgrst, 'reload config'
|
||||
|
||||
The ``"pgrst"`` notification channel is enabled by default. For configuring the channel, see :ref:`db-channel` and :ref:`db-channel-enabled`.
|
||||
|
||||
.. _config_full_list:
|
||||
|
||||
List of parameters
|
||||
==================
|
||||
|
||||
=========================== ======= ================= ==========
|
||||
Name Type Default Reloadable
|
||||
=========================== ======= ================= ==========
|
||||
admin-server-port Int
|
||||
app.settings.* String Y
|
||||
db-anon-role String Y
|
||||
db-channel String pgrst Y
|
||||
db-channel-enabled Boolean True Y
|
||||
db-config Boolean True Y
|
||||
db-extra-search-path String public Y
|
||||
db-max-rows Int ∞ Y
|
||||
db-plan-enabled Boolean False Y
|
||||
db-pool Int 10
|
||||
db-pool-acquisition-timeout Int 10
|
||||
db-pool-max-lifetime Int 1800
|
||||
db-pre-request String Y
|
||||
db-prepared-statements Boolean True Y
|
||||
db-schemas String public Y
|
||||
db-tx-end String commit
|
||||
db-uri String postgresql://
|
||||
db-use-legacy-gucs Boolean True Y
|
||||
jwt-aud String Y
|
||||
jwt-role-claim-key String .role Y
|
||||
jwt-secret String Y
|
||||
jwt-secret-is-base64 Boolean False Y
|
||||
log-level String error Y
|
||||
openapi-mode String follow-privileges Y
|
||||
openapi-security-active Boolean False Y
|
||||
openapi-server-proxy-uri String Y
|
||||
raw-media-types String Y
|
||||
server-host String !4
|
||||
server-port Int 3000
|
||||
server-unix-socket String
|
||||
server-unix-socket-mode String 660
|
||||
=========================== ======= ================= ==========
|
||||
|
||||
.. _admin-server-port:
|
||||
|
||||
admin-server-port
|
||||
-----------------
|
||||
|
||||
=============== =======================
|
||||
**Environment** PGRST_ADMIN_SERVER_PORT
|
||||
**In-Database** `n/a`
|
||||
=============== =======================
|
||||
|
||||
Specifies the port for the :ref:`health_check` endpoints.
|
||||
|
||||
.. _app.settings.*:
|
||||
|
||||
app.settings.*
|
||||
--------------
|
||||
|
||||
=============== ====================
|
||||
**Environment** PGRST_APP_SETTINGS_*
|
||||
**In-Database** pgrst.app_settings_*
|
||||
=============== ====================
|
||||
|
||||
Arbitrary settings that can be used to pass in secret keys directly as strings, or via OS environment variables. For instance: :code:`app.settings.jwt_secret = "$(MYAPP_JWT_SECRET)"` will take :code:`MYAPP_JWT_SECRET` from the environment and make it available to postgresql functions as :code:`current_setting('app.settings.jwt_secret')`.
|
||||
|
||||
.. _db-anon-role:
|
||||
|
||||
db-anon-role
|
||||
------------
|
||||
|
||||
=============== ==================
|
||||
**Environment** PGRST_DB_ANON_ROLE
|
||||
**In-Database** `n/a`
|
||||
=============== ==================
|
||||
|
||||
The database role to use when executing commands on behalf of unauthenticated clients. For more information, see :ref:`roles`.
|
||||
|
||||
When unset anonymous access will be blocked.
|
||||
|
||||
.. _db-channel:
|
||||
|
||||
db-channel
|
||||
----------
|
||||
|
||||
=============== ================
|
||||
**Environment** PGRST_DB_CHANNEL
|
||||
**In-Database** `n/a`
|
||||
=============== ================
|
||||
|
||||
The name of the notification channel that PostgREST uses for :ref:`schema_reloading` and configuration reloading.
|
||||
|
||||
.. _db-channel-enabled:
|
||||
|
||||
db-channel-enabled
|
||||
------------------
|
||||
|
||||
=============== ========================
|
||||
**Environment** PGRST_DB_CHANNEL_ENABLED
|
||||
**In-Database** `n/a`
|
||||
=============== ========================
|
||||
|
||||
When this is set to :code:`true`, the notification channel specified in :ref:`db-channel` is enabled.
|
||||
|
||||
You should set this to ``false`` when using PostgresSQL behind an external connection pooler such as PgBouncer working in transaction pooling mode. See :ref:`this section <external_connection_poolers>` for more information.
|
||||
|
||||
.. _db-config:
|
||||
|
||||
db-config
|
||||
---------
|
||||
|
||||
=============== ===============
|
||||
**Environment** PGRST_DB_CONFIG
|
||||
**In-Database** `n/a`
|
||||
=============== ===============
|
||||
|
||||
Enables the in-database configuration.
|
||||
|
||||
.. _db-extra-search-path:
|
||||
|
||||
db-extra-search-path
|
||||
--------------------
|
||||
|
||||
=============== ==========================
|
||||
**Environment** PGRST_DB_EXTRA_SEARCH_PATH
|
||||
**In-Database** pgrst.db_extra_search_path
|
||||
=============== ==========================
|
||||
|
||||
Extra schemas to add to the `search_path <https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH>`_ of every request. These schemas tables, views and stored procedures **don't get API endpoints**, they can only be referred from the database objects inside your :ref:`db-schemas`.
|
||||
|
||||
This parameter was meant to make it easier to use **PostgreSQL extensions** (like PostGIS) that are outside of the :ref:`db-schemas`.
|
||||
|
||||
Multiple schemas can be added in a comma-separated string, e.g. ``public, extensions``.
|
||||
|
||||
.. _db-max-rows:
|
||||
|
||||
db-max-rows
|
||||
-----------
|
||||
|
||||
*For backwards compatibility, this config parameter is also available without prefix as "max-rows".*
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_MAX_ROWS
|
||||
**In-Database** pgrst.db_max_rows
|
||||
=============== =================
|
||||
|
||||
A hard limit to the number of rows PostgREST will fetch from a view, table, or stored procedure. Limits payload size for accidental or malicious requests.
|
||||
|
||||
.. _db-plan-enabled:
|
||||
|
||||
db-plan-enabled
|
||||
---------------
|
||||
|
||||
=============== =====================
|
||||
**Environment** PGRST_DB_PLAN_ENABLED
|
||||
**In-Database** pgrst.db_plan_enabled
|
||||
=============== =====================
|
||||
|
||||
When this is set to :code:`true`, the execution plan of a request can be retrieved by using the :code:`Accept: application/vnd.pgrst.plan` header. See :ref:`explain_plan`.
|
||||
|
||||
It's recommended to use this in testing environments only since it reveals internal database details.
|
||||
However, if you choose to use it in production you can add a :ref:`db-pre-request` to filter the requests that can use this feature.
|
||||
|
||||
For example, to only allow requests from an IP address to get the execution plans:
|
||||
|
||||
.. 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()
|
||||
returns void as $$
|
||||
declare
|
||||
headers json := current_setting('request.headers', true)::json;
|
||||
client_ip text := coalesce(headers->>'x-forwarded-for', '');
|
||||
accept text := coalesce(headers->>'accept', '');
|
||||
begin
|
||||
if accept like 'application/vnd.pgrst.plan%' and client_ip != '144.96.121.73' then
|
||||
raise insufficient_privilege using
|
||||
message = 'Not allowed to use application/vnd.pgrst.plan';
|
||||
end if;
|
||||
end; $$ language plpgsql;
|
||||
|
||||
-- set this function on your postgrest.conf
|
||||
-- db-pre-request = filter_plan_requests
|
||||
|
||||
.. _db-pool:
|
||||
|
||||
db-pool
|
||||
-------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_POOL
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
Number of connections to keep open in PostgREST's database pool. Having enough here for the maximum expected simultaneous client connections can improve performance. Note it's pointless to set this higher than the :code:`max_connections` GUC in your database.
|
||||
|
||||
.. _db-pool-acquisition-timeout:
|
||||
|
||||
db-pool-acquisition-timeout
|
||||
---------------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_POOL_ACQUISITION_TIMEOUT
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
Specifies the maximum time in seconds that the request will wait for the pool to free up a connection slot to the database. If it times out without acquiring a connection, then the request is aborted and a ``504`` error is returned.
|
||||
|
||||
.. _db-pool-max-lifetime:
|
||||
|
||||
db-pool-max-lifetime
|
||||
--------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_POOL_MAX_LIFETIME
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
Specifies the maximum time in seconds of an existing connection in the pool. When this lifetime is reached, then the connection will be closed and returned to the pool.
|
||||
|
||||
.. _db-pre-request:
|
||||
|
||||
db-pre-request
|
||||
--------------
|
||||
|
||||
*For backwards compatibility, this config parameter is also available without prefix as "pre-request".*
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_PRE_REQUEST
|
||||
**In-Database** pgrst.db_pre_request
|
||||
=============== =================
|
||||
|
||||
A schema-qualified stored procedure name to call right after switching roles for a client request. This provides an opportunity to modify SQL variables or raise an exception to prevent the request from completing.
|
||||
|
||||
.. _db-prepared-statements:
|
||||
|
||||
db-prepared-statements
|
||||
----------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_PREPARED_STATEMENTS
|
||||
**In-Database** pgrst.db_prepared_statements
|
||||
=============== =================
|
||||
|
||||
Enables or disables prepared statements.
|
||||
|
||||
When disabled, the generated queries will be parameterized (invulnerable to SQL injection) but they will not be prepared (cached in the database session). Not using prepared statements will noticeably decrease performance, so it's recommended to always have this setting enabled.
|
||||
|
||||
You should only set this to ``false`` when using PostgresSQL behind an external connection pooler such as PgBouncer working in transaction pooling mode. See :ref:`this section <external_connection_poolers>` for more information.
|
||||
|
||||
.. _db-schemas:
|
||||
|
||||
db-schemas
|
||||
----------
|
||||
|
||||
*For backwards compatibility, this config parameter is also available in singular as "db-schema".*
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_SCHEMAS
|
||||
**In-Database** pgrst.db_schemas
|
||||
=============== =================
|
||||
|
||||
The database schema to expose to REST clients. Tables, views and stored procedures in this schema will get API endpoints.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
db-schemas = "api"
|
||||
|
||||
This schema gets added to the `search_path <https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH>`_ of every request.
|
||||
|
||||
List of schemas
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
You can also specify a list of schemas that can be used for **schema-based multitenancy** and **api versioning** by :ref:`multiple-schemas`. Example:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
db-schemas = "tenant1, tenant2"
|
||||
|
||||
If you don't :ref:`Switch Schemas <multiple-schemas>`, the first schema in the list(``tenant1`` in this case) is chosen as the default schema.
|
||||
|
||||
*Only the chosen schema* gets added to the `search_path <https://www.postgresql.org/docs/current/ddl-schemas.html#DDL-SCHEMAS-PATH>`_ of every request.
|
||||
|
||||
.. warning::
|
||||
|
||||
Never expose private schemas in this way. See :ref:`schema_isolation`.
|
||||
|
||||
.. _db-tx-end:
|
||||
|
||||
db-tx-end
|
||||
---------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_TX_END
|
||||
**In-Database** pgrst.db_tx_end
|
||||
=============== =================
|
||||
|
||||
Specifies how to terminate the database transactions.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# The transaction is always committed
|
||||
db-tx-end = "commit"
|
||||
|
||||
# The transaction is committed unless a "Prefer: tx=rollback" header is sent
|
||||
db-tx-end = "commit-allow-override"
|
||||
|
||||
# The transaction is always rolled back
|
||||
db-tx-end = "rollback"
|
||||
|
||||
# The transaction is rolled back unless a "Prefer: tx=commit" header is sent
|
||||
db-tx-end = "rollback-allow-override"
|
||||
|
||||
.. _db-uri:
|
||||
|
||||
db-uri
|
||||
------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_URI
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
The standard connection PostgreSQL `URI format <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_. Symbols and unusual characters in the password or other fields should be percent encoded to avoid a parse error. If enforcing an SSL connection to the database is required you can use `sslmode <https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS>`_ in the URI, for example ``postgres://user:pass@host:5432/dbname?sslmode=require``.
|
||||
|
||||
When running PostgREST on the same machine as PostgreSQL, it is also possible to connect to the database using a `Unix socket <https://en.wikipedia.org/wiki/Unix_domain_socket>`_ and the `Peer Authentication method <https://www.postgresql.org/docs/current/auth-peer.html>`_ as an alternative to TCP/IP communication and authentication with a password, this also grants higher performance. To do this you can omit the host and the password, e.g. ``postgres://user@/dbname``, see the `libpq connection string <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING>`_ documentation for more details.
|
||||
|
||||
Choosing a value for this parameter beginning with the at sign such as ``@filename`` (e.g. ``@./configs/my-config``) loads the connection string out of an external file.
|
||||
|
||||
.. _db-use-legacy-gucs:
|
||||
|
||||
db-use-legacy-gucs
|
||||
------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_DB_USE_LEGACY_GUCS
|
||||
**In-Database** pgrst.db_use_legacy_gucs
|
||||
=============== =================
|
||||
|
||||
Determine if GUC request settings for headers, cookies and jwt claims use the :ref:`legacy names <guc_legacy_names>` (string with dashes, invalid starting from PostgreSQL v14) with text values instead of the :ref:`new names <guc_req_headers_cookies_claims>` (string without dashes, valid on all PostgreSQL versions) with json values.
|
||||
|
||||
On PostgreSQL versions 14 and above, this parameter is ignored.
|
||||
|
||||
.. _jwt-aud:
|
||||
|
||||
jwt-aud
|
||||
-------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_JWT_AUD
|
||||
**In-Database** pgrst.jwt_aud
|
||||
=============== =================
|
||||
|
||||
Specifies the `JWT audience claim <https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3>`_. If this claim is present in the client provided JWT then you must set this to the same value as in the JWT, otherwise verifying the JWT will fail.
|
||||
|
||||
.. _jwt-role-claim-key:
|
||||
|
||||
jwt-role-claim-key
|
||||
------------------
|
||||
|
||||
*For backwards compatibility, this config parameter is also available without prefix as "role-claim-key".*
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_JWT_ROLE_CLAIM_KEY
|
||||
**In-Database** pgrst.jwt_role_claim_key
|
||||
=============== =================
|
||||
|
||||
A JSPath DSL that specifies the location of the :code:`role` key in the JWT claims. This can be used to consume a JWT provided by a third party service like Auth0, Okta or Keycloak. Usage examples:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# {"postgrest":{"roles": ["other", "author"]}}
|
||||
# the DSL accepts characters that are alphanumerical or one of "_$@" as keys
|
||||
jwt-role-claim-key = ".postgrest.roles[1]"
|
||||
|
||||
# {"https://www.example.com/role": { "key": "author }}
|
||||
# non-alphanumerical characters can go inside quotes(escaped in the config value)
|
||||
jwt-role-claim-key = ".\"https://www.example.com/role\".key"
|
||||
|
||||
.. _jwt-secret:
|
||||
|
||||
jwt-secret
|
||||
----------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_JWT_SECRET
|
||||
**In-Database** pgrst.jwt_secret
|
||||
=============== =================
|
||||
|
||||
The secret or `JSON Web Key (JWK) (or set) <https://datatracker.ietf.org/doc/html/rfc7517>`_ used to decode JWT tokens clients provide for authentication. For security the key must be **at least 32 characters long**. If this parameter is not specified then PostgREST refuses authentication requests. Choosing a value for this parameter beginning with the at sign such as :code:`@filename` loads the secret out of an external file. This is useful for automating deployments. Note that any binary secrets must be base64 encoded. Both symmetric and asymmetric cryptography are supported. For more info see :ref:`asym_keys`.
|
||||
|
||||
Choosing a value for this parameter beginning with the at sign such as ``@filename`` (e.g. ``@./configs/my-config``) loads the secret out of an external file.
|
||||
|
||||
.. warning::
|
||||
|
||||
Only when using the :ref:`file_config`, if the ``jwt-secret`` contains a ``$`` character by itself it will give errors. In this case, use ``$$`` and PostgREST will interpret it as a single ``$`` character.
|
||||
|
||||
.. _jwt-secret-is-base64:
|
||||
|
||||
jwt-secret-is-base64
|
||||
--------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_JWT_SECRET_IS_BASE64
|
||||
**In-Database** pgrst.jwt_secret_is_base64
|
||||
=============== =================
|
||||
|
||||
When this is set to :code:`true`, the value derived from :code:`jwt-secret` will be treated as a base64 encoded secret.
|
||||
|
||||
.. _log-level:
|
||||
|
||||
log-level
|
||||
---------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_LOG_LEVEL
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
Specifies the level of information to be logged while running PostgREST.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# Only startup and db connection recovery messages are logged
|
||||
log-level = "crit"
|
||||
|
||||
# All the "crit" level events plus server errors (status 5xx) are logged
|
||||
log-level = "error"
|
||||
|
||||
# All the "error" level events plus request errors (status 4xx) are logged
|
||||
log-level = "warn"
|
||||
|
||||
# All the "warn" level events plus all requests (every status code) are logged
|
||||
log-level = "info"
|
||||
|
||||
|
||||
Because currently there's no buffering for logging, the levels with minimal logging(``crit/error``) will increase throughput.
|
||||
|
||||
.. _openapi-mode:
|
||||
|
||||
openapi-mode
|
||||
------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_OPENAPI_MODE
|
||||
**In-Database** pgrst.openapi_mode
|
||||
=============== =================
|
||||
|
||||
Specifies how the OpenAPI output should be displayed.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# Follows the privileges of the JWT role claim (or from db-anon-role if the JWT is not sent)
|
||||
# Shows information depending on the permissions that the role making the request has
|
||||
openapi-mode = "follow-privileges"
|
||||
|
||||
# Ignores the privileges of the JWT role claim (or from db-anon-role if the JWT is not sent)
|
||||
# Shows all the exposed information, regardless of the permissions that the role making the request has
|
||||
openapi-mode = "ignore-privileges"
|
||||
|
||||
# Disables the OpenApi output altogether.
|
||||
# Throws a `404 Not Found` error when accessing the API root path
|
||||
openapi-mode = "disabled"
|
||||
|
||||
.. _openapi-security-active:
|
||||
|
||||
openapi-security-active
|
||||
-----------------------
|
||||
|
||||
=============== =============================
|
||||
**Environment** PGRST_OPENAPI_SECURITY_ACTIVE
|
||||
**In-Database** pgrst.openapi_security_active
|
||||
=============== =============================
|
||||
|
||||
When this is set to :code:`true`, security options are included in the :ref:`OpenAPI output <open-api>`.
|
||||
|
||||
.. _openapi-server-proxy-uri:
|
||||
|
||||
openapi-server-proxy-uri
|
||||
------------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_OPENAPI_SERVER_PROXY_URI
|
||||
**In-Database** pgrst.openapi_server_proxy_uri
|
||||
=============== =================
|
||||
|
||||
Overrides the base URL used within the OpenAPI self-documentation hosted at the API root path. Use a complete URI syntax :code:`scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]`. Ex. :code:`https://postgrest.com`
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "0.4.3.0",
|
||||
"title": "PostgREST API",
|
||||
"description": "This is a dynamic API generated by PostgREST"
|
||||
},
|
||||
"host": "postgrest.com:443",
|
||||
"basePath": "/",
|
||||
"schemes": [
|
||||
"https"
|
||||
]
|
||||
}
|
||||
|
||||
.. _raw-media-types:
|
||||
|
||||
raw-media-types
|
||||
---------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_RAW_MEDIA_TYPES
|
||||
**In-Database** pgrst.raw_media_types
|
||||
=============== =================
|
||||
|
||||
This serves to extend the `Media Types <https://en.wikipedia.org/wiki/Media_type>`_ that PostgREST currently accepts through an ``Accept`` header.
|
||||
|
||||
These media types can be requested by following the same rules as the ones defined in :ref:`scalar_return_formats`.
|
||||
|
||||
As an example, the below config would allow you to request an **image** and a **XML** file by doing a request with ``Accept: image/png``
|
||||
or ``Accept: font/woff2``, respectively.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
raw-media-types="image/png, font/woff2"
|
||||
|
||||
.. _server-host:
|
||||
|
||||
server-host
|
||||
-----------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_SERVER_HOST
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
Where to bind the PostgREST web server. In addition to the usual address options, PostgREST interprets these reserved addresses with special meanings:
|
||||
|
||||
* :code:`*` - any IPv4 or IPv6 hostname
|
||||
* :code:`*4` - any IPv4 or IPv6 hostname, IPv4 preferred
|
||||
* :code:`!4` - any IPv4 hostname
|
||||
* :code:`*6` - any IPv4 or IPv6 hostname, IPv6 preferred
|
||||
* :code:`!6` - any IPv6 hostname
|
||||
|
||||
.. _server-port:
|
||||
|
||||
server-port
|
||||
-----------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_SERVER_PORT
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
The TCP port to bind the web server.
|
||||
|
||||
.. _server-unix-socket:
|
||||
|
||||
server-unix-socket
|
||||
------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_SERVER_UNIX_SOCKET
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
`Unix domain socket <https://en.wikipedia.org/wiki/Unix_domain_socket>`_ where to bind the PostgREST web server.
|
||||
If specified, this takes precedence over :ref:`server-port`. Example:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
server-unix-socket = "/tmp/pgrst.sock"
|
||||
|
||||
.. _server-unix-socket-mode:
|
||||
|
||||
server-unix-socket-mode
|
||||
-----------------------
|
||||
|
||||
=============== =================
|
||||
**Environment** PGRST_SERVER_UNIX_SOCKET_MODE
|
||||
**In-Database** `n/a`
|
||||
=============== =================
|
||||
|
||||
`Unix file mode <https://en.wikipedia.org/wiki/File_system_permissions>`_ to be set for the socket specified in :ref:`server-unix-socket`
|
||||
Needs to be a valid octal between 600 and 777.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
server-unix-socket-mode = "660"
|
||||
@@ -0,0 +1,91 @@
|
||||
let
|
||||
# Commit of the Nixpkgs repository that we want to use.
|
||||
nixpkgsVersion = {
|
||||
date = "2021-06-02";
|
||||
rev = "84aa23742f6c72501f9cc209f29c438766f5352d";
|
||||
tarballHash = "0h7xl6q0yjrbl9vm3h6lkxw692nm8bg3wy65gm95a2mivhrdjpxp";
|
||||
};
|
||||
|
||||
# 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;
|
||||
})
|
||||
{ };
|
||||
|
||||
sphinxTabsPkg = ps: ps.callPackage ./extensions/sphinx-tabs.nix { };
|
||||
sphinxCopybuttonPkg = ps: ps.callPackage ./extensions/sphinx-copybutton.nix { };
|
||||
|
||||
python = pkgs.python3.withPackages (ps: [ ps.sphinx ps.sphinx_rtd_theme ps.livereload (sphinxTabsPkg ps) (sphinxCopybuttonPkg ps) ]);
|
||||
in
|
||||
{
|
||||
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 \"{}\""
|
||||
'';
|
||||
|
||||
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
|
||||
'';
|
||||
}
|
||||
@@ -8,19 +8,14 @@ Community Tutorials
|
||||
|
||||
* `PostgREST + Auth0: Create REST API in minutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest-1-introduction/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
|
||||
|
||||
* `PostgREST + PostGIS API tutorial in 5 minutes <https://gis-ops.com/postgrest-postgis-api-tutorial-geospatial-api-in-5-minutes/>`_ -
|
||||
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`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).
|
||||
|
||||
* `How PostgreSQL triggers work when called with a PostgREST PATCH HTTP request <https://blog.fgribreau.com/2020/11/how-postgresql-triggers-works-when.html>`_ - A tutorial to see how the old and new values are set or not when doing a PATCH request to PostgREST.
|
||||
|
||||
* `REST Data Service on YugabyteDB / PostgreSQL <https://dev.to/yugabyte/rest-data-service-on-yugabytedb-postgresql-5f2h>`_
|
||||
|
||||
* `Build data-driven applications with Workers and PostgreSQL <https://developers.cloudflare.com/workers/tutorials/postgres/>`_ - A tutorial on how to integrate with PostgREST and PostgreSQL using Cloudflare Workers.
|
||||
|
||||
* `A poor man's API <https://blog.frankel.ch/poor-man-api>`_ - Shows how to integrate PostgREST with Apache APISIX as an alternative to Nginx.
|
||||
|
||||
.. * `Accessing a PostgreSQL database in Godot 4 via PostgREST <https://peterkingsbury.com/2022/08/16/godot-postgresql-postgrest/>`_
|
||||
|
||||
.. _templates:
|
||||
|
||||
Templates
|
||||
@@ -34,12 +29,28 @@ Templates
|
||||
Example Apps
|
||||
------------
|
||||
|
||||
* `archtika <https://github.com/thiloho/archtika>`_ - self-hosted CMS
|
||||
* `chronicle <https://github.com/srid/chronicle>`_ - tracking a tree of personal memories
|
||||
* `code-du-travail-backoffice <https://github.com/SocialGouv/code-du-travail-backoffice>`_ - data administration portal for the official French Labor Code and Agreements
|
||||
* `delibrium-postgrest <https://gitlab.com/delibrium/delibrium-postgrest/>`_ - example school API and front-end in Vue.js
|
||||
* `elm-workshop <https://github.com/diogob/elm-workshop>`_ - building a simple database query UI
|
||||
* `ember-postgrest-dynamic-ui <https://github.com/benoror/ember-postgrest-dynamic-ui>`_ - generating Ember forms to edit data
|
||||
* `ETH-transactions-storage <https://github.com/Adamant-im/ETH-transactions-storage>`_ - indexer for Ethereum to get transaction list by ETH address
|
||||
* `fullstack template <https://github.com/jenstroeger/fullstack-webapp-template>`_ - a complete fullstack webapp template using PG as db and message queue, Python and Dramatiq to implement async jobs, db migrations, test runners, and more.
|
||||
* `ext-postgrest-crud <https://github.com/timwis/ext-postgrest-crud>`_ - browser-based spreadsheet
|
||||
* `general <https://github.com/PierreRochard/general>`_ - example auth back-end
|
||||
* `goodfilm <https://github.com/tyrchen/goodfilm>`_ - example film API
|
||||
* `guild-operators <https://github.com/cardano-community/koios-artifacts/tree/main/files/grest>`_ - example queries and functions that the Cardano Community uses for their Guild Operators' Repository
|
||||
* `handsontable-postgrest <https://github.com/timwis/handsontable-postgrest>`_ - an excel-like database table editor
|
||||
* `heritage-near-me <https://github.com/CodeforAustralia/heritage-near-me>`_ - Elm and PostgREST with PostGIS
|
||||
* `ng-admin-postgrest <https://github.com/marmelab/ng-admin-postgrest>`_ - automatic database admin panel
|
||||
* `pgrst-dev-setup <https://github.com/Qu4tro/pgrst-dev-setup>`_ - docker-compose and tmuxp setup for experimentation.
|
||||
* `postgres-postgrest-cloudflared-example <https://github.com/cloudflare/postgres-postgrest-cloudflared-example>`_ - docker-compose setup exposing PostgREST using cloudfared
|
||||
* `postgrest-demo <https://github.com/SMRxT/postgrest-demo>`_ - multi-tenant logging system
|
||||
* `postgrest-example <https://github.com/begriffs/postgrest-example>`_ - sqitch versioning for API
|
||||
* `postgrest-sessions-example <https://github.com/monacoremo/postgrest-sessions-example>`_ - example for cookie-based sessions
|
||||
* `postgrest-translation-proxy <https://github.com/NikolayS/postgrest-translation-proxy>`_ - calling to external translation service
|
||||
* `postgrest-ui <https://github.com/tatut/postgrest-ui>`_ - ClojureScript UI components for PostgREST
|
||||
* `postgrest-vercel <https://github.com/seveibar/postgrest-vercel>`_ - run PostgREST on Vercel (Serverless/AWS Lambda)
|
||||
* `PostgrestSkeleton <https://github.com/Recmo/PostgrestSkeleton>`_ - Docker Compose, PostgREST, Nginx and Auth0
|
||||
* `PostGUI <https://github.com/priyank-purohit/PostGUI>`_ - React Material UI admin panel
|
||||
* `prospector <https://github.com/sfcta/prospector>`_ - data warehouse and visualization platform
|
||||
|
||||
@@ -50,22 +61,24 @@ 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
|
||||
|
||||
.. _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-bridge <https://github.com/matthewmueller/pg-bridge>`_ - Amazon SNS
|
||||
* `pg-kinesis-bridge <https://github.com/daurnimator/pg-kinesis-bridge>`_ - Amazon Kinesis
|
||||
* `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
|
||||
* `postgresql-to-amqp <https://github.com/FGRibreau/postgresql-to-amqp>`_ - AMQP
|
||||
* `postgresql2websocket <https://github.com/frafra/postgresql2websocket>`_ - Websockets
|
||||
* `skeeter <https://github.com/SpiderOak/skeeter>`_ - ZeroMQ
|
||||
|
||||
|
||||
.. _eco_extensions:
|
||||
@@ -75,24 +88,41 @@ Extensions
|
||||
|
||||
* `aiodata <https://github.com/Exahilosys/aiodata>`_ - Python, event-based proxy and caching client.
|
||||
* `pg-safeupdate <https://github.com/eradman/pg-safeupdate>`_ - prevent full-table updates or deletes
|
||||
* `postgrest-auth (criles25) <https://github.com/criles25/postgrest-auth>`_ - email based auth/signup
|
||||
* `postgrest-node <https://github.com/seveibar/postgrest-node>`_ - Run a PostgREST server in Node.js via npm module
|
||||
* `postgrest-oauth <https://github.com/nblumoe/postgrest-oauth>`_ - OAuth2 WAI middleware
|
||||
* `postgrest-oauth/api <https://github.com/postgrest-oauth/api>`_ - OAuth2 server
|
||||
* `PostgREST-writeAPI <https://github.com/ppKrauss/PostgREST-writeAPI>`_ - generate Nginx rewrite rules to fit an OpenAPI spec
|
||||
* `spas <https://github.com/srid/spas>`_ - allow file uploads and basic auth
|
||||
|
||||
.. _clientside_libraries:
|
||||
|
||||
Client-Side Libraries
|
||||
---------------------
|
||||
|
||||
* `efcore-postgrest <https://github.com/pedro-gilmora/EF.PostgREST.Provider>`_ - C#
|
||||
* `aor-postgrest-client <https://github.com/tomberek/aor-postgrest-client>`_ - JS, admin-on-rest
|
||||
* `elm-postgrest <https://github.com/john-kelly/elm-postgrest>`_ - Elm
|
||||
* `general-angular <https://github.com/PierreRochard/general-angular>`_ - TypeScript, generate UI from API description
|
||||
* `jarvus-postgrest-apikit <https://github.com/JarvusInnovations/jarvus-postgrest-apikit>`_ - JS, Sencha framework
|
||||
* `mithril-postgrest <https://github.com/catarse/mithril-postgrest>`_ - JS, Mithril
|
||||
* `ng-postgrest <https://github.com/team142/ng-postgrest>`_ - Angular app for browsing, editing data exposed over PostgREST.
|
||||
* `postgrest-client <https://github.com/calebmer/postgrest-client>`_ - JS
|
||||
* `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
|
||||
* `postgrest-py <https://github.com/supabase/postgrest-py>`_ - Python
|
||||
* `postgrest-request <https://github.com/lewisjared/postgrest-request>`_ - JS, SuperAgent
|
||||
* `postgrest-rs <https://github.com/supabase-community/postgrest-rs>`_ - Rust
|
||||
* `postgrest-sharp-client <https://github.com/thejettdurham/postgrest-sharp-client>`_ (needs maintainer) - C#, RestSharp
|
||||
* `postgrest-swift <https://github.com/supabase-community/postgrest-swift>`_ - Swift
|
||||
* `postgrest-url <https://github.com/hugomrdias/postgrest-url>`_ - JS, just for generating query URLs
|
||||
* `postgrest_python_requests_client <https://github.com/davidthewatson/postgrest_python_requests_client>`_ - Python
|
||||
* `postgrester <https://github.com/ivangabriele/postgrester>`_ - JS + Typescript
|
||||
* `postgrestR <https://github.com/clesiemo3/postgrestR>`_ - R
|
||||
* `py-postgrest <https://github.com/Kong/py-postgrest>`_ - Python
|
||||
* `redux-postgrest <https://github.com/andytango/redux-postgrest>`_ - TypeScript/JS, client integrated with (React) Redux.
|
||||
* `vue-postgrest <https://github.com/technowledgy/vue-postgrest>`_ - Vue.js
|
||||
|
||||
|
||||
@@ -1,40 +1,52 @@
|
||||
.. _error_source:
|
||||
|
||||
Errors
|
||||
######
|
||||
Error Source
|
||||
============
|
||||
|
||||
PostgREST error messages follow the PostgreSQL error structure. It includes ``MESSAGE``, ``DETAIL``, ``HINT``, ``ERRCODE`` and will add an HTTP status code to the response.
|
||||
|
||||
.. _postgresql_errors:
|
||||
|
||||
Errors from PostgreSQL
|
||||
======================
|
||||
|
||||
PostgREST will forward errors coming from PostgreSQL. For instance, on a failed constraint:
|
||||
For the most part, error messages will come directly from the database with the same `structure that PostgreSQL uses <https://www.postgresql.org/docs/current/error-style-guide.html>`_. PostgREST will convert the ``MESSAGE``, ``DETAIL``, ``HINT`` and ``ERRCODE`` from the PostgreSQL error to JSON format and add an HTTP status code to the response (see :ref:`status_codes`). For instance, this is the error you will get when querying a nonexistent table:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /projects HTTP/1.1
|
||||
GET /nonexistent_table?id=eq.1 HTTP/1.1
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 400 Bad Request
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Type: application/json; charset=utf-8
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"hint": null,
|
||||
"details": null,
|
||||
"code": "42P01",
|
||||
"message": "relation \"api.nonexistent_table\" does not exist"
|
||||
}
|
||||
|
||||
However, some errors do come from PostgREST itself (such as those related to the :ref:`schema_cache`). These have the same structure as the PostgreSQL errors but are differentiated by the ``PGRST`` prefix in the ``code`` field (see :ref:`pgrst_errors`). For instance, when querying a function that does not exist, the error will be:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /rpc/nonexistent_function HTTP/1.1
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Type: application/json; charset=utf-8
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"code": "23502",
|
||||
"details": "Failing row contains (null, foo, null).",
|
||||
"hint": null,
|
||||
"message": "null value in column \"id\" of relation \"projects\" violates not-null constraint"
|
||||
"hint": "If a new function was created in the database with this name and parameters, try reloading the schema cache.",
|
||||
"details": null
|
||||
"code": "PGRST202",
|
||||
"message": "Could not find the api.nonexistent_function() function in the schema cache"
|
||||
}
|
||||
|
||||
.. _status_codes:
|
||||
|
||||
HTTP Status Codes
|
||||
-----------------
|
||||
=================
|
||||
|
||||
PostgREST translates `PostgreSQL error codes <https://www.postgresql.org/docs/current/errcodes-appendix.html>`_ into HTTP status as follows:
|
||||
|
||||
@@ -69,11 +81,9 @@ PostgREST translates `PostgreSQL error codes <https://www.postgresql.org/docs/cu
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 40* | 500 | transaction rollback |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 53400 | 500 | config limit exceeded |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 53* | 503 | insufficient resources |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 54* | 500 | too complex |
|
||||
| 54* | 413 | too complex |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 55* | 500 | obj not in prerequisite state |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
@@ -95,53 +105,23 @@ PostgREST translates `PostgreSQL error codes <https://www.postgresql.org/docs/cu
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 42P01 | 404 | undefined table |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 42P17 | 500 | infinite recursion |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| 42501 | | if authenticated 403, | insufficient privileges |
|
||||
| | | else 401 | |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
| other | 400 | |
|
||||
+--------------------------+-------------------------+---------------------------------+
|
||||
|
||||
Errors from PostgREST
|
||||
=====================
|
||||
|
||||
Errors that come from PostgREST itself maintain the same structure but differ in the ``PGRST`` prefix in the ``code`` field. For instance, when querying a function that does not exist in the :doc:`schema cache <schema_cache>`:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /rpc/nonexistent_function HTTP/1.1
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 404 Not Found
|
||||
Content-Type: application/json; charset=utf-8
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"hint": "...",
|
||||
"details": null
|
||||
"code": "PGRST202",
|
||||
"message": "Could not find the api.nonexistent_function() function in the schema cache"
|
||||
}
|
||||
|
||||
|
||||
.. _pgrst_errors:
|
||||
|
||||
PostgREST Error Codes
|
||||
---------------------
|
||||
=====================
|
||||
|
||||
PostgREST error codes have the form ``PGRSTgxx``.
|
||||
|
||||
- ``PGRST`` is the prefix that differentiates the error from a PostgreSQL error.
|
||||
- ``g`` is the error group
|
||||
- ``xx`` is the error identifier in the group.
|
||||
PostgREST error codes have the form ``PGRSTgxx``, where ``PGRST`` is the prefix that differentiates the error from a PostgreSQL error, ``g`` is the group where the error belongs and ``xx`` is the number that identifies the error in the group.
|
||||
|
||||
.. _pgrst0**:
|
||||
|
||||
Group 0 - Connection
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
--------------------
|
||||
|
||||
Related to the connection with the database.
|
||||
|
||||
@@ -157,8 +137,8 @@ Related to the connection with the database.
|
||||
| PGRST001 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst002: | 503 | Could not connect with the database when building the |
|
||||
| | | :doc:`Schema Cache <schema_cache>` |
|
||||
| PGRST002 | | due to the PostgreSQL service not running. |
|
||||
| | | :ref:`schema_cache` due to the PostgreSQL service not |
|
||||
| PGRST002 | | running. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst003: | 504 | The request timed out waiting for a pool connection |
|
||||
| | | to be available. See :ref:`db-pool-acquisition-timeout`. |
|
||||
@@ -168,7 +148,7 @@ Related to the connection with the database.
|
||||
.. _pgrst1**:
|
||||
|
||||
Group 1 - Api Request
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
---------------------
|
||||
|
||||
Related to the HTTP request elements.
|
||||
|
||||
@@ -179,7 +159,7 @@ Related to the HTTP request elements.
|
||||
| | | See :ref:`h_filter`, :ref:`operators` and :ref:`ordering`. |
|
||||
| PGRST100 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst101: | 405 | For :ref:`functions <functions>`, only ``GET`` and ``POST`` |
|
||||
| .. _pgrst101: | 405 | For :ref:`functions <s_procs>`, only ``GET`` and ``POST`` |
|
||||
| | | verbs are allowed. Any other verb will throw this error. |
|
||||
| PGRST101 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
@@ -199,7 +179,7 @@ Related to the HTTP request elements.
|
||||
| | | :ref:`switching schemas <multiple-schemas>` is not present |
|
||||
| PGRST106 | | in the :ref:`db-schemas` configuration variable. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst107: | 406 | The ``Accept`` media type sent in the request is invalid. |
|
||||
| .. _pgrst107: | 415 | The ``Content-Type`` sent in the request is invalid. |
|
||||
| | | |
|
||||
| PGRST107 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
@@ -207,6 +187,14 @@ Related to the HTTP request elements.
|
||||
| | | specified in the ``select`` part of the query string. |
|
||||
| PGRST108 | | See :ref:`embed_filters`. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst109: | 400 | Restricting a Deletion or an Update using limits must |
|
||||
| | | include the ordering of a unique column. |
|
||||
| PGRST109 | | See :ref:`limited_update_delete`. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst110: | 400 | When restricting a Deletion or an Update using limits |
|
||||
| | | modifies more rows than the maximum specified in the limit. |
|
||||
| PGRST110 | | See :ref:`limited_update_delete`. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst111: | 500 | An invalid ``response.headers`` was set. |
|
||||
| | | See :ref:`guc_resp_hdrs`. |
|
||||
| PGRST111 | | |
|
||||
@@ -215,6 +203,10 @@ Related to the HTTP request elements.
|
||||
| | | See :ref:`guc_resp_status`. |
|
||||
| PGRST112 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst113: | 406 | More than one column was returned for a scalar result. |
|
||||
| | | See :ref:`scalar_return_formats`. |
|
||||
| PGRST113 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst114: | 400 | For an :ref:`UPSERT using PUT <upsert_put>`, when |
|
||||
| | | :ref:`limits and offsets <limits>` are used. |
|
||||
| PGRST114 | | |
|
||||
@@ -231,67 +223,26 @@ Related to the HTTP request elements.
|
||||
| | | |
|
||||
| PGRST117 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst118: | 400 | Could not order the result using the related table because |
|
||||
| | | there is no many-to-one or one-to-one relationship between |
|
||||
| PGRST118 | | them. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst120: | 400 | An embedded resource can only be filtered using the |
|
||||
| | | ``is.null`` or ``not.is.null`` :ref:`operators <operators>`.|
|
||||
| PGRST120 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst121: | 500 | PostgREST can't parse the JSON objects in RAISE |
|
||||
| | | ``PGRST`` error. See :ref:`raise headers <raise_headers>`. |
|
||||
| PGRST121 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst122: | 400 | Invalid preferences found in ``Prefer`` header with |
|
||||
| | | ``Prefer: handling=strict``. See :ref:`prefer_handling`. |
|
||||
| PGRST122 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst123: | 400 | Aggregate functions are disabled. |
|
||||
| | | See :ref:`db-aggregates-enabled`. |
|
||||
| PGRST123 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst124: | 400 | ``max-affected`` preference is violated. |
|
||||
| | | See :ref:`prefer_max_affected`. |
|
||||
| PGRST124 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst125: | 404 | Invalid path is specified in request URL. |
|
||||
| | | |
|
||||
| PGRST125 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst126: | 404 | Open API config is disabled but API root path is |
|
||||
| | | accessed. See :ref:`openapi-mode`. |
|
||||
| PGRST126 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst127: | 400 | The feature specified in the ``details`` field is not |
|
||||
| | | implemented. |
|
||||
| PGRST127 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst128: | 400 | ``max-affected`` preference is violated with ``RPC`` call. |
|
||||
| | | See :ref:`prefer_max_affected`. |
|
||||
| PGRST128 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
|
||||
|
||||
.. _pgrst2**:
|
||||
|
||||
Group 2 - Schema Cache
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
----------------------
|
||||
|
||||
Related to a :ref:`schema_cache`. Most of the time, these errors are solved by :ref:`schema_reloading`.
|
||||
Related to a :ref:`stale schema cache <stale_schema>`. Most of the time, these errors are solved by :ref:`reloading the schema cache <schema_reloading>`.
|
||||
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| Code | HTTP status | Description |
|
||||
+===============+=============+=============================================================+
|
||||
| .. _pgrst200: | 400 | Caused by stale foreign key relationships, otherwise any of |
|
||||
| .. _pgrst200: | 400 | Caused by :ref:`stale_fk_relationships`, otherwise any of |
|
||||
| | | the embedding resources or the relationship itself may not |
|
||||
| PGRST200 | | exist in the database. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst201: | 300 | An ambiguous embedding request was made. |
|
||||
| | | See :ref:`complex_rels`. |
|
||||
| | | See :ref:`embed_disamb`. |
|
||||
| PGRST201 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst202: | 404 | Caused by a stale function signature, otherwise |
|
||||
| .. _pgrst202: | 404 | Caused by a :ref:`stale_function_signature`, otherwise |
|
||||
| | | the function may not exist in the database. |
|
||||
| PGRST202 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
@@ -305,15 +256,11 @@ Related to a :ref:`schema_cache`. Most of the time, these errors are solved by :
|
||||
| | | in the ``columns`` query parameter is not found. |
|
||||
| PGRST204 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst205: | 404 | Caused when the :ref:`table specified <tables_views>` in |
|
||||
| | | the URI is not found. |
|
||||
| PGRST205 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
|
||||
.. _pgrst3**:
|
||||
|
||||
Group 3 - JWT
|
||||
~~~~~~~~~~~~~
|
||||
-------------
|
||||
|
||||
Related to the authentication process using JWT. You can follow the :ref:`tut1` for an example on how to implement authentication and the :doc:`Authentication page <auth>` for more information on this process.
|
||||
|
||||
@@ -324,25 +271,21 @@ Related to the authentication process using JWT. You can follow the :ref:`tut1`
|
||||
| | | configuration. |
|
||||
| PGRST300 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst301: | 401 | Provided JWT couldn't be decoded or it is invalid. |
|
||||
| | | |
|
||||
| .. _pgrst301: | 401 | Any error related to the verification of the JWT, |
|
||||
| | | which means that the JWT provided is invalid in some way. |
|
||||
| PGRST301 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst302: | 401 | Attempted to do a request without |
|
||||
| | | :ref:`bearer_auth` when the anonymous role |
|
||||
| | | :ref:`authentication <client_auth>` when the anonymous role |
|
||||
| PGRST302 | | is disabled by not setting it in :ref:`db-anon-role`. |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
| .. _pgrst303: | 401 | :ref:`JWT claims validation <jwt_claims_validation>` |
|
||||
| | | or parsing failed. |
|
||||
| PGRST303 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
|
||||
.. The Internal Errors Group X** is always at the end
|
||||
|
||||
.. _pgrst_X**:
|
||||
|
||||
Group X - Internal
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
------------------
|
||||
|
||||
Internal errors. If you encounter any of these, you may have stumbled on a PostgREST bug, please `open an issue <https://github.com/PostgREST/postgrest/issues>`_ and we'll be glad to fix it.
|
||||
|
||||
@@ -353,158 +296,3 @@ Internal errors. If you encounter any of these, you may have stumbled on a Postg
|
||||
| | | to the database. |
|
||||
| PGRSTX00 | | |
|
||||
+---------------+-------------+-------------------------------------------------------------+
|
||||
|
||||
.. _custom_errors:
|
||||
|
||||
Custom Errors
|
||||
=============
|
||||
|
||||
You can customize the errors by using the `RAISE statement <https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE>`_ on functions.
|
||||
|
||||
.. _raise_error:
|
||||
|
||||
RAISE errors with HTTP Status Codes
|
||||
-----------------------------------
|
||||
|
||||
Custom status codes can be done by raising SQL exceptions inside :ref:`functions <functions>`. For instance, here's a saucy function that always responds with an error:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE OR REPLACE FUNCTION just_fail() RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RAISE EXCEPTION 'I refuse!'
|
||||
USING DETAIL = 'Pretty simple',
|
||||
HINT = 'There is nothing you can do.';
|
||||
END
|
||||
$$;
|
||||
|
||||
Calling the function returns HTTP 400 with the body
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"message":"I refuse!",
|
||||
"details":"Pretty simple",
|
||||
"hint":"There is nothing you can do.",
|
||||
"code":"P0001"
|
||||
}
|
||||
|
||||
One way to customize the HTTP status code is by raising particular exceptions according to the PostgREST :ref:`error to status code mapping <status_codes>`. For example, :code:`RAISE insufficient_privilege` will respond with HTTP 401/403 as appropriate.
|
||||
|
||||
For even greater control of the HTTP status code, raise an exception of the ``PTxyz`` type. For instance to respond with HTTP 402, raise ``PT402``:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
RAISE sqlstate 'PT402' using
|
||||
message = 'Payment Required',
|
||||
detail = 'Quota exceeded',
|
||||
hint = 'Upgrade your plan';
|
||||
|
||||
Returns:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 402 Payment Required
|
||||
Content-Type: application/json; charset=utf-8
|
||||
|
||||
{
|
||||
"message": "Payment Required",
|
||||
"details": "Quota exceeded",
|
||||
"hint": "Upgrade your plan",
|
||||
"code": "PT402"
|
||||
}
|
||||
|
||||
.. _raise_headers:
|
||||
|
||||
Add HTTP Headers with RAISE
|
||||
---------------------------
|
||||
|
||||
For full control over headers and status you can raise a ``PGRST`` SQLSTATE error. You can achieve this by adding the ``code``, ``message``, ``detail`` and ``hint`` in the PostgreSQL error message field as a JSON object. Here, the ``details`` and ``hint`` are optional. Similarly, the ``status`` and ``headers`` must be added to the SQL error detail field as a JSON object. For instance:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
RAISE sqlstate 'PGRST' USING
|
||||
message = '{"code":"123","message":"Payment Required","details":"Quota exceeded","hint":"Upgrade your plan"}',
|
||||
detail = '{"status":402,"headers":{"X-Powered-By":"Nerd Rage"}}';
|
||||
|
||||
Returns:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 402 Payment Required
|
||||
Content-Type: application/json; charset=utf-8
|
||||
X-Powered-By: Nerd Rage
|
||||
|
||||
{
|
||||
"message": "Payment Required",
|
||||
"details": "Quota exceeded",
|
||||
"hint": "Upgrade your plan",
|
||||
"code": "123"
|
||||
}
|
||||
|
||||
|
||||
For non standard HTTP status, you can optionally add ``status_text`` to describe the status code. For status code ``419`` the detail field may look like this:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
detail = '{"status":419,"status_text":"Page Expired","headers":{"X-Powered-By":"Nerd Rage"}}';
|
||||
|
||||
If PostgREST can't parse the JSON objects ``message`` and ``detail``, it will throw a ``PGRST121`` error. See :ref:`Errors from PostgREST<pgrst1**>`.
|
||||
|
||||
.. _proxy-status_header:
|
||||
|
||||
Proxy-Status Header
|
||||
===================
|
||||
|
||||
For error cases, the standard `Proxy-Status <https://www.rfc-editor.org/rfc/rfc9209.html#name-the-proxy-status-http-field>`_ header is returned with the error code. The error code comes from either :ref:`PostgREST <pgrst_errors>`, :ref:`PostgreSQL <postgresql_errors>` or :ref:`Custom <custom_errors>` errors. This is useful when doing ``HEAD`` requests where the HTTP status is not descriptive enough.
|
||||
|
||||
For example, doing a request on a table with high count (say 30_000_000), we get:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HEAD /table HTTP/1.1
|
||||
Prefer: count=exact
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 500 Internal Server Error
|
||||
Proxy-Status: PostgREST; error=57014
|
||||
|
||||
The PostgreSQL error code ``57014`` (`ref <https://www.postgresql.org/docs/current/errcodes-appendix.html>`_) reveals that the error is due to a short ``statement_timeout`` value.
|
||||
|
||||
.. _client_error_verbosity:
|
||||
|
||||
Client Error Verbosity
|
||||
======================
|
||||
|
||||
For HTTP clients, the error verbosity can be set via :ref:`client-error-verbosity` config.
|
||||
|
||||
With ``verbose``, it returns ``code``, ``message``, ``details`` and ``hint``.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
curl "localhost:3000/itemsxx"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"code": "PGRST205",
|
||||
"message": "Could not find the table 'public.itemsxx' in the schema cache",
|
||||
"details": "Perhaps you meant the table 'public.items'",
|
||||
"hint": null
|
||||
}
|
||||
|
||||
With ``minimal``, just ``code`` and ``message`` is returned.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
curl "localhost:3000/itemsxx"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"code": "PGRST205",
|
||||
"message": "Could not find the table 'public.itemsxx' in the schema cache"
|
||||
}
|
||||
@@ -1,90 +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/src/executable/Main.hs>`_.
|
||||
|
||||
CLI
|
||||
---
|
||||
|
||||
Main then calls `CLI.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/PostgREST/CLI.hs>`_, which is in charge of :ref:`cli`.
|
||||
|
||||
App
|
||||
---
|
||||
|
||||
`App.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/PostgREST/App.hs>`_ is then in charge of composing the different modules.
|
||||
|
||||
Auth
|
||||
----
|
||||
|
||||
`Auth.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/PostgREST/Auth.hs>`_ is in charge of :ref:`authn`.
|
||||
|
||||
Api Request
|
||||
-----------
|
||||
|
||||
`ApiRequest.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/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/library/PostgREST/Plan.hs>`_ generates an internal AST, filling out-of-band SQL details (like an ``ON CONFLICT (pk)`` clause) required to complete the user request.
|
||||
|
||||
A request might be rejected at this level if it's invalid. For example when doing resource embedding on a nonexistent resource.
|
||||
|
||||
Query
|
||||
-----
|
||||
|
||||
`Query.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/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/library/PostgREST/SchemaCache.hs>`_ is in charge of :ref:`schema_cache`.
|
||||
|
||||
Config
|
||||
------
|
||||
|
||||
`Config.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/PostgREST/Config.hs>`_ is in charge of :ref:`configuration`.
|
||||
|
||||
Admin
|
||||
-----
|
||||
|
||||
`Admin.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/PostgREST/Admin.hs>`_ is in charge of the :ref:`admin_server`.
|
||||
|
||||
Listener
|
||||
--------
|
||||
|
||||
`Reload.hs <https://github.com/PostgREST/postgrest/blob/main/src/library/PostgREST/AppState/Reload.hs>`_ is in charge of the :ref:`listener`.
|
||||
@@ -1,198 +0,0 @@
|
||||
.. _db_authz:
|
||||
|
||||
Database Authorization
|
||||
######################
|
||||
|
||||
Database authorization is the process of granting and verifying database access permissions. PostgreSQL manages permissions using the concept of roles.
|
||||
|
||||
Users and Groups
|
||||
================
|
||||
|
||||
A role can be thought of as either a database user, or a group of database users, depending on how the role is set up.
|
||||
|
||||
Roles for Each Web User
|
||||
-----------------------
|
||||
|
||||
PostgREST can accommodate either viewpoint. If you treat a role as a single user then :ref:`user_impersonation` does most of what you need. When an authenticated user makes a request PostgREST will switch into the database role for that user, which in addition to restricting queries, is available to SQL through the :code:`current_user` variable.
|
||||
|
||||
You can use row-level security to flexibly restrict visibility and access for the current user. Here is an `example <https://www.enterprisedb.com:443/blog/application-users-vs-row-level-security>`_ from Tomas Vondra, a chat table storing messages sent between users. Users can insert rows into it to send messages to other users, and query it to see messages sent to them by other users.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE TABLE chat (
|
||||
message_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
message_time TIMESTAMP NOT NULL DEFAULT now(),
|
||||
message_from NAME NOT NULL DEFAULT current_user,
|
||||
message_to NAME NOT NULL,
|
||||
message_subject VARCHAR(64) NOT NULL,
|
||||
message_body TEXT
|
||||
);
|
||||
|
||||
ALTER TABLE chat ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
We want to enforce a policy that ensures a user can see only those messages sent by them or intended for them. Also we want to prevent a user from forging the ``message_from`` column with another person's name.
|
||||
|
||||
PostgreSQL allows us to set this policy with row-level security:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE POLICY chat_policy ON chat
|
||||
USING ((message_to = current_user) OR (message_from = current_user))
|
||||
WITH CHECK (message_from = current_user)
|
||||
|
||||
Anyone accessing the generated API endpoint for the chat table will see exactly the rows they should, without our needing custom imperative server-side coding.
|
||||
|
||||
.. warning::
|
||||
|
||||
Roles are namespaced per-cluster rather than per-database so they may be prone to collision.
|
||||
|
||||
Web Users Sharing Role
|
||||
----------------------
|
||||
|
||||
Alternately database roles can represent groups instead of (or in addition to) individual users. You may choose that all signed-in users for a web app share the role ``webuser``. You can distinguish individual users by including extra claims in the JWT such as email.
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"role": "webuser",
|
||||
"email": "john@doe.com"
|
||||
}
|
||||
|
||||
SQL code can access claims through PostgREST :ref:`tx_settings`. For instance to get the email claim, call this function:
|
||||
|
||||
.. code:: sql
|
||||
|
||||
current_setting('request.jwt.claims', true)::json->>'email';
|
||||
|
||||
.. note::
|
||||
|
||||
For PostgreSQL < 14
|
||||
|
||||
.. code:: sql
|
||||
|
||||
current_setting('request.jwt.claim.email', true);
|
||||
|
||||
This allows JWT generation services to include extra information and your database code to react to it. For instance the RLS example could be modified to use this ``current_setting`` rather than ``current_user``. The second ``'true'`` argument tells ``current_setting`` to return NULL if the setting is missing from the current configuration.
|
||||
|
||||
Hybrid User-Group Roles
|
||||
-----------------------
|
||||
|
||||
You can mix the group and individual role policies. For instance we could still have a webuser role and individual users which inherit from it:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE webuser NOLOGIN;
|
||||
-- grant this role access to certain tables etc
|
||||
|
||||
CREATE ROLE user000 NOLOGIN;
|
||||
GRANT webuser TO user000;
|
||||
-- now user000 can do whatever webuser can
|
||||
|
||||
GRANT user000 TO authenticator;
|
||||
-- allow authenticator to switch into user000 role
|
||||
-- (the role itself has nologin)
|
||||
|
||||
Schemas
|
||||
=======
|
||||
|
||||
You must explicitly allow roles to access the exposed schemas in :ref:`db-schemas`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT USAGE ON SCHEMA api TO webuser;
|
||||
|
||||
Tables
|
||||
======
|
||||
|
||||
To let web users access tables you must grant them privileges for the operations you want them to do.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT
|
||||
SELECT
|
||||
, INSERT
|
||||
, UPDATE(message_body)
|
||||
, DELETE
|
||||
ON chat TO webuser;
|
||||
|
||||
You can also choose on which table columns the operation is valid. In the above example, the web user can only update the ``message_body`` column.
|
||||
|
||||
.. _func_privs:
|
||||
|
||||
Functions
|
||||
=========
|
||||
|
||||
By default, when a function is created, the privilege to execute it is not restricted by role. The function access is ``PUBLIC`` — executable by all roles (more details at `PostgreSQL Privileges page <https://www.postgresql.org/docs/current/ddl-priv.html>`_). This is not ideal for an API schema. To disable this behavior, you can run the following SQL statement:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
||||
|
||||
This will change the privileges for all functions created in the future in all schemas. Currently there is no way to limit it to a single schema. In our opinion it's a good practice anyway.
|
||||
|
||||
.. note::
|
||||
|
||||
It is however possible to limit the effect of this clause only to functions you define. You can put the above statement at the beginning of the API schema definition, and then at the end reverse it with:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
ALTER DEFAULT PRIVILEGES GRANT EXECUTE ON FUNCTIONS TO PUBLIC;
|
||||
|
||||
This will work because the :code:`alter default privileges` statement has effect on function created *after* it is executed. See `PostgreSQL alter default privileges <https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html>`_ for more details.
|
||||
|
||||
After that, you'll need to grant EXECUTE privileges on functions explicitly:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT EXECUTE ON FUNCTION login TO anonymous;
|
||||
GRANT EXECUTE ON FUNCTION signup TO anonymous;
|
||||
|
||||
You can also grant execute on all functions in a schema to a higher privileged role:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO web_user;
|
||||
|
||||
Security definer
|
||||
----------------
|
||||
|
||||
A function is executed with the privileges of the user who calls it. This means that the user has to have all permissions to do the operations the function performs.
|
||||
If the function accesses private database objects, your :ref:`API roles <roles>` won't be able to successfully execute the function.
|
||||
|
||||
Another option is to define the function with the :code:`SECURITY DEFINER` option. Then only one permission check will take place, the permission to call the function, and the operations in the function will have the authority of the user who owns the function itself.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- login as a user which has privileges on the private schemas
|
||||
|
||||
-- create a sample function
|
||||
create or replace function login(email text, pass text, out token text) as $$
|
||||
begin
|
||||
-- access to a private schema called 'auth'
|
||||
select auth.user_role(email, pass) into _role;
|
||||
-- other operations
|
||||
-- ...
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
Note the ``SECURITY DEFINER`` keywords at the end of the function. See `PostgreSQL documentation <https://www.postgresql.org/docs/current/sql-createfunction.html#SQL-CREATEFUNCTION-SECURITY>`_ for more details.
|
||||
|
||||
Views
|
||||
=====
|
||||
|
||||
Views are invoked with the privileges of the view owner, much like functions with the ``SECURITY DEFINER`` option. When created by a SUPERUSER role, all `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ policies will be bypassed.
|
||||
|
||||
If you're on PostgreSQL >= 15, this behavior can be changed by specifying the ``security_invoker`` option.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE VIEW sample_view WITH (security_invoker = true) AS
|
||||
SELECT * FROM sample_table;
|
||||
|
||||
On PostgreSQL < 15, you can create a non-SUPERUSER role and make this role the view's owner.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE ROLE api_views_owner NOSUPERUSER NOBYPASSRLS;
|
||||
ALTER VIEW sample_view OWNER TO api_views_owner;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.. _external_auth:
|
||||
|
||||
External Authentication
|
||||
-----------------------
|
||||
|
||||
JWT from Auth0
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
An external service like `Auth0 <https://auth0.com/>`_ can do the hard work transforming OAuth from Github, Twitter, Google etc into a JWT suitable for PostgREST. Auth0 can also handle email signup and password reset flows.
|
||||
|
||||
To use Auth0, create `an application <https://auth0.com/docs/get-started/applications>`_ for your app and `an API <https://auth0.com/docs/get-started/apis>`_ for your PostgREST server. Auth0 supports both HS256 and RS256 scheme for the issued tokens for APIs. For simplicity, you may first try HS256 scheme while creating your API on Auth0. Your application should use your PostgREST API's `API identifier <https://auth0.com/docs/get-started/apis/api-settings>`_ by setting it with the `audience parameter <https://auth0.com/docs/secure/tokens/access-tokens/get-access-tokens#control-access-token-audience>`_ during the authorization request. This will ensure that Auth0 will issue an access token for your PostgREST API. For PostgREST to verify the access token, you will need to set ``jwt-secret`` on PostgREST config file with your API's signing secret.
|
||||
@@ -1,102 +0,0 @@
|
||||
.. _nginx:
|
||||
|
||||
Nginx
|
||||
=====
|
||||
|
||||
PostgREST is a fast way to construct a RESTful API. Its default behavior is great for scaffolding in development. When it's time to go to production it works great too, as long as you take precautions.
|
||||
PostgREST is a small sharp tool that focuses on performing the API-to-database mapping. We rely on a reverse proxy like Nginx for additional safeguards.
|
||||
|
||||
The first step is to create an Nginx configuration file that proxies requests to an underlying PostgREST server.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
http {
|
||||
# ...
|
||||
# upstream configuration
|
||||
upstream postgrest {
|
||||
server localhost:3000;
|
||||
}
|
||||
# ...
|
||||
server {
|
||||
# ...
|
||||
# expose to the outside world
|
||||
location /api/ {
|
||||
default_type application/json;
|
||||
proxy_hide_header Content-Location;
|
||||
add_header Content-Location /api/$upstream_http_content_location;
|
||||
proxy_set_header Connection "";
|
||||
proxy_http_version 1.1;
|
||||
proxy_pass http://postgrest/;
|
||||
}
|
||||
# ...
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
For ubuntu, if you already installed nginx through :code:`apt` you can add this to the config file in
|
||||
:code:`/etc/nginx/sites-enabled/default`.
|
||||
|
||||
.. _https:
|
||||
|
||||
HTTPS
|
||||
-----
|
||||
|
||||
PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how <https://nginx.org/en/docs/http/configuring_https_servers.html>`_.
|
||||
|
||||
Rate Limiting
|
||||
-------------
|
||||
|
||||
Nginx supports "leaky bucket" rate limiting (see `official docs <https://nginx.org/en/docs/http/ngx_http_limit_req_module.html>`_). Using standard Nginx configuration, routes can be grouped into *request zones* for rate limiting. For instance we can define a zone for login attempts:
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
|
||||
|
||||
This creates a shared memory zone called "login" to store a log of IP addresses that access the rate limited urls. The space reserved, 10 MB (:code:`10m`) will give us enough space to store a history of 160k requests. We have chosen to allow only allow one request per second (:code:`1r/s`).
|
||||
|
||||
Next we apply the zone to certain routes, like a hypothetical function called :code:`login`.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
location /rpc/login/ {
|
||||
# apply rate limiting
|
||||
limit_req zone=login burst=5;
|
||||
}
|
||||
|
||||
The burst argument tells Nginx to start dropping requests if more than five queue up from a specific IP.
|
||||
|
||||
Nginx rate limiting is general and indiscriminate. To rate limit each authenticated request individually you will need to add logic in a :ref:`Custom Validation <custom_validation>` function.
|
||||
|
||||
Alternate URL Structure
|
||||
-----------------------
|
||||
|
||||
As discussed in :ref:`singular_plural`, there are no special URL forms for singular resources in PostgREST, only operators for filtering. Thus there are no URLs like :code:`/people/1`. It would be specified instead as
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/people?id=eq.1" \
|
||||
-H "Accept: application/vnd.pgrst.object+json"
|
||||
|
||||
This allows compound primary keys and makes the intent for singular response independent of a URL convention.
|
||||
|
||||
Nginx rewrite rules allow you to simulate the familiar URL convention. The following example adds a rewrite rule for all table endpoints, but you'll want to restrict it to those tables that have a numeric simple primary key named "id."
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
# support /endpoint/:id url style
|
||||
location ~ ^/([a-z_]+)/([0-9]+) {
|
||||
|
||||
# make the response singular
|
||||
proxy_set_header Accept 'application/vnd.pgrst.object+json';
|
||||
|
||||
# assuming an upstream named "postgrest"
|
||||
proxy_pass http://postgrest/$1?id=eq.$2;
|
||||
|
||||
}
|
||||
|
||||
.. TODO
|
||||
.. Administration
|
||||
.. API Versioning
|
||||
.. HTTP Caching
|
||||
.. Upgrading
|
||||
@@ -1,25 +0,0 @@
|
||||
.. _schema_isolation:
|
||||
|
||||
Schema Isolation
|
||||
================
|
||||
|
||||
A PostgREST instance exposes all the tables, views, and functions of a single `PostgreSQL schema <https://www.postgresql.org/docs/current/ddl-schemas.html>`_ (a namespace of database objects). This means private data or implementation details can go inside different private schemas and be invisible to HTTP clients.
|
||||
|
||||
It is recommended that you don't expose tables on your API schema. Instead expose views and functions which insulate the internal details from the outside world.
|
||||
This allows you to change the internals of your schema and maintain backwards compatibility. It also keeps your code easier to refactor, and provides a natural way to do API versioning.
|
||||
|
||||
.. container:: svg-container-md
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. See https://github.com/sphinx-doc/sphinx/issues/2240#issuecomment-187366626
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<object width="100%" data="../_static/sch-iso-dark.svg" type="image/svg+xml"></object>
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<object width="100%" data="../_static/sch-iso.svg" type="image/svg+xml"></object>
|
||||
@@ -0,0 +1,33 @@
|
||||
{ lib
|
||||
, buildPythonPackage
|
||||
, fetchFromGitHub
|
||||
, sphinx
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "sphinx-copybutton";
|
||||
version = "0.4.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "executablebooks";
|
||||
repo = "sphinx-copybutton";
|
||||
rev = "v${version}";
|
||||
sha256 = "sha256-vrEIvQeP7AMXSme1PBp0ox5k8Q1rz+1cbHIO+o17Jqc=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [
|
||||
sphinx
|
||||
];
|
||||
|
||||
doCheck = false; # no tests
|
||||
|
||||
pythonImportsCheck = [ "sphinx_copybutton" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "A small sphinx extension to add a \"copy\" button to code blocks";
|
||||
homepage = "https://github.com/executablebooks/sphinx-copybutton";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [ Luflosi ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{ lib
|
||||
, buildPythonPackage
|
||||
, fetchPypi
|
||||
, sphinx
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "sphinx-tabs";
|
||||
version = "3.2.0";
|
||||
|
||||
src = fetchPypi {
|
||||
inherit pname version;
|
||||
sha256 = "sha256:1970aahi6sa7c37cpz8nwgdb2xzf21rk6ykdd1m6w9wvxla7j4rk";
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [
|
||||
sphinx
|
||||
];
|
||||
|
||||
doCheck = false;
|
||||
|
||||
pythonImportsCheck = [ "sphinx_tabs" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Create tabbed content in Sphinx documentation when building HTML";
|
||||
homepage = "https://sphinx-tabs.readthedocs.io";
|
||||
license = licenses.mit;
|
||||
};
|
||||
}
|
||||
@@ -5,20 +5,22 @@ Create a SOAP endpoint
|
||||
|
||||
:author: `fjf2002 <https://github.com/fjf2002>`_
|
||||
|
||||
PostgREST supports :ref:`custom_media`. With a bit of work, SOAP endpoints become possible.
|
||||
PostgREST now has XML support. With a bit of work, SOAP endpoints become possible.
|
||||
|
||||
Please note that PostgREST supports just ``text/xml`` MIME type in request/response headers ``Content-Type`` and ``Accept``.
|
||||
If you have to use other MIME types such as ``application/soap+xml``, you could manipulate the headers in your reverse proxy.
|
||||
|
||||
|
||||
|
||||
Minimal Example
|
||||
---------------
|
||||
|
||||
This example will simply return the request body, inside a tag ``therequestbodywas``.
|
||||
|
||||
Add the following function to your PostgreSQL database:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "text/xml" as pg_catalog.xml;
|
||||
|
||||
CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS "text/xml" AS $$
|
||||
CREATE OR REPLACE FUNCTION my_soap_endpoint(xml) RETURNS xml AS $$
|
||||
DECLARE
|
||||
nsarray CONSTANT text[][] := ARRAY[
|
||||
ARRAY['soapenv', 'http://schemas.xmlsoap.org/soap/envelope/']
|
||||
@@ -77,6 +79,25 @@ and should roughly look like:
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
|
||||
Unfortunately the ``Accept: text/xml`` header is currently mandatory concerning PostgREST, otherwise it will respond
|
||||
with a ``Content-Type: application/json`` header and enclose the response with quotes.
|
||||
(You can check the returned headers by adding ``-v`` to the curl call.)
|
||||
|
||||
If your SOAP clients do not send the ``Accept: text/xml`` header, you can fix that in your nginx reverse proxy
|
||||
by adding something like ...
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
set $accept $http_accept;
|
||||
if ($contentType ~ "^text/xml($|;)") {
|
||||
set $accept "text/xml";
|
||||
}
|
||||
proxy_set_header Accept $accept;
|
||||
|
||||
to your ``location`` nginx configuration.
|
||||
(The given example sets the ``Accept`` header for each request of Content-Type ``text/xml``.)
|
||||
|
||||
|
||||
A more elaborate example
|
||||
------------------------
|
||||
|
||||
@@ -100,7 +121,7 @@ potentially disclosing internals to the client, but instead handle the errors di
|
||||
xmlelement(NAME "soapenv:Body", body)
|
||||
);
|
||||
$function$;
|
||||
|
||||
|
||||
-- helper function
|
||||
CREATE OR REPLACE FUNCTION _soap_exception(
|
||||
faultcode text,
|
||||
@@ -116,9 +137,9 @@ potentially disclosing internals to the client, but instead handle the errors di
|
||||
)
|
||||
);
|
||||
$function$;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION fraction_to_decimal(xml)
|
||||
RETURNS "text/xml"
|
||||
RETURNS xml
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
@@ -186,14 +207,14 @@ The output should roughly look like:
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
For more information concerning PostgREST, cf.
|
||||
|
||||
- :ref:`function_single_unnamed`
|
||||
- :ref:`custom_media`. See :ref:`any_handler`, if you need to support an ``application/soap+xml`` media type or if you want to respond with XML without sending a media type.
|
||||
- :ref:`Nginx reverse proxy <nginx>`
|
||||
- :ref:`s_proc_single_unnamed`
|
||||
- :ref:`scalar_return_formats`
|
||||
- :ref:`Nginx reverse proxy <admin>`
|
||||
|
||||
For SOAP reference, visit
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
.. _debugging_performance_pg_stat_statements:
|
||||
|
||||
Debugging Performance with pg_stat_statements
|
||||
=============================================
|
||||
|
||||
This how-to shows how to get a query identifier through PostgREST and then use it to inspect the same query in ``pg_stat_statements``.
|
||||
|
||||
.. important::
|
||||
|
||||
- :ref:`db-plan-enabled` must be enabled in PostgREST.
|
||||
- PostgreSQL 14 or newer with ``pg_stat_statements`` available.
|
||||
|
||||
Get the Query Identifier from PostgREST
|
||||
---------------------------------------
|
||||
|
||||
Request the plan in JSON format with the ``verbose`` option:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/projects?select=id,name&order=id" \
|
||||
-H "Accept: application/vnd.pgrst.plan+json; options=verbose"
|
||||
|
||||
The response will contain a top-level ``Query Identifier`` field:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"Plan": {
|
||||
"Node Type": "Aggregate"
|
||||
},
|
||||
"Query Identifier": -432192689578025496
|
||||
}
|
||||
]
|
||||
|
||||
Look up the query in pg_stat_statements
|
||||
---------------------------------------
|
||||
|
||||
Use that identifier against ``pg_stat_statements``:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
select
|
||||
calls,
|
||||
total_exec_time,
|
||||
mean_exec_time,
|
||||
rows,
|
||||
query
|
||||
from pg_stat_statements
|
||||
where queryid = -432192689578025496;
|
||||
|
||||
.. csv-table::
|
||||
:header: "calls", "total_exec_time", "mean_exec_time", "rows", "query"
|
||||
|
||||
"13", "0.6355850000000001", "0.04889115384615385", "13", "WITH pgrst_source AS (...)"
|
||||
|
||||
This lets you correlate a PostgREST request with PostgreSQL runtime statistics such as:
|
||||
|
||||
- how often the query ran
|
||||
- total and average execution time
|
||||
- how many rows it produced
|
||||
- the normalized SQL text recorded by PostgreSQL
|
||||
@@ -1,326 +0,0 @@
|
||||
|
||||
.. _providing_html_htmx:
|
||||
|
||||
Providing HTML Content Using Htmx
|
||||
=================================
|
||||
|
||||
:author: `Laurence Isla <https://github.com/laurenceisla>`_
|
||||
|
||||
This how-to shows a way to return HTML content and use the `htmx library <https://htmx.org/>`_ to handle the AJAX requests.
|
||||
Htmx expects an HTML response and uses it to replace an element inside the DOM (see the `htmx introduction <https://htmx.org/docs/#introduction>`_ in the docs).
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-demo.gif
|
||||
|
||||
.. warning::
|
||||
|
||||
This is a proof of concept showing what can be achieved using both technologies.
|
||||
We are working on `plmustache <https://github.com/PostgREST/plmustache>`_ which will further improve the HTML aspect of this how-to.
|
||||
|
||||
Preparatory Configuration
|
||||
-------------------------
|
||||
|
||||
We will make a to-do app based on the :ref:`tut0`, so make sure to complete it before continuing.
|
||||
|
||||
To simplify things, we won't be using authentication, so grant all permissions on the ``todos`` table to the ``web_anon`` user.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
grant all on api.todos to web_anon;
|
||||
grant usage, select on sequence api.todos_id_seq to web_anon;
|
||||
|
||||
Next, add the ``text/html`` as a :ref:`custom_media`. With this, PostgREST can identify the request made by your web browser (with the ``Accept: text/html`` header)
|
||||
and return a raw HTML document file.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "text/html" as text;
|
||||
|
||||
Creating an HTML Response
|
||||
-------------------------
|
||||
|
||||
Let's create a function that returns a basic HTML file, using `Pico CSS <https://picocss.com>`_ for styling and
|
||||
`Ionicons <https://ionic.io/ionicons>`_ to show some icons later.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.index() returns "text/html" as $$
|
||||
select $html$
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PostgREST + HTMX To-Do List</title>
|
||||
<!-- Pico CSS for CSS styling -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<article>
|
||||
<h5 style="text-align: center;">
|
||||
PostgREST + HTMX To-Do List
|
||||
</h5>
|
||||
</article>
|
||||
</main>
|
||||
<!-- Script for Ionicons icons -->
|
||||
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
$html$;
|
||||
$$ language sql;
|
||||
|
||||
The web browser will open the web page at ``http://localhost:3000/rpc/index``.
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-simple.jpg
|
||||
|
||||
.. _html_htmx_list_create:
|
||||
|
||||
Listing and Creating To-Dos
|
||||
---------------------------
|
||||
|
||||
Now, let's show a list of the to-dos already inserted in the database.
|
||||
For that, we'll also need a function to help us sanitize the HTML content that may be present in the task.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.sanitize_html(text) returns text as $$
|
||||
select replace(replace(replace(replace(replace($1, '&', '&'), '"', '"'),'>', '>'),'<', '<'), '''', ''')
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.html_todo(api.todos) returns text as $$
|
||||
select format($html$
|
||||
<div>
|
||||
<%2$s>
|
||||
%3$s
|
||||
</%2$s>
|
||||
</div>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 's' else 'span' end,
|
||||
api.sanitize_html($1.task)
|
||||
);
|
||||
$$ language sql stable;
|
||||
|
||||
create or replace function api.html_all_todos() returns text as $$
|
||||
select coalesce(
|
||||
string_agg(api.html_todo(t), '<hr/>' order by t.id),
|
||||
'<p><em>There is nothing else to do.</em></p>'
|
||||
)
|
||||
from api.todos t;
|
||||
$$ language sql;
|
||||
|
||||
These two functions are used to build the to-do list template. We won't use them as PostgREST endpoints.
|
||||
|
||||
- The ``api.html_todo`` function uses the table ``api.todos`` as a parameter and formats each item into a list element ``<li>``.
|
||||
The PostgreSQL `format <https://www.postgresql.org/docs/current/functions-string.html#FUNCTIONS-STRING-FORMAT>`_ is useful to that end.
|
||||
It replaces the values according to the position in the template, e.g. ``%1$s`` will be replaced with the value of ``$1.id`` (the first parameter).
|
||||
|
||||
- The ``api.html_all_todos`` function returns the ``<ul>`` wrapper for all the list elements.
|
||||
It uses `string_arg <https://www.postgresql.org/docs/current/functions-aggregate.html>`_ to concatenate all the to-dos in a single text value.
|
||||
It also returns an alternative message, instead of a list, when the ``api.todos`` table is empty.
|
||||
|
||||
Next, let's add an endpoint to register a to-do in the database and modify the ``/rpc/index`` page accordingly.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.add_todo(_task text) returns "text/html" as $$
|
||||
insert into api.todos(task) values (_task);
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.index() returns "text/html" as $$
|
||||
select $html$
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PostgREST + HTMX To-Do List</title>
|
||||
<!-- Pico CSS for CSS styling -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet"/>
|
||||
<!-- htmx for AJAX requests -->
|
||||
<script src="https://unpkg.com/htmx.org"></script>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container"
|
||||
style="max-width: 600px"
|
||||
hx-headers='{"Accept": "text/html"}'>
|
||||
<article>
|
||||
<h5 style="text-align: center;">
|
||||
PostgREST + HTMX To-Do List
|
||||
</h5>
|
||||
<form hx-post="/rpc/add_todo"
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="submit"
|
||||
hx-on="htmx:afterRequest: this.reset()">
|
||||
<input type="text" name="_task" placeholder="Add a todo...">
|
||||
</form>
|
||||
<div id="todo-list-area">
|
||||
$html$
|
||||
|| api.html_all_todos() ||
|
||||
$html$
|
||||
<div>
|
||||
</article>
|
||||
</main>
|
||||
<!-- Script for Ionicons icons -->
|
||||
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
$html$;
|
||||
$$ language sql;
|
||||
|
||||
- The ``/rpc/add_todo`` endpoint allows us to add a new to-do using the ``_task`` parameter and returns an ``html`` with all the to-dos in the database.
|
||||
|
||||
- The ``/rpc/index`` now adds the ``hx-headers='{"Accept": "text/html"}'`` tag to the ``<body>``.
|
||||
This will make sure that all htmx elements inside the body send this header, otherwise PostgREST won't recognize it as HTML.
|
||||
|
||||
There is also a ``<form>`` element that uses the htmx library. Let's break it down:
|
||||
|
||||
+ ``hx-post="/rpc/add_todo"``: sends an AJAX POST request to the ``/rpc/add_todo`` endpoint, with the value of the ``_task`` from the ``<input>`` element.
|
||||
|
||||
+ ``hx-target="#todo-list-area"``: the HTML content returned from the request will go inside ``<div id="todo-list-area"></div>`` (which is the list of to-dos).
|
||||
|
||||
+ ``hx-trigger="submit"``: htmx will do this request when submitting the form (by pressing enter while inside the ``<input>``).
|
||||
|
||||
+ ``hx-on="htmx:afterRequest: this.reset()">``: this is a Javascript command that clears the form `after the request is done <https://htmx.org/events/#htmx:afterRequest>`_.
|
||||
|
||||
With this, the ``http://localhost:3000/rpc/index`` page lists all the todos and adds new ones by submitting tasks in the input element.
|
||||
Don't forget to refresh the :ref:`schema cache <schema_reloading>`.
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-insert.gif
|
||||
|
||||
Editing and Deleting To-Dos
|
||||
---------------------------
|
||||
|
||||
Now, let's modify ``api.html_todo`` and make it more functional.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.html_todo(api.todos) returns text as $$
|
||||
select format($html$
|
||||
<div class="grid">
|
||||
<div id="todo-edit-area-%1$s">
|
||||
<form id="edit-task-state-%1$s"
|
||||
hx-post="/rpc/change_todo_state"
|
||||
hx-vals='{"_id": %1$s, "_done": %4$s}'
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="click">
|
||||
<%2$s style="cursor: pointer">
|
||||
%3$s
|
||||
</%2$s>
|
||||
</form>
|
||||
</div>
|
||||
<div style="text-align: right">
|
||||
<button class="outline"
|
||||
hx-get="/rpc/html_editable_task"
|
||||
hx-vals='{"_id": "%1$s"}'
|
||||
hx-target="#todo-edit-area-%1$s"
|
||||
hx-trigger="click">
|
||||
<span>
|
||||
<ion-icon name="create"></ion-icon>
|
||||
</span>
|
||||
</button>
|
||||
<button class="outline contrast"
|
||||
hx-post="/rpc/delete_todo"
|
||||
hx-vals='{"_id": %1$s}'
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="click">
|
||||
<span>
|
||||
<ion-icon name="trash" style="color: #f87171"></ion-icon>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
$html$,
|
||||
$1.id,
|
||||
case when $1.done then 's' else 'span' end,
|
||||
api.sanitize_html($1.task),
|
||||
(not $1.done)::text
|
||||
);
|
||||
$$ language sql stable;
|
||||
|
||||
Let's deconstruct the new htmx features added:
|
||||
|
||||
- The ``<form>`` element is configured as follows:
|
||||
|
||||
+ ``hx-post="/rpc/change_todo_state"``: does an AJAX POST request to that endpoint. It will toggle the ``done`` state of the to-do.
|
||||
|
||||
+ ``hx-vals='{"_id": %1$s, "_done": %4$s}'``: adds the parameters to the request.
|
||||
This is an alternative to using hidden inputs inside the ``<form>``.
|
||||
|
||||
+ ``hx-trigger="click"``: htmx does the request after clicking on the element.
|
||||
|
||||
- For the first ``<button>``:
|
||||
|
||||
+ ``hx-get="/rpc/html_editable_task"``: it does an AJAX GET request to that endpoint.
|
||||
It returns an HTML with an input that will allow us to edit the task.
|
||||
|
||||
+ ``hx-target="#todo-edit-area"``: the returned HTML will replace the element with this id.
|
||||
In this case, this replaces an individual task, not the whole list.
|
||||
|
||||
+ ``hx-vals='{"id": "eq.%1$s"}'``: adds the query parameters to the GET request.
|
||||
Note that this needs the ``eq.`` operator because it represents a table column not a function parameter.
|
||||
|
||||
- For the second ``<button>``:
|
||||
|
||||
+ ``hx-post="/rpc/delete_todo"``: this post request will delete the corresponding to-do.
|
||||
|
||||
Clicking on the first button will enable the task editing.
|
||||
That's why we create the ``api.html_editable_task`` function as an endpoint:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.html_editable_task(_id int) returns "text/html" as $$
|
||||
select format ($html$
|
||||
<form id="edit-task-%1$s"
|
||||
hx-post="/rpc/change_todo_task"
|
||||
hx-headers='{"Accept": "text/html"}'
|
||||
hx-vals='{"_id": %1$s}'
|
||||
hx-target="#todo-list-area"
|
||||
hx-trigger="submit,focusout">
|
||||
<input id="task-%1$s" type="text" name="_task" value="%2$s" autofocus>
|
||||
</form>
|
||||
$html$,
|
||||
id,
|
||||
api.sanitize_html(task)
|
||||
)
|
||||
from api.todos
|
||||
where id = _id;
|
||||
$$ language sql;
|
||||
|
||||
In this example, this will return an input field that allows us to edit the corresponding to-do task.
|
||||
|
||||
Finally, let's add the endpoints that will modify and delete the to-dos in the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function api.change_todo_state(_id int, _done boolean) returns "text/html" as $$
|
||||
update api.todos set done = _done where id = _id;
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.change_todo_task(_id int, _task text) returns "text/html" as $$
|
||||
update api.todos set task = _task where id = _id;
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
create or replace function api.delete_todo(_id int) returns "text/html" as $$
|
||||
delete from api.todos where id = _id;
|
||||
select api.html_all_todos();
|
||||
$$ language sql;
|
||||
|
||||
All of those functions return an HTML list of to-dos that will replace the outdated one:
|
||||
|
||||
- The ``api.change_todo_state`` function updates the ``done`` column using the ``_id`` and the ``_done`` values from the request.
|
||||
|
||||
- The ``api.delete_todo`` function deletes a to-do using the ``_id`` value from the request.
|
||||
|
||||
- The ``api.change_todo_task`` function modifies the ``task`` column using the ``_id`` and the ``_task`` value from the request.
|
||||
|
||||
After refreshing the :ref:`schema cache <schema_reloading>`, the page at ``http://localhost:3000/rpc/index`` will allow us to edit, delete and complete any to-do.
|
||||
|
||||
.. image:: ../_static/how-tos/htmx-edit-delete.gif
|
||||
|
||||
With that, we completed the to-do list functionality.
|
||||
@@ -26,42 +26,18 @@ First, we need a public table for storing the files.
|
||||
, blob bytea
|
||||
);
|
||||
|
||||
Let's assume this table contains an image of two cute kittens with id 42. We can retrieve this image in binary format from our PostgREST API by using :ref:`custom_media`:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "application/octet-stream" as bytea;
|
||||
|
||||
create or replace function file(id int) returns "application/octet-stream" as $$
|
||||
select blob from files where id = file.id;
|
||||
$$ language sql;
|
||||
|
||||
Now we can request the RPC endpoint :code:`/rpc/file?id=42` with the :code:`Accept: application/octet-stream` header.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "localhost:3000/rpc/file?id=42" -H "Accept: application/octet-stream"
|
||||
|
||||
|
||||
Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work. That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
|
||||
Instead, the :code:`Accept: image/webp` header is sent by many web browsers by default.
|
||||
|
||||
Luckily we can change the accepted media type in the function like so:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create domain "image/webp" as bytea;
|
||||
|
||||
create or replace function file(id int) returns "image/webp" as $$
|
||||
select blob from files where id = file.id;
|
||||
$$ language sql;
|
||||
Let's assume this table contains an image of two cute kittens with id 42.
|
||||
We can retrieve this image in binary format from our PostgREST API by requesting :code:`/files?select=blob&id=eq.42` with the :code:`Accept: application/octet-stream` header.
|
||||
Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work.
|
||||
That's because browsers do not send the required :code:`Accept: application/octet-stream` header.
|
||||
|
||||
Luckily we can specify the accepted media types in the :ref:`raw-media-types` configuration variable.
|
||||
In this case, the :code:`Accept: image/webp` header is sent by many web browsers by default, so let's add it to the configuration variable, like this: :code:`raw-media-types="image/webp"`.
|
||||
Now, the image will be displayed in the HTML page:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<img src="http://localhost:3000/file?id=42" alt="Cute Kittens"/>
|
||||
<img src="http://localhost:3000/files?select=blob&id=eq.42" alt="Cute Kittens"/>
|
||||
|
||||
Improved Version
|
||||
----------------
|
||||
@@ -81,20 +57,16 @@ 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.
|
||||
Next, we set up an RPC endpoint that sets 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
|
||||
|
||||
create domain "*/*" as bytea;
|
||||
|
||||
create function file(id int) returns "*/*" as
|
||||
create function file(id int) returns bytea as
|
||||
$$
|
||||
declare headers text;
|
||||
declare blob bytea;
|
||||
@@ -107,7 +79,7 @@ For production, you probably want to configure additional caches, e.g. on the :r
|
||||
from files where files.id = file.id into headers;
|
||||
perform set_config('response.headers', headers, true);
|
||||
select files.blob from files where files.id = file.id into blob;
|
||||
if FOUND -- special var, see https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-DIAGNOSTICS
|
||||
if found
|
||||
then return(blob);
|
||||
else raise sqlstate 'PT404' using
|
||||
message = 'NOT FOUND',
|
||||
|
||||
@@ -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,26 +40,26 @@ 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:`jwt-from-sql`.
|
||||
Concerning the `pgjwt extension <https://github.com/michelp/pgjwt>`_, please cf. to :ref:`client_auth`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
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
|
||||
AS $$
|
||||
DECLARE
|
||||
DECLARE
|
||||
hash_length integer;
|
||||
block_count integer;
|
||||
output bytea;
|
||||
@@ -97,7 +97,7 @@ In order to be able to work with postgres' SCRAM-SHA-256 password hashes, we als
|
||||
--
|
||||
FOR j IN 2 .. count LOOP
|
||||
the_last := ext_pgcrypto.HMAC(the_last, pw::bytea, algorithm);
|
||||
|
||||
|
||||
-- xor the two
|
||||
FOR k IN 1 .. length(xorsum) LOOP
|
||||
xorsum := set_byte(xorsum, k - 1, get_byte(xorsum, k - 1) # get_byte(the_last, k - 1));
|
||||
@@ -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
|
||||
@@ -157,20 +157,25 @@ Here we use the username instead of the email address to identify a user.
|
||||
Logins
|
||||
~~~~~~
|
||||
|
||||
As described in :ref:`jwt-from-sql`, we'll create a JWT token inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
|
||||
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:`jwt-from-sql`, we'll create a JWT token inside our login f
|
||||
SELECT login.username as role,
|
||||
extract(epoch FROM now())::integer + 60*60 AS exp
|
||||
) r
|
||||
INTO token;
|
||||
INTO result;
|
||||
RETURN result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
@@ -204,6 +210,8 @@ anonymous roles. Below is an example of permissions that allow anonymous users t
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- the names "anon" and "authenticator" are configurable and not
|
||||
-- sacred, we simply choose them for clarity
|
||||
CREATE ROLE anon NOINHERIT;
|
||||
CREATE role authenticator NOINHERIT LOGIN PASSWORD 'secret';
|
||||
GRANT anon TO authenticator;
|
||||
@@ -253,11 +261,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 +298,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::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
curl "http://localhost:3000/foobar"
|
||||
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:
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
.. _sql_user_management:
|
||||
|
||||
SQL User Management
|
||||
===================
|
||||
|
||||
As mentioned on :ref:`jwt_generation`, an external service can provide user management and coordinate with the PostgREST server using JWT. It's also possible to support logins entirely through SQL. It's a fair bit of work, so get ready.
|
||||
|
||||
Storing Users and Passwords
|
||||
---------------------------
|
||||
|
||||
The following table, functions, and triggers will live in a :code:`basic_auth` schema that you shouldn't expose publicly in the API. The public views and functions will live in a different schema which internally references this internal information.
|
||||
|
||||
First we'll need a table to keep track of our users:
|
||||
|
||||
.. code:: sql
|
||||
|
||||
-- We put things inside the basic_auth schema to hide
|
||||
-- them from public view. Certain public procs/views will
|
||||
-- refer to helpers and tables inside.
|
||||
|
||||
create table
|
||||
basic_auth.users (
|
||||
email text primary key check ( email ~* '^.+@.+\..+$' ),
|
||||
pass text not null check (length(pass) < 512),
|
||||
role name not null check (length(role) < 512)
|
||||
);
|
||||
|
||||
We would like the role to be a foreign key to actual database roles, however PostgreSQL does not support these constraints against the :code:`pg_roles` table. We'll use a trigger to manually enforce it.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function
|
||||
basic_auth.check_role_exists() returns trigger as $$
|
||||
begin
|
||||
if not exists (select 1 from pg_roles as r where r.rolname = new.role) then
|
||||
raise foreign_key_violation using message =
|
||||
'unknown database role: ' || new.role;
|
||||
return null;
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
create constraint trigger ensure_user_role_exists
|
||||
after insert or update on basic_auth.users
|
||||
for each row
|
||||
execute procedure basic_auth.check_role_exists();
|
||||
|
||||
Next we'll use the pgcrypto extension and a trigger to keep passwords safe in the :code:`users` table.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create extension pgcrypto;
|
||||
|
||||
create function
|
||||
basic_auth.encrypt_pass() returns trigger as $$
|
||||
begin
|
||||
if tg_op = 'INSERT' or new.pass <> old.pass then
|
||||
new.pass = crypt(new.pass, gen_salt('bf'));
|
||||
end if;
|
||||
return new;
|
||||
end
|
||||
$$ language plpgsql;
|
||||
|
||||
create trigger encrypt_pass
|
||||
before insert or update on basic_auth.users
|
||||
for each row
|
||||
execute procedure basic_auth.encrypt_pass();
|
||||
|
||||
With the table in place we can make a helper to check a password against the encrypted column. It returns the database role for a user if the email and password are correct.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create function
|
||||
basic_auth.user_role(email text, pass text) returns name
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
return (
|
||||
select role from basic_auth.users
|
||||
where users.email = user_role.email
|
||||
and users.pass = crypt(user_role.pass, users.pass)
|
||||
);
|
||||
end;
|
||||
$$;
|
||||
|
||||
.. _public_ui:
|
||||
|
||||
Public User Interface
|
||||
---------------------
|
||||
|
||||
In the previous section we created an internal table to store user information. Here we create a login function which takes an email address and password and returns JWT if the credentials match a user in the internal table.
|
||||
|
||||
Permissions
|
||||
~~~~~~~~~~~
|
||||
|
||||
Your database roles need access to the schema, tables, views and functions in order to service HTTP requests.
|
||||
Recall from the :ref:`roles` that PostgREST uses special roles to process requests, namely the authenticator and
|
||||
anonymous roles. Below is an example of permissions that allow anonymous users to create accounts and attempt to log in.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create role anon noinherit;
|
||||
create role authenticator noinherit;
|
||||
grant anon to authenticator;
|
||||
|
||||
Then, add ``db-anon-role`` to the configuration file to allow anonymous requests.
|
||||
|
||||
.. code:: ini
|
||||
|
||||
db-anon-role = "anon"
|
||||
|
||||
.. _jwt-from-sql:
|
||||
|
||||
JWT from SQL
|
||||
~~~~~~~~~~~~
|
||||
|
||||
You can create JWT tokens in SQL using the `pgjwt extension <https://github.com/michelp/pgjwt>`_. It's simple and requires only pgcrypto. If you're on an environment like Amazon RDS which doesn't support installing new extensions, you can still manually run the `SQL inside pgjwt <https://github.com/michelp/pgjwt/blob/master/pgjwt--0.1.1.sql>`_ (you'll need to replace ``@extschema@`` with another schema or just delete it) which creates the functions you will need.
|
||||
|
||||
Next write a function that returns the token. The one below returns a token with a hard-coded role, which expires five minutes after it was issued. Note this function has a hard-coded secret as well.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
CREATE FUNCTION jwt_test(OUT token text) AS $$
|
||||
SELECT public.sign(
|
||||
row_to_json(r), 'reallyreallyreallyreallyverysafe'
|
||||
) AS token
|
||||
FROM (
|
||||
SELECT
|
||||
'my_role'::text as role,
|
||||
extract(epoch from now())::integer + 300 AS exp
|
||||
) r;
|
||||
$$ LANGUAGE sql;
|
||||
|
||||
PostgREST exposes this function to clients via a POST request to ``/rpc/jwt_test``.
|
||||
|
||||
.. note::
|
||||
|
||||
To avoid hard-coding the secret in functions, save it as a property of the database.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- run this once
|
||||
ALTER DATABASE mydb SET "app.jwt_secret" TO 'reallyreallyreallyreallyverysafe';
|
||||
|
||||
-- then all functions can refer to app.jwt_secret
|
||||
SELECT sign(
|
||||
row_to_json(r), current_setting('app.jwt_secret')
|
||||
) AS token
|
||||
FROM ...
|
||||
|
||||
Logins
|
||||
~~~~~~
|
||||
|
||||
As described in `JWT from SQL`_, we'll create a JWT inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- login should be on your exposed schema
|
||||
create function
|
||||
login(email text, pass text, out token text) as $$
|
||||
declare
|
||||
_role name;
|
||||
begin
|
||||
-- check email and password
|
||||
select basic_auth.user_role(email, pass) into _role;
|
||||
if _role is null then
|
||||
raise invalid_password using message = 'invalid user or password';
|
||||
end if;
|
||||
|
||||
select sign(
|
||||
row_to_json(r), 'reallyreallyreallyreallyverysafe'
|
||||
) as token
|
||||
from (
|
||||
select _role as role, login.email as email,
|
||||
extract(epoch from now())::integer + 60*60 as exp
|
||||
) r
|
||||
into token;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
grant execute on function login(text,text) to anon;
|
||||
|
||||
Since the above :code:`login` function is defined as `security definer <https://www.postgresql.org/docs/current/sql-createfunction.html#id-1.9.3.67.10.2>`_,
|
||||
the anonymous user :code:`anon` doesn't need permission to read the :code:`basic_auth.users` table. It doesn't even need permission to access the :code:`basic_auth` schema.
|
||||
:code:`grant execute on function` is included for clarity but it might not be needed, see :ref:`func_privs` for more details.
|
||||
|
||||
An API request to call this function would look like:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/login" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d '{ "email": "foo@bar.com", "pass": "foobar" }'
|
||||
|
||||
The response would look like the snippet below. Try decoding the token at `jwt.io <https://jwt.io/>`_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!)
|
||||
|
||||
.. code:: json
|
||||
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZvb0BiYXIuY29tIiwicGFzcyI6ImZvb2JhciJ9.37066TTRlh-1hXhnA9oO9Pj6lgL6zFuJU0iCHhuCFno"
|
||||
}
|
||||
|
||||
|
||||
Alternatives
|
||||
~~~~~~~~~~~~
|
||||
|
||||
See the how-to :ref:`sql-user-management-using-postgres-users-and-passwords` for a similar way that completely avoids the table :code:`basic_auth.users`.
|
||||
@@ -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``.
|
||||
|
||||
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,152 +286,60 @@ 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
|
||||
-----
|
||||
|
||||
You can handle `Enumerated Types <https://www.postgresql.org/docs/current/datatype-enum.html>`_ using string representations:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type letter_size as enum ('s','m','l','xl');
|
||||
|
||||
create table products (
|
||||
id int primary key generated always as identity,
|
||||
name text,
|
||||
size letter_size
|
||||
);
|
||||
|
||||
To insert or update the value use a string:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -X POST "http://localhost:3000/products" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{ "name": "t-shirt", "size": "l" }
|
||||
EOF
|
||||
|
||||
You can then query and filter the enum using the compatible :ref:`operators <operators>`.
|
||||
For example, to get all the products larger than `m` and ordering them by their size:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/products?select=name,size&size=gt.m&order=size"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"name": "t-shirt",
|
||||
"size": "l"
|
||||
},
|
||||
{
|
||||
"name": "hoodie",
|
||||
"size": "xl"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
hstore
|
||||
------
|
||||
|
||||
You can work with data types belonging to additional supplied modules such as `hstore <https://www.postgresql.org/docs/current/hstore.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- Activate the hstore module in the current database
|
||||
create extension if not exists hstore;
|
||||
|
||||
create table countries (
|
||||
id int primary key,
|
||||
name hstore unique
|
||||
);
|
||||
|
||||
The ``name`` column will have the name of the country in different formats. You can insert values using the string representation for that data type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/countries" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
[
|
||||
{ "id": 1, "name": "common => Egypt, official => \"Arab Republic of Egypt\", native => مصر" },
|
||||
{ "id": 2, "name": "common => Germany, official => \"Federal Republic of Germany\", native => Deutschland" }
|
||||
]
|
||||
EOF
|
||||
|
||||
Notice that the use of ``"`` in the value of the ``name`` column needs to be escaped using a backslash ``\``.
|
||||
|
||||
You can also query and filter the value of a ``hstore`` column using the arrow operators, as you would do for a :ref:`JSON column<json_columns>`. For example, if you want to get the native name of Egypt:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/countries?select=name->>native&name->>common=like.Egypt"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[{ "native": "مصر" }]
|
||||
|
||||
JSON
|
||||
----
|
||||
|
||||
To work with a ``json`` type column, you can handle the value as a JSON object.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table products (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
extra_info json
|
||||
);
|
||||
|
||||
You can insert a new product using a JSON object for the ``extra_info`` column:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Canned fish",
|
||||
"extra_info": {
|
||||
"expiry_date": "2025-12-31",
|
||||
"exportable": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
To query and filter the data see :ref:`json_columns` for a complete reference.
|
||||
|
||||
Ranges
|
||||
------
|
||||
|
||||
@@ -333,23 +355,42 @@ PostgREST allows you to handle `ranges <https://www.postgresql.org/docs/current/
|
||||
|
||||
To insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /events HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
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
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
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
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/events?duration=cs.\[2023-01-01,2023-01-01\]"
|
||||
.. 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
|
||||
|
||||
@@ -387,9 +428,15 @@ Then, create the cast using this function:
|
||||
|
||||
Finally, do the request :ref:`casting the range column <casting_columns>`:
|
||||
|
||||
.. code-block:: bash
|
||||
.. tabs::
|
||||
|
||||
curl "http://localhost:3000/events?select=id,name,duration::json"
|
||||
.. 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
|
||||
|
||||
@@ -420,71 +467,297 @@ Finally, do the request :ref:`casting the range column <casting_columns>`:
|
||||
|
||||
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
|
||||
|
||||
Timestamps
|
||||
----------
|
||||
Bytea
|
||||
-----
|
||||
|
||||
You can use the **time zone** to filter or send data if needed.
|
||||
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 reports (
|
||||
id int primary key
|
||||
, due_date timestamptz
|
||||
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, set the ``Accept: application/octet-stream`` header and select only the
|
||||
``bytea`` type column.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /files?select=file&id=eq.1 HTTP/1.1
|
||||
Accept: application/octet-stream
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/files?select=file&id=eq.1" \
|
||||
-H "Accept: application/octet-stream"
|
||||
|
||||
Use more accurate headers according to the type of the files by using the :ref:`raw-media-types` configuration. For example, adding the ``raw-media-types="image/png"`` setting to the configuration file will allow you to use the ``Accept: image/png`` header:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /files?select=file&id=eq.1 HTTP/1.1
|
||||
Accept: image/png
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/files?select=file&id=eq.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
|
||||
------
|
||||
|
||||
You can work with data types belonging to additional supplied modules such as `hstore <https://www.postgresql.org/docs/current/hstore.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- Activate the hstore module in the current database
|
||||
create extension if not exists hstore;
|
||||
|
||||
create table countries (
|
||||
id int primary key,
|
||||
name hstore unique
|
||||
);
|
||||
|
||||
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:
|
||||
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::
|
||||
|
||||
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" }]'
|
||||
.. code-tab:: http
|
||||
|
||||
Someone located in Cairo can retrieve the data using their local time, too:
|
||||
POST /countries HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
.. code-block:: bash
|
||||
[
|
||||
{ "id": 1, "name": "common => Egypt, official => \"Arab Republic of Egypt\", native => مصر" },
|
||||
{ "id": 2, "name": "common => Germany, official => \"Federal Republic of Germany\", native => Deutschland" }
|
||||
]
|
||||
|
||||
curl "http://localhost:3000/reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo"
|
||||
.. 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:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"due_date": "2022-02-23T19:10:15-05:00"
|
||||
}
|
||||
]
|
||||
[{ "native": "مصر" }]
|
||||
|
||||
The response has the date in the time zone configured by the server: ``UTC -05:00`` (see :ref:`prefer_timezone`).
|
||||
.. _ww_postgis:
|
||||
|
||||
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:
|
||||
PostGIS
|
||||
-------
|
||||
|
||||
.. code-block:: bash
|
||||
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).
|
||||
|
||||
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:: postgres
|
||||
|
||||
-- Activate the postgis module in the current database
|
||||
create extension if not exists postgis;
|
||||
|
||||
create table coverage (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
area geometry
|
||||
);
|
||||
|
||||
To add areas in polygon format, you can use string representation:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /coverage HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{ "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))" }
|
||||
]
|
||||
|
||||
.. 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>`_:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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
|
||||
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"due_date": "2022-02-27T06:00:00-05:00"
|
||||
}
|
||||
]
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[1,0],[1,1],[0,1],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 1,
|
||||
"name": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[10,0],[10,10],[0,10],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"name": "big"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
.. raw:: html
|
||||
If you need to add an extra property, like the area in square units by using ``st_area(area)``, you could add a generated column to the table and it will appear in the ``properties`` key of each ``Feature``.
|
||||
|
||||
<script type="text/javascript">
|
||||
let hash = window.location.hash;
|
||||
.. code-block:: postgres
|
||||
|
||||
const redirects = {
|
||||
// PostGIS
|
||||
'#postgis': '../integrations/postgis.html#postgis',
|
||||
};
|
||||
alter table coverage
|
||||
add square_units double precision generated always as ( st_area(area) ) stored;
|
||||
|
||||
let willRedirectTo = redirects[hash];
|
||||
In the case that you are using older PostGIS versions, then creating a function is your best option:
|
||||
|
||||
if (willRedirectTo) {
|
||||
window.location.href = willRedirectTo;
|
||||
}
|
||||
</script>
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function coverage_geo_collection() returns json as $$
|
||||
select
|
||||
json_build_object(
|
||||
'type', 'FeatureCollection',
|
||||
'features', json_agg(
|
||||
json_build_object(
|
||||
'type', 'Feature',
|
||||
'geometry', st_AsGeoJSON(c.area)::json,
|
||||
'properties', json_build_object('id', c.id, 'name', c.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
from coverage c;
|
||||
$$ language sql;
|
||||
|
||||
Now this query will return the same results:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. 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
|
||||
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[1,0],[1,1],[0,1],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 1,
|
||||
"name": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[10,0],[10,10],[0,10],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"name": "big"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -28,75 +28,48 @@ 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
|
||||
.. image:: _static/2ndquadrant.png
|
||||
:target: https://www.2ndquadrant.com/en/?utm_campaign=External%20Websites&utm_source=PostgREST&utm_medium=Logo
|
||||
:width: 13em
|
||||
|
||||
.. container:: img-light
|
||||
.. image:: _static/retool.png
|
||||
:target: https://retool.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
.. 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
|
||||
:target: https://euronodes.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
:width: 13em
|
||||
|
||||
.. container:: img-dark
|
||||
.. 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
|
||||
|
||||
.. image:: ../static/supabase-dark.svg
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/supabase.svg
|
||||
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/euronodes.svg
|
||||
:target: https://www.euronodes.com/postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/euronodes.svg
|
||||
:target: https://www.euronodes.com/postgrest
|
||||
|
||||
|
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/neon-dark.jpg
|
||||
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/neon.jpg
|
||||
:target: https://neon.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-dark
|
||||
|
||||
.. image:: ../static/bytebase-dark.svg
|
||||
:target: https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
|
||||
.. container:: img-light
|
||||
|
||||
.. image:: ../static/bytebase.svg
|
||||
:target: https://www.bytebase.com/?utm_source=sponsor&utm_campaign=postgrest
|
||||
.. image:: _static/oblivious.jpg
|
||||
:target: https://oblivious.ai/?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
|
||||
.. .. image:: _static/empty.png
|
||||
:target: #sponsors
|
||||
:width: 13em
|
||||
|
||||
|
|
||||
|
||||
Database as Single Source of Truth
|
||||
----------------------------------
|
||||
Motivation
|
||||
----------
|
||||
|
||||
Using PostgREST is an alternative to manual CRUD programming. Custom API servers suffer problems. Writing business logic often duplicates, ignores or hobbles database structure. Object-relational mapping is a leaky abstraction leading to slow imperative code. The PostgREST philosophy establishes a single declarative source of truth: the data itself.
|
||||
|
||||
Declarative Programming
|
||||
-----------------------
|
||||
|
||||
It's easier to ask PostgreSQL to join data for you and let its query planner figure out the details than to loop through rows yourself. It's easier to assign permissions to database objects than to add guards in controllers. (This is especially true for cascading permissions in data dependencies.) It's easier to set constraints than to litter code with sanity checks.
|
||||
It's easier to ask PostgreSQL to join data for you and let its query planner figure out the details than to loop through rows yourself. It's easier to assign permissions to db objects than to add guards in controllers. (This is especially true for cascading permissions in data dependencies.) It's easier to set constraints than to litter code with sanity checks.
|
||||
|
||||
Leak-proof Abstraction
|
||||
----------------------
|
||||
@@ -113,20 +86,21 @@ Getting Support
|
||||
|
||||
The project has a friendly and growing community. For discussions, use the Github `discussions page <https://github.com/PostgREST/postgrest/discussions>`_. You can also report or search for bugs/features on the Github `issues <https://github.com/PostgREST/postgrest/issues>`_ page.
|
||||
|
||||
Releases
|
||||
--------
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Release Notes
|
||||
:titlesonly:
|
||||
:hidden:
|
||||
|
||||
PostgREST follows ``MAJOR.PATCH`` two-part versioning:
|
||||
|
||||
- ``MAJOR``: feature release, may deprecate or remove things.
|
||||
- ``PATCH``: fix/security release only, no features and no behavior changes.
|
||||
|
||||
MAJOR releases are published twice a year, with their scope and target dates tracked through `GitHub milestones <https://github.com/PostgREST/postgrest/milestones>`_.
|
||||
PATCH releases are published on an as-needed basis.
|
||||
|
||||
Starting from ``v14.0``, only even-numbered MAJOR versions are released, reserving odd-numbered MAJOR versions for development.
|
||||
|
||||
All releases are published on `PostgREST's GitHub release page <https://github.com/PostgREST/postgrest/releases>`_, along with the corresponding upgrade guides.
|
||||
v10.2.0 <releases/v10.2.0>
|
||||
v10.0.0 <releases/v10.0.0>
|
||||
v9.0.1 <releases/v9.0.1>
|
||||
v9.0.0 <releases/v9.0.0>
|
||||
releases/v8.0.0
|
||||
releases/v7.0.1
|
||||
releases/v7.0.0
|
||||
releases/v6.0.2
|
||||
releases/v5.2.0
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
@@ -136,74 +110,104 @@ Are you new to PostgREST? This is the place to start!
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Tutorials
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
|
||||
tutorials/*
|
||||
|
||||
Also have a look at :ref:`install` and :ref:`community_tutorials`.
|
||||
- :doc:`tutorials/tut0`
|
||||
- :doc:`tutorials/tut1`
|
||||
|
||||
References
|
||||
----------
|
||||
Also have a look at :doc:`Installation <install>` and :ref:`community_tutorials`.
|
||||
|
||||
Reference guides
|
||||
----------------
|
||||
|
||||
Technical references for PostgREST's functionality.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: References
|
||||
:name: references
|
||||
:maxdepth: 1
|
||||
:caption: API
|
||||
:hidden:
|
||||
|
||||
references/auth.rst
|
||||
references/api.rst
|
||||
references/cli.rst
|
||||
references/transactions.rst
|
||||
references/connection_pool.rst
|
||||
references/schema_cache.rst
|
||||
references/errors.rst
|
||||
references/configuration.rst
|
||||
references/observability.rst
|
||||
references/*
|
||||
|
||||
Explanations
|
||||
------------
|
||||
|
||||
Key concepts in PostgREST.
|
||||
api.rst
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Explanations
|
||||
:name: explanations
|
||||
:maxdepth: 1
|
||||
:caption: Configuration
|
||||
:hidden:
|
||||
|
||||
explanations/*
|
||||
configuration.rst
|
||||
|
||||
How-tos
|
||||
-------
|
||||
.. toctree::
|
||||
:caption: Schema Cache
|
||||
:hidden:
|
||||
|
||||
Recipes that'll help you address specific use-cases.
|
||||
schema_cache.rst
|
||||
|
||||
.. toctree::
|
||||
:caption: Errors
|
||||
:hidden:
|
||||
|
||||
errors.rst
|
||||
|
||||
- :doc:`API <api>`
|
||||
- :doc:`configuration`
|
||||
- :doc:`Schema Cache <schema_cache>`
|
||||
- :doc:`Errors <errors>`
|
||||
|
||||
Topic guides
|
||||
------------
|
||||
|
||||
Explanations of some key concepts in PostgREST.
|
||||
|
||||
.. toctree::
|
||||
:caption: Authentication
|
||||
:hidden:
|
||||
|
||||
auth.rst
|
||||
|
||||
.. toctree::
|
||||
:caption: Schema Structure
|
||||
:hidden:
|
||||
|
||||
schema_structure.rst
|
||||
|
||||
.. toctree::
|
||||
:caption: Administration
|
||||
:hidden:
|
||||
|
||||
admin.rst
|
||||
|
||||
.. toctree::
|
||||
:caption: Installation
|
||||
:hidden:
|
||||
|
||||
install.rst
|
||||
|
||||
- :doc:`Authentication <auth>`
|
||||
- :doc:`Schema Structure <schema_structure>`
|
||||
- :doc:`Administration <admin>`
|
||||
- :doc:`Installation <install>`
|
||||
|
||||
.. _how_tos:
|
||||
|
||||
How-to guides
|
||||
-------------
|
||||
|
||||
These are recipes that'll help you address specific use-cases.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: How-to guides
|
||||
:name: how-tos
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
|
||||
how-tos/sql-user-*
|
||||
how-tos/working-*
|
||||
how-tos/*
|
||||
how-tos/working-with-postgresql-data-types
|
||||
how-tos/providing-images-for-img
|
||||
how-tos/create-soap-endpoint
|
||||
how-tos/sql-user-management-using-postgres-users-and-passwords
|
||||
|
||||
.. _intgrs:
|
||||
|
||||
Integrations
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:caption: Integrations
|
||||
:name: integrations
|
||||
:maxdepth: 1
|
||||
|
||||
integrations/*
|
||||
- :doc:`how-tos/providing-images-for-img`
|
||||
- :doc:`how-tos/working-with-postgresql-data-types`
|
||||
- :doc:`how-tos/create-soap-endpoint`
|
||||
- :doc:`how-tos/sql-user-management-using-postgres-users-and-passwords`
|
||||
|
||||
Ecosystem
|
||||
---------
|
||||
@@ -212,23 +216,40 @@ PostgREST has a growing ecosystem of examples, libraries, and experiments. Here
|
||||
|
||||
.. toctree::
|
||||
:caption: Ecosystem
|
||||
:name: ecosystem
|
||||
:maxdepth: 1
|
||||
:hidden:
|
||||
|
||||
ecosystem.rst
|
||||
|
||||
* :ref:`community_tutorials`
|
||||
* :ref:`templates`
|
||||
* :ref:`eco_example_apps`
|
||||
* :ref:`devops`
|
||||
* :ref:`eco_external_notification`
|
||||
* :ref:`eco_extensions`
|
||||
* :ref:`clientside_libraries`
|
||||
|
||||
|
||||
Release Notes
|
||||
-------------
|
||||
|
||||
Changes among versions.
|
||||
|
||||
- :doc:`releases/v9.0.0`
|
||||
- :doc:`releases/v8.0.0`
|
||||
|
||||
In Production
|
||||
-------------
|
||||
|
||||
Here are some companies that use PostgREST in production.
|
||||
|
||||
* `Catarse <https://www.catarse.me>`_
|
||||
* `Datrium <https://www.datrium.com>`_
|
||||
* `Drip Depot <https://www.dripdepot.com>`_
|
||||
* `Image-charts <https://www.image-charts.com>`_
|
||||
* `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>`_
|
||||
* `Supabase <https://supabase.com>`_
|
||||
|
||||
Testimonials
|
||||
@@ -274,21 +295,4 @@ Testimonials
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Please see the `Contributing guidelines <https://github.com/PostgREST/postgrest/blob/main/CONTRIBUTING.md>`_ in the main PostgREST repository.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript">
|
||||
let hash = window.location.hash;
|
||||
|
||||
const redirects = {
|
||||
// Tables and Views
|
||||
'#release-notes': '#releases',
|
||||
};
|
||||
|
||||
let willRedirectTo = redirects[hash];
|
||||
|
||||
if (willRedirectTo) {
|
||||
window.location.href = willRedirectTo;
|
||||
}
|
||||
</script>
|
||||
Please see the `Contributing guidelines <https://github.com/PostgREST/postgrest/blob/main/.github/CONTRIBUTING.md>`_ in the main PostgREST repository.
|
||||
|
||||
@@ -1,26 +1,59 @@
|
||||
.. _install:
|
||||
|
||||
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 Mac OS X, Windows, Linux and FreeBSD <https://github.com/PostgREST/postgrest/releases/latest>`_ .
|
||||
The Linux binary is a static executable that can be run on any Linux distribution.
|
||||
|
||||
You can also use your OS package manager.
|
||||
|
||||
.. include:: ../shared/installation.rst
|
||||
.. tabs::
|
||||
|
||||
.. _pg-dependency:
|
||||
.. group-tab:: Mac OSX
|
||||
|
||||
Supported PostgreSQL versions
|
||||
=============================
|
||||
You can install PostgREST from the `Homebrew official repo <https://formulae.brew.sh/formula/postgrest>`_.
|
||||
|
||||
=============== =================================
|
||||
**Supported** PostgreSQL >= 14
|
||||
=============== =================================
|
||||
.. code:: bash
|
||||
|
||||
PostgREST works with all PostgreSQL versions still `officially supported <https://www.postgresql.org/support/versioning/>`_.
|
||||
brew install postgrest
|
||||
|
||||
.. group-tab:: FreeBSD
|
||||
|
||||
You can install PostgREST from the `official ports <https://www.freshports.org/www/hs-postgrest>`_.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
pkg install hs-postgrest
|
||||
|
||||
.. group-tab:: Linux
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Arch Linux
|
||||
|
||||
You can install PostgREST from the `community repo <https://archlinux.org/packages/community/x86_64/postgrest>`_.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
pacman -S postgrest
|
||||
|
||||
.. tab:: Nix
|
||||
|
||||
You can install PostgREST from nixpkgs.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
nix-env -i haskellPackages.postgrest
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
You can install PostgREST using `Chocolatey <https://community.chocolatey.org/packages/postgrest>`_ or `Scoop <https://github.com/ScoopInstaller/Scoop>`_.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
choco install postgrest
|
||||
scoop install postgrest
|
||||
|
||||
Running PostgREST
|
||||
=================
|
||||
@@ -62,12 +95,19 @@ For a complete reference of the configuration file, see :ref:`configuration`.
|
||||
|
||||
If you see a dialog box like this on Windows, it may be that the :code:`pg_config` program is not in your system path.
|
||||
|
||||
.. image:: ../_static/win-err-dialog.png
|
||||
.. image:: _static/win-err-dialog.png
|
||||
|
||||
It usually lives in :code:`C:\Program Files\PostgreSQL\<version>\bin`. See this `article <https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/>`_ about how to modify the system path.
|
||||
|
||||
To test that the system path is set correctly, run ``pg_config`` from the command line. You should see it output a list of paths.
|
||||
|
||||
.. _pg-dependency:
|
||||
|
||||
PostgreSQL dependency
|
||||
---------------------
|
||||
|
||||
To use PostgREST you will need an underlying database. We require PostgreSQL 9.6 or greater. You can use something like `Amazon RDS <https://aws.amazon.com/rds/>`_ but installing your own locally is cheaper and more convenient for development. You can also run PostgreSQL in a :ref:`docker container<pg-in-docker>`.
|
||||
|
||||
Docker
|
||||
======
|
||||
|
||||
@@ -75,12 +115,8 @@ You can get the `official PostgREST Docker image <https://hub.docker.com/r/postg
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# pull the latest version
|
||||
docker pull postgrest/postgrest
|
||||
|
||||
# to pull a particular version, use one of the versions on https://hub.docker.com/r/postgrest/postgrest/tags
|
||||
docker pull postgrest/postgrest:<version>
|
||||
|
||||
To configure the container image, use :ref:`env_variables_config`.
|
||||
|
||||
There are two ways to run the PostgREST container: with an existing external database, or through docker-compose.
|
||||
@@ -146,7 +182,6 @@ To avoid having to install the database at all, you can run both it and the serv
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
PGRST_SERVER_HOST: 0.0.0.0 # necessary for `postgrest --ready` flag to work
|
||||
PGRST_DB_URI: postgres://app_user:password@db:5432/app_db
|
||||
PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
|
||||
depends_on:
|
||||
@@ -169,35 +204,17 @@ 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.
|
||||
|
||||
.. _docker_cpu_contraint:
|
||||
|
||||
Docker Resource Constraints
|
||||
---------------------------
|
||||
|
||||
PostgREST does not support ``--cpus`` `constraint option <https://docs.docker.com/engine/containers/resource_constraints/#configure-the-default-cfs-scheduler>`_.
|
||||
|
||||
As a workaround, you may use the `GHC RTS <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/runtime_control.html#runtime-system-rts-options>`_ ``-N`` option. For instance, to limit it to 2 CPU cores, do:
|
||||
|
||||
.. code::
|
||||
|
||||
# Set environment variable GHCRTS set to "-N2"
|
||||
docker run --rm -p 3000:3000 \
|
||||
-e PGRST_DB_URI="postgres://app_user:password@10.0.0.10/postgres" \
|
||||
-e GHCRTS="-N2"
|
||||
postgrest/postgrest
|
||||
|
||||
.. _build_source:
|
||||
|
||||
Building from Source
|
||||
@@ -205,6 +222,10 @@ 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
|
||||
@@ -216,7 +237,7 @@ You can build PostgREST from source with `Stack <https://github.com/commercialha
|
||||
Ubuntu/Debian libpq-dev, libgmp-dev, zlib1g-dev
|
||||
CentOS/Fedora/Red Hat postgresql-devel, zlib-devel, gmp-devel
|
||||
BSD postgresql12-client
|
||||
macOS libpq, gmp
|
||||
OS X libpq, gmp
|
||||
===================== =======================================
|
||||
|
||||
* Build and install binary
|
||||
@@ -235,3 +256,126 @@ You can build PostgREST from source with `Stack <https://github.com/commercialha
|
||||
- `--install-ghc` flag is only needed for the first build and can be omitted in the subsequent builds.
|
||||
|
||||
* Check that the server is installed: :code:`postgrest --help`.
|
||||
|
||||
.. _deploy_heroku:
|
||||
|
||||
Deploying to 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"}'
|
||||
@@ -1,36 +0,0 @@
|
||||
NixOS
|
||||
=====
|
||||
|
||||
Nixpkgs contains a `NixOS module to run PostgREST <https://search.nixos.org/options?channel=unstable&query=services.postgrest&type=options>`_, which can be enabled with ``services.postgrest.enable = true``.
|
||||
|
||||
A PostgreSQL server can be enabled on the same machine with ``services.postgresql.enable = true``. Connections will use the name of the system user as user and database names by default, in this case ``postgrest``.
|
||||
|
||||
A minimal example could look like this:
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
{
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
CREATE ROLE postgrest LOGIN NOINHERIT;
|
||||
CREATE ROLE anon ROLE postgrest;
|
||||
'';
|
||||
};
|
||||
|
||||
services.postgrest = {
|
||||
enable = true;
|
||||
settings.db-anon-role = "anon";
|
||||
settings.db-uri.dbname = "postgres";
|
||||
};
|
||||
}
|
||||
|
||||
This will expose the PostgREST server on localhost on the NixOS machine and allow anonymous access.
|
||||
|
||||
.. tip::
|
||||
NixOS also allows to quickly spin up different PostgreSQL versions or even forks this way. For example, to test the current beta version of `OrioleDB <https://www.orioledb.com>`_, use ``services.postgresql.package = pkgs.orioledb``.
|
||||
@@ -1,30 +0,0 @@
|
||||
pg-safeupdate
|
||||
#############
|
||||
|
||||
.. _block_fulltable:
|
||||
|
||||
Block Full-Table Operations
|
||||
---------------------------
|
||||
|
||||
If the :ref:`active role <user_impersonation>` can delete table rows then the DELETE verb is allowed for clients. Here's an API request to delete old rows from a hypothetical logs table:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE
|
||||
|
||||
Note that it's very easy to delete the **entire table** by omitting the query parameter!
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/logs" -X DELETE
|
||||
|
||||
This can happen accidentally such as by switching a request from a GET to a DELETE. To protect against accidental operations use the `pg-safeupdate <https://github.com/eradman/pg-safeupdate>`_ PostgreSQL extension. It raises an error if UPDATE or DELETE are executed without specifying conditions. To install it you can use the `PGXN <https://pgxn.org/>`_ network:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -E pgxn install safeupdate
|
||||
|
||||
# then add this to postgresql.conf:
|
||||
# shared_preload_libraries='safeupdate';
|
||||
|
||||
This does not protect against malicious actions, since someone can add a url parameter that does not affect the result set. To prevent this you must turn to database permissions, forbidding the wrong people from deleting rows, and using `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ if finer access control is required.
|
||||
@@ -1,154 +0,0 @@
|
||||
.. _ww_postgis:
|
||||
|
||||
PostGIS
|
||||
=======
|
||||
|
||||
To work with `PostGIS <https://postgis.net/>`_ data types such as ``geometry`` or ``geography``, you'll need to `install PostGIS <https://postgis.net/documentation/getting_started/>`_ first.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- Activate the postgis module in the current database
|
||||
create extension if not exists postgis;
|
||||
|
||||
create table coverage (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
area geometry
|
||||
);
|
||||
|
||||
insert into coverage (id, name, area) values
|
||||
(1, 'small', ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))',4326)),
|
||||
(2, 'big', ST_GeomFromText('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', 4326);
|
||||
|
||||
.. _application/geo+json:
|
||||
|
||||
``application/geo+json``
|
||||
------------------------
|
||||
|
||||
PostgREST supports the `standard <https://www.iana.org/assignments/media-types/application/geo+json>`_ ``application/geo+json`` media type which can be used to get the output in `GeoJSON <https://geojson.org/>`_ format. This will work for PostGIS versions 3.0.0 and up and will return the output as a `FeatureCollection Object <https://www.rfc-editor.org/rfc/rfc7946#section-3.3>`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-H "Accept: application/geo+json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[1,0],[1,1],[0,1],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 1,
|
||||
"name": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[10,0],[10,10],[0,10],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"name": "big"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Using generated columns
|
||||
-----------------------
|
||||
|
||||
If you need to add an extra property, like the area in square units by using ``st_area(area)``, you could add a generated column to the table and it will appear in the ``properties`` key of each ``Feature``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
alter table coverage
|
||||
add square_units double precision generated always as ( st_area(area) ) stored;
|
||||
|
||||
In the case that you are using older PostGIS versions, then creating a function is your best option:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function coverage_geo_collection() returns json as $$
|
||||
select
|
||||
json_build_object(
|
||||
'type', 'FeatureCollection',
|
||||
'features', json_agg(
|
||||
json_build_object(
|
||||
'type', 'Feature',
|
||||
'geometry', st_AsGeoJSON(c.area)::json,
|
||||
'properties', json_build_object('id', c.id, 'name', c.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
from coverage c;
|
||||
$$ language sql;
|
||||
|
||||
Now this query will return the same results:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/rpc/coverage_geo_collection"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[1,0],[1,1],[0,1],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 1,
|
||||
"name": "small"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[[0,0],[10,0],[10,10],[0,10],[0,0]]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"id": 2,
|
||||
"name": "big"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Using string representation
|
||||
---------------------------
|
||||
|
||||
To insert areas in polygon format, you can use string representation:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/coverage" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
[
|
||||
{ "id": 3, "name": "strip", "area": "SRID=4326;POLYGON((0 0, 50 0, 50 2, 0 2, 0 0))" },
|
||||
{ "id": 4, "name": "diamond", "area": "SRID=4326;POLYGON((5 0, 10 5, 5 10, 0 5, 5 0))" }
|
||||
]
|
||||
EOF
|
||||
|
||||
PostgREST will automatically cast the ``area`` column into a ``Polygon`` geometry type.
|
||||