Compare commits

..
90 Commits
Author SHA1 Message Date
Wolfgang Walther 6a506d18c0 ci: Actually pass GHC_VERSION to arm build script 2024-05-09 21:54:11 +02:00
Wolfgang Walther 711202c2a0 ci: Make arm scripts fail on error 2024-05-09 21:43:42 +02:00
Wolfgang Walther 9dd3f3c094 ci: Tag arm docker image properly for releases 2024-05-09 21:42:18 +02:00
Wolfgang Walther cf4aa20d7c ci: Extract changelog properly for releases 2024-05-09 21:42:17 +02:00
Wolfgang WaltherandWolfgang Walther 1afd98a739 bump version to 12.0.3 2024-05-09 18:56:47 +02:00
Taimoor ZaeemandWolfgang Walther 077e4a857e fix: fix wrong http status on pg error 42P17 infinite recursion 2024-05-09 18:56:47 +02:00
Laurence IslaandWolfgang Walther 903537d39d fix: OpenAPI now tags a FK correctly on O2O relationships 2024-05-09 18:56:47 +02:00
Taimoor ZaeemandWolfgang Walther 67f6b1c7e8 fix: remove rejected mediatype application/vnd.pgrst.object+json from response 2024-05-09 18:56:47 +02:00
steve-chavezandWolfgang Walther 977710f442 fix: clarify PGRST204 error message 2024-05-09 18:56:47 +02:00
Wolfgang WaltherandWolfgang Walther 687558937c fix: Avoid casting to table type when select= and media type handler are used
Previously using a generic mimetype handler failed when any kind of select= was given, because
we tried to cast the select-result to the original table type. With this change, this cast is
only applied when select=* is given implicitly or explicitly. This is the only case where this
makes sense, because this guarantees that correct columns are selected in the correct order for
this cast to succeed.

Resolves #3160
2024-05-09 18:56:47 +02:00
Wolfgang WaltherandWolfgang Walther 491c7a0891 fix: Return 406 instead of 415 for non-acceptable media type
415 is for Content-Type and 406 for Accept headers.
2024-05-09 18:56:47 +02:00
steve-chavezandWolfgang Walther e4984dc751 fix: wrong subquery error returning as 400 status 2024-05-09 18:56:47 +02:00
steve-chavezandWolfgang Walther 1b0f29a8cc fix: misleading "Starting.." logs on scache reload 2024-05-09 18:56:47 +02:00
Wolfgang Walther a0d3ebf590 ci: Fetch tags before checking whether tag exists 2024-05-09 14:53:16 +02:00
Wolfgang WaltherandWolfgang Walther ac217c120f ci: Automate patch releases and pre-releases
This work by automatically pushing a new tag on main and release
branches after each commit. The tag will be "devel" on main and the
version from postgrest.cabal for release branches. The release
workflow then runs as a tag pipeline, making the actual release.

For release branches, the tag will only be created if a tag for this
version doesn't exist, yet. This means to actually make a new patch
release, we still need to bump the version in postgrest.cabal. We
can automate this later as part of our backport-bot.

This is a back-port of the following commits:
- dd8d51ab
- fe0f2f70
- 58d81334
- 9fe90bf9
- c67f1c39
- 8433f981
- d9ba9a82
- a57d12b1
- b006016d
2024-05-09 14:08:42 +02:00
renovate[bot]andWolfgang Walther 961db7c7ef chore(deps): update actions/checkout action to v4.1.5 2024-05-09 13:37:11 +02:00
Wolfgang Walther c128f03b61 ci: Improve performance for nix jobs in CI
Defaulting to max-jobs = auto should improve build times by using more
cores.

Setting always-allow-substitutes to true should cause all nix
derivations to be cached on cachix, which should improve performance of
the MacOS job dramatically, when no rebuilds need to happen.
2024-05-09 13:10:05 +02:00
Wolfgang Walther b8b34f05df ci: Make artifact-from-cirrus action succeed when cirrus job doesn't start up in PR 2024-05-09 13:10:05 +02:00
Wolfgang Walther 608ee5a9ad ci: Lift restriction to nix 2.13.6 2024-05-05 19:01:52 +02:00
renovate[bot]andWolfgang Walther 9737c5c5f6 chore(deps): update all dependencies 2024-05-05 18:25:18 +02:00
Wolfgang Walther e520f32297 ci: Refactor get_cirrusci_freebsd script to GitHub action
This should make it more reliable and also easier to re-use, if we need
to.

Resolves #2555
2024-05-04 15:02:25 +02:00
renovate[bot]andWolfgang Walther 19dcf7b344 chore(deps): update all dependencies 2024-05-04 12:20:53 +02:00
renovate[bot]andWolfgang Walther a2b7b0e69d chore(deps): update all dependencies 2024-04-26 08:31:43 +02:00
renovate[bot]andWolfgang Walther e01f08392f chore(deps): update actions/download-artifact action to v4.1.7 2024-04-24 20:53:27 +02:00
renovate[bot]andWolfgang Walther e6282d88d8 chore(deps): update all dependencies 2024-04-23 08:07:48 +02:00
Wolfgang Walther 5cab03ad5d ci: Use explicit runner image versions 2024-04-22 21:25:54 +02:00
renovate[bot]andWolfgang Walther b71739ee50 chore(deps): update all dependencies 2024-04-20 12:45:26 +02:00
renovate[bot]andWolfgang Walther 0d60a79532 chore(deps): update codecov/codecov-action action to v4.2.0 2024-04-04 07:17:34 +02:00
renovate[bot]andWolfgang Walther d38e5cdb02 chore(deps): update codecov/codecov-action action to v4.1.1 2024-03-26 19:56:25 +01:00
renovate[bot]andWolfgang Walther 908cdf0b6f chore(deps): update actions/cache action to v4.0.2 2024-03-19 21:59:00 +01:00
renovate[bot]andWolfgang Walther ee55efe0c9 chore(deps): update nixbuild/nix-quick-install-action action to v27 2024-03-15 17:13:16 +01:00
renovate[bot]andWolfgang Walther e60bae8e32 chore(deps): update cachix/install-nix-action action to v26 2024-03-08 16:29:32 +01:00
renovate[bot]andWolfgang Walther ffe44a2e71 chore(deps): update ubuntu:jammy docker digest to 77906da 2024-03-07 16:28:05 +01:00
renovate[bot]andWolfgang Walther dcc6772ce1 chore(deps): update actions/download-artifact action to v4.1.4 2024-03-02 12:10:17 +01:00
Wolfgang Walther 91ffee1d2e chore: Add full semver comment to used actions 2024-03-01 08:58:33 +01:00
renovate[bot]andWolfgang Walther 1b52ce1958 chore(deps): update actions/cache digest to ab5e6d0 2024-03-01 08:12:03 +01:00
Wolfgang Walther 8186e78691 nix: Remove broken docker-based nix development environment
It used to be possible to spin up a nix environment via docker container this way,
but the upstream nixos/nix image has changed and the docker build doesn't succeed
anymore. Since nobody complained about that, we can assume it is not being used
anyway.
2024-02-29 08:22:52 +01:00
renovate[bot]andWolfgang Walther b9186b6d9a chore(deps): update all dependencies 2024-02-27 09:10:29 +01:00
Wolfgang Walther b663a9aa37 ci: Use actions/download-artifact instead of dawidd6/action-download-artifact 2024-02-24 21:58:19 +01:00
renovate[bot]andWolfgang Walther 4d874fb725 chore(deps): pin dependencies 2024-02-24 21:55:30 +01:00
renovate[bot]andWolfgang Walther 68e145550e chore(deps): update all dependencies 2024-02-24 20:38:54 +01:00
Wolfgang Walther 3406e62a77 ci: Remove nix actions cache and prepopulate job
The nix actions cache currently leads to repeated "no space left on
devices" errors for jobs in CI.

The prepopulate job is useless without the nix actions cache, so it
will go away at the same time.
2024-02-24 19:52:25 +01:00
Wolfgang Walther 3e5e8a22e4 ci: Switch action-get-latest-tag to github-action-get-previous-tag
The former is not maintained anymore and produces deprecation warnings in CI.
2024-02-24 19:04:23 +01:00
Wolfgang Walther 414a0daca1 chore(deps): update codecov/codecov-action action to v4 2024-02-24 18:43:11 +01:00
Wolfgang Walther 30f18cf22c docs: Display version number more prominently
Resolves https://github.com/PostgREST/postgrest-docs/issues/734
2024-02-19 21:50:54 +01:00
Wolfgang Walther fd9192836e chore: Add .docs-build to .gitignore to allow switching branches easily
This is only used on the latest main branch, but switching to release branches
is annoying otherwise.
2024-02-19 21:49:48 +01:00
Wolfgang Walther 02d8990ce2 ci: Change names for release branches from rel-MAJOR.MINOR to vMAJOR
This naming scheme gives us the best support for readthedocs.

References #2814
2024-02-17 17:46:11 +01:00
Wolfgang Walther 06821fdbca ci: Refactor conditions to check for branch events 2024-02-17 17:45:44 +01:00
Wolfgang Walther b77a08ff10 ci: Add freebsd executable to releases
This was temporarily disabled, because of timeouts in Cirrus. This seems to work well again.
2024-02-17 15:24:05 +01:00
Wolfgang Walther 278be7074e ci: Fix FreeBSD build on cirrus 2024-02-17 15:24:05 +01:00
Wolfgang Walther b9d46812f8 Merge branch 'docs/v12.0' into rel-12.0 2024-02-17 13:46:34 +01:00
Wolfgang Walther 7f410f3be0 chore: Run postgrest-style on docs files
This prevents CI from failing after the merge.
2024-02-17 13:43:11 +01:00
Wolfgang Walther 8aa8e682a2 chore: Prepare merge of postgrest-docs into postgrest main repo
This avoids some merge conflicts to allow git blame to detect renames properly.
2024-02-17 13:43:11 +01:00
Laurence Isla 867dc58ecc Update sponsor logo 2024-01-15 20:33:30 -05:00
Laurence Isla 6962e41a37 Fix Sphinx version error in RTD (#739) 2024-01-15 20:33:30 -05:00
ParashoeandGitHub 8605edcb6f Spelling correction (#740)
Spell correct "callounter" to "callcounter"
2024-01-15 20:18:20 -05:00
Gabriel MirandaandSteve Chavez efcf36d970 Fix secret command to avoid non-ascii alphanumerics 2023-12-30 23:10:37 -05:00
Laurence Isla f95c5127df Update sponsor image (#728) 2023-12-21 15:22:04 -05:00
Laurence Isla a4e00ffdf6 bump version to 12.0.2 2023-12-20 18:21:19 -05:00
steve-chavezandLaurence Isla cd62e39cd1 fix: empty row on handler function
Closes https://github.com/PostgREST/postgrest/issues/3126
2023-12-20 16:33:34 -05:00
steve-chavezandLaurence Isla ec0f99c686 fix: aggregates not working for all schemas
Closes https://github.com/PostgREST/postgrest/issues/3124
2023-12-20 16:33:31 -05:00
Laurence Isla b2cd365866 chore: add new sponsor 2023-12-20 16:29:37 -05:00
dependabot[bot]andLaurence Isla fcc330311f build(deps): bump actions/upload-artifact from 3 to 4 (#3123) 2023-12-20 16:29:37 -05:00
dependabot[bot]andLaurence Isla e557161b84 build(deps): bump dawidd6/action-download-artifact from 2 to 3 (#3122) 2023-12-20 16:29:37 -05:00
dependabot[bot]andLaurence Isla 962268fd6b build(deps): bump actions/download-artifact from 3 to 4 (#3121) 2023-12-20 16:29:37 -05:00
Laurence Isla cb501a9041 Add warning and fix to htmx how-to 2023-12-19 13:32:51 -05:00
steve-chavez a7d2c8e6ad docs: soap how-to is outdated
`*/*` can now be handled with media type handlers
2023-12-19 00:00:56 -05:00
Laurence Islaandsteve-chavez 40e16bd468 Add new sponsor 2023-12-18 18:23:39 -05:00
Laurence Isla 49e257617e Priorize query parameters instead of headers in limits and pagination 2023-12-13 11:48:53 -05:00
Laurence Isla 474bb93e89 Add meta tags using an Open Graph extension 2023-12-13 11:48:41 -05:00
Laurence Isla d75538ec4a Add missing curl requests in Preferences section 2023-12-13 11:48:32 -05:00
Laurence Islaandsteve-chavez fc58262cb2 Add new sponsor (#723) 2023-12-12 23:05:43 -05:00
steve-chavez fdfc095b0e fix: correct any media type handler 2023-12-12 22:46:56 -05:00
Laurence Islaandsteve-chavez eb582fb4b0 Add missing in-db configuration for jwt-cache-max-lifetime 2023-12-12 22:46:46 -05:00
Laurence Isla cd38da56d5 bump version to 12.0.1 2023-12-12 19:20:47 -05:00
steve-chavezandLaurence Isla 229a4e4cd6 fix: any handler sets a default application/json
Now it sets application/octet-stream as the generic type.
2023-12-12 18:46:40 -05:00
Laurence Isla c3f7440e33 fix: add jwt_cache_max_lifetime as an in-database configuration option (#3102) 2023-12-12 18:46:40 -05:00
Laurence Isla 4e03ee252b chore: add new sponsor 2023-12-12 18:46:40 -05:00
Andrei DziahelandLaurence Isla 0ac4d0d0a9 fix: replace json parser error with generic msg (#3090) 2023-12-12 18:46:40 -05:00
Andrei DziahelandLaurence Isla ceda77c5de ci: cancel in-progress loadtests for PRs as well 2023-12-12 18:46:40 -05:00
Andrei DziahelandLaurence Isla a6e4a0078b ci: test only head of branch 2023-12-12 18:46:40 -05:00
Laurence Isla 15f4157cc5 changelog: move missplaced unreleased fix (#3094) 2023-12-12 18:46:40 -05:00
Laurence Isla 50ea1999b2 fix: allow using special characters in json keys (#3081)
* increase memory size test
2023-12-12 18:46:40 -05:00
Andrei DziahelandLaurence Isla c92f16a2cb ci: similar jobs use a single cache for Nix stores
Jobs that use the setup-nix action will share a single cache, which is created by a previous warm up Job.
2023-12-12 18:46:40 -05:00
dependabot[bot]andLaurence Isla 1cebc03313 build(deps): bump cachix/cachix-action in /.github/actions/setup-nix (#3085) 2023-12-12 18:46:40 -05:00
steve-chavez 9f252678a4 fix: add missing pgrst.server_timing_enabled 2023-12-12 10:57:47 -05:00
steve-chavez 802131aa96 fix: any media type should be bytea 2023-12-05 17:49:46 -05:00
steve-chavez 275bdee47c fix server-timing header section
Also link it to jwt caching
2023-12-02 01:36:26 -05:00
steve-chavez f70e852a0a bump to 12.0.0 2023-12-01 19:41:04 -05:00
steve-chavez 00580bc8cb bump version to 12.0.0 2023-12-01 19:18:07 -05:00
230 changed files with 5647 additions and 8586 deletions
+5 -31
View File
@@ -2,41 +2,15 @@ freebsd_instance:
image_family: freebsd-14-0
build_task:
# Don't change this name without adjusting .github/workflows/build.yaml
# Don't change this name without adjusting .github/workflows/ci.yaml
name: Build FreeBSD (Stack)
install_script: pkg install -y postgresql16-client hs-stack git
only_if: |
$CIRRUS_TAG != '' || $CIRRUS_BRANCH == 'main' || $CIRRUS_BRANCH =~ 'v*' ||
changesInclude(
'.github/workflows/build.yaml',
'.github/actions/artifact-from-cirrus/**',
'.cirrus.yml',
'postgrest.cabal',
'stack.yaml*',
'**.hs'
)
install_script: pkg install -y postgresql13-client hs-stack git
stack_cache:
folders: /.stack
fingerprint_script:
- echo $CIRRUS_OS
- stack --version
- md5sum postgrest.cabal
- md5sum stack.yaml.lock
stack_work_cache:
folders: .stack-work
fingerprint_script:
- echo $CIRRUS_OS
- stack --version
- md5sum postgrest.cabal
- md5sum stack.yaml.lock
- find main src -type f -iname '*.hs' -exec md5sum "{}" +
build_script: |
stack build -j 1 --local-bin-path . --copy-bins --stack-yaml stack-21.7.yaml
strip postgrest
fingerprint_script: cat postgrest.cabal stack.yaml.lock
reupload_on_changes: false
build_script: stack build -j 1 --local-bin-path . --copy-bins
bin_artifacts:
path: postgrest
+1 -3
View File
@@ -7,14 +7,12 @@ When submitting a new feature or fix:
+ `fix`, bug fixes
+ `feat`, new features added
+ `perf`, performance improvements
+ `docs`, updating the documentation
+ `nix`, related to the Nix development environment
+ `ci`, related to the Continuous Integration modules
+ `test`, related to the testing modules
+ `refactor`, refactoring code
+ `deprecate`, deprecating a feature
+ `changelog`, updating the CHANGELOG
+ `chore`, maintenance (build process, updating sponsors, etc.)
+ `chore`, maintenance (changelog, build process, etc.)
+ Other prefixes may be used if necessary
- If there's a breaking change, add `BREAKING CHANGE` and an explanation to your commit message
-->
-35
View File
@@ -1,35 +0,0 @@
name: Cache on main
description: Stores caches on main and release branches only, but restores them on all branches.
inputs:
path:
description: Path(s) to cache
required: true
save-prs:
description: Whether to additionally store the cache in a pull request, too. Should only be used for very small caches.
type: boolean
prefix:
description: Cache key prefix to be used in both primary key and restore-keys.
required: true
suffix:
description: Cache key suffix to be used only in primary key.
required: true
runs:
using: composite
steps:
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
if: ${{ startsWith(github.ref, 'refs/heads/') || (inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
restore-keys: |
${{ runner.os }}-${{ inputs.prefix }}-
- uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
if: ${{ !startsWith(github.ref, 'refs/heads/') && !(inputs.save-prs && startsWith(github.ref, 'refs/pull/')) }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ inputs.prefix }}-${{ inputs.suffix }}
restore-keys: |
${{ runner.os }}-${{ inputs.prefix }}-
+1 -1
View File
@@ -16,7 +16,7 @@ runs:
nix_conf: |-
always-allow-substitutes = true
max-jobs = auto
- uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15
- uses: cachix/cachix-action@18cf96c7c98e048e10a83abd92116114cd8504be # v14
with:
name: postgrest
authToken: ${{ inputs.authToken }}
+11
View File
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /.github/actions/setup-nix
schedule:
interval: weekly
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Tag a release that will be built with Github Actions. The version is
# read from 'postgrest.cabal'.
version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
echo "Tagging version v$version"
git tag -f "v$version"
echo "Pushing tag..."
git push -f origin "refs/tags/v$version"
-25
View File
@@ -1,25 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices"
],
"baseBranches": [
"main",
"/^v[0-9]+/"
],
"rebaseWhen": "conflicted",
"packageRules": [
{
"matchBaseBranches": [ "/^v[0-9]+/" ],
"groupName": "all dependencies"
},
{
"matchPackageNames": ["docutils"],
"allowedVersions": "<0.21.0"
},
{
"matchPackageNames": ["macos"],
"allowedVersions": "<13"
}
]
}
+1 -4
View File
@@ -58,11 +58,8 @@ cd postgrest
git checkout $PGRST_GITHUB_COMMIT
cabal v2-update && cabal v2-build
# Strip unused symbols from executable
PGRST_BIN=$(cabal exec which postgrest | tail -1)
strip $PGRST_BIN
# Copy the built binary to the Dockerfile directory
PGRST_BIN=$(cabal exec which postgrest | tail -1)
cp $PGRST_BIN ~/$DOCKER_BUILD_DIR
# Move and compress the built binary
-203
View File
@@ -1,203 +0,0 @@
name: Build
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/build.yaml
- .github/actions/**
- .github/scripts/**
- .github/*
- '*.nix'
- nix/**
- .cirrus.yml
- cabal.project*
- postgrest.cabal
- stack.yaml*
- '**.hs'
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: build-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
static:
name: Nix - Linux static
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build static executable
run: nix-build -A postgrestStatic
- name: Save built executable as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: postgrest-linux-static-x64
path: result/bin/postgrest
if-no-files-found: error
- name: Build Docker image
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
- name: Save built Docker image as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: postgrest-docker-x64
path: postgrest-docker.tar.gz
if-no-files-found: error
macos:
name: Nix - MacOS
runs-on: macos-12
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Install gnu sed
run: brew install gnu-sed
- name: Build everything
run: |
# The --dry-run will give us a list of derivations to download from cachix and
# derivations to build. We only take those that would have to be built and then build
# those explicitly. This has the advantage that pure verification will not include
# a download anymore, making it much faster. If something needs to be built, only
# the dependencies required to do so will be downloaded, but not everything.
nix-build --dry-run 2>&1 \
| gsed -e '1,/derivations will be built:$/d' -e '/paths will be fetched/Q' \
| xargs nix-build
stack:
strategy:
fail-fast: false
matrix:
include:
- name: Linux
runs-on: ubuntu-22.04
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
# no artifact for Linux, because we use the static build
- name: MacOS
runs-on: macos-12
cache: |
~/.stack/pantry
~/.stack/snapshots
~/.stack/stack.sqlite3
artifact: postgrest-macos-x64
- name: Windows
runs-on: windows-2022
cache: |
~\AppData\Roaming\stack\pantry
~\AppData\Local\Programs\stack\pantry
~\AppData\Roaming\stack\snapshots
~\AppData\Local\Programs\stack\snapshots
~\AppData\Roaming\stack\stack.sqlite3
~\AppData\Local\Programs\stack\stack.sqlite3
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
artifact: postgrest-windows-x64
name: Stack - ${{ matrix.name }}
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: haskell-actions/setup@33585e1a16afa5875e124b0ebc89dd0c2f872c21 # v2.7.3
with:
# This must match the version in stack.yaml's resolver
ghc-version: 9.6.5
enable-stack: true
stack-no-global: true
stack-setup-ghc: true
- name: Cache ~/.stack
uses: ./.github/actions/cache-on-main
with:
path: ${{ matrix.cache }}
prefix: stack
suffix: ${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
- name: Cache .stack-work
uses: ./.github/actions/cache-on-main
with:
path: .stack-work
save-prs: true
prefix: stack-work-${{ hashFiles('postgrest.cabal', 'stack.yaml.lock') }}
suffix: ${{ hashFiles('main/**/*.hs', 'src/**/*.hs') }}
- name: Install dependencies
if: matrix.deps
run: ${{ matrix.deps }}
- name: Build with Stack
run: stack build --lock-file error-on-write --local-bin-path result --copy-bins
- name: Strip Executable
run: strip result/postgrest*
- name: Save built executable as artifact
if: matrix.artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: ${{ matrix.artifact }}
path: |
result/postgrest
result/postgrest.exe
if-no-files-found: error
freebsd:
name: Stack - FreeBSD from CirrusCI
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: ./.github/actions/artifact-from-cirrus
with:
token: ${{ github.token }}
task: Build FreeBSD (Stack)
download: bin
upload: postgrest-freebsd-x64
cabal:
strategy:
matrix:
ghc: ['9.6.4', '9.8.2']
fail-fast: false
name: Cabal - Linux GHC ${{ matrix.ghc }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: haskell-actions/setup@33585e1a16afa5875e124b0ebc89dd0c2f872c21 # v2.7.3
with:
ghc-version: ${{ matrix.ghc }}
- name: Cache .cabal
uses: ./.github/actions/cache-on-main
with:
path: |
~/.cabal/packages
~/.cabal/store
prefix: cabal-${{ matrix.ghc }}
suffix: ${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
- name: Cache dist-newstyle
uses: ./.github/actions/cache-on-main
with:
path: dist-newstyle
save-prs: true
prefix: cabal-${{ matrix.ghc }}-dist-newstyle-${{ hashFiles('postgrest.cabal', 'cabal.project', 'cabal.project.freeze') }}
suffix: ${{ hashFiles('**/*.hs') }}
- name: Install dependencies
run: cabal build --only-dependencies --enable-tests --enable-benchmarks
- name: Build
run: cabal build --enable-tests --enable-benchmarks all
+78
View File
@@ -0,0 +1,78 @@
name: Cachix
# This workflow serves to
# - keep cachix up to date with the main branch
# - incrementally update cachix for large dependency
# updates, e.g. after running postgrest-nixpkgs-upgrade,
# which can cause the main CI workflow to time out
on:
workflow_dispatch:
push:
branches:
- main
- rel-*
tags:
- v*
jobs:
Seed-Cachix:
strategy:
fail-fast: false
matrix:
include:
- os: Linux
runs-on: ubuntu-22.04
- os: MacOS
runs-on: macos-12
name: Seed ${{ matrix.os }}
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Install cachix tooling
run: |
nix-env -f default.nix -iA devTools.pushCachix.bin
postgrest-push-cachix
- name: Seed dynamic postgrest build
run: |
nix-build -A postgrestPackage
postgrest-push-cachix
- name: Seed style tools
run: |
nix-build -A style
postgrest-push-cachix
- name: Seed test tools
run: |
nix-build -A tests
postgrest-push-cachix
- name: Seed static toolchain
if: matrix.os == 'Linux'
run: |
nix-build -A packagesStatic.haskellPackages.hello
postgrest-push-cachix
- name: Seed static postgresql build (for libpq)
if: matrix.os == 'Linux'
run: |
nix-build -A packagesStatic.pkgs.postgresql
postgrest-push-cachix
- name: Seed static postgrest build
if: matrix.os == 'Linux'
run: |
nix-build -A postgrestStatic
postgrest-push-cachix
- name: Build and push everything to Cachix
run: |
nix-build
postgrest-push-cachix
-32
View File
@@ -1,32 +0,0 @@
name: Check
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: style-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint-style:
name: Lint & Style
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: style.lint.bin style.styleCheck.bin
- name: Run linter (check locally with `nix-shell --run postgrest-lint`)
run: postgrest-lint
- name: Run style check (auto-format with `nix-shell --run postgrest-style`)
run: postgrest-style-check
+299 -107
View File
@@ -8,58 +8,263 @@ on:
tags:
- devel
- v*
pull_request:
branches:
- main
- v[0-9]+
concurrency:
# Terminate all previous runs of the same workflow for the same tag, but never
# cancel for the main / release branch pipelines.
group: ci-${{ (github.ref_type == 'tag' && github.ref) || github.run_id }}
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
cancel-in-progress: false
group: ${{ github.workflow }}-${{ github.ref }}
# Terminate all previous runs of the same workflow for pull requests
cancel-in-progress: "${{ github.event_name == 'pull_request' }}"
jobs:
check:
name: Check
if: startsWith(github.ref, 'refs/heads/')
uses: ./.github/workflows/check.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
Lint-Style:
name: Lint & check code style
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: style
- name: Run linter (check locally with `nix-shell --run postgrest-lint`)
run: postgrest-lint
- name: Run style check (auto-format with `nix-shell --run postgrest-style`)
run: postgrest-style-check
docs:
name: Docs
if: startsWith(github.ref, 'refs/heads/')
uses: ./.github/workflows/docs.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
Test-Nix:
name: Test (Nix)
runs-on: ubuntu-22.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: tests
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
run: postgrest-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@5ecb98a3c6b747ed38dc09f787459979aebb39be # v4.3.1
with:
files: ./coverage/codecov.json
token: ${{ secrets.CODECOV_TOKEN }}
- name: Run doctests
if: always()
run: nix-shell --run postgrest-test-doctests
- name: Check the spec tests for idempotence
if: always()
run: postgrest-test-spec-idempotence
test:
name: Test
if: startsWith(github.ref, 'refs/heads/')
uses: ./.github/workflows/test.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
Test-Pg-Nix:
strategy:
fail-fast: false
matrix:
pgVersion: [9.6, 10, 11, 12, 13, 14, 15, 16]
name: Test PG ${{ matrix.pgVersion }} (Nix)
runs-on: ubuntu-22.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: tests withTools
- name: Run spec tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
- name: Run IO tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} -f test/io/fixtures.sql postgrest-test-io -vv
build:
name: Build
uses: ./.github/workflows/build.yaml
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
Test-Memory-Nix:
name: Test memory (Nix)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: memory
- name: Run memory tests
run: postgrest-test-memory
arm:
name: Build / Cabal - aarch64 GHC 9.4.8
if: vars.SSH_ARM_ENABLED
Build-Static-Nix:
name: Build Linux static (Nix)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: tests
- name: Build static executable
run: nix-build -A postgrestStatic
- name: Check static executable
run: postgrest-check-static result/bin/postgrest
- name: Save built executable as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: postgrest-linux-static-x64
path: result/bin/postgrest
if-no-files-found: error
- name: Build Docker image
run: nix-build -A docker.image --out-link postgrest-docker.tar.gz
- name: Save built Docker image as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: postgrest-docker-x64
path: postgrest-docker.tar.gz
if-no-files-found: error
Build-Macos-Nix:
name: Build MacOS (Nix)
runs-on: macos-12
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
- name: Build everything
run: |
nix-build
Build-Stack:
strategy:
fail-fast: false
matrix:
include:
- name: Linux
runs-on: ubuntu-22.04
cache: |
~/.stack
.stack-work
artifact: postgrest-ubuntu-x64
- name: MacOS
runs-on: macos-12
cache: |
~/.stack
.stack-work
artifact: postgrest-macos-x64
- name: Windows
runs-on: windows-2022
cache: |
~\AppData\Roaming\stack
~\AppData\Local\Programs\stack
.stack-work
deps: Add-Content $env:GITHUB_PATH $env:PGBIN
artifact: postgrest-windows-x64
name: Build ${{ matrix.name }} (Stack)
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Stack working files cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ${{ matrix.cache }}
key: cache-stack-${{ runner.os }}-${{ hashFiles('stack.yaml.lock') }}
- name: Install dependencies
if: ${{ matrix.deps }}
run: ${{ matrix.deps }}
- name: Build with Stack
run: stack build --local-bin-path result --copy-bins
- name: Save built executable as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: ${{ matrix.artifact }}
path: |
result/postgrest
result/postgrest.exe
if-no-files-found: error
Get-FreeBSD-CirrusCI:
name: Get FreeBSD build from CirrusCI
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: ./.github/actions/artifact-from-cirrus
with:
token: ${{ github.token }}
task: Build FreeBSD (Stack)
download: bin
upload: postgrest-freebsd-x64
Build-Cabal:
strategy:
matrix:
ghc: ['9.0.2', '9.2.4']
fail-fast: false
name: Build Linux (Cabal, GHC ${{ matrix.ghc }})
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Workaround runner image issue
# https://github.com/actions/runner-images/issues/7061
run: sudo chown -R "$USER" /usr/local/.ghcup
- name: ghcup
run: |
ghcup install ghc ${{ matrix.ghc }}
ghcup set ghc ${{ matrix.ghc }}
- name: Copy cabal.project & fix caching
run: |
mkdir ~/.cabal
cp cabal.project.non-nix cabal.project
- name: Cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: |
~/.cabal/packages
~/.cabal/store
dist-newstyle
key: cache-cabal-${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('**/*.cabal', '**/cabal.project') }}
restore-keys: |
cache-cabal-${{ runner.os }}-${{ matrix.ghc }}-
- name: Install dependencies
run: |
cabal update
cabal build --only-dependencies --enable-tests --enable-benchmarks
- name: Build
run: cabal build --enable-tests --enable-benchmarks all
Build-Cabal-Arm:
strategy:
matrix:
ghc: ['9.2.4']
fail-fast: false
name: Build aarch64 (Cabal, GHC ${{ matrix.ghc }})
if: "${{ github.event_name == 'push' }}"
runs-on: ubuntu-22.04
outputs:
remotepath: ${{ steps.Remote-Dir.outputs.remotepath }}
env:
GITHUB_COMMIT: ${{ github.sha }}
GHC_VERSION: '9.4.8'
GHC_VERSION: ${{ matrix.ghc }}
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- id: Remote-Dir
name: Unique directory name for the remote build
run: echo "remotepath=postgrest-build-$(uuidgen)" >> "$GITHUB_OUTPUT"
@@ -106,30 +311,23 @@ jobs:
if-no-files-found: error
tag:
name: Release / Tag
concurrency:
# Never tag outdated commits on the main branch by skipping superseded commits
group: ci-tag-${{ (github.ref == 'refs/heads/main' && github.ref) || github.run_id }}
# TODO: Enable this once https://github.com/orgs/community/discussions/13015 is solved
cancel-in-progress: false
if: |
vars.RELEASE_ENABLED &&
startsWith(github.ref, 'refs/heads/') &&
needs.docs.result == 'success' &&
needs.test.result == 'success' &&
needs.build.result == 'success' &&
(needs.arm.result == 'skipped' || success())
Tag-Release:
name: Tag Release
if: startsWith(github.ref, 'refs/heads/')
permissions:
contents: write
runs-on: ubuntu-22.04
needs:
- docs
- test
- build
- arm
- Lint-Style
- Test-Nix
- Test-Pg-Nix
- Test-Memory-Nix
- Build-Static-Nix
- Build-Stack
- Get-FreeBSD-CirrusCI
- Build-Cabal-Arm
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
ssh-key: ${{ secrets.POSTGREST_SSH_KEY }}
- name: Tag latest commit
@@ -149,18 +347,21 @@ jobs:
fi
prepare:
name: Release / Prepare
if: |
startsWith(github.ref, 'refs/tags/') &&
needs.build.result == 'success' &&
(needs.arm.result == 'skipped' || success())
Prepare-Release:
name: Prepare release
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-22.04
needs:
- build
- arm
- Lint-Style
- Test-Nix
- Test-Pg-Nix
- Test-Memory-Nix
- Build-Static-Nix
- Build-Stack
- Get-FreeBSD-CirrusCI
- Build-Cabal-Arm
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Check the version to be released
run: |
cabal_version="$(grep -oP '^version:\s*\K.*' postgrest.cabal)"
@@ -190,16 +391,14 @@ jobs:
if-no-files-found: error
github:
name: Release / GitHub
Release-GitHub:
name: Release on GitHub
permissions:
contents: write
runs-on: ubuntu-22.04
needs:
- prepare
if: success() || needs.prepare.result == 'success'
needs: Prepare-Release
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Download all artifacts
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
@@ -260,20 +459,17 @@ jobs:
fi
docker:
name: Release / Docker Hub
Release-Docker:
name: Release on Docker Hub
runs-on: ubuntu-22.04
needs:
- prepare
if: |
vars.DOCKER_REPO && vars.DOCKER_USER &&
(success() || needs.prepare.result == 'success')
- Prepare-Release
env:
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
DOCKER_USER: ${{ vars.DOCKER_USER }}
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Download Docker image
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
@@ -294,42 +490,36 @@ jobs:
else
echo "Skipping push to 'latest' tag for pre-release..."
fi
# TODO: Enable dockerhub description update again, once a solution for the permission problem is found:
# https://github.com/docker/hub-feedback/issues/1927
# - name: Update descriptions on Docker Hub
# env:
# DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
# run: |
# if [[ -z "$ISPRERELEASE" ]]; then
# echo "Updating description on Docker Hub..."
# postgrest-release-dockerhub-description
# else
# echo "Skipping updating description for pre-release..."
# fi
docker-description:
name: Release / Docker Hub Description
runs-on: ubuntu-22.04
if: |
vars.DOCKER_REPO && vars.DOCKER_USER &&
github.ref == 'refs/tags/devel'
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0
with:
username: ${{ vars.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
repository: ${{ vars.DOCKER_REPO }}/postgrest
short-description: ${{ github.event.repository.description }}
readme-filepath: ./docker-hub-readme.md
docker-arm:
name: Release / Docker Hub Arm
Release-Docker-Arm:
name: Release Arm Builds on Docker Hub
runs-on: ubuntu-22.04
needs:
- arm
- docker
- Build-Cabal-Arm
- Release-Docker
env:
GITHUB_COMMIT: ${{ github.sha }}
DOCKER_REPO: ${{ vars.DOCKER_REPO }}
DOCKER_USER: ${{ vars.DOCKER_USER }}
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Publish images for ARM builds on Docker Hub
uses: appleboy/ssh-action@master
env:
REMOTE_DIR: ${{ needs.arm.outputs.remotepath }}
REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }}
with:
host: ${{ secrets.SSH_ARM_HOST }}
username: ubuntu
@@ -339,18 +529,20 @@ jobs:
envs: GITHUB_COMMIT,DOCKER_REPO,DOCKER_USER,DOCKER_PASS,REMOTE_DIR,GITHUB_REF_NAME
script: bash ~/$REMOTE_DIR/docker-publish.sh "$GITHUB_COMMIT" "$DOCKER_REPO" "$DOCKER_USER" "$DOCKER_PASS" "$REMOTE_DIR" "$GITHUB_REF_NAME"
clean-arm:
name: Build / Cleanup
Clean-Arm-Server:
name: Remove copied files from server
needs:
- arm
- docker-arm
if: ${{ always() && vars.SSH_ARM_ENABLED }}
- Build-Cabal-Arm
- Release-Docker-Arm
if: success() ||
needs.Build-Cabal-Arm.result == 'failure' ||
needs.Build-Cabal-Arm.result == 'cancelled' ||
(needs.Build-Cabal-Arm.result == 'success' && !startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-22.04
env:
REMOTE_DIR: ${{ needs.arm.outputs.remotepath }}
REMOTE_DIR: ${{ needs.Build-Cabal-Arm.outputs.remotepath }}
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Remove uploaded files from server
uses: appleboy/ssh-action@master
with:
+28 -45
View File
@@ -1,67 +1,50 @@
name: Docs
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
push:
branches:
- main
- v[0-9]+
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/docs.yaml
- .github/actions/setup-nix/**
- default.nix
- nix/**
- docs/**
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: docs-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
- main
- v[0-9]+
jobs:
build:
name: Build
name: Build docs
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: docs.build.bin
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
- run: nix-env -f docs/default.nix -iA build
- run: postgrest-docs-build
- run: git diff --exit-code HEAD locales || echo "Please commit changes to the locales/ folder after running postgrest-docs-build."
spellcheck:
name: Spellcheck
name: Run spellcheck
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: docs.spellcheck.bin docs.dictcheck.bin
- name: Run spellcheck
run: postgrest-docs-spellcheck
- name: Run dictcheck
run: postgrest-docs-dictcheck
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
- run: nix-env -f docs/default.nix -iA spellcheck
- run: postgrest-docs-spellcheck
dictcheck:
name: Run dictcheck
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
- run: nix-env -f docs/default.nix -iA dictcheck
- run: postgrest-docs-dictcheck
linkcheck:
name: Linkcheck
name: Run linkcheck
if: github.base_ref == 'main'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: docs.linkcheck.bin
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
- run: nix-env -f docs/default.nix -iA linkcheck
- run: postgrest-docs-linkcheck
+70
View File
@@ -0,0 +1,70 @@
name: Loadtest
on:
push:
branches:
- main
tags:
- v*
pull_request:
branches:
- main
jobs:
Loadtest-PR-Nix:
name: Loadtest PR (Nix)
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
fetch-depth: 0
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: loadtest
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
id: get-latest-tag
with:
prefix: v
- name: Run loadtest
run: |
postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }}
postgrest-loadtest-report > loadtest/loadtest.md
- name: Upload report
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: loadtest.md
path: loadtest/loadtest.md
if-no-files-found: error
Loadtest-Merge-Nix:
name: Loadtest Merge (Nix)
if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
fetch-depth: 0
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
id: get-latest-tag
with:
prefix: v
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
tools: loadtest
- name: Run loadtest
run: |
postgrest-loadtest-against ${{ steps.get-latest-tag.outputs.tag }}
postgrest-loadtest-report > loadtest/loadtest.md
- name: Upload report
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: loadtest.md
path: loadtest/loadtest.md
if-no-files-found: error
+3 -9
View File
@@ -2,23 +2,17 @@ name: Upload Reports
on:
workflow_run:
workflows:
- CI
- Test
branches-ignore:
- devel
- v[0-9]+
- v[0-9]+.[0-9]+.[0-9]+
workflows: ["Loadtest"]
types:
- completed
jobs:
loadtest:
upload:
name: Loadtest
permissions:
checks: write
runs-on: ubuntu-22.04
if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure'
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Download from Artifacts
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
-138
View File
@@ -1,138 +0,0 @@
name: Test
on:
workflow_call:
secrets:
CACHIX_AUTH_TOKEN:
required: false
CODECOV_TOKEN:
required: false
pull_request:
branches:
- main
- v[0-9]+
paths:
- .github/workflows/test.yaml
- .github/workflows/report.yaml
- .github/actions/setup-nix/**
- default.nix
- nix/**
- .stylish-haskell.yaml
- cabal.project
- postgrest.cabal
- '**.hs'
- test/**
- '!**.md'
concurrency:
# Terminate all previous runs of the same workflow for pull requests
group: test-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
coverage:
name: Coverage
runs-on: ubuntu-22.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.coverage.bin tests.testDoctests.bin tests.testSpecIdempotence.bin
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
run: postgrest-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1
with:
files: ./coverage/codecov.json
token: ${{ secrets.CODECOV_TOKEN }}
- name: Run doctests
if: always()
run: postgrest-test-doctests
- name: Check the spec tests for idempotence
if: always()
run: postgrest-test-spec-idempotence
postgres:
strategy:
fail-fast: false
matrix:
pgVersion: ["9_6", 10, 11, 12, 13, 14, 15, 16]
name: PG ${{ matrix.pgVersion }}
runs-on: ubuntu-22.04
defaults:
run:
# Hack for enabling color output, see:
# https://github.com/actions/runner/issues/241#issuecomment-842566950
shell: script -qec "bash --noprofile --norc -eo pipefail {0}"
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: tests.testSpec.bin tests.testIO.bin tests.testBigSchema.bin withTools.postgresql-${{ matrix.pgVersion }}.bin
- name: Run spec tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-spec
- name: Run IO tests
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-io -vv
- name: Run IO tests on a big schema
if: always()
run: postgrest-with-postgresql-${{ matrix.pgVersion }} postgrest-test-big-schema -vv
memory:
name: Memory
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: memory.test.bin
- name: Run memory tests
run: postgrest-test-memory
loadtest:
name: Loadtest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 0
- name: Setup Nix Environment
uses: ./.github/actions/setup-nix
with:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
tools: loadtest.loadtestAgainst.bin loadtest.report.bin
- uses: WyriHaximus/github-action-get-previous-tag@04e8485ecb6487243907e330d522ff60f02283ce # v1.4.0
id: get-latest-tag
with:
prefix: v
- name: Run loadtest
run: |
postgrest-loadtest-against main ${{ steps.get-latest-tag.outputs.tag }}
postgrest-loadtest-report > loadtest/loadtest.md
- name: Upload report
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: loadtest.md
path: loadtest/loadtest.md
if-no-files-found: error
View File
+68
View File
@@ -0,0 +1,68 @@
# Architecture
This document describes the high-level architecture of PostgREST.
## Bird's Eye View
```haskell
postgrest :: Request -> Either Error SQLStatement -> Response
```
On the highest level, PostgREST processes an HTTP request, if it's accepted it builds a SQL statement for it, executes it, and produces a response.
## Code Map
This section talks briefly about various important modules.
The starting point of the program is `main/Main.hs`, which calls `src/PostgREST/CLI.hs` which then calls `src/PostgREST/App.hs`.
`App.hs` is then in charge of composing the different modules.
### ApiRequest.hs
PostgREST operates over two types of resources: database relations(tables or views) and database functions; providing different representations(depending on the media type)
for them.
This module is in charge of representing the operation over an `ApiRequest` type. It parses the URL querystring following PostgREST syntax, the request headers, and the request body
(if possible it avoids parsing the body and sends it directly to the db).
A request might be rejected at this level if it's invalid, e.g. providing an unknown media type to PostgREST or using an unknown HTTP method.
### Plan.hs
Using the Schema Cache, this module enables more complex functionality(like resource embedding) by enriching the ApiRequest. It generates Plan types(`ReadPlan`, `MutatePlan`)
that then will be used to generate a SQL statement.
A request might be rejected at this level if it's invalid, e.g. by doing resource embedding on a nonexistent resource.
An OPTIONS request doesn't require a plan to be generated.
### Query.hs
This module constructs single SQL statements that can be parametrized and prepared. Only at this stage a PostgreSQL connection from the pool is used.
A query might fail(and be rollbacked) at this level if it doesn't comply to certain conditions, e.g. by not returning a single row when a ``Accept: application/vnd.pgrst.object`` header is specified.
An OPTIONS request doesn't require a query to be executed.
### Response.hs
This module constructs the HTTP response body with the right headers.
It builds the OpenAPI response using the schema cache.
### Auth.hs
This module provides functions to deal with JWT authorization.
### SchemaCache.hs
This queries the PostgreSQL system catalogs and caches the metadata into a SchemaCache type,
### AppState.hs
The state of the App which is kept across requests.
This spawns threads which are used to execute concurrent jobs.
Jobs include connection recover and a listener for the PostgreSQL LISTEN command.
+4 -4
View File
@@ -9,7 +9,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
<tr>
<td align="center" valign="middle">
<a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/cybertec.svg">
<img width="296px" src="static/cybertec-new.png">
</a>
</td>
<td align="center" valign="middle">
@@ -32,7 +32,7 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.png">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.png">
</a>
</td>
<td align="center" valign="middle">
@@ -75,12 +75,12 @@ PostgREST ongoing development is only possible thanks to our Sponsors and Backer
<tr>
<td align="center" valign="middle">
<a href="https://www.timescale.com?utm_campaign=postgrest&utm_source=sponsor&utm_medium=referral&utm_content=github" target="_blank">
<img width="222px" src="static/timescaledb.png">
<img width="222px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/timescaledb.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=postgrest" target="_blank">
<img max-width="222px" height="88" src="static/retool.png">
<img max-width="222px" height="88" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/retool.png">
</a>
</td>
<td align="center" valign="middle">
-55
View File
@@ -5,55 +5,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
## [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
@@ -121,7 +72,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
- Removed [db-use-legacy-gucs](https://postgrest.org/en/v11.2/references/configuration.html#db-use-legacy-gucs) - @laurenceisla
+ All PostgreSQL versions now use GUCs in JSON format for [Headers, Cookies and JWT claims](https://postgrest.org/en/v12/references/transactions.html#request-headers-cookies-and-jwt-claims).
## [11.2.2] - 2023-10-25
@@ -425,11 +375,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #2312, Using `Prefer: return=representation` no longer returns a `Location` header - @laurenceisla
- #1984, For the cases where one to one relationships are detected, json objects will be returned instead of json arrays of length 1
+ If you wish to override this behavior, you can use computed relationships to return arrays again
+ You can get the newly detected one-to-one relationships by using the `--dump-schema` option and filtering with [jq](https://github.com/jqlang/jq).
```
./postgrest --dump-schema \
| jq '[.dbRelationships | .[] | .[1] | .[] | select(.relCardinality.tag == "O2O" and .relFTableIsView == false and .relTableIsView == false) | del(.relFTableIsView,.relTableIsView,.tag,.relIsSelf)]'
```
## [9.0.1] - 2022-06-03
+4 -4
View File
@@ -1,4 +1,4 @@
![Logo](static/postgrest.png "Logo")
![Logo](static/bigger-logo.png "Logo")
[![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg?colorB=F96854)](https://www.patreon.com/postgrest)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/postgrest)
@@ -20,7 +20,7 @@ API than you are likely to write from scratch.
<tr>
<td align="center" valign="middle">
<a href="https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest" target="_blank">
<img width="296px" src="static/cybertec.svg">
<img width="296px" src="static/cybertec-new.png">
</a>
</td>
<td align="center" valign="middle">
@@ -43,7 +43,7 @@ API than you are likely to write from scratch.
</td>
<td align="center" valign="middle">
<a href="https://supabase.io?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage" target="_blank">
<img width="296px" src="static/supabase.png">
<img width="296px" src="https://raw.githubusercontent.com/PostgREST/postgrest/main/static/supabase.png">
</a>
</td>
<td align="center" valign="middle">
@@ -68,7 +68,7 @@ Big thanks to our sponsors! You can join them by supporting PostgREST on [Patreo
```
## [Documentation](http://postgrest.org)
Latest documentation is at [postgrest.org](http://postgrest.org). You can contribute to the docs in [PostgREST/postgrest/docs](https://github.com/PostgREST/postgrest/tree/main/docs).
Latest documentation is at [postgrest.org](http://postgrest.org). You can contribute to the docs in [PostgREST/postgrest-docs](https://github.com/PostgREST/postgrest-docs).
## Performance
-4
View File
@@ -1,4 +0,0 @@
packages: postgrest.cabal
tests: true
package *
ghc-options: -split-sections
+1 -1
View File
@@ -1 +1 @@
index-state: hackage.haskell.org 2024-05-17T23:41:49Z
index-state: hackage.haskell.org 2023-10-13T13:54:33Z
+20
View File
@@ -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
+49 -28
View File
@@ -1,23 +1,12 @@
{ system ? builtins.currentSystem
, compiler ? "ghc948"
, # Commit of the Nixpkgs repository that we want to use.
nixpkgsVersion ? import nix/nixpkgs-version.nix
, # Nix files that describe the Nixpkgs repository. We evaluate the expression
# using `import` below.
nixpkgs ? let inherit (nixpkgsVersion) owner repo rev tarballHash; in
builtins.fetchTarball {
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
sha256 = tarballHash;
}
}:
{ system ? builtins.currentSystem }:
let
name =
"postgrest";
compiler =
"ghc924";
# PostgREST source files, filtered based on the rules in the .gitignore files
# and file extensions. We want to include as litte as possible, as the files
# added here will increase the space used in the Nix store and trigger the
@@ -27,6 +16,18 @@ let
(pkgs.gitignoreSource ./.)
[ ".cabal" ".hs" ".lhs" "LICENSE" ];
# Commit of the Nixpkgs repository that we want to use.
nixpkgsVersion =
import nix/nixpkgs-version.nix;
# Nix files that describe the Nixpkgs repository. We evaluate the expression
# using `import` below.
nixpkgs =
builtins.fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
sha256 = nixpkgsVersion.tarballHash;
};
allOverlays =
import nix/overlays;
@@ -35,10 +36,10 @@ let
allOverlays.build-toolbox
allOverlays.checked-shell-script
allOverlays.gitignore
allOverlays.postgresql-libpq
allOverlays.postgis
(allOverlays.postgresql-default { inherit patches; })
allOverlays.postgresql-legacy
allOverlays.postgresql-future
allOverlays.postgis
(allOverlays.haskell-packages { inherit compiler; })
allOverlays.slocat
];
@@ -49,28 +50,52 @@ let
postgresqlVersions =
[
{ name = "postgresql-16"; postgresql = pkgs.postgresql_16.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{
name = "postgresql-16";
postgresql = pkgs.postgresql_16.withPackages (p: [
p.postgis
(p.pg_safeupdate.overrideAttrs (old: {
installPhase = ''
mkdir -p $out/bin
cp safeupdate.dylib safeupdate.so || true
install -D safeupdate.so -t $out/lib
'';
}))
]);
}
{ name = "postgresql-15"; postgresql = pkgs.postgresql_15.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-10"; postgresql = pkgs.postgresql_10.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-9_6"; postgresql = pkgs.postgresql_9_6.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
{ name = "postgresql-9.6"; postgresql = pkgs.postgresql_9_6.withPackages (p: [ p.postgis p.pg_safeupdate ]); }
];
patches =
pkgs.callPackage nix/patches { };
# Dynamic derivation for PostgREST
postgrest =
pkgs.haskell.packages."${compiler}".callCabal2nix name src { };
staticHaskellPackage = import nix/static.nix { inherit compiler name pkgs src; };
# Functionality that derives a fully static Haskell package based on
# nh2/static-haskell-nix
staticHaskellPackage =
import nix/static-haskell-package.nix { inherit nixpkgs system compiler patches allOverlays; };
# Static executable.
postgrestStatic =
lib.justStaticExecutables (lib.dontCheck (staticHaskellPackage name src).package);
packagesStatic = (staticHaskellPackage name src).survey;
# Options passed to cabal in dev tools and tests
devCabalOptions =
"-f dev --test-show-detail=direct";
profiledHaskellPackages =
pkgs.haskell.packages."${compiler}".extend (_: super:
pkgs.haskell.packages."${compiler}".extend (self: super:
{
mkDerivation =
args:
@@ -111,16 +136,12 @@ rec {
pkgs.callPackage nix/tools/cabalTools.nix { inherit devCabalOptions postgrest; };
withTools =
pkgs.callPackage nix/tools/withTools.nix { inherit postgresqlVersions postgrest; };
pkgs.callPackage nix/tools/withTools.nix { inherit cabalTools devCabalOptions postgresqlVersions postgrest; };
# Development tools.
devTools =
pkgs.callPackage nix/tools/devTools.nix { inherit tests style devCabalOptions hsie withTools; };
# Documentation tools.
docs =
pkgs.callPackage nix/tools/docs.nix { };
# Load testing tools.
loadtest =
pkgs.callPackage nix/tools/loadtest.nix { inherit withTools; };
@@ -151,8 +172,8 @@ rec {
};
} // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux rec {
# Static executable.
inherit (staticHaskellPackage) postgrestStatic;
inherit (staticHaskellPackage) packagesStatic;
inherit postgrestStatic;
inherit packagesStatic;
# Docker images and loading script.
docker =
+1 -1
View File
@@ -5,4 +5,4 @@ Pipfile.lock
_diagrams/db.pdf
misspellings
unuseddict
*.mo
.history
+11 -19
View File
@@ -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.
+4 -1
View File
@@ -17,7 +17,10 @@ The schema structure diagram is done with LaTeX. You can use a GUI like https://
Then use this command to generate the png file.
```bash
postgrest-docs-render
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.
-70
View File
@@ -1,70 +0,0 @@
@startuml
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] <-l~ Listener
HTTPADMIN -[hidden]r- CLI
Listener -[hidden]r- [Schema Cache]
Listener -[hidden]r- [Config]
[Schema Cache] -l[hidden]- [Config]
[Schema Cache] -l[hidden]- [Admin]
[Schema Cache] -l[hidden]- CLI
}
database "PostgreSQL" {
node "API schema" as API {
rectangle "Functions, Views"
}
node Authorization {
rectangle "Roles, GRANT, RLS"
}
rectangle "Tables, extensions" as tbs
API -d- tbs
API -[hidden]l- Authorization
}
:user:
hexagon Proxy
:user: .r-> Proxy
HTTPAPI <.l- Proxy
:operator: .d-> HTTPADMIN
:operator: .d-> CLI
PostgreSQL <.developer : "\t"
Listener -r.> "PostgreSQL"
"Connection Pool" -r.> "PostgreSQL" : "\t\t"
note bottom of Auth
Authenticates the user request
end note
note bottom of ApiRequest
Parses the URL syntax
end note
note bottom of Query
Generates the SQL
end note
note top of Listener
LISTEN session
end note
@enduml
+1 -1
View File
@@ -52,7 +52,7 @@
% Text Node
\draw (414,127) node [anchor=north west][inner sep=0.75pt] [align=left] {tables};
% Text Node
\draw (300,203) node [anchor=north west][inner sep=0.75pt] [color={rgb, 255:red, 0; green, 0; blue, 0 } ,opacity=1 ] [align=center] { \\ views \\ + \\ \ \ functions};
\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}};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

-56
View File
@@ -28,7 +28,6 @@ div.line-block {
#sponsors img{
margin: 10px;
width: 13em; /* ".. image::" does not apply width properly to SVGs */
}
#thanks{
@@ -94,58 +93,3 @@ div.line-block {
#api span.caption-text {
display: none;
}
/* Tweaks for dark mode from extension: sphinx-rtd-dark-theme */
html[data-theme="dark"] .highlight {
background-color: #17181c !important;
}
html[data-theme="dark"] .sphinx-tabs-tab {
color: var(--dark-link-color);
}
html[data-theme="dark"] .sphinx-tabs-panel {
border: 1px solid #404040;
border-top: 0;
background: #141414;
}
html[data-theme="dark"] .sphinx-tabs-tab[aria-selected="true"] {
border: 1px solid #404040;
border-bottom: 1px solid #141414;
background-color: #141414;
}
html[data-theme="dark"] [role="tablist"] {
border-bottom: 1px solid #404040;
}
html[data-theme="dark"] .btn-neutral {
color: white !important;
}
html[data-theme="dark"] .img-dark {
display: inline;
}
html:not([data-theme="dark"]) .img-dark {
display: none;
}
html[data-theme="dark"] .img-light {
display: none;
}
html:not([data-theme="dark"]) .img-light {
display: inline;
}
html[data-theme="dark"] .img-translucent img {
background-color: #cccccc;
}
.img-translucent img {
transition: background-color 0.3s;
margin-bottom: 24px;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 187 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

+1 -13
View File
@@ -32,7 +32,6 @@ extensions = [
"sphinx_tabs.tabs",
"sphinx_copybutton",
"sphinxext.opengraph",
"sphinx_rtd_dark_mode",
]
# Add any paths that contain templates here, relative to this directory.
@@ -50,7 +49,7 @@ source_suffix = ".rst"
master_doc = "index"
# This is overriden by readthedocs with the version tag anyway
version = "devel"
version = "12.0"
# To avoid repetition in <title> we set this to an empty string.
release = ""
@@ -295,20 +294,9 @@ def setup(app):
# taken from https://github.com/sphinx-doc/sphinx/blob/82dad44e5bd3776ecb6fd8ded656bc8151d0e63d/sphinx/util/requests.py#L42
user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0"
# TODO: these pages are returning 403 right now. Verify if they can be fixed by addressing the issue:
# https://github.com/PostgREST/postgrest/issues/3544
linkcheck_ignore = [
r"https://stackoverflow.com/",
r"https://www.patreon.com/postgrest",
r"https://blog.frankel.ch/poor-man-api",
]
# sphinx-tabs configuration
sphinx_tabs_disable_tab_closing = True
# sphinx_rtd_dark_mode configuration
default_dark_mode = False
# sphinxext-opengraph configuration
ogp_image = "_images/logo.png"
+100
View File
@@ -0,0 +1,100 @@
let
# Commit of the Nixpkgs repository that we want to use.
nixpkgsVersion = {
date = "2023-03-25";
rev = "dbf5322e93bcc6cfc52268367a8ad21c09d76fea";
tarballHash = "0lwk4v9dkvd28xpqch0b0jrac4xl9lwm6snrnzx8k5lby72kmkng";
};
# Nix files that describe the Nixpkgs repository. We evaluate the expression
# using `import` below.
pkgs = import
(fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${nixpkgsVersion.rev}.tar.gz";
sha256 = nixpkgsVersion.tarballHash;
})
{ };
python = pkgs.python3.withPackages (ps: [ ps.sphinx ps.sphinx_rtd_theme ps.livereload ps.sphinx-tabs ps.sphinx-copybutton ps.sphinxext-opengraph ]);
in
rec {
inherit pkgs;
build =
pkgs.writeShellScriptBin "postgrest-docs-build"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
# clean previous build, otherwise some errors might be supressed
rm -rf _build
${python}/bin/sphinx-build --color -W -b html -a -n . _build
'';
serve =
pkgs.writeShellScriptBin "postgrest-docs-serve"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
# livereload_docs.py needs to find "sphinx-build"
PATH=${python}/bin:$PATH
${python}/bin/python livereload_docs.py
'';
spellcheck =
pkgs.writeShellScriptBin "postgrest-docs-spellcheck"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
FILES=$(find . -type f -iname '*.rst' | tr '\n' ' ')
cat $FILES \
| grep -v '^\(\.\.\| \)' \
| sed 's/`.*`//g' \
| ${pkgs.aspell}/bin/aspell -d ${pkgs.aspellDicts.en}/lib/aspell/en_US -p ./postgrest.dict list \
| sort -f \
| tee misspellings
test ! -s misspellings
'';
# dictcheck detects obsolete entries in postgrest.dict, that are not used anymore
dictcheck =
pkgs.writeShellScriptBin "postgrest-docs-dictcheck"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
FILES=$(find . -type f -iname '*.rst' | tr '\n' ' ')
cat postgrest.dict \
| tail -n+2 \
| tr '\n' '\0' \
| xargs -0 -n 1 -i \
sh -c "grep \"{}\" $FILES > /dev/null || echo \"{}\"" \
| tee unuseddict
test ! -s unuseddict
'';
linkcheck =
pkgs.writeShellScriptBin "postgrest-docs-linkcheck"
''
set -euo pipefail
cd "$(${pkgs.git}/bin/git rev-parse --show-toplevel)/docs"
${python}/bin/sphinx-build --color -b linkcheck . _build
'';
check =
pkgs.writeShellScriptBin "postgrest-docs-check"
''
set -euo pipefail
${build}/bin/postgrest-docs-build
${dictcheck}/bin/postgrest-docs-dictcheck
${linkcheck}/bin/postgrest-docs-linkcheck
${spellcheck}/bin/postgrest-docs-spellcheck
'';
}
+7 -9
View File
@@ -9,7 +9,7 @@ Community Tutorials
* `PostgREST + Auth0: Create REST API in mintutes, and add social login using Auth0 <https://samkhawase.com/blog/postgrest/>`_ - A step-by-step tutorial to show how to dockerize and integrate Auth0 to PostgREST service.
* `PostgREST + PostGIS API tutorial in 5 minutes <https://gis-ops.com/postgrest-postgis-api-tutorial-geospatial-api-in-5-minutes/>`_ -
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`functions` interface.
In this tutorial, GIS • OPS shows how to perform PostGIS calculations through PostgREST :ref:`s_procs` interface.
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
@@ -20,9 +20,7 @@ Community Tutorials
* `Build data-driven applications with Workers and PostgreSQL <https://developers.cloudflare.com/workers/tutorials/postgres/>`_ - A tutorial on how to integrate with PostgREST and PostgreSQL using Cloudflare Workers.
* `A poor man's API <https://blog.frankel.ch/poor-man-api>`_ - Shows how to integrate PostgREST with Apache APISIX as an alternative to Nginx.
.. * `Accessing a PostgreSQL database in Godot 4 via PostgREST <https://peterkingsbury.com/2022/08/16/godot-postgresql-postgrest/>`_
.. * `A poor man's API <https://blog.frankel.ch/poor-man-api>`_ - Shows how to integrate PostgREST with Apache APISIX as an alternative to Nginx.
.. _templates:
@@ -51,16 +49,16 @@ DevOps
* `cloudgov-demo-postgrest <https://github.com/GSA/cloudgov-demo-postgrest>`_ - demo for a federally-compliant REST API on cloud.gov
* `cloudstark/helm-charts <https://github.com/cloudstark/helm-charts/tree/master/postgrest>`_ - helm chart to deploy PostgREST to a Kubernetes cluster via a Deployment and Service
* `cyril-sabourault/postgrest-cloud-run <https://github.com/cyril-sabourault/postgrest-cloud-run>`_ - expose a PostgreSQL database on Cloud SQL using Cloud Run
* `eyberg/postgrest <https://repo.ops.city/v2/packages/eyberg/postgrest/10.1.1/x86_64/show>`_ - run PostgREST as a Nanos unikernel
* `jbkarle/postgrest <https://github.com/jbkarle/postgrest>`_ - helm chart with a demo database for development and test purposes
* `Limezest/postgrest-cloud-run <https://github.com/Limezest/postgrest-cloud-run>`_ - expose a PostgreSQL database on Cloud SQL using Cloud Run
* `eyberg/postgrest <https://repo.ops.city/v2/packages/eyberg/postgrest/10.1.1/x86_64/show>`_ - run PostgREST as a Nanos unikernel
.. _eco_external_notification:
External Notification
---------------------
These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows functions to initiate actions outside the database such as sending emails.
These are PostgreSQL bridges that propagate LISTEN/NOTIFY to external queues for further processing. This allows stored procedures to initiate actions outside the database such as sending emails.
* `pg-notify-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
@@ -84,8 +82,8 @@ Client-Side Libraries
---------------------
* `postgrest-csharp <https://github.com/supabase-community/postgrest-csharp>`_ - C#
* `postgrest-dart <https://github.com/supabase/postgrest-dart>`_ - Dart
* `postgrest-ex <https://github.com/supabase-community/postgrest-ex>`_ - Elixir
* `postgrest-dart <https://github.com/supabase-community/postgrest-dart>`_ - Dart
* `postgrest-ex <https://github.com/J0/postgrest-ex>`_ - Elixir
* `postgrest-go <https://github.com/supabase-community/postgrest-go>`_ - Go
* `postgrest-js <https://github.com/supabase/postgrest-js>`_ - TypeScript/JavaScript
* `postgrest-kt <https://github.com/supabase-community/postgrest-kt>`_ - Kotlin
-81
View File
@@ -1,81 +0,0 @@
Architecture
############
This page describes the architecture of PostgREST.
Bird's Eye View
===============
.. image:: ../_static/arch.png
Code Map
========
This section talks briefly about various important modules.
Main
----
The starting point of the program is `Main.hs <https://github.com/PostgREST/postgrest/blob/main/main/Main.hs>`_.
CLI
---
Main then calls `CLI.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/CLI.hs>`_, which is in charge of :ref:`cli`.
App
---
`App.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/App.hs>`_ is then in charge of composing the different modules.
Auth
----
`Auth.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Auth.hs>`_ is in charge of :ref:`authn`.
Api Request
-----------
`ApiRequest.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/ApiRequest.hs>`_ is in charge of parsing the URL query string (following PostgREST syntax), the request headers, and the request body.
A request might be rejected at this level if it's invalid. For example when providing an unknown media type to PostgREST or using an unknown HTTP method.
Plan
----
Using the Schema Cache, `Plan.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Plan.hs>`_ fills in out-of-band SQL details (like an ``ON CONFLICT (pk)`` clause) required to complete the user request.
A request might be rejected at this level if it's invalid. For example when doing resource embedding on a nonexistent resource.
Query
-----
`Query.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Query.hs>`_ generates the SQL queries (parametrized and prepared) required to satisfy the user request.
Only at this stage a connection from the pool might be used.
Schema Cache
------------
`SchemaCache.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/SchemaCache.hs>`_ is in charge of :ref:`schema_cache`.
Config
------
`Config.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Config.hs>`_ is in charge of :ref:`configuration`.
Admin
-----
`Admin.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Admin.hs>`_ is in charge of the :ref:`admin_server`.
HTTP
----
The HTTP server is provided by `Warp <https://aosabook.org/en/posa/warp.html>`_.
Listener
--------
`Listener.hs <https://github.com/PostgREST/postgrest/blob/main/src/PostgREST/Listener.hs>`_ is in charge of maintaining a `LISTEN session <https://www.postgresql.org/docs/current/sql-listen.html>`_
that keeps the :ref:`schema_cache` and the :ref:`in_db_config` up to date.
+3 -3
View File
@@ -156,7 +156,7 @@ You can also grant execute on all functions in a schema to a higher privileged r
Security definer
----------------
A function is executed with the privileges of the user who calls it. This means that the user has to have all permissions to do the operations the function performs.
A function is executed with the privileges of the user who calls it. This means that the user has to have all permissions to do the operations the procedure performs.
If the function accesses private database objects, your :ref:`API roles <roles>` won't be able to successfully execute the function.
Another option is to define the function with the :code:`SECURITY DEFINER` option. Then only one permission check will take place, the permission to call the function, and the operations in the function will have the authority of the user who owns the function itself.
@@ -166,7 +166,7 @@ Another option is to define the function with the :code:`SECURITY DEFINER` optio
-- login as a user wich has privileges on the private schemas
-- create a sample function
create or replace function login(email text, pass text, out token text) as $$
create or replace function login(email text, pass text) returns jwt_token as $$
begin
-- access to a private schema called 'auth'
select auth.user_role(email, pass) into _role;
@@ -180,7 +180,7 @@ Note the ``SECURITY DEFINER`` keywords at the end of the function. See `PostgreS
Views
=====
Views are invoked with the privileges of the view owner, much like functions with the ``SECURITY DEFINER`` option. When created by a SUPERUSER role, all `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ policies will be bypassed.
Views are invoked with the privileges of the view owner, much like stored procedures with the ``SECURITY DEFINER`` option. When created by a SUPERUSER role, all `row-level security <https://www.postgresql.org/docs/current/ddl-rowsecurity.html>`_ policies will be bypassed.
If you're on PostgreSQL >= 15, this behavior can be changed by specifying the ``security_invoker`` option.
+12 -15
View File
@@ -21,12 +21,6 @@ Supported PostgreSQL versions
PostgREST works with all PostgreSQL versions starting from 9.6.
.. note::
Support for PostgreSQL versions 9.6, 10 and 11 is deprecated. From this on version onwards, PostgREST will only support non-end-of-life PostgreSQL versions.
See https://www.postgresql.org/support/versioning/.
Running PostgREST
=================
@@ -169,15 +163,14 @@ If you want to have a visual overview of your API in your browser you can add sw
.. code-block:: yaml
# in services:
swagger:
image: swaggerapi/swagger-ui
ports:
- "8080:8080"
expose:
- "8080"
environment:
API_URL: http://localhost:3000/
swagger:
image: swaggerapi/swagger-ui
ports:
- "8080:8080"
expose:
- "8080"
environment:
API_URL: http://localhost:3000/
With this you can see the swagger-ui in your browser on port 8080.
@@ -188,6 +181,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/README/#how-to-install-stack>`_ for your platform
+12 -5
View File
@@ -42,7 +42,7 @@ The first step is to create an Nginx configuration file that proxies requests to
HTTPS
-----
PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how <https://nginx.org/en/docs/http/configuring_https_servers.html>`_.
PostgREST aims to do one thing well: add an HTTP interface to a PostgreSQL database. To keep the code small and focused we do not implement HTTPS. Use a reverse proxy such as NGINX to add this, `here's how <https://nginx.org/en/docs/http/configuring_https_servers.html>`_. Note that some Platforms as a Service like Heroku also add SSL automatically in their load balancer.
Rate Limiting
-------------
@@ -55,7 +55,7 @@ Nginx supports "leaky bucket" rate limiting (see `official docs <https://nginx.o
This creates a shared memory zone called "login" to store a log of IP addresses that access the rate limited urls. The space reserved, 10 MB (:code:`10m`) will give us enough space to store a history of 160k requests. We have chosen to allow only allow one request per second (:code:`1r/s`).
Next we apply the zone to certain routes, like a hypothetical function called :code:`login`.
Next we apply the zone to certain routes, like a hypothetical stored procedure called :code:`login`.
.. code-block:: nginx
@@ -73,10 +73,17 @@ Alternate URL Structure
As discussed in :ref:`singular_plural`, there are no special URL forms for singular resources in PostgREST, only operators for filtering. Thus there are no URLs like :code:`/people/1`. It would be specified instead as
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?id=eq.1" \
-H "Accept: application/vnd.pgrst.object+json"
.. code-tab:: http
GET /people?id=eq.1 HTTP/1.1
Accept: application/vnd.pgrst.object+json
.. code-tab:: bash Curl
curl "http://localhost:3000/people?id=eq.1" \
-H "Accept: application/vnd.pgrst.object+json"
This allows compound primary keys and makes the intent for singular response independent of a URL convention.
+3 -5
View File
@@ -7,11 +7,9 @@
Schema Isolation
================
A PostgREST instance exposes all the tables, views, and functions of a single `PostgreSQL schema <https://www.postgresql.org/docs/current/ddl-schemas.html>`_ (a namespace of database objects). This means private data or implementation details can go inside different private schemas and be invisible to HTTP clients.
A PostgREST instance exposes all the tables, views, and stored procedures of a single `PostgreSQL schema <https://www.postgresql.org/docs/current/ddl-schemas.html>`_ (a namespace of database objects). This means private data or implementation details can go inside different private schemas and be invisible to HTTP clients.
It is recommended that you don't expose tables on your API schema. Instead expose views and functions which insulate the internal details from the outside world.
It is recommended that you don't expose tables on your API schema. Instead expose views and stored procedures which insulate the internal details from the outside world.
This allows you to change the internals of your schema and maintain backwards compatibility. It also keeps your code easier to refactor, and provides a natural way to do API versioning.
.. container:: img-translucent
.. image:: ../_static/db.png
.. image:: ../_static/db.png
+1 -1
View File
@@ -191,7 +191,7 @@ References
For more information concerning PostgREST, cf.
- :ref:`function_single_unnamed`
- :ref:`s_proc_single_unnamed`
- :ref:`custom_media`. See :ref:`any_handler`, if you need to support an ``application/soap+xml`` media type or if you want to respond with XML without sending a media type.
- :ref:`Nginx reverse proxy <nginx>`
+107 -112
View File
@@ -38,8 +38,7 @@ and return a raw HTML document file.
Creating an HTML Response
-------------------------
Let's create a function that returns a basic HTML file, using `Pico CSS <https://picocss.com>`_ for styling and
`Ionicons <https://ionic.io/ionicons>`_ to show some icons later.
Let's create a function that returns a basic HTML file, using `Tailwind CSS <https://v2.tailwindcss.com/>`_ for styling.
.. code-block:: postgres
@@ -51,20 +50,15 @@ Let's create a function that returns a basic HTML file, using `Pico CSS <https:/
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title>
<!-- Pico CSS for CSS styling -->
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet" />
<!-- Tailwind for CSS styling -->
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
<main class="container">
<article>
<h5 style="text-align: center;">
PostgREST + HTMX To-Do List
</h5>
</article>
</main>
<!-- Script for Ionicons icons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<body class="bg-gray-900">
<div class="flex justify-center">
<div class="max-w-lg mt-5 p-6 bg-gray-800 border border-gray-800 rounded-lg shadow-xl">
<h5 class="mb-3 text-2xl font-bold tracking-tight text-white">PostgREST + HTMX To-Do List</h5>
</div>
</div>
</body>
</html>
$html$;
@@ -90,22 +84,24 @@ For that, we'll also need a function to help us sanitize the HTML content that m
create or replace function api.html_todo(api.todos) returns text as $$
select format($html$
<div>
<%2$s>
<li class="py-3">
<span class="ml-2 %2$s">
%3$s
</%2$s>
</div>
</span>
</li>
$html$,
$1.id,
case when $1.done then 's' else 'span' end,
case when $1.done then 'line-through text-gray-400' else '' end,
api.sanitize_html($1.task)
);
$$ language sql stable;
create or replace function api.html_all_todos() returns text as $$
select coalesce(
string_agg(api.html_todo(t), '<hr/>' order by t.id),
'<p><em>There is nothing else to do.</em></p>'
'<ul id="todo-list" role="list" class="divide-y divide-gray-700 text-gray-100">'
|| string_agg(api.html_todo(t), '' order by t.id) ||
'</ul>',
'<p class="text-gray-100">There is nothing else to do.</p>'
)
from api.todos t;
$$ language sql;
@@ -130,44 +126,39 @@ Next, let's add an endpoint to register a to-do in the database and modify the `
$$ language sql;
create or replace function api.index() returns "text/html" as $$
select $html$
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title>
<!-- Pico CSS for CSS styling -->
<link href="https://cdn.jsdelivr.net/npm/@picocss/pico@next/css/pico.min.css" rel="stylesheet"/>
<!-- htmx for AJAX requests -->
<script src="https://unpkg.com/htmx.org"></script>
</head>
<body>
<main class="container"
style="max-width: 600px"
hx-headers='{"Accept": "text/html"}'>
<article>
<h5 style="text-align: center;">
PostgREST + HTMX To-Do List
</h5>
<form hx-post="/rpc/add_todo"
hx-target="#todo-list-area"
hx-trigger="submit"
hx-on="htmx:afterRequest: this.reset()">
<input type="text" name="_task" placeholder="Add a todo...">
</form>
<div id="todo-list-area">
$html$
|| api.html_all_todos() ||
$html$
<div>
</article>
</main>
<!-- Script for Ionicons icons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
</body>
</html>
select $html$
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostgREST + HTMX To-Do List</title>
<!-- Tailwind for CSS styling -->
<link href="https://unpkg.com/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<!-- htmx for AJAX requests -->
<script src="https://unpkg.com/htmx.org"></script>
</head>
<body class="bg-gray-900"
hx-headers='{"Accept": "text/html"}'>
<div class="flex justify-center">
<div class="max-w-lg mt-5 p-6 bg-gray-800 border border-gray-800 rounded-lg shadow-xl">
<h5 class="mb-3 text-2xl font-bold tracking-tight text-white">PostgREST + HTMX To-Do List</h5>
<form hx-post="/rpc/add_todo"
hx-target="#todo-list-area"
hx-trigger="submit"
hx-on="htmx:afterRequest: this.reset()">
<input class="bg-gray-50 border text-sm rounded-lg block w-full p-2.5 mb-3 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
type="text" name="_task" placeholder="Add a todo...">
</form>
<div id="todo-list-area">
$html$
|| api.html_all_todos() ||
$html$
<div>
</div>
</div>
</body>
</html>
$html$;
$$ language sql;
@@ -199,46 +190,49 @@ Now, let's modify ``api.html_todo`` and make it more functional.
.. code-block:: postgres
create or replace function api.html_todo(api.todos) returns text as $$
select format($html$
<div class="grid">
<div id="todo-edit-area-%1$s">
<form id="edit-task-state-%1$s"
hx-post="/rpc/change_todo_state"
hx-vals='{"_id": %1$s, "_done": %4$s}'
select format($html$
<li class="py-3">
<div class="flex justify-between items-center">
<div id="todo-edit-area-%1$s" class="pr-5">
<form id="edit-task-state-%1$s"
hx-post="/rpc/change_todo_state"
hx-vals='{"_id": %1$s, "_done": %4$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<span class="ml-2 %2$s cursor-pointer">
%3$s
</span>
</form>
</div>
<div>
<button class="p-1.5 rounded-full hover:bg-gray-700 focus:ring-gray-800"
hx-get="/rpc/html_editable_task"
hx-vals='{"_id": "%1$s"}'
hx-target="#todo-edit-area-%1$s"
hx-trigger="click">
<svg class="w-4 h-4 text-blue-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
<path d="M12.687 14.408a3.01 3.01 0 0 1-1.533.821l-3.566.713a3 3 0 0 1-3.53-3.53l.713-3.566a3.01 3.01 0 0 1 .821-1.533L10.905 2H2.167A2.169 2.169 0 0 0 0 4.167v11.666A2.169 2.169 0 0 0 2.167 18h11.666A2.169 2.169 0 0 0 16 15.833V11.1l-3.313 3.308Zm5.53-9.065.546-.546a2.518 2.518 0 0 0 0-3.56 2.576 2.576 0 0 0-3.559 0l-.547.547 3.56 3.56Z"/>
<path d="M13.243 3.2 7.359 9.081a.5.5 0 0 0-.136.256L6.51 12.9a.5.5 0 0 0 .59.59l3.566-.713a.5.5 0 0 0 .255-.136L16.8 6.757 13.243 3.2Z"/>
</svg>
</button>
<button class="p-1.5 rounded-full hover:bg-gray-700 focus:ring-gray-800"
hx-post="/rpc/delete_todo"
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<%2$s style="cursor: pointer">
%3$s
</%2$s>
</form>
</div>
<div style="text-align: right">
<button class="outline"
hx-get="/rpc/html_editable_task"
hx-vals='{"_id": "%1$s"}'
hx-target="#todo-edit-area-%1$s"
hx-trigger="click">
<span>
<ion-icon name="create"></ion-icon>
</span>
</button>
<button class="outline contrast"
hx-post="/rpc/delete_todo"
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="click">
<span>
<ion-icon name="trash" style="color: #f87171"></ion-icon>
</span>
</button>
</div>
<svg class="w-4 h-4 text-red-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h16M7 8v8m4-8v8M7 1h4a1 1 0 0 1 1 1v3H6V2a1 1 0 0 1 1-1ZM3 5h12v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5Z"/>
</svg>
</button>
</div>
$html$,
$1.id,
case when $1.done then 's' else 'span' end,
api.sanitize_html($1.task),
(not $1.done)::text
);
</div>
</li>
$html$,
$1.id,
case when $1.done then 'line-through text-gray-400' else '' end,
api.sanitize_html($1.task),
(not $1.done)::text
);
$$ language sql stable;
Let's deconstruct the new htmx features added:
@@ -273,21 +267,22 @@ That's why we create the ``api.html_editable_task`` function as an endpoint:
.. code-block:: postgres
create or replace function api.html_editable_task(_id int) returns "text/html" as $$
select format ($html$
<form id="edit-task-%1$s"
hx-post="/rpc/change_todo_task"
hx-headers='{"Accept": "text/html"}'
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="submit,focusout">
<input id="task-%1$s" type="text" name="_task" value="%2$s" autofocus>
</form>
$html$,
id,
api.sanitize_html(task)
)
from api.todos
where id = _id;
select format ($html$
<form id="edit-task-%1$s"
hx-post="/rpc/change_todo_task"
hx-headers='{"Accept": "text/html"}'
hx-vals='{"_id": %1$s}'
hx-target="#todo-list-area"
hx-trigger="submit,focusout">
<input class="bg-gray-50 border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 text-white focus:ring-blue-500 focus:border-blue-500"
id="task-%1$s" type="text" name="_task" value="%2$s" autofocus>
</form>
$html$,
id,
api.sanitize_html(task)
)
from api.todos
where id = _id;
$$ language sql;
In this example, this will return an input field that allows us to edit the corresponding to-do task.
+2 -4
View File
@@ -81,14 +81,12 @@ First, in addition to the minimal example, we need to store the media types and
.. code-block:: postgres
alter table files
add column type text generated always as (byteamagic_mime(substr(blob, 0, 4100))) stored,
add column type text,
add column name text;
This uses the :code:`byteamagic_mime()` function from the `pg_byteamagic extension <https://github.com/nmandery/pg_byteamagic>`_ to automatically generate the type in the :code:`files` table. To guess the type of a file, it's generally enough to look at the beginning of the file, which is more efficient.
Next, we set modify the function to set the content type and filename.
We use this opportunity to configure some basic, client-side caching.
For production, you probably want to configure additional caches, e.g. on the :ref:`reverse proxy <nginx>`.
For production, you probably want to configure additional caches, e.g. on the :ref:`reverse proxy <admin>`.
.. code-block:: postgres
@@ -31,7 +31,7 @@ As in :ref:`sql_user_management`, we create a :code:`basic_auth` schema:
-- We put things inside the basic_auth schema to hide
-- them from public view. Certain public procs/views will
-- refer to helpers and tables inside.
CREATE SCHEMA basic_auth;
CREATE SCHEMA IF NOT EXISTS basic_auth;
As in :ref:`sql_user_management`, we create the :code:`pgcrypto` and :code:`pgjwt` extensions. Here we prefer to put the extensions in its own schemas:
@@ -40,7 +40,7 @@ As in :ref:`sql_user_management`, we create the :code:`pgcrypto` and :code:`pgjw
CREATE SCHEMA ext_pgcrypto;
ALTER SCHEMA ext_pgcrypto OWNER TO postgres;
CREATE EXTENSION pgcrypto WITH SCHEMA ext_pgcrypto;
CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA ext_pgcrypto;
Concerning the `pgjwt extension <https://github.com/michelp/pgjwt>`_, please cf. to :ref:`client_auth`.
@@ -49,12 +49,12 @@ Concerning the `pgjwt extension <https://github.com/michelp/pgjwt>`_, please cf.
CREATE SCHEMA ext_pgjwt;
ALTER SCHEMA ext_pgjwt OWNER TO postgres;
CREATE EXTENSION pgjwt WITH SCHEMA ext_pgjwt;
CREATE EXTENSION IF NOT EXISTS pgjwt WITH SCHEMA ext_pgjwt;
In order to be able to work with postgres' SCRAM-SHA-256 password hashes, we also need the PBKDF2 key derivation function. Luckily there is `a PL/pgSQL implementation on stackoverflow <https://stackoverflow.com/a/72805848>`_:
In order to be able to work with postgres' SCRAM-SHA-256 password hashes, we also need the PBKDF2 key derivation function. Luckily there is `a PL/pgSQL implementation on stackoverflow <https://stackoverflow.com/q/47162200/2337147>`_:
.. code-block:: postgres
.. code-block:: plpgsql
CREATE FUNCTION basic_auth.pbkdf2(salt bytea, pw text, count integer, desired_length integer, algorithm text) RETURNS bytea
LANGUAGE plpgsql IMMUTABLE
@@ -117,10 +117,10 @@ In order to be able to work with postgres' SCRAM-SHA-256 password hashes, we als
ALTER FUNCTION basic_auth.pbkdf2(salt bytea, pw text, count integer, desired_length integer, algorithm text) OWNER TO postgres;
Analogous to how :ref:`sql_user_management` creates the function :code:`basic_auth.user_role`, we create a helper function to check the user's password, here with another name and signature (since we want the username, not an email address).
Analogous to :ref:`sql_user_management` creates the function :code:`basic_auth.user_role`, we create a helper function to check the user's password, here with another name and signature (since we want the username, not an email address).
But contrary to :ref:`sql_user_management`, this function does not use a dedicated :code:`users` table with passwords, but instead utilizes the built-in table `pg_catalog.pg_authid <https://www.postgresql.org/docs/current/catalog-pg-authid.html>`_:
.. code-block:: postgres
.. code-block:: plpgsql
CREATE FUNCTION basic_auth.check_user_pass(username text, password text) RETURNS name
LANGUAGE sql
@@ -160,17 +160,22 @@ Logins
As described in :ref:`client_auth`, we'll create a JWT token inside our login function. Note that you'll need to adjust the secret key which is hard-coded in this example to a secure (at least thirty-two character) secret of your choosing.
.. code-block:: postgres
.. code-block:: plpgsql
CREATE TYPE basic_auth.jwt_token AS (
token text
);
-- if you are not using psql, you need to replace :DBNAME with the current database's name.
ALTER DATABASE :DBNAME SET "app.jwt_secret" to 'reallyreallyreallyreallyverysafe';
CREATE FUNCTION public.login(username text, password text, OUT token text)
CREATE FUNCTION public.login(username text, password text) RETURNS basic_auth.jwt_token
LANGUAGE plpgsql security definer
AS $$
DECLARE
_role name;
result basic_auth.jwt_token;
BEGIN
-- check email and password
SELECT basic_auth.check_user_pass(username, password) INTO _role;
@@ -185,7 +190,8 @@ As described in :ref:`client_auth`, we'll create a JWT token inside our login fu
SELECT login.username as role,
extract(epoch FROM now())::integer + 60*60 AS exp
) r
INTO token;
INTO result;
RETURN result;
END;
$$;
@@ -253,11 +259,19 @@ Test at the REST level
~~~~~~~~~~~~~~~~~~~~~~
An API request to call this function would look like:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/login" \
-X POST -H "Content-Type: application/json" \
-d '{ "username": "foo", "password": "bar" }'
.. code-tab:: http
POST /rpc/login HTTP/1.1
{ "username": "foo", "password": "bar" }
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/login" \
-X POST -H "Content-Type: application/json" \
-d '{ "username": "foo", "password": "bar" }'
The response would look like the snippet below. Try decoding the token at `jwt.io <https://jwt.io/>`_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!)
@@ -282,18 +296,31 @@ Let's add a table, intended for the :code:`foo` user:
Now try to get the table's contents with:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/foobar"
.. code-tab:: http
GET /foobar HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/foobar"
This should fail --- of course, we haven't specified the user, thus PostgREST falls back to the :code:`anon` user and denies access.
Add an :code:`Authorization` header. Please use the token value from the login function call above instead of the one provided below.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/foobar" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY"
.. code-tab:: http
GET /foobar HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY
.. code-tab:: bash Curl
curl "http://localhost:3000/foobar" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZm9vIiwiZXhwIjoxNjY4MTkyMjAyfQ.zzdHCBjfkqDQLQ8D7CHO3cIALF6KBCsfPTWgwhCiHCY"
This will fail again --- we get :code:`Permission denied to set role`. We forgot to allow the authenticator role to switch into this user by executing:
+40 -18
View File
@@ -17,8 +17,9 @@ First we'll need a table to keep track of our users:
-- We put things inside the basic_auth schema to hide
-- them from public view. Certain public procs/views will
-- refer to helpers and tables inside.
create schema if not exists basic_auth;
create table
create table if not exists
basic_auth.users (
email text primary key check ( email ~* '^.+@.+\..+$' ),
pass text not null check (length(pass) < 512),
@@ -27,9 +28,9 @@ First we'll need a table to keep track of our users:
We would like the role to be a foreign key to actual database roles, however PostgreSQL does not support these constraints against the :code:`pg_roles` table. We'll use a trigger to manually enforce it.
.. code-block:: postgres
.. code-block:: plpgsql
create function
create or replace function
basic_auth.check_role_exists() returns trigger as $$
begin
if not exists (select 1 from pg_roles as r where r.rolname = new.role) then
@@ -41,6 +42,7 @@ We would like the role to be a foreign key to actual database roles, however Pos
end
$$ language plpgsql;
drop trigger if exists ensure_user_role_exists on basic_auth.users;
create constraint trigger ensure_user_role_exists
after insert or update on basic_auth.users
for each row
@@ -48,11 +50,11 @@ We would like the role to be a foreign key to actual database roles, however Pos
Next we'll use the pgcrypto extension and a trigger to keep passwords safe in the :code:`users` table.
.. code-block:: postgres
.. code-block:: plpgsql
create extension pgcrypto;
create extension if not exists pgcrypto;
create function
create or replace function
basic_auth.encrypt_pass() returns trigger as $$
begin
if tg_op = 'INSERT' or new.pass <> old.pass then
@@ -62,6 +64,7 @@ Next we'll use the pgcrypto extension and a trigger to keep passwords safe in th
end
$$ language plpgsql;
drop trigger if exists encrypt_pass on basic_auth.users;
create trigger encrypt_pass
before insert or update on basic_auth.users
for each row
@@ -69,9 +72,9 @@ Next we'll use the pgcrypto extension and a trigger to keep passwords safe in th
With the table in place we can make a helper to check a password against the encrypted column. It returns the database role for a user if the email and password are correct.
.. code-block:: postgres
.. code-block:: plpgsql
create function
create or replace function
basic_auth.user_role(email text, pass text) returns name
language plpgsql
as $$
@@ -115,11 +118,15 @@ JWT from SQL
You can create JWT tokens in SQL using the `pgjwt extension <https://github.com/michelp/pgjwt>`_. It's simple and requires only pgcrypto. If you're on an environment like Amazon RDS which doesn't support installing new extensions, you can still manually run the `SQL inside pgjwt <https://github.com/michelp/pgjwt/blob/master/pgjwt--0.1.1.sql>`_ (you'll need to replace ``@extschema@`` with another schema or just delete it) which creates the functions you will need.
Next write a function that returns the token. The one below returns a token with a hard-coded role, which expires five minutes after it was issued. Note this function has a hard-coded secret as well.
Next write a stored procedure that returns the token. The one below returns a token with a hard-coded role, which expires five minutes after it was issued. Note this function has a hard-coded secret as well.
.. code-block:: postgres
CREATE FUNCTION jwt_test(OUT token text) AS $$
CREATE TYPE jwt_token AS (
token text
);
CREATE FUNCTION jwt_test() RETURNS public.jwt_token AS $$
SELECT public.sign(
row_to_json(r), 'reallyreallyreallyreallyverysafe'
) AS token
@@ -134,7 +141,7 @@ PostgREST exposes this function to clients via a POST request to ``/rpc/jwt_test
.. note::
To avoid hard-coding the secret in functions, save it as a property of the database.
To avoid hard-coding the secret in stored procedures, save it as a property of the database.
.. code-block:: postgres
@@ -154,11 +161,17 @@ As described in `JWT from SQL`_, we'll create a JWT inside our login function. N
.. code-block:: postgres
-- add type
CREATE TYPE basic_auth.jwt_token AS (
token text
);
-- login should be on your exposed schema
create function
login(email text, pass text, out token text) as $$
create or replace function
login(email text, pass text) returns basic_auth.jwt_token as $$
declare
_role name;
result basic_auth.jwt_token;
begin
-- check email and password
select basic_auth.user_role(email, pass) into _role;
@@ -173,7 +186,8 @@ As described in `JWT from SQL`_, we'll create a JWT inside our login function. N
select _role as role, login.email as email,
extract(epoch from now())::integer + 60*60 as exp
) r
into token;
into result;
return result;
end;
$$ language plpgsql security definer;
@@ -185,11 +199,19 @@ the anonymous user :code:`anon` doesn't need permission to read the :code:`basic
An API request to call this function would look like:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/login" \
-X POST -H "Content-Type: application/json" \
-d '{ "email": "foo@bar.com", "pass": "foobar" }'
.. code-tab:: http
POST /rpc/login HTTP/1.1
{ "email": "foo@bar.com", "pass": "foobar" }
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/login" \
-X POST -H "Content-Type: application/json" \
-d '{ "email": "foo@bar.com", "pass": "foobar" }'
The response would look like the snippet below. Try decoding the token at `jwt.io <https://jwt.io/>`_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!)
@@ -5,13 +5,133 @@ Working with PostgreSQL data types
:author: `Laurence Isla <https://github.com/laurenceisla>`_
PostgREST makes use of PostgreSQL string representations to work with data types. Thanks to this, you can use special values, such as ``now`` for timestamps, ``yes`` for booleans or time values including the time zones. This page describes how you can take advantage of these string representations and some alternatives to perform operations on different PostgreSQL data types.
PostgREST makes use of PostgreSQL string representations to work with data types. Thanks to this, you can use special values, such as ``now`` for timestamps, ``yes`` for booleans or time values including the time zones. This page describes how you can take advantage of these string representations to perform operations on different PostgreSQL data types.
.. contents::
:local:
:depth: 1
.. NOTE: Titles are ordered alphabetically. New entries should respect this order.
Timestamps
----------
You can use the **time zone** to filter or send data if needed.
.. code-block:: postgres
create table reports (
id int primary key
, due_date timestamptz
);
Suppose you are located in Sydney and want create a report with the date in the local time zone. Your request should look like this:
.. tabs::
.. code-tab:: http
POST /reports HTTP/1.1
Content-Type: application/json
[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },
{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]
.. code-tab:: bash Curl
curl "http://localhost:3000/reports" \
-X POST -H "Content-Type: application/json" \
-d '[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]'
Someone located in Cairo can retrieve the data using their local time, too:
.. tabs::
.. code-tab:: http
GET /reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo"
.. code-block:: json
[
{
"id": 1,
"due_date": "2022-02-23T19:10:15-05:00"
}
]
The response has the date in the time zone configured by the server: ``UTC -05:00`` (see :ref:`prefer_timezone`).
You can use other comparative filters and also all the `PostgreSQL special date/time input values <https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE>`_ as illustrated in this example:
.. tabs::
.. code-tab:: http
GET /reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch)) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch))"
.. code-block:: json
[
{
"id": 2,
"due_date": "2022-02-27T06:00:00-05:00"
}
]
JSON
----
To work with a ``json`` type column, you can handle the value as a JSON object.
.. code-block:: postgres
create table products (
id int primary key,
name text unique,
extra_info json
);
You can insert a new product using a JSON object for the ``extra_info`` column:
.. tabs::
.. code-tab:: http
POST /products HTTP/1.1
Content-Type: application/json
{
"id": 1,
"name": "Canned fish",
"extra_info": {
"expiry_date": "2025-12-31",
"exportable": true
}
}
.. code-tab:: bash Curl
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"name": "Canned fish",
"extra_info": {
"expiry_date": "2025-12-31",
"exportable": true
}
}
EOF
To query and filter the data see :ref:`json_columns` for a complete reference.
Arrays
------
@@ -29,33 +149,61 @@ To handle `array types <https://www.postgresql.org/docs/current/arrays.html>`_ y
You can insert a new value using string representation.
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /movies HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/movies" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"title": "Paddington",
"tags": "{family,comedy,not streamable}",
"performance_times": "{12:40,15:00,20:00}"
}
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/movies" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"title": "Paddington",
"tags": "{family,comedy,not streamable}",
"performance_times": "{12:40,15:00,20:00}"
}
EOF
Or you could send the same data using JSON array format:
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /movies HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/movies" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"title": "Paddington",
"tags": ["family", "comedy", "not streamable"],
"performance_times": ["12:40", "15:00", "20:00"]
}
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/movies" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"title": "Paddington",
"tags": ["family", "comedy", "not streamable"],
"performance_times": ["12:40", "15:00", "20:00"]
}
EOF
To query the data you can use arrow operators. See :ref:`composite_array_columns`.
@@ -72,21 +220,38 @@ Similarly to one-dimensional arrays, both the string representation and JSON arr
You can now update the item using JSON array format:
.. code-block:: bash
.. tabs::
.. code-tab:: http
PATCH /movies?id=eq.1 HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/movies?id=eq.1" \
-X PATCH -H "Content-Type: application/json" \
-d @- << EOF
{
"cinema_floor_auditorium": [ [ [1,2], [6,7] ], [ [3,5], [8,9] ] ]
}
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/movies?id=eq.1" \
-X PATCH -H "Content-Type: application/json" \
-d @- << EOF
{
"cinema_floor_auditorium": [ [ [1,2], [6,7] ], [ [3,5], [8,9] ] ]
}
EOF
Then, for example, to query the auditoriums that are located in the first cinema (position 0 in the array) and on the second floor (position 1 in the next inner array), we can use the arrow operators this way:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1"
.. code-tab:: http
GET /movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/movies?select=title,auditorium:cinema_floor_auditorium->0->1&id=eq.1"
.. code-block:: json
@@ -97,57 +262,6 @@ Then, for example, to query the auditoriums that are located in the first cinema
}
]
Bytea
-----
To send raw binary to PostgREST you need a function with a single unnamed parameter of `bytea type <https://www.postgresql.org/docs/current/datatype-binary.html>`_.
.. code-block:: postgres
create table files (
id int primary key generated always as identity,
file bytea
);
create function upload_binary(bytea) returns void as $$
insert into files (file) values ($1);
$$ language sql;
Let's download the PostgREST logo for our test.
.. code-block:: bash
curl "https://postgrest.org/en/latest/_images/logo.png" -o postgrest-logo.png
Now, to send the file ``postgrest-logo.png`` we need to set the ``Content-Type: application/octet-stream`` header in the request:
.. code-block:: bash
curl "http://localhost:3000/rpc/upload_binary" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary "@postgrest-logo.png"
To get the image from the database, use :ref:`custom_media` like so:
.. code-block:: postgres
create domain "image/png" as bytea;
create or replace get_image(id int) returns "image/png" as $$
select file from files where id = $1;
$$ language sql;
.. code-block:: bash
curl "http://localhost:3000/get_image?id=1" \
-H "Accept: image/png"
See :ref:`providing_img` for a step-by-step example on how to handle images in HTML.
.. warning::
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
Composite Types
---------------
@@ -172,79 +286,253 @@ With PostgREST, you have two options to handle `composite type columns <https://
On one hand you can insert values using string representation.
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /products HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{ "id": 2, "size": "(0.7,0.5,1.8,\"m\")" }
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{ "id": 2, "size": "(0.7,0.5,1.8,\"m\")" }
EOF
Or you could insert the same data in JSON format.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 2,
"size": {
"length": 0.7,
"width": 0.5,
"height": 1.8,
"unit": "m"
.. code-tab:: http
POST /products HTTP/1.1
Content-Type: application/json
{
"id": 2,
"size": {
"length": 0.7,
"width": 0.5,
"height": 1.8,
"unit": "m"
}
}
}
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 2,
"size": {
"length": 0.7,
"width": 0.5,
"height": 1.8,
"unit": "m"
}
}
EOF
You can also query the data using arrow operators. See :ref:`composite_array_columns`.
Enums
-----
Ranges
------
You can handle `Enumerated Types <https://www.postgresql.org/docs/current/datatype-enum.html>`_ using string representations:
PostgREST allows you to handle `ranges <https://www.postgresql.org/docs/current/rangetypes.html>`_.
.. code-block:: postgres
create type letter_size as enum ('s','m','l','xl');
create table events (
id int primary key,
name text unique,
duration tsrange
);
create table products (
id int primary key generated always as identity,
name text,
size letter_size
);
To insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type:
To insert or update the value use a string:
.. tabs::
.. code-block:: bash
.. code-tab:: http
curl -X POST "http://localhost:3000/products" \
-H "Content-Type: application/json" \
-d @- << EOF
{ "name": "t-shirt", "size": "l" }
EOF
POST /events HTTP/1.1
Content-Type: application/json
You can then query and filter the enum using the compatible :ref:`operators <operators>`.
For example, to get all the products larger than `m` and ordering them by their size:
{
"id": 1,
"name": "New Year's Party",
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
}
.. code-block:: bash
.. code-tab:: bash Curl
curl "http://localhost:3000/products?select=name,size&size=gt.m&order=size"
curl "http://localhost:3000/events" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"name": "New Year's Party",
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
}
EOF
You can use range :ref:`operators <operators>` to filter the data. But, in this case, requesting a filter like ``events?duration=cs.2023-01-01`` will return an error, because PostgreSQL needs an explicit cast from string to timestamp. A workaround is to use a range starting and ending in the same date:
.. tabs::
.. code-tab:: http
GET /events?duration=cs.[2023-01-01,2023-01-01] HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/events?duration=cs.\[2023-01-01,2023-01-01\]"
.. code-block:: json
[
{
"name": "t-shirt",
"size": "l"
},
{
"name": "hoodie",
"size": "xl"
"id": 1,
"name": "New Year's Party",
"duration": "[\"2022-12-31 11:00:00\",\"2023-01-01 06:00:00\"]"
}
]
.. _casting_range_to_json:
Casting a Range to a JSON Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As you may have noticed, the ``tsrange`` value is returned as a string literal. To return it as a JSON value, first you need to create a function that will do the conversion from a ``tsrange`` type:
.. code-block:: postgres
create or replace function tsrange_to_json(tsrange) returns json as $$
select json_build_object(
'lower', lower($1)
, 'upper', upper($1)
, 'lower_inc', lower_inc($1)
, 'upper_inc', upper_inc($1)
);
$$ language sql;
Then, create the cast using this function:
.. code-block:: postgres
create cast (tsrange as json) with function tsrange_to_json(tsrange) as assignment;
Finally, do the request :ref:`casting the range column <casting_columns>`:
.. tabs::
.. code-tab:: http
GET /events?select=id,name,duration::json HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/events?select=id,name,duration::json"
.. code-block:: json
[
{
"id": 1,
"name": "New Year's Party",
"duration": {
"lower": "2022-12-31T11:00:00",
"upper": "2023-01-01T06:00:00",
"lower_inc": true,
"upper_inc": true
}
}
]
.. note::
If you don't want to modify casts for built-in types, an option would be to `create a custom type <https://www.postgresql.org/docs/current/sql-createtype.html>`_
for your own ``tsrange`` and add its own cast.
.. code-block:: postgres
create type mytsrange as range (subtype = timestamp, subtype_diff = tsrange_subdiff);
-- define column types and casting function analogously to the above example
-- ...
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
Bytea
-----
To send raw binary to PostgREST you need a function with a single unnamed parameter of `bytea type <https://www.postgresql.org/docs/current/datatype-binary.html>`_.
.. code-block:: postgres
create table files (
id int primary key generated always as identity,
file bytea
);
create function upload_binary(bytea) returns void as $$
insert into files (file) values ($1);
$$ language sql;
Let's download the PostgREST logo for our test.
.. code-block:: bash
curl "https://postgrest.org/en/latest/_images/logo.png" -o postgrest-logo.png
Now, to send the file ``postgrest-logo.png`` we need to set the ``Content-Type: application/octet-stream`` header in the request:
.. tabs::
.. code-tab:: http
POST /rpc/upload_binary HTTP/1.1
Content-Type: application/octet-stream
postgrest-logo.png
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/upload_binary" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary "@postgrest-logo.png"
To get the image from the database, use :ref:`custom_media` like so:
.. code-block:: postgres
create domain "image/png" as bytea;
create or replace get_image(id int) returns "image/png" as $$
select file from files where id = $1;
$$ language sql;
.. tabs::
.. code-tab:: http
GET /get_image?id=1 HTTP/1.1
Accept: image/png
.. code-tab:: bash Curl
curl "http://localhost:3000/get_image?id=1" \
-H "Accept: image/png"
See :ref:`providing_img` for a step-by-step example on how to handle images in HTML.
.. warning::
Be careful when saving binaries in the database, having a separate storage service for these is preferable in most cases. See `Storing Binary files in the Database <https://wiki.postgresql.org/wiki/BinaryFilesInDB>`_.
hstore
------
@@ -263,67 +551,53 @@ You can work with data types belonging to additional supplied modules such as `h
The ``name`` column will have the name of the country in different formats. You can insert values using the string representation for that data type:
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /countries HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/countries" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
[
{ "id": 1, "name": "common => Egypt, official => \"Arab Republic of Egypt\", native => مصر" },
{ "id": 2, "name": "common => Germany, official => \"Federal Republic of Germany\", native => Deutschland" }
]
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/countries" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
[
{ "id": 1, "name": "common => Egypt, official => \"Arab Republic of Egypt\", native => مصر" },
{ "id": 2, "name": "common => Germany, official => \"Federal Republic of Germany\", native => Deutschland" }
]
EOF
Notice that the use of ``"`` in the value of the ``name`` column needs to be escaped using a backslash ``\``.
You can also query and filter the value of a ``hstore`` column using the arrow operators, as you would do for a :ref:`JSON column<json_columns>`. For example, if you want to get the native name of Egypt:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/countries?select=name->>native&name->>common=like.Egypt"
.. code-tab:: http
GET /countries?select=name->>native&name->>common=like.Egypt HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/countries?select=name->>native&name->>common=like.Egypt"
.. code-block:: json
[{ "native": "مصر" }]
JSON
----
To work with a ``json`` type column, you can handle the value as a JSON object.
.. code-block:: postgres
create table products (
id int primary key,
name text unique,
extra_info json
);
You can insert a new product using a JSON object for the ``extra_info`` column:
.. code-block:: bash
curl "http://localhost:3000/products" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"name": "Canned fish",
"extra_info": {
"expiry_date": "2025-12-31",
"exportable": true
}
}
EOF
To query and filter the data see :ref:`json_columns` for a complete reference.
.. _ww_postgis:
PostGIS
-------
You can use the string representation for `PostGIS <https://postgis.net/>`_ data types such as ``geometry`` or ``geography`` (you need to `install PostGIS <https://postgis.net/documentation/getting_started/>`_ first).
You can use the string representation for `PostGIS <https://postgis.net/>`_ data types such as ``geometry`` or ``geography`` (you need to `install PostGIS <https://postgis.net/install/>`_ first).
.. code-block:: postgres
@@ -338,23 +612,42 @@ You can use the string representation for `PostGIS <https://postgis.net/>`_ data
To add areas in polygon format, you can use string representation:
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /coverage HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/coverage" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
[
{ "id": 1, "name": "small", "area": "SRID=4326;POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))" },
{ "id": 2, "name": "big", "area": "SRID=4326;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))" }
]
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/coverage" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
[
{ "id": 1, "name": "small", "area": "SRID=4326;POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))" },
{ "id": 2, "name": "big", "area": "SRID=4326;POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))" }
]
EOF
Now, when you request the information, PostgREST will automatically cast the ``area`` column into a ``Polygon`` geometry type. Although this is useful, you may need the whole output to be in `GeoJSON <https://geojson.org/>`_ format out of the box, which can be done by including the ``Accept: application/geo+json`` in the request. This will work for PostGIS versions 3.0.0 and up and will return the output as a `FeatureCollection Object <https://www.rfc-editor.org/rfc/rfc7946#section-3.3>`_:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/coverage" \
-H "Accept: application/geo+json"
.. code-tab:: http
GET /coverage HTTP/1.1
Accept: application/geo+json
.. code-tab:: bash Curl
curl "http://localhost:3000/coverage" \
-H "Accept: application/geo+json"
.. code-block:: json
@@ -418,9 +711,15 @@ In the case that you are using older PostGIS versions, then creating a function
Now this query will return the same results:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/coverage_geo_collection"
.. code-tab:: http
GET /rpc/coverage_geo_collection HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/coverage_geo_collection"
.. code-block:: json
@@ -455,157 +754,3 @@ Now this query will return the same results:
}
]
}
Ranges
------
PostgREST allows you to handle `ranges <https://www.postgresql.org/docs/current/rangetypes.html>`_.
.. code-block:: postgres
create table events (
id int primary key,
name text unique,
duration tsrange
);
To insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type:
.. code-block:: bash
curl "http://localhost:3000/events" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"id": 1,
"name": "New Year's Party",
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
}
EOF
You can use range :ref:`operators <operators>` to filter the data. But, in this case, requesting a filter like ``events?duration=cs.2023-01-01`` will return an error, because PostgreSQL needs an explicit cast from string to timestamp. A workaround is to use a range starting and ending in the same date:
.. code-block:: bash
curl "http://localhost:3000/events?duration=cs.\[2023-01-01,2023-01-01\]"
.. code-block:: json
[
{
"id": 1,
"name": "New Year's Party",
"duration": "[\"2022-12-31 11:00:00\",\"2023-01-01 06:00:00\"]"
}
]
.. _casting_range_to_json:
Casting a Range to a JSON Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As you may have noticed, the ``tsrange`` value is returned as a string literal. To return it as a JSON value, first you need to create a function that will do the conversion from a ``tsrange`` type:
.. code-block:: postgres
create or replace function tsrange_to_json(tsrange) returns json as $$
select json_build_object(
'lower', lower($1)
, 'upper', upper($1)
, 'lower_inc', lower_inc($1)
, 'upper_inc', upper_inc($1)
);
$$ language sql;
Then, create the cast using this function:
.. code-block:: postgres
create cast (tsrange as json) with function tsrange_to_json(tsrange) as assignment;
Finally, do the request :ref:`casting the range column <casting_columns>`:
.. code-block:: bash
curl "http://localhost:3000/events?select=id,name,duration::json"
.. code-block:: json
[
{
"id": 1,
"name": "New Year's Party",
"duration": {
"lower": "2022-12-31T11:00:00",
"upper": "2023-01-01T06:00:00",
"lower_inc": true,
"upper_inc": true
}
}
]
.. note::
If you don't want to modify casts for built-in types, an option would be to `create a custom type <https://www.postgresql.org/docs/current/sql-createtype.html>`_
for your own ``tsrange`` and add its own cast.
.. code-block:: postgres
create type mytsrange as range (subtype = timestamp, subtype_diff = tsrange_subdiff);
-- define column types and casting function analogously to the above example
-- ...
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
Timestamps
----------
You can use the **time zone** to filter or send data if needed.
.. code-block:: postgres
create table reports (
id int primary key
, due_date timestamptz
);
Suppose you are located in Sydney and want create a report with the date in the local time zone. Your request should look like this:
.. code-block:: bash
curl "http://localhost:3000/reports" \
-X POST -H "Content-Type: application/json" \
-d '[{ "id": 1, "due_date": "2022-02-24 11:10:15 Australia/Sydney" },{ "id": 2, "due_date": "2022-02-27 22:00:00 Australia/Sydney" }]'
Someone located in Cairo can retrieve the data using their local time, too:
.. code-block:: bash
curl "http://localhost:3000/reports?due_date=eq.2022-02-24+02:10:15+Africa/Cairo"
.. code-block:: json
[
{
"id": 1,
"due_date": "2022-02-23T19:10:15-05:00"
}
]
The response has the date in the time zone configured by the server: ``UTC -05:00`` (see :ref:`prefer_timezone`).
You can use other comparative filters and also all the `PostgreSQL special date/time input values <https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE>`_ as illustrated in this example:
.. code-block:: bash
curl "http://localhost:3000/reports?or=(and(due_date.gte.today,due_date.lte.tomorrow),and(due_date.gt.-infinity,due_date.lte.epoch))"
.. code-block:: json
[
{
"id": 2,
"due_date": "2022-02-27T06:00:00-05:00"
}
]
+21 -44
View File
@@ -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
@@ -34,59 +34,38 @@ Sponsors
.. container:: image-container
.. container:: img-dark
.. image:: _static/cybertec-new.png
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
:width: 13em
.. image:: ../static/cybertec-dark.svg
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
.. container:: img-light
.. image:: ../static/cybertec.svg
:target: https://www.cybertec-postgresql.com/en/?utm_source=postgrest.org&utm_medium=referral&utm_campaign=postgrest
.. image:: ../static/gnuhost.png
.. image:: _static/gnuhost.png
:target: https://gnuhost.eu/?utm_source=sponsor&utm_campaign=postgrest
:width: 13em
.. container:: img-dark
.. image:: ../static/neon-dark.jpg
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
.. container:: img-light
.. image:: ../static/neon.jpg
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
.. image:: _static/neon.jpg
:target: https://neon.tech/?utm_source=sponsor&utm_campaign=postgrest
:width: 13em
|
.. container:: img-dark
.. image:: _static/code-build.webp
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
:width: 13em
.. image:: ../static/code-build-dark.png
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
.. image:: _static/supabase.png
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
:width: 13em
.. container:: img-light
.. image:: ../static/code-build.png
:target: https://code.build/?utm_source=sponsor&utm_campaign=postgrest
.. container:: img-dark
.. image:: ../static/supabase-dark.png
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. container:: img-light
.. image:: ../static/supabase.png
:target: https://supabase.com/?utm_source=postgrest%20backers&utm_medium=open%20source%20partner&utm_campaign=postgrest%20backers%20github&utm_term=homepage
.. image:: ../static/tembo.png
.. image:: _static/tembo.png
:target: https://tembo.io/?utm_source=sponsor&utm_campaign=postgrest
:width: 13em
.. The static/empty.png(created with `convert -size 320x95 xc:#fcfcfc empty.png`) is an ugly workaround
to create space and center the logos. It's not easy to layout with restructuredText.
.. .. image:: _static/empty.png
:target: #sponsors
:width: 13em
|
@@ -147,13 +126,11 @@ Technical references for PostgREST's functionality.
references/auth.rst
references/api.rst
references/cli.rst
references/transactions.rst
references/connection_pool.rst
references/schema_cache.rst
references/errors.rst
references/configuration.rst
references/observability.rst
references/*
Explanations
@@ -218,11 +195,11 @@ Here are some companies that use PostgREST in production.
* `Datrium <https://www.datrium.com>`_
* `Drip Depot <https://www.dripdepot.com>`_
* `Image-charts <https://www.image-charts.com>`_
* `Moat <https://www.oracle.com/advertising/measurement/>`_
* `Moat <https://www.moat.com>`_
* `Netwo <https://www.netwo.io>`_
* `Nimbus <https://www.nimbusfacility.com/sg/home>`_
* `Nimbus <https://www.nimbusforwork.com>`_
- See how Nimbus uses PostgREST in `Paul Copplestone's blog post <https://paul.copplest.one/blog/nimbus-tech-2019-04.html>`_.
* `OpenBooking <https://openbooking.ch>`_
* `OpenBooking <https://www.openbooking.ch>`_
* `Redsmin <https://www.redsmin.com>`_
* `Sompani <https://www.sompani.com>`_
* `Supabase <https://supabase.com>`_
+122
View File
@@ -0,0 +1,122 @@
.. _deploy_heroku:
Heroku
======
1. Log into Heroku using the `Heroku CLI <https://devcenter.heroku.com/articles/heroku-cli>`_:
.. code-block:: bash
# If you have multiple Heroku accounts, use flag '--interactive' to switch between them
heroku login --interactive
2. Create a new Heroku app using the PostgREST buildpack:
.. code-block:: bash
mkdir ${YOUR_APP_NAME}
cd ${YOUR_APP_NAME}
git init .
heroku apps:create ${YOUR_APP_NAME} --buildpack https://github.com/PostgREST/postgrest-heroku.git
heroku git:remote -a ${YOUR_APP_NAME}
3. Create a new Heroku PostgreSQL add-on attached to the app and keep notes of the assigned add-on name (e.g. :code:`postgresql-curly-58902`) referred later as ${HEROKU_PG_DB_NAME}
.. code-block:: bash
heroku addons:create heroku-postgresql:standard-0 -a ${YOUR_APP_NAME}
# wait until the add-on is available
heroku pg:wait -a ${YOUR_APP_NAME}
4. Create the necessary user roles according to the
`PostgREST documentation <https://postgrest.org/en/stable/auth.html>`_:
.. code-block:: bash
heroku pg:credentials:create --name api_user -a ${YOUR_APP_NAME}
# use the following command to ensure the new credential state is active before attaching it
heroku pg:credentials -a ${YOUR_APP_NAME}
heroku addons:attach ${HEROKU_PG_DB_NAME} --credential api_user -a ${YOUR_APP_NAME}
5. Connect to the PostgreSQL database and create some sample data:
.. code-block:: bash
heroku psql -a ${YOUR_APP_NAME}
.. code-block:: postgres
# from the psql command prompt execute the following commands:
create schema api;
create table api.todos (
id serial primary key,
done boolean not null default false,
task text not null,
due timestamptz
);
insert into api.todos (task) values
('finish tutorial 0'), ('pat self on back');
grant usage on schema api to api_user;
grant select on api.todos to api_user;
6. Create the :code:`Procfile`:
.. code-block:: bash
web: PGRST_SERVER_HOST=0.0.0.0 PGRST_SERVER_PORT=${PORT} PGRST_DB_URI=${PGRST_DB_URI:-${DATABASE_URL}} ./postgrest-${POSTGREST_VER}
..
Set the following environment variables on Heroku:
.. code-block:: bash
heroku config:set POSTGREST_VER=10.0.0
heroku config:set PGRST_DB_SCHEMA=api
heroku config:set PGRST_DB_ANON_ROLE=api_user
..
PGRST_DB_URI can be set if an external database is used or if it's different from the default Heroku DATABASE_URL. This latter is used if nothing is provided.
POSTGREST_VER is mandatory to select and build the required PostgREST release.
See https://postgrest.org/en/stable/configuration.html#environment-variables for the full list of environment variables.
7. Build and deploy your app:
.. code-block:: bash
git add Procfile
git commit -m "PostgREST on Heroku"
git push heroku master
..
Your Heroku app should be live at :code:`${YOUR_APP_NAME}.herokuapp.com`
8. Test your app
From a terminal display the application logs:
.. code-block:: bash
heroku logs -t
..
From a different terminal retrieve with curl the records previously created:
.. code-block:: bash
curl https://${YOUR_APP_NAME}.herokuapp.com/todos
..
and test that any attempt to modify the table via a read-only user is not allowed:
.. code-block:: bash
curl https://${YOUR_APP_NAME}.herokuapp.com/todos -X POST \
-H "Content-Type: application/json" \
-d '{"task": "do bad thing"}'
+20
View File
@@ -9,3 +9,23 @@ JWT from Auth0
An external service like `Auth0 <https://auth0.com/>`_ can do the hard work transforming OAuth from Github, Twitter, Google etc into a JWT suitable for PostgREST. Auth0 can also handle email signup and password reset flows.
To use Auth0, create `an application <https://auth0.com/docs/get-started/applications>`_ for your app and `an API <https://auth0.com/docs/get-started/apis>`_ for your PostgREST server. Auth0 supports both HS256 and RS256 scheme for the issued tokens for APIs. For simplicity, you may first try HS256 scheme while creating your API on Auth0. Your application should use your PostgREST API's `API identifier <https://auth0.com/docs/get-started/apis/api-settings>`_ by setting it with the `audience parameter <https://auth0.com/docs/secure/tokens/access-tokens/get-access-tokens#control-access-token-audience>`_ during the authorization request. This will ensure that Auth0 will issue an access token for your PostgREST API. For PostgREST to verify the access token, you will need to set ``jwt-secret`` on PostgREST config file with your API's signing secret.
.. note::
Our code requires a database role in the JWT. To add it you need to save the database role in Auth0 `app metadata <https://auth0.com/docs/manage-users/user-accounts/metadata/manage-metadata-rules>`_. Then, you will need to write `a rule <https://auth0.com/docs/customize/rules>`_ that will extract the role from the user's app_metadata and set it as a `custom claim <https://auth0.com/docs/get-started/apis/scopes/sample-use-cases-scopes-and-claims#add-custom-claims-to-a-token>`_ in the access token. Note that, you may use Auth0's `core authorization feature <https://auth0.com/docs/manage-users/access-control/rbac>`_ for more complex use cases. Metadata solution is mentioned here for simplicity.
.. code:: javascript
function (user, context, callback) {
// Follow the documentations at
// https://postgrest.org/en/latest/configuration.html#db-role-claim-key
// to set a custom role claim on PostgREST
// and use it as custom claim attribute in this rule
const myRoleClaim = 'https://myapp.com/role';
user.app_metadata = user.app_metadata || {};
context.accessToken[myRoleClaim] = user.app_metadata.role;
callback(null, user, context);
}
+16 -4
View File
@@ -8,15 +8,27 @@ Block Full-Table Operations
If the :ref:`active role <user_impersonation>` can delete table rows then the DELETE verb is allowed for clients. Here's an API request to delete old rows from a hypothetical logs table:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE
.. code-tab:: http
DELETE /logs?time=lt.1991-08-06 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE
Note that it's very easy to delete the **entire table** by omitting the query parameter!
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/logs" -X DELETE
.. code-tab:: http
DELETE /logs HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/logs" -X DELETE
This can happen accidentally such as by switching a request from a GET to a DELETE. To protect against accidental operations use the `pg-safeupdate <https://github.com/eradman/pg-safeupdate>`_ PostgreSQL extension. It raises an error if UPDATE or DELETE are executed without specifying conditions. To install it you can use the `PGXN <https://pgxn.org/>`_ network:
+1 -1
View File
@@ -1,7 +1,7 @@
systemd
=======
For Linux distributions that use **systemd** (Ubuntu, Debian, Arch Linux) you can create a daemon in the following way.
For Linux distributions that use **systemd** (Ubuntu, Debian, Archlinux) you can create a daemon in the following way.
First, create postgrest configuration in ``/etc/postgrest/config``
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env python
from livereload import Server, shell
from subprocess import call
## Build docs at startup
call(["sphinx-build", "-b", "html", "-a", "-n", ".", "_build"])
server = Server()
server.watch("**/*.rst", shell("sphinx-build -b html -a -n . _build"))
# For custom port and host
# server.serve(root='_build/', host='192.168.1.2')
server.serve(root="_build/")
+6 -7
View File
@@ -1,21 +1,22 @@
personal_ws-1.1 en 0 utf-8
api
API's
APIs
APISIX
Archlinux
aud
Auth
auth
authenticator
backoff
balancer
booleans
buildpack
BOM
Bytea
Cardano
casted
cd
centric
CLI
coercible
conf
Cloudflare
@@ -29,8 +30,6 @@ DDL
DOM
DevOps
dockerize
enum
Enums
eq
ETH
Ethereum
@@ -39,6 +38,7 @@ filename
FreeBSD
fts
GeoJSON
GHC
Github
Google
grantor
@@ -47,6 +47,7 @@ Greenplum
gte
GUC
Haskell
Heroku
HMAC
htmx
Htmx
@@ -94,12 +95,10 @@ npm
nxl
nxr
OAuth
Observability
OpenAPI
openapi
ORM
ov
parametrized
passphrase
PBKDF
PgBouncer
@@ -150,13 +149,13 @@ spreaded
Spreaded
SQL
sql
SQLSTATE
sr
SSL
stateful
stdout
supervisees
SvelteKit
syslog
systemd
todo
todos
@@ -1,19 +1,12 @@
.. _observability:
.. _admin:
Observability
#############
Observability allows measuring a system's current state based on the data it generates, such as logs, metrics, and traces.
.. contents::
:depth: 1
:local:
:backlinks: none
Admin
#####
.. _pgrst_logging:
Logs
====
Logging
-------
PostgREST logs basic request information to ``stdout``, including the authenticated user if available, the requesting IP address and user agent, the URL requested, and HTTP response status.
@@ -22,20 +15,33 @@ PostgREST logs basic request information to ``stdout``, including the authentica
127.0.0.1 - user [26/Jul/2021:01:56:38 -0500] "GET /clients HTTP/1.1" 200 - "" "curl/7.64.0"
127.0.0.1 - anonymous [26/Jul/2021:01:56:48 -0500] "GET /unexistent HTTP/1.1" 404 - "" "curl/7.64.0"
For diagnostic information about the server itself, PostgREST logs to ``stderr``. It includes the server version and also the version of the connected PostgreSQL.
For diagnostic information about the server itself, PostgREST logs to ``stderr``.
.. code::
06/May/2024:08:16:11 -0500: Starting PostgREST 12.1...
06/May/2024:08:16:11 -0500: Attempting to connect to the database...
06/May/2024:08:16:11 -0500: Successfully connected to PostgreSQL 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit
06/May/2024:08:16:11 -0500: Listening on port 3000
12/Jun/2021:17:47:39 -0500: Starting PostgREST 11.1.0...
12/Jun/2021:17:47:39 -0500: Attempting to connect to the database...
12/Jun/2021:17:47:39 -0500: Listening on port 3000
12/Jun/2021:17:47:39 -0500: Connection successful
12/Jun/2021:17:47:39 -0500: Config re-loaded
12/Jun/2021:17:47:40 -0500: Schema cache loaded
Database Logs
-------------
.. note::
When running it in an SSH session you must detach it from stdout or it will be terminated when the session closes. The easiest technique is redirecting the output to a log file or to the syslog:
.. code-block:: bash
ssh foo@example.com \
'postgrest foo.conf </dev/null >/var/log/postgrest.log 2>&1 &'
# another option is to pipe the output into "logger -t postgrest"
Currently PostgREST doesn't log the SQL commands executed against the underlying database.
Database Logs
~~~~~~~~~~~~~
To find the SQL operations, you can watch the database logs. By default PostgreSQL does not keep these logs, so you'll need to make the configuration changes below.
Find :code:`postgresql.conf` inside your PostgreSQL data directory (to find that, issue the command :code:`show data_directory;`). Either find the settings scattered throughout the file and change them to the following values, or append this block of code to the end of the configuration file.
@@ -75,97 +81,12 @@ Restart the database and watch the log file in real-time to understand how HTTP
docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres
docker logs -f <container-id>
.. _metrics:
Server Version
--------------
Metrics
=======
When debugging a problem it's important to verify the running PostgREST version. There are three ways to do this:
The ``metrics`` endpoint on the :ref:`admin_server` endpoint provides metrics in `Prometheus text format <https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format>`_.
.. code-block:: bash
curl "http://localhost:3001/metrics"
# HELP pgrst_schema_cache_query_time_seconds The query time in seconds of the last schema cache load
# TYPE pgrst_schema_cache_query_time_seconds gauge
pgrst_schema_cache_query_time_seconds 1.5937927e-2
# HELP pgrst_schema_cache_loads_total The total number of times the schema cache was loaded
# TYPE pgrst_schema_cache_loads_total counter
pgrst_schema_cache_loads_total 1.0
...
Schema Cache Metrics
--------------------
Metrics related to the :ref:`schema_cache`.
pgrst_schema_cache_query_time_seconds
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
======== =======
**Type** Gauge
======== =======
The query time in seconds of the last schema cache load.
pgrst_schema_cache_loads_total
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
========== ==========================
**Type** Counter
**Labels** ``status``: SUCCESS | FAIL
========== ==========================
The total number of times the schema cache was loaded.
Connection Pool Metrics
-----------------------
Metrics related to the :ref:`connection_pool`.
pgrst_db_pool_timeouts_total
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
======== =======
**Type** Counter
======== =======
The total number of pool connection timeouts.
pgrst_db_pool_available
~~~~~~~~~~~~~~~~~~~~~~~
======== =======
**Type** Gauge
======== =======
Available connections in the pool.
pgrst_db_pool_waiting
~~~~~~~~~~~~~~~~~~~~~
======== =======
**Type** Gauge
======== =======
Requests waiting to acquire a pool connection
pgrst_db_pool_max
~~~~~~~~~~~~~~~~~
======== =======
**Type** Gauge
======== =======
Max pool connections.
Traces
======
Server Version Header
---------------------
When debugging a problem it's important to verify the running PostgREST version. For this you can look at the :code:`Server` HTTP response header that is returned on every request.
- Look for the :code:`Server` HTTP response header that is returned on every request.
.. code::
@@ -173,6 +94,26 @@ When debugging a problem it's important to verify the running PostgREST version.
Server: postgrest/11.0.1
- Query ``application_name`` on `pg_stat_activity <https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW>`_.
.. code-block:: psql
select distinct application_name
from pg_stat_activity
where application_name ilike '%postgrest%';
application_name
------------------------------
PostgREST 11.1.0
.. important::
- The server sets the `fallback_application_name <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-FALLBACK-APPLICATION-NAME>`_ to the connection URI for this query to work. To override the value set ``application_name`` on the connection string.
- The version will only be set if it's a valid URI (`RFC 3986 <https://datatracker.ietf.org/doc/html/rfc3986>`_). This means any special characters must be urlencoded.
- The version will not be set if the connection string is in `keyword/value format <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-KEYWORD-VALUE>`_.
- The ``stderr`` logs also contain the version, as noted on :ref:`pgrst_logging`.
.. _trace_header:
Trace Header
@@ -184,10 +125,18 @@ You can enable tracing HTTP requests by setting :ref:`server-trace-header`. Spec
server-trace-header = "X-Request-Id"
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/users" \
-H "X-Request-Id: 123"
.. code-tab:: http
GET /users HTTP/1.1
X-Request-Id: 123
.. code-tab:: bash Curl
curl "http://localhost:3000/users" \
-H "X-Request-Id: 123"
.. code::
@@ -202,9 +151,15 @@ Server-Timing Header
You can enable the `Server-Timing <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing>`_ header by setting :ref:`server-timing-enabled` on.
This header communicates metrics of the different phases in the request-response cycle.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/users" -i
.. code-tab:: http
GET /users HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/users" -i
.. code::
@@ -231,22 +186,36 @@ Execution plan
You can get the `EXPLAIN execution plan <https://www.postgresql.org/docs/current/sql-explain.html>`_ of a request by adding the ``Accept: application/vnd.pgrst.plan`` header.
This is enabled by :ref:`db-plan-enabled` (false by default).
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/users?select=name&order=id" \
-H "Accept: application/vnd.pgrst.plan"
.. code-tab:: http
.. code-block:: postgres
GET /users?select=name&order=id HTTP/1.1
Accept: application/vnd.pgrst.plan
.. code-tab:: bash Curl
curl "http://localhost:3000/users?select=name&order=id" \
-H "Accept: application/vnd.pgrst.plan"
.. code-block:: psql
Aggregate (cost=73.65..73.68 rows=1 width=112)
-> Index Scan using users_pkey on users (cost=0.15..60.90 rows=850 width=36)
The output of the plan is generated in ``text`` format by default but you can change it to JSON by using the ``+json`` suffix.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/users?select=name&order=id" \
-H "Accept: application/vnd.pgrst.plan+json"
.. code-tab:: http
GET /users?select=name&order=id HTTP/1.1
Accept: application/vnd.pgrst.plan+json
.. code-tab:: bash Curl
curl "http://localhost:3000/users?select=name&order=id" \
-H "Accept: application/vnd.pgrst.plan+json"
.. code-block:: json
@@ -296,7 +265,7 @@ However, if you choose to use it in production you can add a :ref:`db-pre-reques
For example, to only allow requests from an IP address to get the execution plans:
.. code-block:: postgres
.. code-block:: postgresql
-- Assuming a proxy(Nginx, Cloudflare, etc) passes an "X-Forwarded-For" header(https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For)
create or replace function filter_plan_requests()
@@ -315,19 +284,34 @@ For example, to only allow requests from an IP address to get the execution plan
-- set this function on your postgrest.conf
-- db-pre-request = filter_plan_requests
.. raw:: html
<script type="text/javascript">
let hash = window.location.hash;
.. _health_check:
const redirects = {
'#health_check': 'health_check.html',
'#server-version': '#server-version-header',
};
Health Check
------------
let willRedirectTo = redirects[hash];
You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
if (willRedirectTo) {
window.location.href = willRedirectTo;
}
</script>
To do this, set the configuration variable :ref:`admin-server-port` to the port number of your preference. Two endpoints ``live`` and ``ready`` will then be available.
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``503`` otherwise.
The ``ready`` endpoint also checks the state of both the Database Connection and the :ref:`schema_cache`. A request will return ``200 OK`` if it is ready or ``503`` if not.
For instance, to verify if PostgREST is running at ``localhost:3000`` while the ``admin-server-port`` is set to ``3001``:
.. tabs::
.. code-tab:: http
GET localhost:3001/live HTTP/1.1
.. code-tab:: bash Curl
curl -I "http://localhost:3001/live"
.. code-block:: http
HTTP/1.1 200 OK
If you have a machine with multiple network interfaces and multiple PostgREST instances in the same port, you need to specify a unique :ref:`hostname <server-host>` in the configuration of each PostgREST instance for the health check to work correctly. Don't use the special values(``!4``, ``*``, etc) in this case because the health check could report a false positive.
-93
View File
@@ -1,93 +0,0 @@
.. _admin_server:
Admin Server
############
PostgREST provides an admin server that can be enabled by setting :ref:`admin-server-port`.
.. _health_check:
Health Check
============
You can enable a health check to verify if PostgREST is available for client requests. Also to check the status of its internal state.
Two endpoints ``live`` and ``ready`` will then be available.
.. important::
If you have a machine with multiple network interfaces and multiple PostgREST instances in the same port, you need to specify a unique :ref:`hostname <server-host>`
in the configuration of each PostgREST instance for the health check to work correctly. Don't use the special values(``!4``, ``*``, etc) in this case because the health check
could report a false positive.
Live
----
The ``live`` endpoint verifies if PostgREST is running on its configured port. A request will return ``200 OK`` if PostgREST is alive or ``500`` otherwise.
For instance, to verify if PostgREST is running while the ``admin-server-port`` is set to ``3001``:
.. code-block:: bash
curl -I "http://localhost:3001/live"
.. code-block:: http
HTTP/1.1 200 OK
Ready
-----
Additionally to the ``live`` check, the ``ready`` endpoint checks the state of the :ref:`connection_pool` and the :ref:`schema_cache`. A request will return ``200 OK`` if both are good or ``503`` if not.
.. code-block:: bash
curl -I "http://localhost:3001/ready"
.. code-block:: http
HTTP/1.1 200 OK
PostgREST will try to recover from the ``503`` state with :ref:`automatic_recovery`.
Metrics
=======
Provides :ref:`metrics`.
Runtime Configuration
=====================
Provides a ``config`` endpoint that returns the runtime :ref:`configuration`.
.. code-block:: bash
curl "http://localhost:3001/config"
.. code-block::
db-aggregates-enabled = false
db-anon-role = "web_anon"
db-channel = "pgrst"
db-channel-enabled = false
...
Runtime Schema Cache
====================
Provides the ``schema_cache`` endpoint that prints the runtime :ref:`schema_cache`.
.. code-block:: bash
curl "http://localhost:3001/schema_cache"
.. code-block:: json
{
"dbMediaHandlers": ["..."],
"dbRelationships": ["..."],
"dbRepresentations": ["..."],
"dbRoutines": ["..."],
"dbTables": ["..."],
"dbTimezones": ["..."]
}
+29 -29
View File
@@ -3,14 +3,14 @@
API
###
PostgREST exposes three database objects of a schema as resources: tables, views and functions.
PostgREST exposes three database objects of a schema as resources: tables, views and stored procedures.
.. toctree::
:glob:
:maxdepth: 1
api/tables_views.rst
api/functions.rst
api/stored_procedures.rst
api/schemas.rst
api/computed_fields.rst
api/domain_representations.rst
@@ -30,22 +30,22 @@ PostgREST exposes three database objects of a schema as resources: tables, views
const redirects = {
// Tables and Views
'#horizontal-filtering-rows': 'api/tables_views.html#horizontal-filtering',
'#horizontal-filtering-rows': 'api/tables_views.html#horizontal-filtering-rows',
'#operators': 'api/tables_views.html#operators',
'#logical-operators': 'api/tables_views.html#logical-operators',
'#pattern-matching': 'api/tables_views.html#pattern-matching',
'#full-text-search': 'api/tables_views.html#full-text-search',
'#vertical-filtering-columns': 'api/tables_views.html#vertical-filtering',
'#vertical-filtering-columns': 'api/tables_views.html#vertical-filtering-columns',
'#renaming-columns': 'api/tables_views.html#renaming-columns',
'#casting-columns': 'api/tables_views.html#casting-columns',
'#json-columns': 'api/tables_views.html#json-columns',
'#composite-array-columns': 'api/tables_views.html#composite-array-columns',
'#computed-virtual-columns': 'api/computed_fields.html',
'#computed-virtual-columns': 'api/computed_fields.html#computed-fields',
'#ordering': 'api/tables_views.html#ordering',
'#limits-and-pagination': 'api/pagination_count.html',
'#exact-count': 'api/pagination_count.html#exact-count',
'#planned-count': 'api/pagination_count.html#planned-count',
'#estimated-count': 'api/pagination_count.html#estimated-count',
'#limits-and-pagination': 'api/tables_views.html#limits-and-pagination',
'#exact-count': 'api/tables_views.html#exact-count',
'#planned-count': 'api/tables_views.html#planned-count',
'#estimated-count': 'api/tables_views.html#estimated-count',
'#updates': 'api/tables_views.html#update',
'#insertions': 'api/tables_views.html#insert',
'#bulk-insert': 'api/tables_views.html#bulk-insert',
@@ -55,15 +55,15 @@ PostgREST exposes three database objects of a schema as resources: tables, views
'#put': 'api/tables_views.html#put',
'#deletions': 'api/tables_views.html#delete',
'#limited-updates-deletions': 'api/tables_views.html#limited-update-delete',
// Functions
'#stored-procedures': 'api/functions.html',
'#calling-functions-with-a-single-json-parameter': 'api/functions.html#functions-with-a-single-json-parameter',
'#calling-functions-with-a-single-unnamed-parameter': 'api/functions.html#functions-with-a-single-unnamed-parameter',
'#calling-functions-with-array-parameters': 'api/functions.html#functions-with-array-parameters',
'#calling-variadic-functions': 'api/functions.html#variadic-functions',
'#scalar-functions': 'api/functions.html#scalar-functions',
'#function-filters': 'api/functions.html#table-valued-functions',
'#overloaded-functions': 'api/functions.html#overloaded-functions',
// Stored procedures
'#stored-procedures': 'api/stored_procedures.html#stored-procedures',
'#calling-functions-with-a-single-json-parameter': 'api/stored_procedures.html#functions-with-a-single-json-parameter',
'#calling-functions-with-a-single-unnamed-parameter': 'api/stored_procedures.html#functions-with-a-single-unnamed-parameter',
'#calling-functions-with-array-parameters': 'api/stored_procedures.html#functions-with-array-parameters',
'#calling-variadic-functions': 'api/stored_procedures.html#variadic-functions',
'#scalar-functions': 'api/stored_procedures.html#scalar-functions',
'#function-filters': 'api/stored_procedures.html#table-valued-functions',
'#overloaded-functions': 'api/stored_procedures.html#overloaded-functions',
// Schemas
'#switching-schemas': 'api/schemas.html',
// Resource Embedding
@@ -76,21 +76,21 @@ PostgREST exposes three database objects of a schema as resources: tables, views
'#nested-embedding': 'api/resource_embedding.html#nested-embedding',
'#embedded-filters': 'api/resource_embedding.html#embedded-filters',
'#embedding-with-top-level-filtering': 'api/resource_embedding.html#top-level-filtering',
'#embedding-partitioned-tables': 'api/resource_embedding.html#foreign-key-joins-on-partitioned-tables',
'#embedding-views': 'api/resource_embedding.html#foreign-key-joins-on-views',
'#embedding-chains-of-views': 'api/resource_embedding.html#foreign-key-joins-on-chains-of-views',
'#embedding-on-stored-procedures': 'api/resource_embedding.html#foreign-key-joins-on-table-valued-functions',
'#embedding-after-insertions-updates-deletions': 'api/resource_embedding.html#foreign-key-joins-on-writes',
'#embedding-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships',
'#target-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships',
'#hint-disambiguation': 'api/resource_embedding.html#foreign-key-joins-on-multiple-foreign-key-relationships',
'#embedding-partitioned-tables': 'api/resource_embedding.html#embedding-partitioned-tables',
'#embedding-views': 'api/resource_embedding.html#embedding-views',
'#embedding-chains-of-views': 'api/resource_embedding.html#embedding-chains-of-views',
'#embedding-on-stored-procedures': 'api/resource_embedding.html#embedding-on-stored-procedures',
'#embedding-after-insertions-updates-deletions': 'api/resource_embedding.html#embedding-after-insertions-updates-deletions',
'#embedding-disambiguation': 'api/resource_embedding.html#embedding-disambiguation',
'#target-disambiguation': 'api/resource_embedding.html#target-disambiguation',
'#hint-disambiguation': 'api/resource_embedding.html#hint-disambiguation',
"#embedding-through-join-tables": "api/resource_embedding.html#many-to-many-relationships",
// OpenAPI
'#openapi-support': 'api/openapi.html',
// Resource Representation
'#response-format': 'api/resource_representation.html#response-format',
'#singular-or-plural': 'api/resource_representation.html#singular-or-plural',
'#response-formats-for-scalar-responses': 'api/functions.html#scalar-functions',
'#response-formats-for-scalar-responses': 'api/resource_representation.html#scalar-function-response-format',
// CORS
'#cors': 'api/cors.html',
// OPTIONS
@@ -109,9 +109,9 @@ PostgREST exposes three database objects of a schema as resources: tables, views
'#setting-response-headers': 'transactions.html#response-headers',
'#setting-headers-via-pre-request': 'transactions.html#setting-headers-via-pre-request',
'#setting-response-status-code': 'transactions.html#response-status-code',
'#raise-errors-with-http-status-codes': 'errors.html#raise-errors-with-http-status-codes',
'#raise-errors-with-http-status-codes': 'transactions.html#raise-errors-with-http-status-codes',
// Admin
'#execution-plan': 'observability.html#execution-plan',
'#execution-plan': 'admin.html#execution-plan',
// Deprecated
'#bulk-call': '../releases/v11.0.1.html#breaking-changes',
};
+73 -19
View File
@@ -12,9 +12,15 @@ PostgREST supports the following aggregate functions: ``avg()``, ``count()``, ``
To use an aggregate function, you append the function to a value in the ``select`` parameter, like so:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=amount.sum()"
.. code-tab:: http
GET /orders?select=amount.sum() HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=amount.sum()"
With the above query, PostgREST will return a single row with a single column named ``sum`` that contains the sum of all the values in the ``amount`` column:
@@ -30,9 +36,15 @@ You can use multiple aggregate functions by just adding more columns with aggreg
To group by other columns, you simply add those columns to the ``select`` parameter. For instance:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
.. code-tab:: http
GET /orders?select=amount.sum(),amount.avg(),order_date HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=amount.sum(),amount.avg(),order_date"
This will return a row for each unique value in the ``order_date`` column, with the sum and average of the ``amount`` column for all rows that share the same ``order_date``:
@@ -63,9 +75,15 @@ The Case of ``count()``
``count()`` is treated specially, as it can be used without an associated column. Take for example the following query:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=count(),order_date"
.. code-tab:: http
GET /orders?select=count(),order_date HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=count(),order_date"
This would return a row for each unique value in the ``order_date`` column, with the count of all rows that share the same ``order_date``:
@@ -82,7 +100,7 @@ This would return a row for each unique value in the ``order_date`` column, with
}
]
When ``count()`` is used with an associated column, its behavior is slightly different: It will return the count of all values that are not ``NULL``. This is due to how PostgreSQL itself implements the ``count()`` function.
When ``count()`` is used with an associated column, its behaviour is slightly different: It will return the count of all values that are not ``NULL``. This is due to how PostgreSQL itself implements the ``count()`` function.
Renaming and Casting
====================
@@ -106,9 +124,15 @@ For instance, imagine that the ``orders`` table has a JSON column, ``order_detai
Therefore, you will need to first cast the input value to a type that is compatible with ``sum()`` (e.g. ``numeric``). Casting the input value is done in exactly the same way as casting any other value:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
.. code-tab:: http
GET /orders?select=order_details->tax_amount::numeric.sum() HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=order_details->tax_amount::numeric.sum()"
With this, you will receive the sum of the casted ``tax_amount`` value:
@@ -125,9 +149,15 @@ Casting the Value of the Output
Now let's return to an example involving the ``amount`` column of the ``orders`` table. Imagine that we want to get the rounded average of the ``amount`` column. One way to do this is to use the ``avg()`` aggregate function and then to cast the output value of the function to ``int``. To cast the value of the output of the function, we simply place the cast *after* the aggregate function:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=amount.avg()::int"
.. code-tab:: http
GET /orders?select=amount.avg()::int HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=amount.avg()::int"
You will then receive the rounded average as the result:
@@ -154,9 +184,15 @@ Using an embedded resource as a grouping column allows you to use data from an a
For example, imagine that the ``orders`` table from the examples above is related to a ``customers`` table. If you want to get the sum of the ``amount`` column grouped by the ``name`` column from the ``customers`` table, you can include the customer name, using the standard :ref:`resource_embedding` syntax, and perform a sum on the ``amount`` column.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
.. code-tab:: http
GET /orders?select=amount.sum(),customers(name) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=amount.sum(),customers(name)"
You will then get the summed amount, along with the embedded customer resource:
@@ -187,9 +223,15 @@ When embedding a resource, you can apply aggregate functions to columns from the
Continuing with the example relationship between ``orders`` and ``customers`` from the previous section, imagine that you want to fetch the ``name``, ``city``, and ``state`` for each customer, along with the sum of amount of the customer's orders, grouped by the order date. This can be done in the following way:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/customers?select=name,city,state,orders(amount.sum(),order_date)"
.. code-tab:: http
GET /customers?select=name,city,state,orders(amount.sum(),order_date) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/customers?select=name,city,state,orders(amount.sum(),order_date)"
.. code-block:: json
@@ -238,9 +280,15 @@ Grouping with Columns from a Spreaded Resource
For instance, assume you want to sum the ``amount`` column from the ``orders`` table, using the ``city`` and ``state`` columns from the ``customers`` table as grouping columns. To achieve this, you may select these two columns from the ``customers`` table and spread them; they will then be used as grouping columns:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=amount.sum(),...customers(city,state)
.. code-tab:: http
GET /orders?select=amount.sum(),...customers(city,state) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=amount.sum(),...customers(city,state)
The result will be the same as if ``city`` and ``state`` were columns from the ``orders`` table:
@@ -264,9 +312,15 @@ Aggregate Functions with Columns from a Spreaded Resource
Now imagine that the ``customers`` table has a ``joined_date`` column that represents the date that the customer joined. You want to get both the most recent and the oldest ``joined_date`` for customers that placed an order on every distinct order date. This can be expressed as follows:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=order_date,...customers(joined_date.max(),joined_date.min())
.. code-tab:: http
GET /orders?select=order_date,...customers(joined_date.max(),joined_date.min()) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=order_date,...customers(joined_date.max(),joined_date.min())
As columns from a spreaded resource are treated as if they were columns from the top-level resource, the ``max()`` and ``min()`` are applied *within* the context of the top-level, rather than within the context of the embedded resource, as in the previous section.
+25 -7
View File
@@ -30,9 +30,15 @@ Horizontal Filtering on Computed Fields
CREATE INDEX people_full_name_idx ON people
USING GIN (to_tsvector('english', full_name(people)));
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?full_name=fts.Beckett"
.. code-tab:: http
GET /people?full_name=fts.Beckett HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?full_name=fts.Beckett"
.. code-block:: json
@@ -45,9 +51,15 @@ Vertical Filtering on Computed Fields
Computed fields won't appear on the response by default but you can use :ref:`v_filter` to include them:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=full_name,job"
.. code-tab:: http
GET /people?select=full_name,job HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=full_name,job"
.. code-block:: json
@@ -60,13 +72,19 @@ Ordering on Computed Fields
:ref:`ordering` on computed fields is also possible:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?order=full_name.desc"
.. code-tab:: http
GET /people?order=full_name.desc HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?order=full_name.desc"
.. important::
Computed fields must be created in the :ref:`exposed schema <db-schemas>` or in a schema in the :ref:`extra search path <db-extra-search-path>` to be used in this way. When placing the computed field in the :ref:`exposed schema <db-schemas>` you can use an **unnamed** parameter, as in the example above, to prevent it from being exposed as an :ref:`RPC <functions>` under ``/rpc``.
Computed columns must be created in the :ref:`exposed schema <db-schemas>` or in a schema in the :ref:`extra search path <db-extra-search-path>` to be used in this way. When placing the computed column in the :ref:`exposed schema <db-schemas>` you can use an **unnamed** parameter, as in the example above, to prevent it from being exposed as an :ref:`RPC <s_procs>` under ``/rpc``.
.. note::
+16 -6
View File
@@ -10,13 +10,22 @@ It also handles `preflight requests <https://developer.mozilla.org/en-US/docs/Gl
A ``POST`` preflight request would look like this:
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/items" \
-X OPTIONS \
-H "Origin: http://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type"
.. code-tab:: http
OPTIONS /items HTTP/1.1
Origin: http://example.com
Access-Control-Allow-Method: POST
Access-Control-Allow-Headers: Content-Type
.. code-tab:: bash Curl
curl -i "http://localhost:3000/items" \
-X OPTIONS \
-H "Origin: http://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type"
.. code-block:: http
@@ -38,3 +47,4 @@ With the following config setting, PostgREST will accept CORS requests from doma
.. code-block::
server-cors-allowed-origins="http://example.com, http://example2.com"
+37 -13
View File
@@ -58,10 +58,17 @@ Then create a CAST to tell PostgREST to convert it automatically whenever a JSON
With this you can obtain the data in the shortened format.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/profiles" \
-H "Accept: application/json"
.. code-tab:: http
GET /profiles HTTP/1.1
Accept: application/json
.. code-tab:: bash Curl
curl "http://localhost:3000/profiles" \
-H "Accept: application/json"
.. code-block:: json
@@ -95,10 +102,17 @@ PostgREST considers the URL query string to be, in the most generic sense, ``tex
Now you can filter as usual.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
-H "Accept: application/json"
.. code-tab:: http
GET /profiles?id=eq.hGxP/ZLOTeeNEY4pkp9OxA== HTTP/1.1
Accept: application/json
.. code-tab:: bash Curl
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
-H "Accept: application/json"
.. code-block:: json
@@ -125,16 +139,26 @@ To accept the shortened format in a JSON request body, for example when creating
Now we can :ref:`insert` (or :ref:`update`) as usual.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/profiles" \
-H "Prefer: return=representation" \
-H "Content-Type: application/json" \
-d @- <<JSON
.. code-tab:: http
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
POST /profiles HTTP/1.1
Content-Type: application/json
Prefer: return=representation
JSON
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
.. code-tab:: bash Curl
curl "http://localhost:3000/profiles" \
-H "Prefer: return=representation" \
-H "Content-Type: application/json" \
-d @- <<JSON
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
JSON
The response:
+20 -26
View File
@@ -11,7 +11,7 @@ Media types are expressed as type aliases using `domains <https://www.postgresql
CREATE DOMAIN "application/json" AS json;
Using these domains, :ref:`functions <functions>` can become handlers and `user-defined aggregates <https://www.postgresql.org/docs/current/xaggr.html>`_ can serve as handlers for :ref:`tables_views` and :ref:`table_functions`.
Using these domains, :ref:`functions <s_procs>` can become handlers and `user-defined aggregates <https://www.postgresql.org/docs/current/xaggr.html>`_ can serve as handlers for :ref:`tables_views` and :ref:`table_functions`.
.. important::
@@ -95,18 +95,16 @@ Now we'll use it on a new aggregate defined for the ``lines`` table.
, sfunc = twkb_handler_transition
);
.. note::
Make a quick test on SQL to see it working.
You can test see this aggregate working with:
.. code-block:: psql
.. code-block:: psql
SELECT twkb_agg(l) from lines l;
SELECT twkb_agg(l) from lines l;
twkb_agg
---------------------------------------------------------------
\xa20002c09a0cc09a0c80ea3080ea30a2000280b51880b51880ea3080ea30
(1 row)
twkb_agg
---------------------------------------------------------------
\xa20002c09a0cc09a0c80ea3080ea30a2000280b51880b51880ea3080ea30
(1 row)
Now you can request the table endpoint with the ``twkb`` media type:
@@ -186,20 +184,16 @@ Now use the transition and final function as part of the new aggregate.
, finalfunc = bom_csv_final
);
.. note::
.. code-block:: psql
You can test this with:
select bom_csv_agg(l) from lines l;
bom_csv_agg
-----------------------------------------------------------------------------------------------------
id,name,geom +
1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440+
2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840+
.. code-block:: psql
select bom_csv_agg(l) from lines l;
bom_csv_agg
-----------------------------------------------------------------------------------------------------
id,name,geom +
1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440+
2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840+
(1 row)
(1 row)
And request it like:
@@ -221,11 +215,11 @@ And request it like:
The "Any" Handler
=================
For more flexibility, you can also define a catch-all handler by using a domain named ``*/*`` (any media type). This handler obeys the following rules:
For more flexibility, you can also define a catch-all handler by using a domain named ``*/*`` (any media type). This obeys to the following rules:
- It responds to all media types and even to requests that don't include an ``Accept`` header.
- It sets the ``Content-Type`` header to ``application/octet-stream`` by default, but this can be overridden inside the function with :ref:`guc_resp_hdrs`.
- It overrides all other handlers (:ref:`builtin <builtin_media>` or custom), so it's better to do it for an isolated function or view.
- Responds to all media types and even to requests that don't include an ``Accept`` header.
- Sets the ``Content-Type`` header to ``application/octet-stream`` by default, but this can be overridden inside the function with :ref:`guc_resp_hdrs`.
- This overrides all other handlers (:ref:`builtin <builtin_media>` or custom), so it's better to do it for an isolated function or view.
Let's define an any handler for a view that will always respond with ``XML`` output. It will accept ``text/xml``, ``application/xml``, ``*/*`` and reject other media types.
+11 -5
View File
@@ -11,7 +11,7 @@ PostgREST automatically serves a full `OpenAPI <https://www.openapis.org/>`_ des
For extra customization, the OpenAPI output contains a "description" field for every `SQL comment <https://www.postgresql.org/docs/current/sql-comment.html>`_ on any database object. For instance,
.. code-block:: postgres
.. code-block:: sql
COMMENT ON SCHEMA mammals IS
'A warm-blooded vertebrate animal of a class that is distinguished by the secretion of milk by females for the nourishment of the young';
@@ -26,7 +26,7 @@ These unsavory comments will appear in the generated JSON as the fields, ``info.
Also if you wish to generate a ``summary`` field you can do it by having a multiple line comment, the ``summary`` will be the first line and the ``description`` the lines that follow it:
.. code-block:: postgres
.. code-block:: plpgsql
COMMENT ON TABLE entities IS
$$Entities summary
@@ -37,7 +37,7 @@ Also if you wish to generate a ``summary`` field you can do it by having a multi
Similarly, you can override the API title by commenting the schema.
.. code-block:: postgres
.. code-block:: plpgsql
COMMENT ON SCHEMA api IS
$$FooBar API
@@ -81,9 +81,15 @@ You can override the whole default response with a function result. To do this,
end
$_$ language plpgsql;
.. code-block:: bash
.. tabs::
curl http://localhost:3000
.. code-tab:: http
GET / HTTP/1.1
.. code-tab:: bash Curl
curl http://localhost:3000
.. code-block:: http
+8 -2
View File
@@ -7,9 +7,15 @@ You can verify which HTTP methods are allowed on endpoints for tables and views
For a table named ``people``, OPTIONS would show:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" -X OPTIONS -i
.. code-tab:: http
OPTIONS /people HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people" -X OPTIONS -i
.. code-block:: http
+64 -20
View File
@@ -23,9 +23,15 @@ Query Parameters
One way to request limits and offsets is by using query parameters. For example:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?limit=15&offset=30"
.. code-tab:: http
GET /people?limit=15&offset=30 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?limit=15&offset=30"
This method is also useful for embedded resources, which we will cover in another section. The server always responds with range headers even if you use query parameters to limit the query.
@@ -35,11 +41,19 @@ Range Header
You can use headers to specify the range of rows desired.
This request gets the first twenty people:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" -i \
-H "Range-Unit: items" \
-H "Range: 0-19"
.. code-tab:: http
GET /people HTTP/1.1
Range-Unit: items
Range: 0-19
.. code-tab:: bash Curl
curl "http://localhost:3000/people" -i \
-H "Range-Unit: items" \
-H "Range: 0-19"
Note that the server may respond with fewer if unable to meet your request:
@@ -68,12 +82,21 @@ Exact Count
To get the exact count, use ``Prefer: count=exact``.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/bigtable" -I \
-H "Range-Unit: items" \
-H "Range: 0-24" \
-H "Prefer: count=exact"
.. code-tab:: http
HEAD /bigtable HTTP/1.1
Range-Unit: items
Range: 0-24
Prefer: count=exact
.. code-tab:: bash Curl
curl "http://localhost:3000/bigtable" -I \
-H "Range-Unit: items" \
-H "Range: 0-24" \
-H "Prefer: count=exact"
Note that the larger the table the slower this query runs in the database. The server will respond with the selected range and total
@@ -91,10 +114,17 @@ Planned Count
To avoid the shortcomings of :ref:`exact count <exact_count>`, PostgREST can leverage PostgreSQL statistics and get a fairly accurate and fast count.
To do this, specify the ``Prefer: count=planned`` header.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/bigtable?limit=25" -I \
-H "Prefer: count=planned"
.. code-tab:: http
HEAD /bigtable?limit=25 HTTP/1.1
Prefer: count=planned
.. code-tab:: bash Curl
curl "http://localhost:3000/bigtable?limit=25" -I \
-H "Prefer: count=planned"
.. code-block:: http
@@ -121,10 +151,17 @@ defined by :ref:`db-max-rows`.
Here's an example. Suppose we set ``db-max-rows=1000`` and ``smalltable`` has 321 rows, then we'll get the exact count:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/smalltable?limit=25" -I \
-H "Prefer: count=estimated"
.. code-tab:: http
HEAD /smalltable?limit=25 HTTP/1.1
Prefer: count=estimated
.. code-tab:: bash Curl
curl "http://localhost:3000/smalltable?limit=25" -I \
-H "Prefer: count=estimated"
.. code-block:: http
@@ -133,10 +170,17 @@ Here's an example. Suppose we set ``db-max-rows=1000`` and ``smalltable`` has 32
If we make a similar request on ``bigtable``, which has 3573458 rows, we would get the planned count:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/bigtable?limit=25" -I \
-H "Prefer: count=estimated"
.. code-tab:: http
HEAD /bigtable?limit=25 HTTP/1.1
Prefer: count=estimated
.. code-tab:: bash Curl
curl "http://localhost:3000/bigtable?limit=25" -I \
-H "Prefer: count=estimated"
.. code-block:: http
+83 -112
View File
@@ -13,9 +13,6 @@ The following preferences are supported.
- ``Prefer: count``. See :ref:`prefer_count`.
- ``Prefer: resolution``. See :ref:`prefer_resolution`.
- ``Prefer: missing``. See :ref:`bulk_insert_default`.
- ``Prefer: max-affected``, See :ref:`prefer_max_affected`.
- ``Prefer: tx``. See :ref:`prefer_tx`.
- ``Prefer: params``. See :ref:`prefer_params`.
.. _prefer_handling:
@@ -26,10 +23,17 @@ The server ignores unrecognized or unfulfillable preferences by default. You can
``handling=strict`` will throw an error if you specify invalid preferences. For instance:
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/projects" \
-H "Prefer: handling=strict, foo, bar"
.. code-tab:: http
GET /projects HTTP/1.1
Prefer: handling=strict, foo, bar
.. code-tab:: bash Curl
curl -i "http://localhost:3000/projects" \
-H "Prefer: handling=strict, foo, bar"
.. code-block:: http
@@ -48,10 +52,17 @@ The server ignores unrecognized or unfulfillable preferences by default. You can
``handling=lenient`` ignores invalid preferences.
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/projects" \
-H "Prefer: handling=lenient, foo, bar"
.. code-tab:: http
GET /projects HTTP/1.1
Prefer: handling=lenient, foo, bar
.. code-tab:: bash Curl
curl -i "http://localhost:3000/projects" \
-H "Prefer: handling=lenient, foo, bar"
.. code-block:: http
@@ -63,13 +74,20 @@ The server ignores unrecognized or unfulfillable preferences by default. You can
Timezone
========
The ``timezone`` preference allows you to change the `PostgreSQL timezone <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-TIMEZONE>`_. It accepts all time zones in `pg_timezone_names <https://www.postgresql.org/docs/current/view-pg-timezone-names.html>`_.
The ``timezone`` preference allows you to change the `PostgreSQL timezone <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-TIMEZONE>`_. It accepts all timezones in `pg_timezone_names <https://www.postgresql.org/docs/current/view-pg-timezone-names.html>`_.
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/timestamps" \
-H "Prefer: timezone=America/Los_Angeles"
.. code-tab:: http
GET /timestamps HTTP/1.1
Prefer: timezone=America/Los_Angeles
.. code-tab:: bash Curl
curl -i "http://localhost:3000/timestamps" \
-H "Prefer: timezone=America/Los_Angeles"
.. code-block:: http
@@ -85,12 +103,19 @@ The ``timezone`` preference allows you to change the `PostgreSQL timezone <https
{"t":"2023-10-18T09:37:59.611-07:00"}
]
For an invalid time zone, PostgREST returns values with the default time zone (configured on ``postgresql.conf`` or as a setting on the :ref:`authenticator <roles>`).
For an invalid timezone, PostgREST returns values with the default timezone (configured on ``postgresql.conf`` or as a setting on the :ref:`authenticator <roles>`).
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/timestamps" \
-H "Prefer: timezone=Jupiter/Red_Spot"
.. code-tab:: http
GET /timestamps HTTP/1.1
Prefer: timezone=Jupiter/Red_Spot
.. code-tab:: bash Curl
curl -i "http://localhost:3000/timestamps" \
-H "Prefer: timezone=Jupiter/Red_Spot"
.. code-block:: http
@@ -107,12 +132,19 @@ For an invalid time zone, PostgREST returns values with the default time zone (c
Note that there's no ``Preference-Applied`` in the response.
However, with ``handling=strict``, an invalid time zone preference will throw an :ref:`error <pgrst122>`.
However, with ``handling=strict``, an invalid timezone preference will throw an :ref:`error <pgrst122>`.
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/timestamps" \
-H "Prefer: handling=strict, timezone=Jupiter/Red_Spot"
.. code-tab:: http
GET /timestamps HTTP/1.1
Prefer: handling=strict, timezone=Jupiter/Red_Spot
.. code-tab:: bash Curl
curl -i "http://localhost:3000/timestamps" \
-H "Prefer: handling=strict, timezone=Jupiter/Red_Spot"
.. code-block:: http
@@ -136,12 +168,21 @@ Headers Only
If the table has a primary key, the response can contain a :code:`Location` header describing where to find the new object by including the header :code:`Prefer: return=headers-only` in the request. Make sure that the table is not write-only, otherwise constructing the :code:`Location` header will cause a permissions error.
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/projects" -X POST \
-H "Content-Type: application/json" \
-H "Prefer: return=headers-only" \
-d '{"id":33, "name": "x"}'
.. code-tab:: http
POST /projects HTTP/1.1
Prefer: return=headers-only
{"id":33, "name": "x"}
.. code-tab:: bash Curl
curl -i "http://localhost:3000/projects" -X POST \
-H "Content-Type: application/json" \
-H "Prefer: return=headers-only" \
-d '{"id":33, "name": "x"}'
.. code-block:: http
@@ -154,101 +195,31 @@ Full
On the other end of the spectrum you can get the full created object back in the response to your request by including the header :code:`Prefer: return=representation`. That way you won't have to make another HTTP call to discover properties that may have been filled in on the server side. You can also apply the standard :ref:`v_filter` to these results.
.. code-block:: bash
.. tabs::
curl -i "http://localhost:3000/projects" -X POST \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"id":33, "name": "x"}'
.. code-tab:: http
.. code-block:: http
POST /projects HTTP/1.1
Content-Type: application/json; charset=utf-8
Prefer: return=representation
{"id":33, "name": "x"}
.. code-tab:: bash Curl
curl -i "http://localhost:3000/projects" -X POST \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"id":33, "name": "x"}'
.. code::
HTTP/1.1 201 Created
Preference-Applied: return=representation
.. code-block:: json
[
{
"id": 33,
"name": "x"
}
]
.. _prefer_tx:
Transaction End Preference
==========================
The ``tx`` preference can be set to specify if the :ref:`transaction <transactions>` will end in a COMMIT or ROLLBACK. This preference is not enabled by default but can be activated with :ref:`db-tx-end`.
.. code-block:: bash
curl -i "http://localhost:3000/projects" -X POST \
-H "Content-Type: application/json" \
-H "Prefer: tx=rollback, return=representation" \
-d '{"name": "Project X"}'
.. code-block:: http
HTTP/1.1 200 OK
Preference-Applied: tx=rollback, return=representation
{"id": 35, "name": "Project X"}
.. _prefer_max_affected:
Max Affected
============
You can set a limit to the amount of resources affected in a request by sending ``max-affected`` preference. This feature works in combination with ``handling=strict`` preference. ``max-affected`` would be ignored with lenient handling. The "affected resources" are the number of rows returned by ``DELETE`` and ``PATCH`` requests. This is also supported through ``RPC`` calls.
To illustrate the use of this preference, consider the following scenario where the ``items`` table contains 14 rows.
.. code-block:: bash
curl -i "http://localhost:3000/items?id=lt.15 -X DELETE \
-H "Content-Type: application/json" \
-H "Prefer: handling=strict, max-affected=10"
.. code-block:: http
HTTP/1.1 400 Bad Request
.. code-block:: json
{
"code": "PGRST124",
"message": "Query result exceeds max-affected preference constraint",
"details": "The query affects 14 rows",
"hint": null
}
.. _prefer_params:
Single JSON object as Function Parameter
----------------------------------------
.. warning::
Using this preference is **deprecated** in favor of :ref:`function_single_json`.
:code:`Prefer: params=single-object` allows sending the JSON request body as the single argument of a :ref:`function <functions>`.
.. code-block:: postgres
CREATE FUNCTION mult_them(param json) RETURNS int AS $$
SELECT (param->>'x')::int * (param->>'y')::int
$$ LANGUAGE SQL;
.. code-block:: bash
curl "http://localhost:3000/rpc/mult_them" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: params=single-object" \
-d '{ "x": 4, "y": 2 }'
.. code-block:: json
8
+283 -224
View File
@@ -57,11 +57,11 @@ For example, consider a database of films and their awards:
language text
);
create table technical_specs(
film_id int references films(id) primary key,
runtime time,
camera text,
sound text
CREATE TABLE technical_specs(
film_id INT REFERENCES films UNIQUE,
runtime TIME,
camera TEXT,
sound TEXT
);
create table roles(
@@ -91,9 +91,15 @@ Many-to-one relationships
Since ``films`` has a **foreign key** to ``directors``, this establishes a many-to-one relationship. This enables us to request all the films and the director for each film.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/films?select=title,directors(id,last_name)"
.. code-tab:: http
GET /films?select=title,directors(id,last_name) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,directors(id,last_name)"
.. code-block:: json
@@ -122,9 +128,15 @@ Note that the embedded ``directors`` is returned as a JSON object because of the
Since the table name is plural, we can be more accurate by making it singular with an alias.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/films?select=title,director:directors(id,last_name)"
.. code-tab:: http
GET /films?select=title,director:directors(id,last_name) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,director:directors(id,last_name)"
.. code-block:: json
@@ -145,9 +157,15 @@ One-to-many relationships
The **foreign key reference** establishes the inverse one-to-many relationship. In this case, ``films`` returns as a JSON array because of the “to-many” end.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/directors?select=last_name,films(title)"
.. code-tab:: http
GET /directors?select=last_name,films(title) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/directors?select=last_name,films(title)"
.. code-block:: json
@@ -178,7 +196,7 @@ The join table determines many-to-many relationships. It must contain foreign ke
The join table is also detected if the composite key has additional columns.
.. code-block:: postgres
.. code-block:: postgresql
create table roles(
id int generated always as identity,
@@ -188,9 +206,15 @@ The join table is also detected if the composite key has additional columns.
, primary key(id, film_id, actor_id)
);
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/actors?select=first_name,last_name,films(title)"
.. code-tab:: http
GET /actors?select=first_name,last_name,films(title) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/actors?select=first_name,last_name,films(title)"
.. code-block:: json
@@ -214,18 +238,24 @@ One-to-one relationships are detected in two ways.
- When the foreign key is a primary key as specified in the :ref:`sample film database <erd_film>`.
- When the foreign key has a unique constraint.
.. code-block:: postgres
.. code-block:: postgresql
create table technical_specs(
film_id int references films(id) unique,
runtime time,
camera text,
sound text
CREATE TABLE technical_specs(
film_id INT REFERENCES films UNIQUE,
runtime TIME,
camera TEXT,
sound TEXT
);
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/films?select=title,technical_specs(camera)"
.. code-tab:: http
GET /films?select=title,technical_specs(camera) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,technical_specs(camera)"
.. code-block:: json
@@ -246,7 +276,7 @@ You can manually define relationships by using functions. This is useful for dat
Assuming there's a foreign table ``premieres`` that we want to relate to ``films``.
.. code-block:: postgres
.. code-block:: postgresql
create foreign table premieres (
id integer,
@@ -262,9 +292,15 @@ Assuming there's a foreign table ``premieres`` that we want to relate to ``films
The above function defines a relationship between ``premieres`` (the parameter) and ``films`` (the return type). Since there's a ``rows 1``, this defines a many-to-one relationship.
The name of the function ``film`` is arbitrary and can be used to do the embedding:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/premieres?select=location,film(name)"
.. code-tab:: http
GET /premieres?select=location,film(name) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/premieres?select=location,film(name)"
.. code-block:: json
@@ -287,9 +323,15 @@ Now let's define the opposite one-to-many relationship.
In this case there's an implicit ``ROWS 1000`` defined by PostgreSQL(`search "result_rows" on this PostgreSQL doc <https://www.postgresql.org/docs/current/sql-createfunction.html>`_).
We consider any value greater than 1 as "many" so this defines a one-to-many relationship.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/films?select=name,premieres(name)"
.. code-tab:: http
GET /films?select=name,premieres(name) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=name,premieres(name)"
.. code-block:: json
@@ -375,9 +417,15 @@ For example, suppose you have the following ``orders`` and ``addresses`` tables:
Since the ``orders`` table has two foreign keys to the ``addresses`` table, a foreign key join is ambiguous and PostgREST will respond with an error:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/orders?select=*,addresses(*)" -i
.. code-tab:: http
GET /orders?select=*,addresses(*) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=*,addresses(*)" -i
.. code-block:: http
@@ -407,12 +455,15 @@ Since the ``orders`` table has two foreign keys to the ``addresses`` table, a fo
To successfully join ``orders`` with ``addresses``, we can follow the error ``hint`` which tells us to add the foreign key name as ``!billing`` or ``!shipping``.
Note that the foreign keys have been named explicitly in the :ref:`SQL definition above <multiple_m2o>`. To make the result clearer we'll also alias the tables:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/orders?select=name,billing_address:addresses!billing(name),shipping_address:addresses!shipping(name)"
.. code-tab:: http
curl --get "http://localhost:3000/orders" \
-d "select=name,billing_address:addresses!billing(name),shipping_address:addresses!shipping(name)"
GET /orders?select=name,billing_address:addresses!billing(name),shipping_address:addresses!shipping(name) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/orders?select=name,billing_address:addresses!billing(name),shipping_address:addresses!shipping(name)"
.. code-block:: json
@@ -435,13 +486,15 @@ Multiple One-To-Many
Let's take the tables from :ref:`multiple_m2o`. To get the opposite one-to-many relationship, we can also specify the foreign key name:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/addresses?select=name,billing_orders:orders!billing(name),shipping_orders!shipping(name)&id=eq.1"
.. code-tab:: http
curl --get "http://localhost:3000/addresses" \
-d "select=name,billing_orders:orders!billing(name),shipping_orders!shipping(name)" \
-d "id=eq.1"
GET /addresses?select=name,billing_orders:orders!billing(name),shipping_orders!shipping(name)&id=eq.1 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/addresses?select=name,billing_orders:orders!billing(name),shipping_orders!shipping(name)&id=eq.1"
.. code-block:: json
@@ -485,7 +538,7 @@ Recursive One-To-One
To get either side of the Recursive One-To-One relationship, create the functions:
.. code-block:: postgres
.. code-block:: postgresql
create or replace function predecessor(presidents) returns setof presidents rows 1 as $$
select * from presidents where id = $1.predecessor_id
@@ -497,13 +550,15 @@ To get either side of the Recursive One-To-One relationship, create the function
Now, to query a president with their predecessor and successor:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/presidents?select=last_name,predecessor(last_name),successor(last_name)&id=eq.2"
.. code-tab:: http
curl --get "http://localhost:3000/presidents" \
-d "select=last_name,predecessor(last_name),successor(last_name)" \
-d "id=eq.2"
GET /presidents?select=last_name,predecessor(last_name),successor(last_name)&id=eq.2 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/presidents?select=last_name,predecessor(last_name),successor(last_name)&id=eq.2"
.. code-block:: json
@@ -541,7 +596,7 @@ Recursive One-To-Many
To get the One-To-Many embedding, that is, the supervisors with their supervisees, create a function like this one:
.. code-block:: postgres
.. code-block:: postgresql
create or replace function supervisees(employees) returns setof employees as $$
select * from employees where supervisor_id = $1.id
@@ -549,13 +604,15 @@ To get the One-To-Many embedding, that is, the supervisors with their supervisee
Now, the query would be:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/employees?select=last_name,supervisees(last_name)&id=eq.1"
.. code-tab:: http
curl --get "http://localhost:3000/employees" \
-d "select=last_name,supervisees(last_name)" \
-d "id=eq.1"
GET /employees?select=last_name,supervisees(last_name)&id=eq.1 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/employees?select=last_name,supervisees(last_name)&id=eq.1"
.. code-block:: json
@@ -577,7 +634,7 @@ Recursive Many-To-One
Let's take the same ``employees`` table from :ref:`recursive_o2m_embed`.
To get the Many-To-One relationship, that is, the employees with their respective supervisor, you need to create a function like this one:
.. code-block:: postgres
.. code-block:: postgresql
create or replace function supervisor(employees) returns setof employees rows 1 as $$
select * from employees where id = $1.supervisor_id
@@ -585,13 +642,15 @@ To get the Many-To-One relationship, that is, the employees with their respectiv
Then, the query would be:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/employees?select=last_name,supervisor(last_name)&id=eq.3"
.. code-tab:: http
curl --get "http://localhost:3000/employees" \
-d "select=last_name,supervisor(last_name)" \
-d "id=eq.3"
GET /employees?select=last_name,supervisor(last_name)&id=eq.3 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/employees?select=last_name,supervisor(last_name)&id=eq.3"
.. code-block:: json
@@ -633,7 +692,7 @@ Recursive Many-To-Many
To get all the subscribers of a user as well as the ones they're following, define these functions:
.. code-block:: postgres
.. code-block:: postgresql
create or replace function subscribers(users) returns setof users as $$
select u.*
@@ -653,13 +712,15 @@ To get all the subscribers of a user as well as the ones they're following, defi
Then, the request would be:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/users?select=username,subscribers(username),following(username)&id=eq.4"
.. code-tab:: http
curl --get "http://localhost:3000/users" \
-d "select=username,subscribers(username),following(username)" \
-d "id=eq.4"
GET /users?select=username,subscribers(username),following(username)&id=eq.4 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/users?select=username,subscribers(username),following(username)&id=eq.4"
.. code-block:: json
@@ -712,13 +773,15 @@ For example, let's create the ``box_office`` partitioned table that has the gros
Since it contains the ``films_id`` foreign key, it is possible to join ``box_office`` and ``films``:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/box_office?select=bo_date,gross_revenue,films(title)&gross_revenue=gte.1000000"
.. code-tab:: http
curl --get "http://localhost:3000/box_office" \
-d "select=bo_date,gross_revenue,films(title)" \
-d "gross_revenue=gte.1000000"
GET /box_office?select=bo_date,gross_revenue,films(title)&gross_revenue=gte.1000000 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/box_office?select=bo_date,gross_revenue,films(title)&gross_revenue=gte.1000000"
.. note::
@@ -751,13 +814,15 @@ For instance, the following view has ``nominations``, ``films`` and ``competitio
Since this view contains ``nominations.film_id``, which has a **foreign key** relationship to ``films``, then we can join the ``films`` table. Similarly, because the view contains ``films.id``, then we can also join the ``roles`` and the ``actors`` tables (the last one in a many-to-many relationship):
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/nominations_view?select=film_title,films(language),roles(character),actors(last_name,first_name)&rank=eq.5"
.. code-tab:: http
curl --get "http://localhost:3000/nominations_view" \
-d "select=film_title,films(language),roles(character),actors(last_name,first_name)" \
-d "rank=eq.5"
GET /nominations_view?select=film_title,films(language),roles(character),actors(last_name,first_name)&rank=eq.5 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/nominations_view?select=film_title,films(language),roles(character),actors(last_name,first_name)&rank=eq.5"
It's also possible to foreign key join `Materialized Views <https://www.postgresql.org/docs/current/rules-materializedviews.html>`_.
@@ -778,16 +843,16 @@ Foreign Key Joins on Chains of Views
Views can also depend on other views, which in turn depend on the actual base table. For PostgREST to pick up those chains recursively to any depth, all the views must be in the search path, so either in the exposed schema (:ref:`db-schemas`) or in one of the schemas set in :ref:`db-extra-search-path`. This does not apply to the base table, which could be in a private schema as well. See :ref:`schema_isolation` for more details.
.. _function_embed:
.. _s_proc_embed:
Foreign Key Joins on Table-Valued Functions
===========================================
If you have a :ref:`Function <functions>` that returns a table type, you can do a Foreign Key join on the result.
If you have a :ref:`Stored Procedure <s_procs>` that returns a table type, you can do a Foreign Key join on the result.
Here's a sample function (notice the ``RETURNS SETOF films``).
.. code-block:: postgres
.. code-block:: plpgsql
CREATE FUNCTION getallfilms() RETURNS SETOF films AS $$
SELECT * FROM films;
@@ -795,13 +860,15 @@ Here's a sample function (notice the ``RETURNS SETOF films``).
A request with ``directors`` embedded:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/rpc/getallfilms?select=title,directors(id,last_name)&title=like.*Workers*"
.. code-tab:: http
curl --get "http://localhost:3000/rpc/getallfilms" \
-d "select=title,directors(id,last_name)" \
-d "title=like.*Workers*"
GET /rpc/getallfilms?select=title,directors(id,last_name)&title=like.*Workers* HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/getallfilms?select=title,directors(id,last_name)&title=like.*Workers*"
.. code-block:: json
@@ -823,20 +890,36 @@ You can join related database objects after doing :ref:`insert`, :ref:`update` o
Say you want to insert a **film** and then get some of its attributes plus join its **director**.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/films?select=title,year,director:directors(first_name,last_name)" \
-H "Prefer: return=representation" \
-d @- << EOF
{
.. code-tab:: http
POST /films?select=title,year,director:directors(first_name,last_name) HTTP/1.1
Prefer: return=representation
{
"id": 100,
"director_id": 40,
"title": "127 hours",
"year": 2010,
"rating": 7.6,
"language": "english"
}
EOF
}
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,year,director:directors(first_name,last_name)" \
-H "Prefer: return=representation" \
-d @- << EOF
{
"id": 100,
"director_id": 40,
"title": "127 hours",
"year": 2010,
"rating": 7.6,
"language": "english"
}
EOF
Response:
@@ -858,9 +941,15 @@ Nested Embedding
If you want to embed through join tables but need more control on the intermediate resources, you can do nested embedding. For instance, you can request the Actors, their Roles and the Films for those Roles:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/actors?select=roles(character,films(title,year))"
.. code-tab:: http
GET /actors?select=roles(character,films(title,year)) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/actors?select=roles(character,films(title,year))"
.. _embed_filters:
@@ -869,65 +958,77 @@ Embedded Filters
Embedded resources can be shaped similarly to their top-level counterparts. To do so, prefix the query parameters with the name of the embedded resource. For instance, to order the actors in each film:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=*,actors(*)&actors.order=last_name,first_name"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=*,actors(*)" \
-d "actors.order=last_name,first_name"
GET /films?select=*,actors(*)&actors.order=last_name,first_name HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=*,actors(*)&actors.order=last_name,first_name"
This sorts the list of actors in each film but does *not* change the order of the films themselves. To filter the roles returned with each film:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=*,roles(*)&roles.character=in.(Chico,Harpo,Groucho)"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=*,roles(*)" \
-d "roles.character=in.(Chico,Harpo,Groucho)"
GET /films?select=*,roles(*)&roles.character=in.(Chico,Harpo,Groucho) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=*,roles(*)&roles.character=in.(Chico,Harpo,Groucho)"
Once again, this restricts the roles included to certain characters but does not filter the films in any way. Films without any of those characters would be included along with empty character lists.
An ``or`` filter can be used for a similar operation:
An ``or`` filter can be used for a similar operation:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=*,roles(*)&roles.or=(character.eq.Gummo,character.eq.Zeppo)"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=*,roles(*)" \
-d "roles.or=(character.eq.Gummo,character.eq.Zeppo)"
GET /films?select=*,roles(*)&roles.or=(character.eq.Gummo,character.eq.Zeppo) HTTP/1.1
However, this only works for columns inside ``roles``. See :ref:`how to use "or" across multiple resources <or_embed_rels>`.
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=*,roles(*)&roles.or=(character.eq.Gummo,character.eq.Zeppo)"
Limit and offset operations are possible:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=*,actors(*)&actors.limit=10&actors.offset=2"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=*,actors(*)" \
-d "actors.limit=10" \
-d "actors.offset=2"
GET /films?select=*,actors(*)&actors.limit=10&actors.offset=2 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=*,actors(*)&actors.limit=10&actors.offset=2"
Embedded resources can be aliased and filters can be applied on these aliases:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=*,actors(*)&actors.limit=10&actors.offset=2"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=*,90_comps:competitions(name),91_comps:competitions(name)" \
-d "90_comps.year=eq.1990" \
-d "91_comps.year=eq.1991"
GET /films?select=*,90_comps:competitions(name),91_comps:competitions(name)&90_comps.year=eq.1990&91_comps.year=eq.1991 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=*,90_comps:competitions(name),91_comps:competitions(name)&90_comps.year=eq.1990&91_comps.year=eq.1991"
Filters can also be applied on nested embedded resources:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/films?select=*,roles(*,actors(*))&roles.actors.order=last_name&roles.actors.first_name=like.*Tom*"
.. code-tab:: http
GET /films?select=*,roles(*,actors(*))&roles.actors.order=last_name&roles.actors.first_name=like.*Tom* HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=*,roles(*,actors(*))&roles.actors.order=last_name&roles.actors.first_name=like.*Tom*"
The result will show the nested actors named Tom and order them by last name. Aliases can also be used instead of the resource names to filter the nested tables.
@@ -938,13 +1039,15 @@ Top-level Filtering
By default, :ref:`embed_filters` don't change the top-level resource(``films``) rows at all:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,actors(first_name,last_name)&actors.first_name=eq.Jehanne
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,actors(first_name,last_name)" \
-d "actors.first_name=eq.Jehanne"
GET /films?select=title,actors(first_name,last_name)&actors.first_name=eq.Jehanne HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,actors(first_name,last_name)&actors.first_name=eq.Jehanne
.. code-block:: json
@@ -970,13 +1073,15 @@ By default, :ref:`embed_filters` don't change the top-level resource(``films``)
In order to filter the top level rows you need to add ``!inner`` to the embedded resource. For instance, to get **only** the films that have an actor named ``Jehanne``:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,actors!inner(first_name,last_name)&actors.first_name=eq.Jehanne"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,actors!inner(first_name,last_name)" \
-d "actors.first_name=eq.Jehanne"
GET /films?select=title,actors!inner(first_name,last_name)&actors.first_name=eq.Jehanne HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,actors!inner(first_name,last_name)&actors.first_name=eq.Jehanne"
.. code-block:: json
@@ -1001,94 +1106,40 @@ Null filtering on the embedded resources can behave the same as ``!inner``. Whil
For example, doing ``actors=not.is.null`` returns the same result as ``actors!inner(*)``:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,actors(*)&actors=not.is.null"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,actors(*)" \
-d "actors=not.is.null"
GET /films?select=title,actors(*)&actors=not.is.null HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,actors(*)&actors=not.is.null"
The ``is.null`` filter can be used in embedded resources to perform an anti-join. To get all the films that do not have any nominations:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,nominations()&nominations=is.null"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,nominations()" \
-d "nominations=is.null"
GET /films?select=title,nominations()&nominations=is.null HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,nominations()&nominations=is.null"
Both ``is.null`` and ``not.is.null`` can be included inside the `or` operator. For instance, to get the films that have no actors **or** directors registered yet:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,nominations()&nominations=is.null"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d select=title,actors(*),directors(*)" \
-d "or=(actors.is.null,directors.is.null)"
GET /films?select=title,actors(*),directors(*)&or=(actors.is.null,directors.is.null) HTTP/1.1
.. _or_embed_rels:
.. code-tab:: bash Curl
OR filtering across Embedded Resources
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can also use ``not.is.null`` to make an ``or`` filter across multiple resources.
For instance, to show the films with actors **or** directors named John:
.. code-block:: bash
# curl "http://localhost:3000/films?select=title,actors(),directors()&directors.first_name=eq.John&actors.first_name=eq.John&or=(directors.not.is.null,actors.not.is.null)"
curl --get "http://localhost:3000/films" \
-d "select=title,actors(),directors()" \
-d "directors.first_name=eq.John" \
-d "actors.first_name=eq.John" \
-d "or=(directors.not.is.null,actors.not.is.null)"
.. code-block:: json
[
{ "title": "Pulp Fiction" },
{ "title": "The Thing" },
".."
]
Here, we use :ref:`empty embeds <empty_embed>` because retrieving their info would be restricted by the filters.
For example, the ``directors`` embedding would return ``null`` if its ``first_name`` is not John.
To solve this, you need to add extra embedded resources and use the empty ones for filtering.
From the above example:
.. code-block:: bash
# curl "http://localhost:3000/films?select=title,act:actors(),dir:directors(),actors(first_name),directors(first_name)&dir.first_name=eq.John&act.first_name=eq.John&or=(dir.not.is.null,act.not.is.null)"
curl --get "http://localhost:3000/films" \
# We need to use aliases like "act" and "dir" to filter the empty embeds
-d "select=title,act:actors(),dir:directors(),actors(first_name),directors(first_name)" \
-d "dir.first_name=eq.John" \
-d "act.first_name=eq.John" \
-d "or=(dir.not.is.null,act.not.is.null)"
.. code-block:: json
[
{
"title": "Pulp Fiction",
"actors": [
{ "first_name": "John" },
{ "first_name": "Samuel" },
{ "first_name": "Uma" },
".."
]
"directors": {
"first_name": "Quentin"
}
},
".."
]
curl "http://localhost:3000/films?select=title,actors(*),directors(*)&or=(actors.is.null,directors.is.null)"
.. _empty_embed:
@@ -1099,14 +1150,15 @@ You can leave an embedded resource empty, this helps with filtering in some case
To filter the films by actors but not include them:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,actors()&actors.first_name=eq.Jehanne&actors=not.is.null"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,actors()" \
-d "actors.first_name=eq.Jehanne" \
-d "actors=not.is.null"
GET /films?select=title,actors()&actors.first_name=eq.Jehanne&actors=not.is.null HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,actors()&actors.first_name=eq.Jehanne&actors=not.is.null"
.. code-block:: json
@@ -1125,13 +1177,15 @@ On :ref:`Many-to-One <many-to-one>` and :ref:`One-to-One <one-to-one>` relations
For example, to arrange the films in descending order using the director's last name.
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,directors(last_name)&order=directors(last_name).desc"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,directors(last_name)" \
-d "order=directors(last_name).desc"
GET /films?select=title,directors(last_name)&order=directors(last_name).desc HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,directors(last_name)&order=directors(last_name).desc"
.. _spread_embed:
@@ -1140,13 +1194,15 @@ Spread embedded resource
On many-to-one and one-to-one relationships, you can "spread" the embedded resource. That is, remove the surrounding JSON object for the embedded resource columns.
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,...directors(director_last_name:last_name)&title=like.*Workers*"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,...directors(director_last_name:last_name)" \
-d "title=like.*Workers*"
GET /films?select=title,...directors(director_last_name:last_name)&title=like.*Workers* HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,...directors(director_last_name:last_name)&title=like.*Workers*"
.. code-block:: json
@@ -1161,13 +1217,15 @@ Note that there is no ``"directors"`` object. Also the embed columns can be alia
You can use this to get the columns of a join table in a many-to-many relationship. For instance, to get films and its actors, but including the ``character`` column from the roles table:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/films?select=title,actors:roles(character,...actors(first_name,last_name))&title=like.*Lighthouse*"
.. code-tab:: http
curl --get "http://localhost:3000/films" \
-d "select=title,actors:roles(character,...actors(first_name,last_name))" \
-d "title=like.*Lighthouse*"
GET /films?select=title,actors:roles(character,...actors(first_name,last_name))&title=like.*Lighthouse* HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/films?select=title,actors:roles(character,...actors(first_name,last_name))&title=like.*Lighthouse*"
.. code-block:: json
@@ -1187,3 +1245,4 @@ You can use this to get the columns of a join table in a many-to-many relationsh
.. note::
The spread operator ``...`` is borrowed from the Javascript `spread syntax <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax>`_.
+45 -17
View File
@@ -11,10 +11,17 @@ Response Format
Use the Accept request header to specify the acceptable format (or formats) for the response:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" \
-H "Accept: application/json"
.. code-tab:: http
GET /people HTTP/1.1
Accept: application/json
.. code-tab:: bash Curl
curl "http://localhost:3000/people" \
-H "Accept: application/json"
.. _builtin_media:
@@ -23,7 +30,7 @@ Builtin Media Type Handlers
Builtin handlers are offered for common standard media types.
* ``text/csv`` and ``application/json``, for all API endpoints. See :ref:`tables_views` and :ref:`functions`.
* ``text/csv`` and ``application/json``, for all API endpoints. See :ref:`tables_views` and :ref:`s_procs`.
* ``application/openapi+json``, for the root endpoint. See :ref:`open-api`.
* ``application/geo+json``, see :ref:`ww_postgis`.
* ``*/*``, resolves to ``application/json`` for API endpoints and to ``application/openapi+json`` for the root endpoint.
@@ -35,10 +42,17 @@ The following vendor media types handlers are also supported.
Any unrecognized media type will throw an error.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" \
-H "Accept: unknown/unknown"
.. code-tab:: http
GET /people HTTP/1.1
Accept: unknown/unknown
.. code-tab:: bash Curl
curl "http://localhost:3000/people" \
-H "Accept: unknown/unknown"
.. code-block:: http
@@ -63,10 +77,17 @@ By default PostgREST returns all JSON results in an array, even when there is on
This can be inconvenient for client code. To return the first result as an object unenclosed by an array, specify :code:`vnd.pgrst.object` as part of the :code:`Accept` header
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/items?id=eq.1" \
-H "Accept: application/vnd.pgrst.object+json"
.. code-tab:: http
GET /items?id=eq.1 HTTP/1.1
Accept: application/vnd.pgrst.object+json
.. code-tab:: bash Curl
curl "http://localhost:3000/items?id=eq.1" \
-H "Accept: application/vnd.pgrst.object+json"
This returns
@@ -108,12 +129,19 @@ By default PostgREST returns all JSON null values. For example, requesting ``/pr
{ "id": 13, "name": "Y", "client_id": null, "another_col": null }
]
On large result sets, the unused keys with ``null`` values can waste bandwidth unnecessarily. To remove them, specify ``nulls=stripped`` as a parameter of ``application/vnd.pgrst.array``:
On large result sets, the unused keys with ``null`` values can waste bandwith unnecessarily. To remove them, specify ``nulls=stripped`` as a parameter of ``application/vnd.pgrst.array``:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/projects?id=gt.10" \
-H "Accept: application/vnd.pgrst.array+json;nulls=stripped"
.. code-tab:: http
GET /projects?id=gt.10 HTTP/1.1
Accept: application/vnd.pgrst.array+json;nulls=stripped
.. code-tab:: bash Curl
curl "http://localhost:3000/projects?id=gt.10" \
-H "Accept: application/vnd.pgrst.array+json;nulls=stripped"
This returns
@@ -136,12 +164,12 @@ The server handles the following request body media types:
* ``application/x-www-form-urlencoded``
* ``text/csv``
For :ref:`tables_views` this works on ``POST``, ``PATCH`` and ``PUT`` methods. For :ref:`functions`, it works on ``POST`` methods.
For :ref:`tables_views` this works on ``POST``, ``PATCH`` and ``PUT`` methods. For :ref:`s_procs`, it works on ``POST`` methods.
For functions there are three additional types:
For stored procedures there are three additional types:
* ``application/octet-stream``
* ``text/plain``
* ``text/xml``
See :ref:`function_single_unnamed`.
See :ref:`s_proc_single_unnamed`.
+41 -16
View File
@@ -42,34 +42,59 @@ GET/HEAD
For GET or HEAD, select the schema with ``Accept-Profile``.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/items" \
-H "Accept-Profile: tenant2"
.. code-tab:: http
GET /items HTTP/1.1
Accept-Profile: tenant2
.. code-tab:: bash Curl
curl "http://localhost:3000/items" \
-H "Accept-Profile: tenant2"
Other methods
~~~~~~~~~~~~~
For POST, PATCH, PUT and DELETE, select the schema with ``Content-Profile``.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/items" \
-X POST -H "Content-Type: application/json" \
-H "Content-Profile: tenant2" \
-d '{...}'
.. code-tab:: http
You can also select the schema for :ref:`functions` and :ref:`open-api`.
POST /items HTTP/1.1
Content-Profile: tenant2
{...}
.. code-tab:: bash Curl
curl "http://localhost:3000/items" \
-X POST -H "Content-Type: application/json" \
-H "Content-Profile: tenant2" \
-d '{...}'
You can also select the schema for :ref:`s_procs` and :ref:`open-api`.
Restricted schemas
~~~~~~~~~~~~~~~~~~
You can only switch to a schema included in :ref:`db-schemas`. Using another schema will result in an error:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/items" \
-H "Accept-Profile: tenant3"
.. code-tab:: http
GET /items HTTP/1.1
Accept-Profile: tenant3
{...}
.. code-tab:: bash Curl
curl "http://localhost:3000/items" \
-H "Accept-Profile: tenant3"
.. code-block::
@@ -88,7 +113,7 @@ To add schemas dynamically, you can use :ref:`in_db_config` plus :ref:`config re
- If the schemas' names have a pattern, like a ``tenant_`` prefix, do:
.. code-block:: postgres
.. code-block:: postgresql
create or replace function postgrest.pre_config()
returns void as $$
@@ -100,7 +125,7 @@ To add schemas dynamically, you can use :ref:`in_db_config` plus :ref:`config re
- If there's no name pattern but they're created with a particular role (``CREATE SCHEMA mine AUTHORIZATION joe``), do:
.. code-block:: postgres
.. code-block:: postgresql
create or replace function postgrest.pre_config()
returns void as $$
@@ -112,7 +137,7 @@ To add schemas dynamically, you can use :ref:`in_db_config` plus :ref:`config re
- Otherwise, you might need to create a table that stores the allowed schemas.
.. code-block:: postgres
.. code-block:: postgresql
create table postgrest.config (schemas text);
@@ -125,7 +150,7 @@ To add schemas dynamically, you can use :ref:`in_db_config` plus :ref:`config re
Then each time you add an schema, do:
.. code-block:: postgres
.. code-block:: postgresql
NOTIFY pgrst, 'reload config';
NOTIFY pgrst, 'reload schema';
@@ -1,25 +1,21 @@
.. _functions:
.. _s_procs:
Functions as RPC
================
Stored Procedures
=================
*"A single resource can be the equivalent of a database function, with the power to abstract state changes over any number of storage items"* -- `Roy T. Fielding <http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743>`_
*"A single resource can be the equivalent of a database stored procedure, with the power to abstract state changes over any number of storage items"* -- `Roy T. Fielding <http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-743>`_
Functions can perform any operation allowed by PostgreSQL (read data, modify data, :ref:`raise errors <raise_error>`, and even DDL operations). Every function in the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` is executable under the :code:`/rpc` prefix.
Procedures can perform any operations allowed by PostgreSQL (read data, modify data, :ref:`raise errors <raise_error>`, and even DDL operations). Every stored procedure in the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` is executable under the :code:`/rpc` prefix.
If they return table types, functions can:
If they return table types, Stored Procedures can:
- Use all the same :ref:`read filters as Tables and Views <read>` (horizontal/vertical filtering, counts, limits, etc.).
- Use :ref:`Resource Embedding <function_embed>`, if the returned table type has relationships to other tables.
- Use :ref:`Resource Embedding <s_proc_embed>`, if the returned table type has relationships to other tables.
.. note::
Why the ``/rpc`` prefix? PostgreSQL allows a table or view to have the same name as a function. The prefix allows us to avoid routes collisions.
.. warning::
`Stored Procedures <https://www.postgresql.org/docs/current/xproc.html>`_ are not supported.
Calling with POST
-----------------
@@ -27,7 +23,7 @@ To supply arguments in an API call, include a JSON object in the request payload
For instance, assume we have created this function in the database.
.. code-block:: postgres
.. code-block:: plpgsql
CREATE FUNCTION add_them(a integer, b integer)
RETURNS integer AS $$
@@ -40,11 +36,19 @@ For instance, assume we have created this function in the database.
The client can call it by posting an object like
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/add_them" \
-X POST -H "Content-Type: application/json" \
-d '{ "a": 1, "b": 2 }'
.. code-tab:: http
POST /rpc/add_them HTTP/1.1
{ "a": 1, "b": 2 }
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/add_them" \
-X POST -H "Content-Type: application/json" \
-d '{ "a": 1, "b": 2 }'
.. code-block:: json
@@ -63,31 +67,80 @@ Calling with GET
If the function doesn't modify the database, it will also run under the GET method (see :ref:`access_mode`).
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
.. code-tab:: http
GET /rpc/add_them?a=1&b=2 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
The function parameter names match the JSON object keys in the POST case, for the GET case they match the query parameters ``?a=1&b=2``.
.. _function_single_json:
.. _s_proc_single_json:
Functions with a single unnamed JSON parameter
----------------------------------------------
Functions with a single JSON parameter
--------------------------------------
If you want the JSON request body to be sent as a single argument, you can create a function with a single unnamed ``json`` or ``jsonb`` parameter.
For this the ``Content-Type: application/json`` header must be included in the request.
You can also call a function that takes a single parameter of type JSON by sending the header :code:`Prefer: params=single-object` with your request. That way the JSON request body will be used as the single argument.
.. code-block:: postgres
.. code-block:: plpgsql
CREATE FUNCTION mult_them(param json) RETURNS int AS $$
SELECT (param->>'x')::int * (param->>'y')::int
$$ LANGUAGE SQL;
.. tabs::
.. code-tab:: http
POST /rpc/mult_them HTTP/1.1
Prefer: params=single-object
{ "x": 4, "y": 2 }
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/mult_them" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: params=single-object" \
-d '{ "x": 4, "y": 2 }'
.. code-block:: json
8
.. _s_proc_single_unnamed:
Functions with a single unnamed parameter
-----------------------------------------
You can make a POST request to a function with a single unnamed parameter to send raw ``json/jsonb``, ``bytea``, ``text`` or ``xml`` data.
To send raw JSON, the function must have a single unnamed ``json`` or ``jsonb`` parameter and the header ``Content-Type: application/json`` must be included in the request.
.. code-block:: plpgsql
CREATE FUNCTION mult_them(json) RETURNS int AS $$
SELECT ($1->>'x')::int * ($1->>'y')::int
$$ LANGUAGE SQL;
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/mult_them" \
-X POST -H "Content-Type: application/json" \
-d '{ "x": 4, "y": 2 }'
.. code-tab:: http
POST /rpc/mult_them HTTP/1.1
Content-Type: application/json
{ "x": 4, "y": 2 }
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/mult_them" \
-X POST -H "Content-Type: application/json" \
-d '{ "x": 4, "y": 2 }'
.. code-block:: json
@@ -95,24 +148,13 @@ For this the ``Content-Type: application/json`` header must be included in the r
.. note::
If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
.. warning::
Sending the JSON request body as a single argument is also possible with :ref:`Prefer: params=single-object <prefer_params>` but this method is **deprecated**.
.. _function_single_unnamed:
Functions with a single unnamed parameter
-----------------------------------------
You can make a POST request to a function with a single unnamed parameter to send raw ``bytea``, ``text`` or ``xml`` data.
If an overloaded function has a single ``json`` or ``jsonb`` unnamed parameter, PostgREST will call this function as a fallback provided that no other overloaded function is found with the parameters sent in the POST request.
To send raw XML, the parameter type must be ``xml`` and the header ``Content-Type: text/xml`` must be included in the request.
To send raw binary, the parameter type must be ``bytea`` and the header ``Content-Type: application/octet-stream`` must be included in the request.
.. code-block:: postgres
.. code-block:: plpgsql
CREATE TABLE files(blob bytea);
@@ -120,11 +162,20 @@ To send raw binary, the parameter type must be ``bytea`` and the header ``Conten
INSERT INTO files(blob) VALUES ($1);
$$ LANGUAGE SQL;
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/upload_binary" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary "@file_name.ext"
.. code-tab:: http
POST /rpc/upload_binary HTTP/1.1
Content-Type: application/octet-stream
file_name.ext
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/upload_binary" \
-X POST -H "Content-Type: application/octet-stream" \
--data-binary "@file_name.ext"
.. code-block:: http
@@ -134,7 +185,7 @@ To send raw binary, the parameter type must be ``bytea`` and the header ``Conten
To send raw text, the parameter type must be ``text`` and the header ``Content-Type: text/plain`` must be included in the request.
.. _functions_array:
.. _s_procs_array:
Functions with array parameters
-------------------------------
@@ -147,11 +198,20 @@ You can call a function that takes an array parameter:
SELECT array_agg(n + 1) FROM unnest($1) AS n;
$$ language sql;
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{"arr": [1,2,3,4]}'
.. code-tab:: http
POST /rpc/plus_one HTTP/1.1
Content-Type: application/json
{"arr": [1,2,3,4]}
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{"arr": [1,2,3,4]}'
.. code-block:: json
@@ -160,23 +220,37 @@ You can call a function that takes an array parameter:
For calling the function with GET, you can pass the array as an `array literal <https://www.postgresql.org/docs/current/arrays.html#ARRAYS-INPUT>`_,
as in ``{1,2,3,4}``. Note that the curly brackets have to be urlencoded(``{`` is ``%7B`` and ``}`` is ``%7D``).
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D'"
.. code-tab:: http
GET /rpc/plus_one?arr=%7B1,2,3,4%7D' HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D'"
.. note::
For versions prior to PostgreSQL 10, to pass a PostgreSQL native array on a POST payload, you need to quote it and use an array literal:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{ "arr": "{1,2,3,4}" }'
.. code-tab:: http
POST /rpc/plus_one HTTP/1.1
{ "arr": "{1,2,3,4}" }
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{ "arr": "{1,2,3,4}" }'
In these versions we recommend using function parameters of type JSON to accept arrays from the client.
.. _functions_variadic:
.. _s_procs_variadic:
Variadic functions
------------------
@@ -189,11 +263,20 @@ You can call a variadic function by passing a JSON array in a POST request:
SELECT array_agg(n + 1) FROM unnest($1) AS n;
$$ language sql;
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{"v": [1,2,3,4]}'
.. code-tab:: http
POST /rpc/plus_one HTTP/1.1
Content-Type: application/json
{"v": [1,2,3,4]}
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/json" \
-d '{"v": [1,2,3,4]}'
.. code-block:: json
@@ -201,36 +284,63 @@ You can call a variadic function by passing a JSON array in a POST request:
In a GET request, you can repeat the same parameter name:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4"
.. code-tab:: http
GET /rpc/plus_one?v=1&v=2&v=3&v=4 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4"
Repeating also works in POST requests with ``Content-Type: application/x-www-form-urlencoded``:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
-d 'v=1&v=2&v=3&v=4'
.. code-tab:: http
POST /rpc/plus_one HTTP/1.1
Content-Type: application/x-www-form-urlencoded
v=1&v=2&v=3&v=4
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/plus_one" \
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
-d 'v=1&v=2&v=3&v=4'
.. _table_functions:
Table-Valued Functions
----------------------
A function that returns a table type can be filtered using the same filters as :ref:`tables and views <tables_views>`. They can also use :ref:`Resource Embedding <function_embed>`.
A function that returns a table type can be filtered using the same filters as :ref:`tables and views <tables_views>`. They can also use :ref:`Resource Embedding <s_proc_embed>`.
.. code-block:: postgres
CREATE FUNCTION best_films_2017() RETURNS SETOF films ..
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)"
.. code-tab:: http
.. code-block:: bash
GET /rpc/best_films_2017?select=title,director:directors(*) HTTP/1.1
curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc"
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)"
.. tabs::
.. code-tab:: http
GET /rpc/best_films_2017?rating=gt.8&order=title.desc HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc"
.. _function_inlining:
@@ -251,12 +361,19 @@ For example, for the following function:
Let's get its :ref:`explain_plan` when calling it with filters applied:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/getallprojects?id=eq.1" \
-H "Accept: application/vnd.pgrst.plan"
.. code-tab:: http
.. code-block:: postgres
GET /rpc/getallprojects?id=eq.1 HTTP/1.1
Accept: application/vnd.pgrst.plan
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/getallprojects?id=eq.1" \
-H "Accept: application/vnd.pgrst.plan"
.. code-block:: psql
Aggregate (cost=8.18..8.20 rows=1 width=112)
-> Index Scan using projects_pkey on projects (cost=0.15..8.17 rows=1 width=40)
@@ -271,17 +388,29 @@ Scalar functions
PostgREST will detect if the function is scalar or table-valued and will shape the response format accordingly:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
.. code-tab:: http
GET /rpc/add_them?a=1&b=2 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
.. code-block:: json
3
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/best_films_2017"
.. code-tab:: http
GET /rpc/best_films_2017 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/best_films_2017"
.. code-block:: json
@@ -306,9 +435,15 @@ Functions that return ``record`` or ``SETOF record`` are supported:
select * from projects;
$$ language sql;
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/projects_setof_record"
.. code-tab:: http
GET /rpc/projects_setof_record HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/projects_setof_record"
.. code-block:: json
@@ -331,13 +466,25 @@ You can call overloaded functions with different number of arguments.
CREATE FUNCTION rental_duration(customer_id integer, from_date date) ..
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/rental_duration?customer_id=232"
.. code-tab:: http
.. code-block:: bash
GET /rpc/rental_duration?customer_id=232 HTTP/1.1
curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01"
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/rental_duration?customer_id=232"
.. tabs::
.. code-tab:: http
GET /rpc/rental_duration?customer_id=232&from_date=2018-07-01 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01"
.. important::
+462 -221
View File
@@ -3,34 +3,31 @@
Tables and Views
################
All tables and views of the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` are available for querying. They are exposed in one-level deep routes.
For instance the full contents of a table `people` is returned at
.. code-block:: bash
curl "http://localhost:3000/people"
There are no deeply/nested/routes. Each route provides OPTIONS, GET, HEAD, POST, PATCH, and DELETE verbs depending entirely on database permissions.
.. note::
Why not provide nested routes? Many APIs allow nesting to retrieve related information, such as :code:`/films/1/director`. We offer a more flexible mechanism (inspired by GraphQL) to embed related resources. This is covered on :ref:`resource_embedding`.
All views and tables of the :ref:`exposed schema <schemas>` and accessible by the :ref:`active database role <roles>` are available for querying. They are exposed in one-level deep routes.
.. _read:
Read
====
.. _head_req:
For instance the full contents of a table `people` is returned at
GET and HEAD
------------
.. tabs::
Using the GET method, you can retrieve tables and views rows. The default :ref:`res_format` is JSON.
.. code-tab:: http
GET /people HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people"
There are no deeply/nested/routes. Each route provides OPTIONS, GET, HEAD, POST, PATCH, and DELETE verbs depending entirely on database permissions.
.. note::
Why not provide nested routes? Many APIs allow nesting to retrieve related information, such as :code:`/films/1/director`. We offer a more flexible mechanism (inspired by GraphQL) to embed related information. It can handle one-to-many and many-to-many relationships. This is covered in the section about :ref:`resource_embedding`.
A HEAD method will behave identically to GET except that no response body will be returned (`RFC 2616 <https://datatracker.ietf.org/doc/html/rfc2616#section-9.4>`_).
As an optimization, the generated query won't execute an aggregate (to avoid unnecessary data transfer).
.. _h_filter:
@@ -39,15 +36,27 @@ Horizontal Filtering
You can filter result rows by adding conditions on columns. For instance, to return people aged under 13 years old:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?age=lt.13"
.. code-tab:: http
GET /people?age=lt.13 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?age=lt.13"
You can evaluate multiple conditions on columns by adding more query string parameters. For instance, to return people who are 18 or older **and** are students:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?age=gte.18&student=is.true"
.. code-tab:: http
GET /people?age=gte.18&student=is.true HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?age=gte.18&student=is.true"
.. _operators:
@@ -91,13 +100,13 @@ adj :code:`-|-` is adjacent to, e.g. :code:`?range=adj.(
not :code:`NOT` negates another operator, see :ref:`logical_operators`
or :code:`OR` logical :code:`OR`, see :ref:`logical_operators`
and :code:`AND` logical :code:`AND`, see :ref:`logical_operators`
all :code:`ALL` comparison matches all the values in the list, see :ref:`modifiers`
any :code:`ANY` comparison matches any value in the list, see :ref:`modifiers`
all :code:`ALL` comparison matches all the values in the list, see :ref:`logical_operators`
any :code:`ANY` comparison matches any value in the list, see :ref:`logical_operators`
============ ======================== ==================================================================================
For more complicated filters you will have to create a new view in the database, or use a function. For instance, here's a view to show "today's stories" including possibly older pinned stories:
For more complicated filters you will have to create a new view in the database, or use a stored procedure. For instance, here's a view to show "today's stories" including possibly older pinned stories:
.. code-block:: postgres
.. code-block:: postgresql
CREATE VIEW fresh_stories AS
SELECT *
@@ -108,9 +117,15 @@ For more complicated filters you will have to create a new view in the database,
The view will provide a new endpoint:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/fresh_stories"
.. code-tab:: http
GET /fresh_stories HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/fresh_stories"
.. _logical_operators:
@@ -119,28 +134,29 @@ Logical operators
Multiple conditions on columns are evaluated using ``AND`` by default, but you can combine them using ``OR`` with the ``or`` operator. For example, to return people under 18 **or** over 21:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?or=(age.lt.18,age.gt.21)"
.. code-tab:: http
GET /people?or=(age.lt.18,age.gt.21) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?or=(age.lt.18,age.gt.21)"
To **negate** any operator, you can prefix it with :code:`not` like :code:`?a=not.eq.2` or :code:`?not.and=(a.gte.0,a.lte.100)` .
You can also apply complex logic to the conditions:
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/people?grade=gte.90&student=is.true&or=(age.eq.14,not.and(age.gte.11,age.lte.17))"
.. code-tab:: http
curl --get "http://localhost:3000/people" \
-d "grade=gte.90" \
-d "student=is.true" \
-d "or=(age.eq.14,not.and(age.gte.11,age.lte.17))"
GET /people?grade=gte.90&student=is.true&or=(age.eq.14,not.and(age.gte.11,age.lte.17)) HTTP/1.1
If the filter value has a :ref:`reserved character <reserved-chars>`, then you need to wrap it in double quotes:
.. code-tab:: bash Curl
.. code-block:: bash
curl -g 'http://localhost:3000/survey?or=(age_range.adj."[18,21)",age_range.cs."[30,35]")'
curl "http://localhost:3000/people?grade=gte.90&student=is.true&or=(age.eq.14,not.and(age.gte.11,age.lte.17))"
.. _modifiers:
@@ -151,15 +167,27 @@ You may further simplify the logic using the ``any/all`` modifiers of ``eq,like,
For instance, to avoid repeating the same column for ``or``, use ``any`` to get people with last names that start with O or P:
.. code-block:: bash
.. tabs::
curl -g "http://localhost:3000/people?last_name=like(any).{O*,P*}"
.. code-tab:: http
GET /people?last_name=like(any).{O*,P*} HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?last_name=like(any).{O*,P*}"
In a similar way, you can use ``all`` to avoid repeating the same column for ``and``. To get the people with last names that start with O and end with n:
.. code-block:: bash
.. tabs::
curl -g "http://localhost:3000/people?last_name=like(all).{O*,*n}"
.. code-tab:: http
GET /people?last_name=like(all).{O*,*n} HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?last_name=like(all).{O*,*n}"
.. _pattern_matching:
@@ -177,21 +205,45 @@ Full-Text Search
The :code:`fts` filter mentioned above has a number of options to support flexible textual queries, namely the choice of plain vs phrase search and the language used for stemming. Suppose that :code:`tsearch` is a table with column :code:`my_tsv`, of type `tsvector <https://www.postgresql.org/docs/current/datatype-textsearch.html>`_. The following examples illustrate the possibilities.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/tsearch?my_tsv=fts(french).amusant"
.. code-tab:: http
.. code-block:: bash
GET /tsearch?my_tsv=fts(french).amusant HTTP/1.1
curl "http://localhost:3000/tsearch?my_tsv=plfts.The%20Fat%20Cats"
.. code-tab:: bash Curl
.. code-block:: bash
curl "http://localhost:3000/tsearch?my_tsv=fts(french).amusant"
curl "http://localhost:3000/tsearch?my_tsv=not.phfts(english).The%20Fat%20Cats"
.. tabs::
.. code-block:: bash
.. code-tab:: http
curl "http://localhost:3000/tsearch?my_tsv=not.wfts(french).amusant"
GET /tsearch?my_tsv=plfts.The%20Fat%20Cats HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/tsearch?my_tsv=plfts.The%20Fat%20Cats"
.. tabs::
.. code-tab:: http
GET /tsearch?my_tsv=not.phfts(english).The%20Fat%20Cats HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/tsearch?my_tsv=not.phfts(english).The%20Fat%20Cats"
.. tabs::
.. code-tab:: http
GET /tsearch?my_tsv=not.wfts(french).amusant HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/tsearch?my_tsv=not.wfts(french).amusant"
Using `websearch_to_tsquery` requires PostgreSQL of version at least 11.0 and will raise an error in earlier versions of the database.
@@ -202,9 +254,15 @@ Vertical Filtering
When certain columns are wide (such as those holding binary data), it is more efficient for the server to withhold them in a response. The client can specify which columns are required using the :code:`select` parameter.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=first_name,age"
.. code-tab:: http
GET /people?select=first_name,age HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=first_name,age"
.. code-block:: json
@@ -222,9 +280,15 @@ Renaming Columns
You can rename the columns by prefixing them with an alias followed by the colon ``:`` operator.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=fullName:full_name,birthDate:birth_date"
.. code-tab:: http
GET /people?select=fullName:full_name,birthDate:birth_date HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=fullName:full_name,birthDate:birth_date"
.. code-block:: json
@@ -233,12 +297,36 @@ You can rename the columns by prefixing them with an alias followed by the colon
{"fullName": "Jane Doe", "birthDate": "01/12/1998"}
]
.. _casting_columns:
Casting Columns
~~~~~~~~~~~~~~~
Casting the columns is possible by suffixing them with the double colon ``::`` plus the desired type.
.. tabs::
.. code-tab:: http
GET /people?select=full_name,salary::text HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=full_name,salary::text"
.. code-block:: json
[
{"full_name": "John Doe", "salary": "90000.00"},
{"full_name": "Jane Doe", "salary": "120000.00"}
]
.. _json_columns:
JSON Columns
~~~~~~~~~~~~
------------
To further reduce the data transferred, you can specify a path for a ``json`` or ``jsonb`` column using the arrow operators(``->`` or ``->>``) as per the `PostgreSQL docs <https://www.postgresql.org/docs/current/functions-json.html>`__.
You can specify a path for a ``json`` or ``jsonb`` column using the arrow operators(``->`` or ``->>``) as per the `PostgreSQL docs <https://www.postgresql.org/docs/current/functions-json.html>`__.
.. code-block:: postgres
@@ -247,9 +335,15 @@ To further reduce the data transferred, you can specify a path for a ``json`` or
json_data json
);
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=id,json_data->>blood_type,json_data->phones"
.. code-tab:: http
GET /people?select=id,json_data->>blood_type,json_data->phones HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=id,json_data->>blood_type,json_data->phones"
.. code-block:: json
@@ -258,9 +352,15 @@ To further reduce the data transferred, you can specify a path for a ``json`` or
{ "id": 2, "blood_type": "O+", "phones": [{"country_code": "43", "number": "512-446-4988"}, {"country_code": "43", "number": "213-891-5979"}] }
]
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=id,json_data->phones->0->>number"
.. code-tab:: http
GET /people?select=id,json_data->phones->0->>number HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=id,json_data->phones->0->>number"
.. code-block:: json
@@ -271,9 +371,15 @@ To further reduce the data transferred, you can specify a path for a ``json`` or
This also works with filters:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=id,json_data->blood_type&json_data->>blood_type=eq.A-"
.. code-tab:: http
GET /people?select=id,json_data->blood_type&json_data->>blood_type=eq.A- HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=id,json_data->blood_type&json_data->>blood_type=eq.A-"
.. code-block:: json
@@ -285,9 +391,15 @@ This also works with filters:
Note that ``->>`` is used to compare ``blood_type`` as ``text``. To compare with an integer value use ``->``:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?select=id,json_data->age&json_data->age=gt.20"
.. code-tab:: http
GET /people?select=id,json_data->age&json_data->age=gt.20 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?select=id,json_data->age&json_data->age=gt.20"
.. code-block:: json
@@ -296,25 +408,10 @@ Note that ``->>`` is used to compare ``blood_type`` as ``text``. To compare with
{ "id": 12, "age": 30 },
{ "id": 15, "age": 35 }
]
Ordering is also supported:
.. code-block:: bash
curl "http://localhost:3000/people?select=id,json_data->age&order=json_data->>age.desc"
.. code-block:: json
[
{ "id": 15, "age": 35 },
{ "id": 12, "age": 30 },
{ "id": 11, "age": 25 }
]
.. _composite_array_columns:
Composite / Array Columns
~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------
The arrow operators(``->``, ``->>``) can also be used for accessing composite fields and array elements.
@@ -331,13 +428,15 @@ The arrow operators(``->``, ``->>``) can also be used for accessing composite fi
languages text[]
);
.. code-block:: bash
.. tabs::
# curl "http://localhost:3000/countries?select=id,location->>lat,location->>long,primary_language:languages->0&location->lat=gte.19"
.. code-tab:: http
curl --get "http://localhost:3000/countries" \
-d "select=id,location->>lat,location->>long,primary_language:languages->0" \
-d "location->lat=gte.19"
GET /countries?select=id,location->>lat,location->>long,primary_language:languages->0&location->lat=gte.19 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/countries?select=id,location->>lat,location->>long,primary_language:languages->0&location->lat=gte.19"
.. code-block:: json
@@ -358,28 +457,6 @@ The arrow operators(``->``, ``->>``) can also be used for accessing composite fi
CREATE INDEX ON mytable ((to_jsonb(data) -> 'identification' ->> 'registration_number'));
.. _casting_columns:
Casting Columns
~~~~~~~~~~~~~~~
Casting the columns is possible by suffixing them with the double colon ``::`` plus the desired type.
.. code-block:: bash
curl "http://localhost:3000/people?select=full_name,salary::text"
.. code-block:: json
[
{"full_name": "John Doe", "salary": "90000.00"},
{"full_name": "Jane Doe", "salary": "120000.00"}
]
.. note::
To prevent invalidating :ref:`index_usage`, casting on horizontal filtering is not allowed. To do this, you can use :ref:`computed_cols`.
.. _ordering:
Ordering
@@ -387,56 +464,69 @@ Ordering
The reserved word ``order`` reorders the response rows. It uses a comma-separated list of columns and directions:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?order=age.desc,height.asc"
.. code-tab:: http
GET /people?order=age.desc,height.asc HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?order=age.desc,height.asc"
If no direction is specified it defaults to ascending order:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?order=age"
.. code-tab:: http
GET /people?order=age HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?order=age"
If you care where nulls are sorted, add ``nullsfirst`` or ``nullslast``:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?order=age.nullsfirst"
.. code-tab:: http
.. code-block:: bash
GET /people?order=age.nullsfirst HTTP/1.1
curl "http://localhost:3000/people?order=age.desc.nullslast"
.. code-tab:: bash Curl
curl "http://localhost:3000/people?order=age.nullsfirst"
.. tabs::
.. code-tab:: http
GET /people?order=age.desc.nullslast HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/people?order=age.desc.nullslast"
You can also sort on fields of :ref:`composite_array_columns` or :ref:`json_columns`.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/countries?order=location->>lat"
.. code-tab:: http
.. _index_usage:
GET /countries?order=location->>lat HTTP/1.1
Index Usage
-----------
.. code-tab:: bash Curl
Indexes work transparently when using horizontal filtering, vertical filtering and ordering. For example, when having:
curl "http://localhost:3000/countries?order=location->>lat"
.. code-block:: postgresql
.. _head_req:
create index salary_idx on employees (salary);
HEAD
----
We can confirm that a filter on employees uses the index by getting the :ref:`explain_plan`.
.. code-block:: bash
curl 'localhost:3000/employees?salary=eq.36000' -H "Accept: application/vnd.pgrst.plan"
Aggregate (cost=9.52..9.54 rows=1 width=144)
-> Bitmap Heap Scan on employees (cost=4.16..9.50 rows=2 width=136)
Recheck Cond: (salary = '$36,000.00'::money)
-> Bitmap Index Scan on salary_idx (cost=0.00..4.16 rows=2 width=0)
Index Cond: (salary = '$36,000.00'::money)
There we can see `"Index Cond" <https://www.pgmustard.com/docs/explain/index-cond>`_, which confirms the index is being used by the query planner.
A HEAD method will behave identically to GET except that no body will be returned (`RFC 2616 <https://datatracker.ietf.org/doc/html/rfc2616#section-9.4>`_) .
As an optimization, the generated query won't execute an aggregate (to avoid unnecessary data transfer).
.. _insert:
@@ -447,28 +537,45 @@ All tables and `auto-updatable views <https://www.postgresql.org/docs/current/sq
To create a row in a database table post a JSON object whose keys are the names of the columns you would like to create. Missing properties will be set to default values when applicable.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/table_name" \
-X POST -H "Content-Type: application/json" \
-d '{ "col1": "value1", "col2": "value2" }'
.. code-tab:: http
POST /table_name HTTP/1.1
{ "col1": "value1", "col2": "value2" }
.. code-tab:: bash Curl
curl "http://localhost:3000/table_name" \
-X POST -H "Content-Type: application/json" \
-d '{ "col1": "value1", "col2": "value2" }'
.. code::
HTTP/1.1 201 Created
No response body will be returned by default but you can use :ref:`prefer_return` to get the affected resource and :ref:`resource_embedding` to add related resources.
No response body will be returned by default but you can use :ref:`prefer_return` to get the affected resource.
x-www-form-urlencoded
---------------------
URL encoded payloads can be posted with ``Content-Type: application/x-www-form-urlencoded``.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" \
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
-d "name=John+Doe&age=50&weight=80"
.. code-tab:: http
POST /people HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=John+Doe&age=50&weight=80
.. code-tab:: bash Curl
curl "http://localhost:3000/people" \
-X POST -H "Content-Type: application/x-www-form-urlencoded" \
-d "name=John+Doe&age=50&weight=80"
.. note::
@@ -488,7 +595,7 @@ URL encoded payloads can be posted with ``Content-Type: application/x-www-form-u
It's recommended that you `use triggers instead of rules <https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_rules>`_.
Insertion on views with complex `rules <https://www.postgresql.org/docs/current/sql-createrule.html>`_ might not work out of the box with PostgREST due to its usage of CTEs.
If you want to keep using rules, a workaround is to wrap the view insertion in a function and call it through the :ref:`functions` interface.
If you want to keep using rules, a workaround is to wrap the view insertion in a stored procedure and call it through the :ref:`s_procs` interface.
For more details, see this `github issue <https://github.com/PostgREST/postgrest/issues/1283>`_.
.. _bulk_insert:
@@ -500,30 +607,53 @@ Bulk insert works exactly like single row insert except that you provide either
To bulk insert CSV simply post to a table route with :code:`Content-Type: text/csv` and include the names of the columns as the first row. For instance
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" \
-X POST -H "Content-Type: text/csv" \
--data-binary @- << EOF
name,age,height
J Doe,62,70
Jonas,10,55
EOF
.. code-tab:: http
POST /people HTTP/1.1
Content-Type: text/csv
name,age,height
J Doe,62,70
Jonas,10,55
.. code-tab:: bash Curl
curl "http://localhost:3000/people" \
-X POST -H "Content-Type: text/csv" \
--data-binary @- << EOF
name,age,height
J Doe,62,70
Jonas,10,55
EOF
An empty field (:code:`,,`) is coerced to an empty string and the reserved word :code:`NULL` is mapped to the SQL null value. Note that there should be no spaces between the column names and commas.
To bulk insert JSON post an array of objects having all-matching keys
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /people HTTP/1.1
Content-Type: application/json
curl "http://localhost:3000/people" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
[
{ "name": "J Doe", "age": 62, "height": 70 },
{ "name": "Janus", "age": 10, "height": 55 }
]
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/people" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
[
{ "name": "J Doe", "age": 62, "height": 70 },
{ "name": "Janus", "age": 10, "height": 55 }
]
EOF
.. _bulk_insert_default:
@@ -544,25 +674,50 @@ Having:
A request:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/foo?columns=id,bar,baz" \
-H "Content-Type: application/json" \
-H "Prefer: missing=default, return=representation" \
-d @- << EOF
[
{ "bar": "val1" },
{ "bar": "val2", "baz": 15 }
]
EOF
.. code-tab:: http
POST /foo?columns=id,bar,baz HTTP/1.1
Content-Type: application/json
Prefer: missing=default, return=representation
[
{ "bar": "val1"
}
, { "bar": "val2"
, "baz": 15
}
]
.. code-tab:: bash Curl
curl "http://localhost:3000/foo?columns=id,bar,baz" \
-H "Content-Type: application/json" \
-H "Prefer: missing=default, return=representation" \
-d @- << EOF
[
{ "bar": "val1"
}
, { "bar": "val2"
, "baz": 15
}
]
EOF
Will result in:
.. code-block:: json
[
{ "id": 1, "bar": "val1", "baz": 100 },
{ "id": 2, "bar": "val2", "baz": 15 }
{ "id": 1
, "bar": "val1"
, "baz": 100
}
, { "id": 2
, "bar": "val2"
, "baz": 15
}
]
.. _specify_columns:
@@ -572,21 +727,38 @@ Specifying Columns
By using the :code:`columns` query parameter it's possible to specify the payload keys that will be inserted and ignore the rest of the payload.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/datasets?columns=source,publication_date,figure" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"source": "Natural Disaster Prevention and Control",
"publication_date": "2015-09-11",
"figure": 1100,
"location": "...",
"comment": "...",
"extra": "...",
"stuff": "..."
}
EOF
.. code-tab:: http
POST /datasets?columns=source,publication_date,figure HTTP/1.1
Content-Type: application/json
{
"source": "Natural Disaster Prevention and Control",
"publication_date": "2015-09-11",
"figure": 1100,
"location": "...",
"comment": "...",
"extra": "...",
"stuff": "..."
}
.. code-tab:: bash Curl
curl "http://localhost:3000/datasets?columns=source,publication_date,figure" \
-X POST -H "Content-Type: application/json" \
-d @- << EOF
{
"source": "Natural Disaster Prevention and Control",
"publication_date": "2015-09-11",
"figure": 1100,
"location": "...",
"comment": "...",
"extra": "...",
"stuff": "..."
}
EOF
In this case, only **source**, **publication_date** and **figure** will be inserted. The rest of the JSON keys will be ignored.
@@ -600,13 +772,21 @@ Update
To update a row or rows in a table, use the PATCH verb. Use :ref:`h_filter` to specify which record(s) to update. Here is an example query setting the :code:`category` column to child for all people below a certain age.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people?age=lt.13" \
-X PATCH -H "Content-Type: application/json" \
-d '{ "category": "child" }'
.. code-tab:: http
Updates also support :ref:`prefer_return`, :ref:`resource_embedding` and :ref:`v_filter`.
PATCH /people?age=lt.13 HTTP/1.1
{ "category": "child" }
.. code-tab:: bash Curl
curl "http://localhost:3000/people?age=lt.13" \
-X PATCH -H "Content-Type: application/json" \
-d '{ "category": "child" }'
Updates also support :ref:`prefer_return` plus :ref:`v_filter`.
.. warning::
@@ -621,18 +801,31 @@ Upsert
You can make an upsert with :code:`POST` and the :code:`Prefer: resolution=merge-duplicates` header:
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /employees HTTP/1.1
Prefer: resolution=merge-duplicates
curl "http://localhost:3000/employees" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: resolution=merge-duplicates" \
-d @- << EOF
[
{ "id": 1, "name": "Old employee 1", "salary": 30000 },
{ "id": 2, "name": "Old employee 2", "salary": 42000 },
{ "id": 3, "name": "New employee 3", "salary": 50000 }
]
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/employees" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: resolution=merge-duplicates" \
-d @- << EOF
[
{ "id": 1, "name": "Old employee 1", "salary": 30000 },
{ "id": 2, "name": "Old employee 2", "salary": 42000 },
{ "id": 3, "name": "New employee 3", "salary": 50000 }
]
EOF
By default, upsert operates based on the primary key columns, you must specify all of them. You can also choose to ignore the duplicates with :code:`Prefer: resolution=ignore-duplicates`. This works best when the primary key is natural, but it's also possible to use it if the primary key is surrogate (example: "id serial primary key"). For more details read `this issue <https://github.com/PostgREST/postgrest/issues/1118>`_.
@@ -646,18 +839,31 @@ On Conflict
By specifying the ``on_conflict`` query parameter, you can make upsert work on a column(s) that has a UNIQUE constraint.
.. code-block:: bash
.. tabs::
.. code-tab:: http
POST /employees?on_conflict=name HTTP/1.1
Prefer: resolution=merge-duplicates
curl "http://localhost:3000/employees?on_conflict=name" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: resolution=merge-duplicates" \
-d @- << EOF
[
{ "name": "Old employee 1", "salary": 40000 },
{ "name": "Old employee 2", "salary": 52000 },
{ "name": "New employee 3", "salary": 60000 }
]
EOF
.. code-tab:: bash Curl
curl "http://localhost:3000/employees?on_conflict=name" \
-X POST -H "Content-Type: application/json" \
-H "Prefer: resolution=merge-duplicates" \
-d @- << EOF
[
{ "name": "Old employee 1", "salary": 40000 },
{ "name": "Old employee 2", "salary": 52000 },
{ "name": "New employee 3", "salary": 60000 }
]
EOF
.. _upsert_put:
@@ -666,11 +872,19 @@ PUT
A single row upsert can be done by using :code:`PUT` and filtering the primary key columns with :code:`eq`:
.. code-block:: bash
.. tabs::
curl "http://localhost/employees?id=eq.4" \
-X PUT -H "Content-Type: application/json" \
-d '{ "id": 4, "name": "Sara B.", "salary": 60000 }'
.. code-tab:: http
PUT /employees?id=eq.4 HTTP/1.1
{ "id": 4, "name": "Sara B.", "salary": 60000 }
.. code-tab:: bash Curl
curl "http://localhost/employees?id=eq.4" \
-X PUT -H "Content-Type: application/json" \
-d '{ "id": 4, "name": "Sara B.", "salary": 60000 }'
All the columns must be specified in the request body, including the primary key columns.
@@ -681,16 +895,29 @@ Delete
To delete rows in a table, use the DELETE verb plus :ref:`h_filter`. For instance deleting inactive users:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/user?active=is.false" -X DELETE
.. code-tab:: http
Deletions also support :ref:`prefer_return`, :ref:`resource_embedding` and :ref:`v_filter`.
DELETE /user?active=is.false HTTP/1.1
.. code-block:: bash
.. code-tab:: bash Curl
curl "http://localhost:3000/user?id=eq.1" -X DELETE \
-H "Prefer: return=representation"
curl "http://localhost:3000/user?active=is.false" -X DELETE
Deletions also support :ref:`prefer_return` plus :ref:`v_filter`.
.. tabs::
.. code-tab:: http
DELETE /user?id=eq.1 HTTP/1.1
Prefer: return=representation
.. code-tab:: bash Curl
curl "http://localhost:3000/user?id=eq.1" -X DELETE \
-H "Prefer: return=representation"
.. code-block:: json
@@ -707,15 +934,29 @@ Limited Update/Delete
You can limit the amount of affected rows by :ref:`update` or :ref:`delete` with the ``limit`` query parameter. For this, you must add an explicit ``order`` on a unique column(s).
.. code-block:: bash
.. tabs::
curl -X PATCH "/users?limit=10&order=id&last_login=lt.2020-01-01" \
-H "Content-Type: application/json" \
-d '{ "status": "inactive" }'
.. code-tab:: http
.. code-block:: bash
PATCH /users?limit=10&order=id&last_login=lt.2017-01-01 HTTP/1.1
curl -X DELETE "http://localhost:3000/users?limit=10&order=id&status=eq.inactive"
{ "status": "inactive" }
.. code-tab:: bash Curl
curl -X PATCH "/users?limit=10&order=id&last_login=lt.2020-01-01" \
-H "Content-Type: application/json" \
-d '{ "status": "inactive" }'
.. tabs::
.. code-tab:: http
DELETE /users?limit=10&order=id&status=eq.inactive HTTP/1.1
.. code-tab:: bash Curl
curl -X DELETE "http://localhost:3000/users?limit=10&order=id&status=eq.inactive"
If your table has no unique columns, you can use the `ctid <https://www.postgresql.org/docs/current/ddl-system-columns.html>`_ system column.
+42 -11
View File
@@ -12,7 +12,7 @@ URL Grammar
Custom Queries
--------------
The PostgREST URL grammar limits the kinds of queries clients can perform. It prevents arbitrary, potentially poorly constructed and slow client queries. It's good for quality of service, but means database administrators must create custom views and functions to provide richer endpoints. The most common causes for custom endpoints are
The PostgREST URL grammar limits the kinds of queries clients can perform. It prevents arbitrary, potentially poorly constructed and slow client queries. It's good for quality of service, but means database administrators must create custom views and stored procedures to provide richer endpoints. The most common causes for custom endpoints are
* Table unions
* More complicated joins than those provided by :ref:`resource_embedding`.
@@ -31,9 +31,15 @@ To request this:
Do this:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
.. code-tab:: http
GET /%D9%85%D9%88%D8%A7%D8%B1%D8%AF HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
.. _tabs-cols-w-spaces:
@@ -42,9 +48,15 @@ Table / Columns with spaces
You can request table/columns with spaces in them by percent encoding the spaces with ``%20``:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/Order%20Items?Unit%20Price=lt.200"
.. code-tab:: http
GET /Order%20Items?Unit%20Price=lt.200 HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/Order%20Items?Unit%20Price=lt.200"
.. _reserved-chars:
@@ -55,25 +67,44 @@ If filters include PostgREST reserved characters(``,``, ``.``, ``:``, ``()``) yo
Here ``Hebdon,John`` and ``Williams,Mary`` are values.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22)"
.. code-tab:: http
GET /employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22)"
Here ``information.cpe`` is a column name.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/vulnerabilities?%22information.cpe%22=like.*MS*"
.. code-tab:: http
GET /vulnerabilities?%22information.cpe%22=like.*MS* HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/vulnerabilities?%22information.cpe%22=like.*MS*"
If the value filtered by the ``in`` operator has a double quote (``"``), you can escape it using a backslash ``"\""``. A backslash itself can be used with a double backslash ``"\\"``.
Here ``Quote:"`` and ``Backslash:\`` are percent-encoded values. Note that ``%5C`` is the percent-encoded backslash.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/marks?name=in.(%22Quote:%5C%22%22,%22Backslash:%5C%5C%22)"
.. code-tab:: http
GET /marks?name=in.(%22Quote:%5C%22%22,%22Backslash:%5C%5C%22) HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/marks?name=in.(%22Quote:%5C%22%22,%22Backslash:%5C%5C%22)"
.. note::
Some HTTP libraries might encode URLs automatically(e.g. :code:`axios`). In these cases you should use double quotes
:code:`""` directly instead of :code:`%22`.
+14 -5
View File
@@ -86,10 +86,17 @@ Client Auth
To make an authenticated request the client must include an :code:`Authorization` HTTP header with the value :code:`Bearer <jwt>`. For instance:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/foo" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4"
.. 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``).
@@ -168,7 +175,9 @@ There are at least three types of common critiques against using JWT: 1) against
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.
Another 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.
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.
@@ -177,7 +186,7 @@ PostgREST uses JWT mainly for authentication and authorization purposes and enco
Custom Validation
-----------------
PostgREST does not enforce any extra constraints besides JWT validation. An example of an extra constraint would be to immediately revoke access for a certain user. Using :ref:`db-pre-request` you can specify a function to call immediately after :ref:`user_impersonation` and before the main query itself runs.
PostgREST does not enforce any extra constraints besides JWT validation. An example of an extra constraint would be to immediately revoke access for a certain user. Using :ref:`db-pre-request` you can specify a stored procedure to call immediately after :ref:`user_impersonation` and before the main query itself runs.
.. code:: ini
-51
View File
@@ -1,51 +0,0 @@
.. _cli:
CLI
===
PostgREST provides a CLI with the commands listed below:
Help
----
.. code:: bash
$ postgrest [-h|--help]
Shows all the commands available.
Version
-------
.. code:: bash
$ postgrest [-v|--version]
Prints the PostgREST version.
Example
-------
.. code:: bash
$ postgrest [-e|--example]
Shows example configuration options.
Dump Config
-----------
.. code:: bash
$ postgrest [--dump-config]
Dumps the loaded :ref:`configuration` values, considering the configuration file, environment variables and :ref:`in_db_config`.
Dump Schema
-----------
.. code:: bash
$ postgrest [--dump-schema]
Dumps the schema cache in JSON format.
+12 -33
View File
@@ -80,7 +80,7 @@ You can also configure the server with database settings by using a :ref:`pre-co
PGRST_DB_PRE_CONFIG = "postgrest.pre_config"
.. code-block:: postgres
.. code-block:: postgresql
-- create a dedicated schema, hidden from the API
create schema postgrest;
@@ -121,8 +121,8 @@ It's possible to reload PostgREST's configuration without restarting the server.
.. _config_reloading_signal:
Configuration Reload with signal
--------------------------------
Reload with signal
------------------
To reload the configuration via signal, send a SIGUSR2 signal to the server process.
@@ -132,8 +132,8 @@ To reload the configuration via signal, send a SIGUSR2 signal to the server proc
.. _config_reloading_notify:
Configuration Reload with NOTIFY
--------------------------------
Reload with NOTIFY
------------------
To reload the configuration from within the database, you can use a NOTIFY command.
@@ -161,7 +161,7 @@ admin-server-port
**In-Database** `n/a`
=============== =======================
Specifies the port for the :ref:`admin_server`.
Specifies the port for the :ref:`health_check` endpoints.
.. _app.settings.*:
@@ -176,7 +176,7 @@ app.settings.*
**In-Database** `n/a`
=============== =======================
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')`.
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-aggregates-enabled:
@@ -230,7 +230,7 @@ db-channel
**In-Database** `n/a`
=============== =======================
The name of the notification channel that PostgREST uses for :ref:`schema_reloading_notify` and :ref:`config_reloading_notify`.
The name of the notification channel that PostgREST uses for :ref:`schema_reloading` and configuration reloading.
.. _db-channel-enabled:
@@ -292,27 +292,12 @@ 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 functions **don't get API endpoints**, they can only be referred from the database objects inside your :ref:`db-schemas`.
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-hoisted-tx-settings:
db-hoisted-tx-settings
----------------------
=============== ==================================================================================
**Type** String
**Default** statement_timeout, plan_filter.statement_cost_limit, default_transaction_isolation
**Reloadable** Y
**Environment** PGRST_DB_HOISTED_TX_SETTINGS
**In-Database** pgrst.db_hoisted_tx_settings
=============== ==================================================================================
Hoisted settings are allowed to be applied as transaction-scoped function settings. Multiple settings can be added in a comma-separated string, e.g. ``work_mem, statement_timeout``.
.. _db-max-rows:
db-max-rows
@@ -328,7 +313,7 @@ db-max-rows
*For backwards compatibility, this config parameter is also available without prefix as "max-rows".*
A hard limit to the number of rows PostgREST will fetch from a view, table, or function. Limits payload size for accidental or malicious requests.
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:
@@ -439,7 +424,7 @@ db-pre-request
*For backwards compatibility, this config parameter is also available without prefix as "pre-request".*
A schema-qualified function name to call right after the :ref:`tx_settings` are set. See :ref:`pre-request`.
A schema-qualified stored procedure name to call right after the :ref:`tx_settings` are set. See :ref:`pre-request`.
.. _db-prepared-statements:
@@ -502,7 +487,7 @@ db-tx-end
**Default** commit
**Reloadable** N
**Environment** PGRST_DB_TX_END
**In-Database** pgrst.db_tx_end
**In-Database** `n/a`
=============== =================================
Specifies how to terminate the database transactions.
@@ -584,10 +569,6 @@ 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.
.. warning::
Using this setting will only reject tokens with a different audience claim. Tokens **without** audience claim will still be accepted.
.. _jwt-role-claim-key:
jwt-role-claim-key
@@ -695,8 +676,6 @@ log-level
# All the "warn" level events plus all requests (every status code) are logged
log-level = "info"
# All the above plus events for development purposes are logged
log-level = "debug"
Because currently there's no buffering for logging, the levels with minimal logging(``crit/error``) will increase throughput.
+6 -21
View File
@@ -7,6 +7,11 @@ A connection pool is a cache of reusable database connections. It allows serving
Minimizing connections is paramount to performance. Each PostgreSQL connection creates a process, having too many can exhaust available resources.
Connection String
-----------------
For connecting to the database, the pool requires a connection string. You can configure it using :ref:`db-uri`.
.. _pool_growth_limit:
.. _dyn_conn_pool:
@@ -17,26 +22,6 @@ To conserve system resources, PostgREST uses a dynamic connection pool. This ena
- If all the connections are being used, a new connection is added. The pool can grow until it reaches the :ref:`db-pool` size. Note that its pointless to set this higher than the ``max_connections`` setting in your database.
- If a connection is unused for a period of time (:ref:`db-pool-max-idletime`), it will be released.
- For connecting to the database, the :ref:`authenticator <roles>` role is used. You can configure this using :ref:`db-uri`.
Connection Application Name
~~~~~~~~~~~~~~~~~~~~~~~~~~~
PostgREST sets the connection `application_name <https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-FALLBACK-APPLICATION-NAME>`_ for all of its used connections.
This is useful for PostgreSQL statistics and logs.
For example, you can query `pg_stat_activity <https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW>`_ to get the PostgREST version:
.. code-block:: postgres
select distinct usename, application_name
from pg_stat_activity
where usename = 'authenticator';
usename | application_name
---------------+--------------------------
authenticator | PostgREST 12.1
Connection lifetime
-------------------
@@ -75,7 +60,7 @@ If the request reaches the timeout, it will be aborted with the following respon
- Reduce write requests. Do :ref:`bulk_insert` (or :ref:`upsert`) instead of inserting rows one by one.
- Reduce read requests. Use :ref:`resource_embedding`. Combine unrelated data into a single request using custom database views or functions.
- Use :ref:`functions` for combining read and write logic into a single request.
- Use :ref:`s_procs` for combining read and write logic into a single request.
- Increase the :ref:`db-pool` size.
+12 -12
View File
@@ -67,11 +67,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 |
+--------------------------+-------------------------+---------------------------------+
@@ -177,7 +175,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 | | |
+---------------+-------------+-------------------------------------------------------------+
@@ -249,7 +247,7 @@ Related to the HTTP request elements.
| | | ``is.null`` or ``not.is.null`` :ref:`operators <operators>`.|
| PGRST120 | | |
+---------------+-------------+-------------------------------------------------------------+
| .. _pgrst121: | 500 | PostgREST can't parse the JSON objects in RAISE |
| .. _pgrst121: | 400 | PostgREST can't parse the JSON objects in RAISE |
| | | ``PGRST`` error. See :ref:`raise headers <raise_headers>`. |
| PGRST121 | | |
+---------------+-------------+-------------------------------------------------------------+
@@ -263,7 +261,7 @@ Related to the HTTP request elements.
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 |
@@ -341,9 +339,9 @@ You can customize the errors by using the `RAISE statement <https://www.postgres
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:
Custom status codes can be done by raising SQL exceptions inside :ref:`functions <s_procs>`. For instance, here's a saucy function that always responds with an error:
.. code-block:: postgres
.. code-block:: postgresql
CREATE OR REPLACE FUNCTION just_fail() RETURNS void
LANGUAGE plpgsql
@@ -370,7 +368,7 @@ One way to customize the HTTP status code is by raising particular exceptions ac
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
.. code-block:: sql
RAISE sqlstate 'PT402' using
message = 'Payment Required',
@@ -396,9 +394,9 @@ Returns:
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:
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
.. code-block:: sql
RAISE sqlstate 'PGRST' USING
message = '{"code":"123","message":"Payment Required","details":"Quota exceeded","hint":"Upgrade your plan"}',
@@ -422,8 +420,10 @@ Returns:
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
.. code-block:: sql
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**>`.
+39 -23
View File
@@ -3,29 +3,43 @@
Schema Cache
============
PostgREST requires metadata from the database schema to provide a REST API that abstracts SQL details. One example of this is the interface for :ref:`resource_embedding`.
Some PostgREST features need metadata from the database schema. Getting this metadata requires expensive queries. To avoid repeating this work, PostgREST uses a schema cache.
Getting this metadata requires expensive queries. To avoid repeating this work, PostgREST uses a schema cache.
+--------------------------------------------+-------------------------------------------------------------------------------+
| Feature | Required Metadata |
+============================================+===============================================================================+
| :ref:`resource_embedding` | Foreign key constraints |
+--------------------------------------------+-------------------------------------------------------------------------------+
| :ref:`Stored Functions <s_procs>` | Function signature (parameters, return type, volatility and |
| | `overloading <https://www.postgresql.org/docs/current/xfunc-overload.html>`_) |
+--------------------------------------------+-------------------------------------------------------------------------------+
| :ref:`Upserts <upsert>` | Primary keys |
+--------------------------------------------+-------------------------------------------------------------------------------+
| :ref:`Insertions <insert>` | Primary keys (optional: only if the Location header is requested) |
+--------------------------------------------+-------------------------------------------------------------------------------+
| :ref:`OPTIONS requests <options_requests>` | View INSTEAD OF TRIGGERS and primary keys |
+--------------------------------------------+-------------------------------------------------------------------------------+
| :ref:`open-api` | Table columns, primary keys and foreign keys |
+ +-------------------------------------------------------------------------------+
| | View columns and INSTEAD OF TRIGGERS |
+ +-------------------------------------------------------------------------------+
| | Function signature |
+--------------------------------------------+-------------------------------------------------------------------------------+
.. _stale_schema:
Stale Schema Cache
------------------
One operational problem that comes with a cache is that it can go stale. This can happen for PostgREST when you make changes to the metadata before mentioned. Requests that depend on the metadata will fail.
You can solve this by reloading the cache manually or automatically.
.. _schema_reloading:
Schema Cache Reloading
----------------------
To not let the schema cache go stale (happens when you make changes to the database), you need to reload it.
You can do this with UNIX signals or with PostgreSQL notifications. It's also possible to do this automatically using `event triggers <https://www.postgresql.org/docs/current/event-trigger-definition.html>`_.
.. note::
- Requests will wait until the schema cache reload is done. This to prevent client errors due to an stale schema cache.
- If you are using the :ref:`in_db_config`, a schema cache reload will :ref:`reload the configuration<config_reloading>` as well.
.. _schema_reloading_signals:
Schema Cache Reloading with Unix Signals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To manually reload the cache without restarting the PostgREST server, send a SIGUSR1 signal to the server process.
.. code:: bash
@@ -42,14 +56,16 @@ For docker you can do:
# or in docker-compose
docker-compose kill -s SIGUSR1 <service>
Theres no downtime when reloading the schema cache. The reloading will happen on a background thread while serving requests.
.. _schema_reloading_notify:
Schema Cache Reloading with NOTIFY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Reloading with NOTIFY
~~~~~~~~~~~~~~~~~~~~~
PostgREST also allows you to reload its schema cache through PostgreSQL `NOTIFY <https://www.postgresql.org/docs/current/sql-notify.html>`_.
.. code-block:: postgres
.. code-block:: postgresql
NOTIFY pgrst, 'reload schema'
@@ -62,9 +78,9 @@ The ``pgrst`` notification channel is enabled by default. For configuring the ch
Automatic Schema Cache Reloading
--------------------------------
You can do automatic reloading and forget there is a schema cache. For this use an `event trigger <https://www.postgresql.org/docs/current/event-trigger-definition.html>`_ and ``NOTIFY``.
You can do automatic schema cache reloading in a pure SQL way and forget about stale schema cache errors. For this use an `event trigger <https://www.postgresql.org/docs/current/event-trigger-definition.html>`_ and ``NOTIFY``.
.. code-block:: postgres
.. code-block:: postgresql
-- Create an event trigger function
CREATE OR REPLACE FUNCTION pgrst_watch() RETURNS event_trigger
@@ -84,7 +100,7 @@ Now, whenever the ``pgrst_watch`` trigger fires, PostgREST will auto-reload the
To disable auto reloading, drop the trigger.
.. code-block:: postgres
.. code-block:: postgresql
DROP EVENT TRIGGER pgrst_watch
@@ -94,7 +110,7 @@ Finer-Grained Event Trigger
You can refine the previous event trigger to only react to the events relevant to the schema cache. This also prevents unnecessary
reloading when creating temporary tables inside functions.
.. code-block:: postgres
.. code-block:: postgresql
-- watch CREATE and ALTER
CREATE OR REPLACE FUNCTION pgrst_ddl_watch() RETURNS event_trigger AS $$
+46 -29
View File
@@ -5,7 +5,7 @@ Transactions
After :ref:`user_impersonation`, every request to an :doc:`API resource <api>` runs inside a transaction. The sequence of the transaction is as follows:
.. code-block:: postgres
.. code-block:: postgresql
START TRANSACTION; -- <Access Mode> <Isolation Level>
-- <Transaction-scoped settings>
@@ -21,7 +21,7 @@ The access mode determines whether the transaction can modify the database or no
Modifying the database inside READ ONLY transactions is not possible. PostgREST uses this fact to enforce HTTP semantics in GET and HEAD requests. Consider the following:
.. code-block:: postgres
.. code-block:: postgresql
CREATE SEQUENCE callcounter_count START 1;
@@ -30,9 +30,15 @@ Modifying the database inside READ ONLY transactions is not possible. PostgREST
Since the ``callcounter`` view modifies the sequence, calling it with GET or HEAD will result in an error:
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/callcounter"
.. code-tab:: http
GET /callcounter HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/callcounter"
.. code-block:: http
@@ -58,7 +64,7 @@ The access mode on :ref:`tables_views` is determined by the HTTP method.
Access Mode on Functions
~~~~~~~~~~~~~~~~~~~~~~~~
:ref:`functions` additionally depend on the function `volatility <https://www.postgresql.org/docs/current/xfunc-volatility.html>`_.
:ref:`s_procs` additionally depend on the function `volatility <https://www.postgresql.org/docs/current/xfunc-volatility.html>`_.
.. list-table::
:header-rows: 2
@@ -92,7 +98,7 @@ Isolation Level
Every transaction uses the PostgreSQL default isolation level: READ COMMITTED. Unless you modify `default_transaction_isolation <https://www.postgresql.org/docs/15/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION>`_ for an impersonated role or function.
.. code-block:: postgres
.. code-block:: postgresql
ALTER ROLE webuser SET default_transaction_isolation TO 'repeatable read';
@@ -100,7 +106,7 @@ Every ``webuser`` gets its queries executed with ``default_transaction_isolation
Or to change the isolation level per function call.
.. code-block:: postgres
.. code-block:: postgresql
CREATE OR REPLACE FUNCTION myfunc()
RETURNS text as $$
@@ -118,7 +124,7 @@ PostgREST uses settings tied to the transaction lifetime. These can be used to g
You can get these with ``current_setting``
.. code-block:: postgres
.. code-block:: postgresql
-- request settings use the ``request.`` prefix.
SELECT
@@ -126,7 +132,7 @@ You can get these with ``current_setting``
And you can set them with ``set_config``
.. code-block:: postgres
.. code-block:: postgresql
-- response settings use the ``response.`` prefix.
SELECT
@@ -139,7 +145,7 @@ Request Headers, Cookies and JWT claims
PostgREST stores the headers, cookies and headers as JSON. To get them:
.. code-block:: postgres
.. code-block:: postgresql
-- To get all the headers sent in the request
SELECT current_setting('request.headers', true)::json;
@@ -162,7 +168,7 @@ PostgREST stores the headers, cookies and headers as JSON. To get them:
+ This is considered expected behavior by PostgreSQL. For more details, see `this discussion <https://www.postgresql.org/message-id/flat/CAB_pDVVa84w7hXhzvyuMTb8f5kKV3bee_p9QTZZ58Rg7zYM7sw%40mail.gmail.com>`_.
+ To avoid this inconsistency, you can create a wrapper function like:
.. code-block:: postgres
.. code-block:: postgresql
CREATE FUNCTION my_current_setting(text) RETURNS text
LANGUAGE SQL AS $$
@@ -176,7 +182,7 @@ Request Path and Method
The path and method are stored as ``text``.
.. code-block:: postgres
.. code-block:: postgresql
SELECT current_setting('request.path', true);
@@ -187,7 +193,7 @@ Request Role and Search Path
Because of :ref:`user_impersonation`, PostgREST sets the standard ``role``. You can get this in different ways:
.. code-block:: postgres
.. code-block:: postgresql
SELECT current_role;
@@ -204,7 +210,7 @@ Response Headers
You can set ``response.headers`` to add headers to the HTTP response. For instance, this statement would add caching headers to the response:
.. code-block:: postgres
.. code-block:: sql
-- tell client to cache response for two days
@@ -240,9 +246,15 @@ You can set the ``response.status`` to override the default status code PostgRES
end;
$$ language plpgsql;
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/rpc/teapot" -i
.. code-tab:: http
GET /rpc/teapot HTTP/1.1
.. code-tab:: bash Curl
curl "http://localhost:3000/rpc/teapot" -i
.. code-block:: http
@@ -265,7 +277,7 @@ This allows finer-grained control over actions made by a role.
For example, consider `statement_timeout <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT>`__. It allows you to abort any statement that takes more than a specified time. It is disabled by default.
.. code-block:: postgres
.. code-block:: postgresql
ALTER ROLE authenticator SET statement_timeout TO '10s';
ALTER ROLE anonymous SET statement_timeout TO '1s';
@@ -280,7 +292,7 @@ For more details see `Understanding Postgres Parameter Context <https://www.ente
However, starting from PostgreSQL 15, you can grant privileges for these settings with:
.. code-block:: postgres
.. code-block:: postgresql
GRANT SET ON PARAMETER <setting> TO <authenticator>;
@@ -290,7 +302,7 @@ Function Settings
In addition to :ref:`impersonated_settings`, PostgREST will also apply function settings as transaction-scoped settings. This allows functions settings to override
the impersonated and connection role settings.
.. code-block:: postgres
.. code-block:: postgresql
CREATE OR REPLACE FUNCTION myfunc()
RETURNS void as $$
@@ -299,25 +311,23 @@ the impersonated and connection role settings.
LANGUAGE SQL
SET statement_timeout TO '4s';
When calling the above function (see :ref:`functions`), the statement timeout will be 4 seconds.
When calling the above function (see :ref:`s_procs`), the statement timeout will be 4 seconds.
.. note::
Only the transactions that are hoisted by config :ref:`db-hoisted-tx-settings` will be applied.
Currently, only ``statement_timeout`` is applied for functions.
.. _main_query:
Main query
----------
The main query is generated by requesting :ref:`tables_views` or :ref:`functions`. All generated queries use prepared statements (:ref:`db-prepared-statements`).
.. _tx_end:
The main query is generated by requesting :ref:`tables_views` or :ref:`s_procs`. All generated queries use prepared statements (:ref:`db-prepared-statements`).
Transaction End
---------------
If the transaction doesn't fail, it will always end in a COMMIT. Unless :ref:`db-tx-end` is configured to ROLLBACK in any case or conditionally with the :ref:`prefer_tx`. This is useful for testing purposes.
If the transaction doesn't fail, it will always end in a COMMIT. Unless :ref:`db-tx-end` is configured to ROLLBACK in any case or conditionally with ``Prefer: tx=rollback``. This can be used for testing purposes.
Aborting transactions
---------------------
@@ -340,7 +350,7 @@ Setting headers via pre-request
As an example, let's add some cache headers for all requests that come from an Internet Explorer(6 or 7) browser.
.. code-block:: postgres
.. code-block:: postgresql
create or replace function custom_headers()
returns void as $$
@@ -358,7 +368,14 @@ As an example, let's add some cache headers for all requests that come from an I
Now when you make a GET request to a table or view, you'll get the cache headers.
.. code-block:: bash
.. tabs::
curl "http://localhost:3000/people" -i \
-H "User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)"
.. code-tab:: http
GET /people HTTP/1.1
User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)
.. code-tab:: bash Curl
curl "http://localhost:3000/people" -i \
-H "User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)"
+4 -6
View File
@@ -1,9 +1,7 @@
docutils==0.20.1
sphinx-copybutton
sphinx-intl
sphinx-rtd-theme>=0.5.1
sphinx-rtd-dark-mode>=1.3.0
sphinx-tabs>=3.2.0
sphinx>=5.0.2
sphinxext-opengraph==0.9.1
sphinx-copybutton
sphinx-rtd-theme>=0.5.1
sphinx-tabs>=3.2.0
urllib3==2.2.1
sphinxext-opengraph==0.9.1
+22
View File
@@ -0,0 +1,22 @@
let
docs =
import ./default.nix;
inherit (docs) pkgs;
in
pkgs.mkShell {
name = "postgrest-docs";
buildInputs = [
docs.build
docs.serve
docs.spellcheck
docs.dictcheck
docs.linkcheck
docs.check
];
shellHook = ''
export HISTFILE=.history
'';
}
+12 -33
View File
@@ -9,11 +9,9 @@ Welcome to PostgREST! In this pre-tutorial we're going to get things running so
PostgREST is a standalone web server which turns a PostgreSQL database into a RESTful API. It serves an API that is customized based on the structure of the underlying database.
.. container:: img-translucent
.. image:: ../_static/tuts/tut0-request-flow.png
.. image:: ../_static/tuts/tut0-request-flow.png
To make an API we'll simply be building a database. All the endpoints and permissions come from database objects like tables, views, roles, and functions. These tutorials will cover a number of common scenarios and how to model them in the database.
To make an API we'll simply be building a database. All the endpoints and permissions come from database objects like tables, views, roles, and stored procedures. These tutorials will cover a number of common scenarios and how to model them in the database.
By the end of this tutorial you'll have a working database, PostgREST server, and a simple single-user todo list API.
@@ -31,22 +29,11 @@ If Docker is not installed, you can get it `here <https://www.docker.com/get-sta
.. code-block:: bash
sudo docker run --name tutorial -p 5432:5432 \
-e POSTGRES_PASSWORD=notused \
sudo docker run --name tutorial -p 5433:5432 \
-e POSTGRES_PASSWORD=mysecretpassword \
-d postgres
This will run the Docker instance as a daemon and expose port 5432 to the host system so that it looks like an ordinary PostgreSQL server to the rest of the system.
.. note::
This only works if there is no other PostgreSQL instance running on the default port on your computer. If this port is already in use, you will receive a message similar to this:
.. code-block:: text
docker: Error response from daemon: [...]: Bind for 0.0.0.0:5432 failed: port is already allocated.
In this case, you will need to change the **first** of the two 5432 to something else, for example to :code:`5433:5432`. Remember to also adjust the port in your config file in Step 5!
This will run the Docker instance as a daemon and expose port 5433 to the host system so that it looks like an ordinary PostgreSQL server to the rest of the system.
Step 3. Install PostgREST
-------------------------
@@ -135,7 +122,7 @@ You should see the psql command prompt:
::
psql (16.2)
psql (9.6.3)
Type "help" for help.
postgres=#
@@ -151,7 +138,7 @@ Our API will have one endpoint, :code:`/todos`, which will come from a table.
.. code-block:: postgres
create table api.todos (
id int primary key generated by default as identity,
id serial primary key,
done boolean not null default false,
task text not null,
due timestamptz
@@ -192,17 +179,13 @@ PostgREST can use a configuration file to tell it how to connect to the database
.. code-block:: ini
db-uri = "postgres://authenticator:mysecretpassword@localhost:5432/postgres"
db-uri = "postgres://authenticator:mysecretpassword@localhost:5433/postgres"
db-schemas = "api"
db-anon-role = "web_anon"
The configuration file has other :ref:`options <configuration>`, but this is all we need.
If you are not using Docker, make sure that your port number is correct and replace `postgres` with the name of the database where you added the todos table.
.. note::
In case you had to adjust the port in Step 2, remember to adjust the port here, too!
Now run the server:
.. code-block:: bash
@@ -213,17 +196,13 @@ Now run the server:
# Running postgrest binary
./postgrest tutorial.conf
You should see something similar to:
You should see
.. code-block:: text
Starting PostgREST 12.0.2...
Listening on port 3000
Attempting to connect to the database...
Connection successful
Listening on port 3000
Config reloaded
Listening for notifications on the pgrst channel
Schema cache loaded
It's now ready to serve web requests. There are many nice graphical API exploration tools you can use, but for this tutorial we'll use :code:`curl` because it's likely to be installed on your system already. Open a new terminal (leaving the one open that PostgREST is running inside). Try doing an HTTP request for the todos.
@@ -263,9 +242,9 @@ Response is 401 Unauthorized:
.. code-block:: json
{
"code": "42501",
"details": null,
"hint": null,
"details": null,
"code": "42501",
"message": "permission denied for table todos"
}
+20 -19
View File
@@ -22,32 +22,33 @@ The previous tutorial created a :code:`web_anon` role in the database with which
grant usage on schema api to todo_user;
grant all on api.todos to todo_user;
grant usage, select on sequence api.todos_id_seq to todo_user;
Step 2. Make a Secret
---------------------
Clients authenticate with the API using JSON Web Tokens. These are JSON objects which are cryptographically signed using a secret only known to the server. Because clients do not know this secret, they cannot tamper with the contents of their tokens. PostgREST will detect counterfeit tokens and will reject them.
Clients authenticate with the API using JSON Web Tokens. These are JSON objects which are cryptographically signed using a password known to only us and the server. Because clients do not know the password, they cannot tamper with the contents of their tokens. PostgREST will detect counterfeit tokens and will reject them.
Let's create a secret and provide it to PostgREST. Think of a nice long one, or use a tool to generate it. **Your secret must be at least 32 characters long.**
Let's create a password and provide it to PostgREST. Think of a nice long one, or use a tool to generate it. **Your password must be at least 32 characters long.**
.. note::
Unix tools can generate a nice secret for you:
Unix tools can generate a nice password for you:
.. code-block:: bash
# Allow "tr" to process non-utf8 byte sequences
export LC_CTYPE=C
# Read random bytes keeping only alphanumerics and add the secret to the configuration file
echo "jwt-secret = \"$(< /dev/urandom tr -dc A-Za-z0-9 | head -c32)\"" >> tutorial.conf
# read random bytes and keep only alphanumerics
echo "jwt-secret = \"$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c32)\"" >> tutorial.conf
Check that the :code:`tutorial.conf` (created in the previous tutorial) has the secret set in :code:`jwt-secret`:
check the :code:`tutorial.conf` (created in the previous tutorial) the line with the password:
.. code-block:: bash
.. code-block:: ini
# THE SECRET MUST BE AT LEAST 32 CHARS LONG
# PASSWORD MUST BE AT LEAST 32 CHARS LONG
cat tutorial.conf
If the PostgREST server is still running from the previous tutorial, restart it to load the updated configuration file.
@@ -62,11 +63,11 @@ Ordinarily your own code in the database or in another server will create and si
How to create a token at https://jwt.io
**Remember to fill in the secret you generated rather than the word "secret".** After you have filled in the secret and payload, the encoded data on the left will update. Copy the encoded token.
**Remember to fill in the password you generated rather than the word "secret".** After you have filled in the password and payload, the encoded data on the left will update. Copy the encoded token.
.. note::
While the token may look well obscured, it's easy to reverse engineer the payload. The token is merely signed, not encrypted, so don't put things inside that you don't want a determined client to see. While it is possible to read the payload of the token, it is not possible to read the secret with which it was signed.
While the token may look well obscured, it's easy to reverse engineer the payload. The token is merely signed, not encrypted, so don't put things inside that you don't want a determined client to see.
Step 4. Make a Request
----------------------
@@ -123,7 +124,7 @@ A request for the todos shows three of them, and all completed.
Step 5. Add Expiration
----------------------
Currently our authentication token is valid for all eternity. The server, as long as it continues using the same JWT secret, will honor the token.
Currently our authentication token is valid for all eternity. The server, as long as it continues using the same JWT password, will honor the token.
It's better policy to include an expiration timestamp for tokens using the :code:`exp` claim. This is one of two JWT claims that PostgREST treats specially.
@@ -139,7 +140,7 @@ It's better policy to include an expiration timestamp for tokens using the :code
Epoch time is defined as the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), January 1st 1970, minus the number of leap seconds that have taken place since then.
To observe expiration in action, we'll add an :code:`exp` claim of five minutes in the future to our previous token. First find the epoch value of five minutes from now. In :code:`psql` run this:
To observe expiration in action, we'll add an :code:`exp` claim of five minutes in the future to our previous token. First find the epoch value of five minutes from now. In psql run this:
.. code-block:: postgres
@@ -154,7 +155,7 @@ Go back to jwt.io and change the payload to
"exp": 123456789
}
**NOTE**: Don't forget to change the dummy epoch value :code:`123456789` in the snippet above to the epoch value returned by the :code:`psql` command.
**NOTE**: Don't forget to change the dummy epoch value :code:`123456789` in the snippet above to the epoch value returned by the psql command.
Copy the updated token as before, and save it as a new environment variable.
@@ -174,9 +175,9 @@ After expiration, the API returns HTTP 401 Unauthorized:
.. code-block:: json
{
"code": "PGRST301",
"details": null,
"hint": null,
"details": null,
"code": "PGRST301",
"message": "JWT expired"
}
@@ -202,11 +203,11 @@ Save it to an environment variable:
export WAYWARD_TOKEN="<paste new token>"
PostgREST allows us to specify a function to run during attempted authentication. The function can do whatever it likes, including raising an exception to terminate the request.
PostgREST allows us to specify a stored procedure to run during attempted authentication. The function can do whatever it likes, including raising an exception to terminate the request.
First make a new schema and add the function:
.. code-block:: postgres
.. code-block:: plpgsql
create schema auth;
grant usage on schema auth to web_anon, todo_user;
@@ -254,8 +255,8 @@ The server responds with 403 Forbidden:
.. code-block:: json
{
"code": "42501",
"details": null,
"hint": "Nope, we are on to you",
"details": null,
"code": "42501",
"message": "insufficient_privilege"
}
-70
View File
@@ -248,27 +248,6 @@ $ nix-shell --run postgrest-style
There is also `postgrest-style-check` that exits with a non-zero exit code if
the check resulted in any uncommitted changes. It's mostly useful for CI.
## Documentation
The following commands can help you when working on the PostgREST docs:
```bash
# Build the docs
[nix-shell]$ postgrest-docs-build
# Build the docs and start a livereload server on `http://localhost:5500`
[nix-shell]$ postgrest-docs-serve
# Run aspell, to verify spelling mistakes
[nix-shell]$ postgrest-docs-spellcheck
# Detect obsolete entries in postgrest.dict
[nix-shell]$ postgrest-docs-dictcheck
# Build and run all the validation scripts
[nix-shell]$ postgrest-docs-check
```
## General development tools
Tools like `postgrest-build`, `postgrest-run`, `postgrest-repl` etc. are simple wrappers around
@@ -302,55 +281,6 @@ ghci> decodeMediaType "application/json"
MTApplicationJSON
```
## Working with locally modified Haskell packages
Sometimes, we need to modify Haskell libraries in order to debug them or enhance them.
For example, if you want to debug the [`hasql-pool`](https://hackage.haskell.org/package/hasql-pool)
library:
First, copy the package to the repo root. We'll use GitHub in this example.
```bash
$ git clone --depth=1 --branch=0.10.1 https://github.com/nikita-volkov/hasql-pool.git
$ rm -rf ./hasql-pool/.git
```
Then, pin the local package to the [`haskell-packages.nix`](./overlays/haskell-packages.nix) file.
```nix
overrides =
# ...
rec {
# Different subpath may be needed if the cabal file is not in the library's base directory
hasql-pool = lib.dontCheck
(prev.callCabal2nixWithOptions "hasql-pool" ../../hasql-pool "--subpath=." {} );
};
```
Next, both [`cabal.project`](/cabal.project) and [`stack.yaml`](/stack.yaml) need to be updated
with the local library:
```cabal
-- cabal.project
packages:
./hasql-pool/hasql-pool.cabal
```
```yaml
# stack.yaml
extra-deps:
- ./hasql-pool/hasql-pool.cabal
```
Lastly, run `nix-shell` to build the local package. You don't need to exit and
enter the Nix shell every time you modify the library's code, re-executing
`postgrest-run` should be enough.
This is done for development purposes only. Local libraries must not be left
in production ready code.
## Tour
The following is not required for working on PostgREST with Nix, but it will

Some files were not shown because too many files have changed in this diff Show More